Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8b0d60fdd | |||
| 5e32f4d7dc | |||
| 8008c8e2cc | |||
| 2cc5ffb50f | |||
| e7949377a2 | |||
| 592f2b3e16 | |||
| 7506e911f7 | |||
| 09bbfe8a7d | |||
| bb1bd949c3 | |||
| 406b71250e | |||
| 6228cd8953 | |||
| 2623fedec9 | |||
| bfdad895ce | |||
| dce8138e12 | |||
| 7864ebfc41 | |||
| 442bcc5cee | |||
| e8aac6d41e | |||
| 2bbe737101 | |||
| 8856b3f1bc | |||
| aadc36f45c | |||
| 6c56b6f83c | |||
| 018ec3bcda | |||
| bc358bd9ed | |||
| f8eef66810 | |||
| ac2bad1de0 | |||
| 6cbfcb9d64 | |||
| 39df9c2ec0 | |||
| 138f1d28b3 | |||
| e2abc4dbeb | |||
| 0cfc85933c | |||
| c6fd5e6db2 | |||
| 8e57febf9e | |||
| ed38a5b450 | |||
| 16b3199892 | |||
| d13a776970 | |||
| 418b9c0f5d | |||
| c35f333daa | |||
| 668acb4afe | |||
| ac0690ec9f | |||
| cef2e850d7 | |||
| 4b469f2f49 | |||
| b0b2e8ceb5 | |||
| 82a9bf0a0f | |||
| 417083b0c1 | |||
| 25ed0d168e | |||
| d7b599d03b | |||
| 3f52cae04b | |||
| 59c3636139 | |||
| 472d793627 | |||
| 43b98ed9a5 | |||
| 69d2517dd2 | |||
| 0f6567e66b | |||
| c81f7d6291 | |||
| e9c18add5e | |||
| 4071c30b8b | |||
| ff1afcb8dc | |||
| d2c0891a68 | |||
| a557733d30 | |||
| 0f7ab6c803 | |||
| b7088c44f2 | |||
| 00294ff6f6 | |||
| 1d61675ef7 | |||
| 6a8d22f9f0 | |||
| 6811b5a692 | |||
| 9ff37e2874 | |||
| f022fb6b8a | |||
| 37564621e0 | |||
| 5057e1a38e | |||
| e781919434 | |||
| 3715e0c4d2 | |||
| 3ff996e946 | |||
| 6011bd9208 | |||
| d1e16abe34 | |||
| bc13f5c1e6 | |||
| ecf3ebf16d | |||
| 08fd3afc34 | |||
| 2528c03315 | |||
| d322e557a3 | |||
| 9fd17d769e | |||
| ca68186351 | |||
| 8d6f5d637b | |||
| c6790fe271 | |||
| 6402370eaf | |||
| f1ca709f25 | |||
| 51160b033b | |||
| c4884f8f8f | |||
| 647567abd2 | |||
| f123dbf730 | |||
| bc0d36ed54 | |||
| 745f874ccf | |||
| ea36d4ec7a | |||
| bb5623a621 | |||
| cf6c601c47 | |||
| 8efe906e87 | |||
| 3924c04e56 | |||
| a9401d4a0f | |||
| 53ec882e53 | |||
| 7387db4a69 | |||
| 9ef84f9b31 | |||
| f9c928d45c | |||
| 70860860d5 | |||
| 6a98b28427 | |||
| 9ee7819462 | |||
| 5a320e148c | |||
| 767aa23841 | |||
| 227aa69677 | |||
| 722c372f2d | |||
| 92e02c48dc | |||
| 3449746ead | |||
| ee52f75927 | |||
| 9c8cc8de2e | |||
| ea279338e0 | |||
| f15dc75908 | |||
| c00684ebbb | |||
| d0f4ae5109 | |||
| 187fbef207 | |||
| 4c655b13c3 | |||
| 2276b37741 | |||
| 7c5d0b9ae2 | |||
| b8b2d3d2bf | |||
| 3ec4d2218e | |||
| dc5b34546a | |||
| 6577038f22 | |||
| 93abaf99be | |||
| dd24e6058f | |||
| ded3a79bbc | |||
| ab0970e33f | |||
| 49d5d77a82 | |||
| 0cf8820e94 | |||
| 1ef3585cdc | |||
| 25ff6ca01f | |||
| c22bff5948 |
+36
-5
@@ -1,24 +1,55 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
## v0.1905.29-beta
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
- Auto save functionality for all symptoms
|
||||||
|
- Add donation section to about
|
||||||
|
- Styling fixes
|
||||||
|
- Clearer labels on cycle day overview
|
||||||
|
- Rename mucus to cervical mucus
|
||||||
|
- Set show more on homescreen to default and get rid of more/less switch
|
||||||
|
- Add loading screen to data import
|
||||||
|
- Fix line width in chart
|
||||||
|
- Removes logo and adds header on the main login screen
|
||||||
|
- Nicer formatting for past bleeding prediction
|
||||||
|
- Fixes prediction range in drop on homescreen
|
||||||
|
- Removes permissions not required for debug or production
|
||||||
|
- Temperature screen styling update
|
||||||
|
|
||||||
|
## v0.0.3 - 2019-04-17
|
||||||
|
|
||||||
## [0.0.2] - 2019-04-16
|
|
||||||
## Second updated beta release version
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
- Removes Google services from notification library and use fork of react-native-push-notification: <https://github.com/github:jfr3000/react-native-push-notification>
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Button functionality in settings for password
|
||||||
|
|
||||||
|
## v0.0.2 - 2019-04-09
|
||||||
|
|
||||||
|
## Second updated beta release version
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
- First day of the week in calendar is now Monday instead of Sunday
|
- First day of the week in calendar is now Monday instead of Sunday
|
||||||
- Minor styling consistency
|
- Minor styling consistency
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Typos
|
- Typos
|
||||||
- Bleeding value is visible in shortcut from Homescreen
|
- Bleeding value is visible in shortcut from Homescreen
|
||||||
- Delete button for sex, pain and mood
|
- Delete button for sex, pain and mood
|
||||||
- Dates on chart
|
- Dates on chart
|
||||||
|
|
||||||
## [0.0.1] - 2019-02-15
|
## v0.0.1 - 2019-02-15
|
||||||
|
|
||||||
## First beta release version
|
## First beta release version
|
||||||
|
|
||||||
### Added (list of core functionality)
|
### Added (list of core functionality)
|
||||||
|
|
||||||
- you can track your menstrual bleeding
|
- you can track your menstrual bleeding
|
||||||
- you can track symptoms related to natural family planning (nfp), i.e. basal temperature and mucus or cervix
|
- you can track symptoms related to natural family planning (nfp), i.e. basal temperature and mucus or cervix
|
||||||
- you can use nfp symptoms for fertility awareness (drip implements the sympto-thermal method)
|
- you can use nfp symptoms for fertility awareness (drip implements the sympto-thermal method)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle and/or for fertility awareness!
|
A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle and/or for fertility awareness!
|
||||||
Find more information on [our website](https://bloodyhealth.gitlab.io/).
|
Find more information on [our website](https://bloodyhealth.gitlab.io/).
|
||||||
|
|
||||||
The app is build in React Native and currently developed for Android.
|
The app is built in React Native and currently developed for Android.
|
||||||
|
|
||||||
Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/drip/blob/master/CONTRIBUTING.md).
|
Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/drip/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/
|
|||||||
|
|
||||||
1. Install [Android Studio](https://developer.android.com/studio/) - you'll need it to install some dependencies.
|
1. Install [Android Studio](https://developer.android.com/studio/) - you'll need it to install some dependencies.
|
||||||
|
|
||||||
1. Make sure you are running Node 8 (newer versions won’t work). It's easiest to switch Node versions using `nvm`, here’s how to do it:
|
1. Make sure you are running Node 10 (newer versions won’t work). It's easiest to switch Node versions using `nvm`, here’s how to do it:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
|
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
|
||||||
$ nvm install v8
|
$ nvm install v10
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Clone this repository:
|
1. Clone this repository:
|
||||||
@@ -75,10 +75,6 @@ If you get error messages about `adb` not being found on your path:
|
|||||||
$ ln -s ~/Library/Android/sdk/platform-tools/adb /usr/local/bin/adb
|
$ ln -s ~/Library/Android/sdk/platform-tools/adb /usr/local/bin/adb
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Windows 10] react native problems
|
|
||||||
|
|
||||||
Unfortunately, the react native version we use doesn't work on Windows 10 it seems, find [more info here](https://github.com/facebook/react-native/issues/20015).
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
You can run the tests with:
|
You can run the tests with:
|
||||||
```
|
```
|
||||||
@@ -96,7 +92,7 @@ More information about how the app calculates fertility status and bleeding pred
|
|||||||
|
|
||||||
## Adding a new tracking icon
|
## Adding a new tracking icon
|
||||||
|
|
||||||
1. We use [fontello](http://fontello.com/) to create icon fonts for us. You need to upload the complete set of tracking icons (bleeding, mucus, ...) including the new icon you wish to add, all in SVG.
|
1. We use [fontello](http://fontello.com/) to create icon fonts for us. You need to upload the complete set of tracking icons (bleeding, cervical mucus, ...) including the new icon you wish to add, all in SVG.
|
||||||
2. Download webfont from fontello
|
2. Download webfont from fontello
|
||||||
3. Copy both the content of `config.json` and `font.tff` into `assets/fonts`, replacing it with the current content of `config-drip-icon-font.json` and `drip-icon-font.tff`.
|
3. Copy both the content of `config.json` and `font.tff` into `assets/fonts`, replacing it with the current content of `config-drip-icon-font.json` and `drip-icon-font.tff`.
|
||||||
4. Now run the following command in your console:
|
4. Now run the following command in your console:
|
||||||
|
|||||||
+13
-13
@@ -102,10 +102,10 @@ android {
|
|||||||
applicationId "com.drip"
|
applicationId "com.drip"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 3
|
||||||
versionName "1.0"
|
versionName "0.1905.29-beta"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -123,7 +123,7 @@ android {
|
|||||||
reset()
|
reset()
|
||||||
enable enableSeparateBuildPerCPUArchitecture
|
enable enableSeparateBuildPerCPUArchitecture
|
||||||
universalApk false // If true, also generate a universal APK
|
universalApk false // If true, also generate a universal APK
|
||||||
include "armeabi-v7a", "x86"
|
include "armeabi-v7a", "arm64-v8a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -138,7 +138,7 @@ android {
|
|||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
// For each separate APK per architecture, set a unique version code as described here:
|
// For each separate APK per architecture, set a unique version code as described here:
|
||||||
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
|
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
|
||||||
def versionCodes = ["armeabi-v7a":1, "x86":2]
|
def versionCodes = ["armeabi-v7a":1, "arm64-v8a":2]
|
||||||
def abi = output.getFilter(OutputFile.ABI)
|
def abi = output.getFilter(OutputFile.ABI)
|
||||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||||
output.versionCodeOverride =
|
output.versionCodeOverride =
|
||||||
@@ -149,14 +149,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':nodejs-mobile-react-native')
|
implementation project(':realm')
|
||||||
compile project(':react-native-restart')
|
implementation project(':react-native-vector-icons')
|
||||||
compile project(':react-native-push-notification')
|
implementation project(':react-native-share')
|
||||||
compile project(':react-native-vector-icons')
|
implementation project(':react-native-restart')
|
||||||
compile project(':react-native-fs')
|
implementation project(':react-native-push-notification')
|
||||||
compile project(':react-native-document-picker')
|
implementation project(':react-native-fs')
|
||||||
compile project(':react-native-share')
|
implementation project(':react-native-document-picker')
|
||||||
compile project(':realm')
|
implementation project(':nodejs-mobile-react-native')
|
||||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||||
compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||||
compile "com.facebook.react:react-native:+" // From node_modules
|
compile "com.facebook.react:react-native:+" // From node_modules
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission tools:node="remove" android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
<uses-permission tools:node="remove" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission tools:node="remove" android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<permission
|
<permission
|
||||||
android:name="${applicationId}.permission.C2D_MESSAGE"
|
android:name="${applicationId}.permission.C2D_MESSAGE"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.drip"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
<uses-permission tools:node="remove" android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -29,19 +29,14 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
buildToolsVersion = "26.0.3"
|
buildToolsVersion = "27.0.3"
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
compileSdkVersion = 26
|
compileSdkVersion = 27
|
||||||
targetSdkVersion = 26
|
targetSdkVersion = 27
|
||||||
supportLibVersion = "26.1.0"
|
supportLibVersion = "27.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {project ->
|
subprojects {project ->
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
maven { url = 'https://dl.bintray.com/android/android-tools/' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// https://stackoverflow.com/questions/52613089/getting-verifyreleaseresources-error-after-upgrading-react-native
|
// https://stackoverflow.com/questions/52613089/getting-verifyreleaseresources-error-after-upgrading-react-native
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
if (project.hasProperty("android")) {
|
if (project.hasProperty("android")) {
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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 = {
|
||||||
|
secureTextEntry: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTextInput.defaultProps = {
|
||||||
|
style: []
|
||||||
|
}
|
||||||
@@ -18,19 +18,6 @@ export default function AppText(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActionHint(props) {
|
|
||||||
if(props.isVisible) {
|
|
||||||
return (
|
|
||||||
<AppText
|
|
||||||
style={[styles.actionHint, props.style]}>
|
|
||||||
{props.children}
|
|
||||||
</AppText>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SymptomSectionHeader(props) {
|
export function SymptomSectionHeader(props) {
|
||||||
return (
|
return (
|
||||||
<AppText style={styles.symptomViewHeading}>
|
<AppText style={styles.symptomViewHeading}>
|
||||||
|
|||||||
+9
-29
@@ -11,7 +11,6 @@ import SettingsMenu from './settings/settings-menu'
|
|||||||
import settingsViews from './settings'
|
import settingsViews from './settings'
|
||||||
import Stats from './stats'
|
import Stats from './stats'
|
||||||
import {headerTitles, menuTitles} from '../i18n/en/labels'
|
import {headerTitles, menuTitles} from '../i18n/en/labels'
|
||||||
import InfoSymptom from './cycle-day/symptoms/info-symptom'
|
|
||||||
import setupNotifications from '../lib/notifications'
|
import setupNotifications from '../lib/notifications'
|
||||||
|
|
||||||
// design wants everyhting lowercased, but we don't
|
// design wants everyhting lowercased, but we don't
|
||||||
@@ -22,7 +21,6 @@ const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
|
|||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
const HOME_PAGE = 'Home'
|
const HOME_PAGE = 'Home'
|
||||||
const INFO_SYMPTOM_PAGE = 'InfoSymptom'
|
|
||||||
const CYCLE_DAY_PAGE = 'CycleDay'
|
const CYCLE_DAY_PAGE = 'CycleDay'
|
||||||
const SETTINGS_MENU_PAGE = 'SettingsMenu'
|
const SETTINGS_MENU_PAGE = 'SettingsMenu'
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ export default class App extends Component {
|
|||||||
if (this.isMenuItem()) {
|
if (this.isMenuItem()) {
|
||||||
this.menuOrigin = currentPage
|
this.menuOrigin = currentPage
|
||||||
}
|
}
|
||||||
if (!this.isSymptomView() && !this.isInfoSymptomView()) {
|
if (!this.isSymptomView()) {
|
||||||
this.originForSymptomView = currentPage
|
this.originForSymptomView = currentPage
|
||||||
}
|
}
|
||||||
this.setState({ currentPage: pageName, currentProps: props })
|
this.setState({ currentPage: pageName, currentProps: props })
|
||||||
@@ -66,10 +64,6 @@ export default class App extends Component {
|
|||||||
this.navigate(SETTINGS_MENU_PAGE)
|
this.navigate(SETTINGS_MENU_PAGE)
|
||||||
} else if (currentPage === CYCLE_DAY_PAGE) {
|
} else if (currentPage === CYCLE_DAY_PAGE) {
|
||||||
this.navigate(this.menuOrigin)
|
this.navigate(this.menuOrigin)
|
||||||
} else if (this.isInfoSymptomView()) {
|
|
||||||
const { date, cycleDay, symptomView } = currentProps
|
|
||||||
this.navigate(
|
|
||||||
symptomView, { date, cycleDay })
|
|
||||||
} else {
|
} else {
|
||||||
this.navigate(HOME_PAGE)
|
this.navigate(HOME_PAGE)
|
||||||
}
|
}
|
||||||
@@ -84,10 +78,6 @@ export default class App extends Component {
|
|||||||
return Object.keys(symptomViews).includes(this.state.currentPage)
|
return Object.keys(symptomViews).includes(this.state.currentPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
isInfoSymptomView() {
|
|
||||||
return this.state.currentPage === INFO_SYMPTOM_PAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
isSettingsView() {
|
isSettingsView() {
|
||||||
return Object.keys(settingsViews).includes(this.state.currentPage)
|
return Object.keys(settingsViews).includes(this.state.currentPage)
|
||||||
}
|
}
|
||||||
@@ -104,42 +94,32 @@ export default class App extends Component {
|
|||||||
Calendar,
|
Calendar,
|
||||||
CycleDay,
|
CycleDay,
|
||||||
Chart,
|
Chart,
|
||||||
InfoSymptom,
|
|
||||||
SettingsMenu,
|
SettingsMenu,
|
||||||
...settingsViews,
|
...settingsViews,
|
||||||
Stats,
|
Stats,
|
||||||
...symptomViews
|
...symptomViews
|
||||||
}
|
}
|
||||||
const page = pages[currentPage]
|
const Page = pages[currentPage]
|
||||||
const title = headerTitlesLowerCase[currentPage]
|
const title = headerTitlesLowerCase[currentPage]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
{this.isDefaultView() &&
|
{this.isDefaultView() &&
|
||||||
<Header title={title} />
|
<Header title={title} />
|
||||||
}
|
}
|
||||||
{(this.isInfoSymptomView() || this.isSettingsView()) &&
|
{(this.isSettingsView()) &&
|
||||||
<Header
|
<Header
|
||||||
title={title}
|
title={title}
|
||||||
showBackButton={true}
|
showBackButton={true}
|
||||||
goBack={this.handleBackButtonPress}
|
goBack={this.handleBackButtonPress}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{this.isSymptomView() &&
|
|
||||||
<Header
|
|
||||||
title={title}
|
|
||||||
isSymptomView={true}
|
|
||||||
goBack={this.handleBackButtonPress}
|
|
||||||
date={currentProps.date}
|
|
||||||
goToSymptomInfo={() => this.navigate(INFO_SYMPTOM_PAGE, {
|
|
||||||
symptomView: currentPage,
|
|
||||||
...currentProps
|
|
||||||
})}
|
|
||||||
/>}
|
|
||||||
|
|
||||||
{React.createElement(page, {
|
<Page
|
||||||
navigate: this.navigate,
|
navigate={this.navigate}
|
||||||
...currentProps
|
{...currentProps}
|
||||||
})}
|
handleBackButtonPress={this.handleBackButtonPress}
|
||||||
|
/>
|
||||||
|
|
||||||
{!this.isSymptomView() &&
|
{!this.isSymptomView() &&
|
||||||
<Menu navigate={this.navigate} currentPage={currentPage} />
|
<Menu navigate={this.navigate} currentPage={currentPage} />
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { cycleDayColor } from '../../styles'
|
|||||||
import { scaleObservable } from '../../local-storage'
|
import { scaleObservable } from '../../local-storage'
|
||||||
import config from '../../config'
|
import config from '../../config'
|
||||||
import AppText from '../app-text'
|
import AppText from '../app-text'
|
||||||
|
import AppLoadingView from '../app-loading'
|
||||||
import { shared as labels } from '../../i18n/en/labels'
|
import { shared as labels } from '../../i18n/en/labels'
|
||||||
import DripIcon from '../../assets/drip-icons'
|
import DripIcon from '../../assets/drip-icons'
|
||||||
import DripHomeIcon from '../../assets/drip-home-icons'
|
import DripHomeIcon from '../../assets/drip-home-icons'
|
||||||
@@ -133,11 +134,7 @@ export default class CycleChart extends Component {
|
|||||||
onLayout={this.onLayout}
|
onLayout={this.onLayout}
|
||||||
style={{ flexDirection: 'row', flex: 1 }}
|
style={{ flexDirection: 'row', flex: 1 }}
|
||||||
>
|
>
|
||||||
{!this.state.chartLoaded &&
|
{!this.state.chartLoaded && <AppLoadingView />}
|
||||||
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
|
|
||||||
<AppText>{labels.loading}</AppText>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
|
|
||||||
{this.state.chartHeight && this.state.chartLoaded &&
|
{this.state.chartHeight && this.state.chartLoaded &&
|
||||||
<View>
|
<View>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export const dotRadius = 5
|
|||||||
const lineWidth = 1.5
|
const lineWidth = 1.5
|
||||||
const colorLtl = '#feb47b'
|
const colorLtl = '#feb47b'
|
||||||
const gridColor = '#d3d3d3'
|
const gridColor = '#d3d3d3'
|
||||||
const gridLineWidth = 0.5
|
const gridLineWidthVertical = 0.6
|
||||||
|
const gridLineWidthHorizontal = 0.3
|
||||||
const numberLabelFontSize = 13
|
const numberLabelFontSize = 13
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
@@ -44,7 +45,7 @@ const styles = {
|
|||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
color: gridColor,
|
color: gridColor,
|
||||||
width: gridLineWidth,
|
width: gridLineWidthVertical,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
symptomIcon: {
|
symptomIcon: {
|
||||||
@@ -110,10 +111,10 @@ const styles = {
|
|||||||
},
|
},
|
||||||
horizontalGrid: {
|
horizontalGrid: {
|
||||||
position:'absolute',
|
position:'absolute',
|
||||||
borderColor: gridColor,
|
|
||||||
borderWidth: gridLineWidth,
|
|
||||||
width: '100%',
|
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
|
borderBottomColor: gridColor,
|
||||||
|
borderBottomWidth: gridLineWidthHorizontal,
|
||||||
|
width: '100%',
|
||||||
left: config.columnWidth
|
left: config.columnWidth
|
||||||
},
|
},
|
||||||
nfpLine: {
|
nfpLine: {
|
||||||
|
|||||||
@@ -11,15 +11,11 @@ import { getCycleDay } from '../../db'
|
|||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
import styles from '../../styles'
|
import styles from '../../styles'
|
||||||
import * as labels from '../../i18n/en/cycle-day'
|
import * as labels from '../../i18n/en/cycle-day'
|
||||||
|
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
||||||
import AppText from '../app-text'
|
import AppText from '../app-text'
|
||||||
import DripIcon from '../../assets/drip-icons'
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
|
||||||
const bleedingLabels = labels.bleeding.labels
|
const bleedingLabels = labels.bleeding.labels
|
||||||
const feelingLabels = labels.mucus.feeling.categories
|
|
||||||
const textureLabels = labels.mucus.texture.categories
|
|
||||||
const openingLabels = labels.cervix.opening.categories
|
|
||||||
const firmnessLabels = labels.cervix.firmness.categories
|
|
||||||
const positionLabels = labels.cervix.position.categories
|
|
||||||
const intensityLabels = labels.intensity
|
const intensityLabels = labels.intensity
|
||||||
const sexLabels = labels.sex.categories
|
const sexLabels = labels.sex.categories
|
||||||
const contraceptiveLabels = labels.contraceptives.categories
|
const contraceptiveLabels = labels.contraceptives.categories
|
||||||
@@ -72,28 +68,25 @@ export default class CycleDayOverView extends Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mucus: mucus => {
|
mucus: mucus => {
|
||||||
const categories = ['feeling', 'texture', 'value']
|
const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
|
||||||
if (categories.every(c => isNumber(mucus[c]))) {
|
let label = filledCategories.map(category => {
|
||||||
let mucusLabel = [feelingLabels[mucus.feeling], textureLabels[mucus.texture]].join(', ')
|
return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
|
||||||
mucusLabel += `\n${labels.mucusNFP[mucus.value]}`
|
}).join(', ')
|
||||||
if (mucus.exclude) mucusLabel = `(${mucusLabel})`
|
|
||||||
return mucusLabel
|
if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
|
||||||
}
|
if (mucus.exclude) label = `(${label})`
|
||||||
|
|
||||||
|
return label
|
||||||
},
|
},
|
||||||
cervix: cervix => {
|
cervix: cervix => {
|
||||||
let cervixLabel = []
|
const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
|
||||||
if (isNumber(cervix.opening) && isNumber(cervix.firmness)) {
|
let label = filledCategories.map(category => {
|
||||||
cervixLabel.push(
|
return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
|
||||||
openingLabels[cervix.opening],
|
}).join(', ')
|
||||||
firmnessLabels[cervix.firmness]
|
|
||||||
)
|
if (cervix.exclude) label = `(${label})`
|
||||||
if (isNumber(cervix.position)) {
|
|
||||||
cervixLabel.push(positionLabels[cervix.position])
|
return label
|
||||||
}
|
|
||||||
cervixLabel = cervixLabel.join(', ')
|
|
||||||
if (cervix.exclude) cervixLabel = `(${cervixLabel})`
|
|
||||||
return cervixLabel
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
note: note => {
|
note: note => {
|
||||||
return note.value
|
return note.value
|
||||||
@@ -188,7 +181,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
<ScrollView>
|
<ScrollView>
|
||||||
<View style={styles.symptomBoxesView}>
|
<View style={styles.symptomBoxesView}>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Bleeding'
|
title={symptomTitles.bleeding}
|
||||||
onPress={() => this.navigate('BleedingEditView')}
|
onPress={() => this.navigate('BleedingEditView')}
|
||||||
data={this.getLabel('bleeding')}
|
data={this.getLabel('bleeding')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -196,7 +189,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Temperature'
|
title={symptomTitles.temperature}
|
||||||
onPress={() => this.navigate('TemperatureEditView')}
|
onPress={() => this.navigate('TemperatureEditView')}
|
||||||
data={this.getLabel('temperature')}
|
data={this.getLabel('temperature')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -204,7 +197,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Mucus'
|
title={symptomTitles.mucus}
|
||||||
onPress={() => this.navigate('MucusEditView')}
|
onPress={() => this.navigate('MucusEditView')}
|
||||||
data={this.getLabel('mucus')}
|
data={this.getLabel('mucus')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -212,7 +205,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Cervix'
|
title={symptomTitles.cervix}
|
||||||
onPress={() => this.navigate('CervixEditView')}
|
onPress={() => this.navigate('CervixEditView')}
|
||||||
data={this.getLabel('cervix')}
|
data={this.getLabel('cervix')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -220,7 +213,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Desire'
|
title={symptomTitles.desire}
|
||||||
onPress={() => this.navigate('DesireEditView')}
|
onPress={() => this.navigate('DesireEditView')}
|
||||||
data={this.getLabel('desire')}
|
data={this.getLabel('desire')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -228,7 +221,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Sex'
|
title={symptomTitles.sex}
|
||||||
onPress={() => this.navigate('SexEditView')}
|
onPress={() => this.navigate('SexEditView')}
|
||||||
data={this.getLabel('sex')}
|
data={this.getLabel('sex')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -236,7 +229,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Pain'
|
title={symptomTitles.pain}
|
||||||
onPress={() => this.navigate('PainEditView')}
|
onPress={() => this.navigate('PainEditView')}
|
||||||
data={this.getLabel('pain')}
|
data={this.getLabel('pain')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -244,7 +237,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Mood'
|
title={symptomTitles.mood}
|
||||||
onPress={() => this.navigate('MoodEditView')}
|
onPress={() => this.navigate('MoodEditView')}
|
||||||
data={this.getLabel('mood')}
|
data={this.getLabel('mood')}
|
||||||
disabled={dateInFuture}
|
disabled={dateInFuture}
|
||||||
@@ -252,7 +245,7 @@ export default class CycleDayOverView extends Component {
|
|||||||
>
|
>
|
||||||
</SymptomBox>
|
</SymptomBox>
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
title='Note'
|
title={symptomTitles.note}
|
||||||
onPress={() => this.navigate('NoteEditView')}
|
onPress={() => this.navigate('NoteEditView')}
|
||||||
data={this.getLabel('note')}
|
data={this.getLabel('note')}
|
||||||
iconName='drip-icon-note'
|
iconName='drip-icon-note'
|
||||||
@@ -285,7 +278,10 @@ class SymptomBox extends Component {
|
|||||||
>
|
>
|
||||||
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
|
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
|
||||||
<DripIcon name={this.props.iconName} size={50} color={hasData ? 'white' : 'black'}/>
|
<DripIcon name={this.props.iconName} size={50} color={hasData ? 'white' : 'black'}/>
|
||||||
<AppText style={[textActive, disabledStyle]}>
|
<AppText
|
||||||
|
style={[textActive, disabledStyle, {fontSize: 15}]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
{this.props.title.toLowerCase()}
|
{this.props.title.toLowerCase()}
|
||||||
</AppText>
|
</AppText>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import {
|
|
||||||
View, TouchableOpacity, Text, Alert, ToastAndroid
|
|
||||||
} from 'react-native'
|
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import styles, {iconStyles} from '../../../styles'
|
|
||||||
import {sharedDialogs as labels} from '../../../i18n/en/cycle-day'
|
|
||||||
|
|
||||||
|
|
||||||
export default class ActionButtonFooter extends Component {
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
symptom,
|
|
||||||
currentSymptomValue,
|
|
||||||
date,
|
|
||||||
saveAction,
|
|
||||||
saveDisabled,
|
|
||||||
navigate,
|
|
||||||
autoShowDayView = true}
|
|
||||||
= this.props
|
|
||||||
const navigateToOverView = () => navigate('CycleDay', {date})
|
|
||||||
const buttons = [
|
|
||||||
{
|
|
||||||
title: labels.delete,
|
|
||||||
action: () => {
|
|
||||||
Alert.alert(
|
|
||||||
labels.areYouSureTitle,
|
|
||||||
labels.areYouSureToDelete,
|
|
||||||
[{
|
|
||||||
text: labels.cancel,
|
|
||||||
style: 'cancel'
|
|
||||||
}, {
|
|
||||||
text: labels.reallyDeleteData,
|
|
||||||
onPress: () => {
|
|
||||||
saveSymptom(symptom, date)
|
|
||||||
navigateToOverView()
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
disabledCondition: (!currentSymptomValue ||
|
|
||||||
(Object.keys(currentSymptomValue).length === 0 && currentSymptomValue.constructor === Object) ||
|
|
||||||
(Object.values(currentSymptomValue).every(x => !x) && currentSymptomValue.constructor === Object)
|
|
||||||
),
|
|
||||||
icon: 'delete-outline'
|
|
||||||
}, {
|
|
||||||
title: labels.save,
|
|
||||||
action: () => {
|
|
||||||
if(saveDisabled) {
|
|
||||||
ToastAndroid.show(labels.disabledInfo, ToastAndroid.LONG)
|
|
||||||
} else {
|
|
||||||
saveAction()
|
|
||||||
if (autoShowDayView) navigateToOverView()
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
disabledCondition: saveDisabled,
|
|
||||||
icon: 'content-save-outline'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return (
|
|
||||||
<View style={styles.menu}>
|
|
||||||
{buttons.map(({ title, action, disabledCondition, icon }, i) => {
|
|
||||||
const textStyle = [styles.menuText]
|
|
||||||
if (disabledCondition) {
|
|
||||||
textStyle.push(styles.menuTextInActive)
|
|
||||||
}
|
|
||||||
const iconStyle = disabledCondition ?
|
|
||||||
Object.assign(
|
|
||||||
{},
|
|
||||||
iconStyles.menuIcon,
|
|
||||||
iconStyles.menuIconInactive
|
|
||||||
)
|
|
||||||
:
|
|
||||||
iconStyles.menuIcon
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={action}
|
|
||||||
style={styles.menuItem}
|
|
||||||
key={i.toString()}
|
|
||||||
>
|
|
||||||
<Icon name={icon} {...iconStyle} />
|
|
||||||
<Text style={textStyle}>
|
|
||||||
{title.toLowerCase()}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,39 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
Switch,
|
Switch,
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { bleeding } from '../../../i18n/en/cycle-day'
|
import { bleeding } from '../../../i18n/en/cycle-day'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
import SelectTabGroup from '../select-tab-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Bleeding extends Component {
|
export default class Bleeding extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.bleeding = cycleDay && cycleDay.bleeding
|
this.bleeding = cycleDay && cycleDay.bleeding
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentValue: this.bleeding && this.bleeding.value,
|
currentValue: this.bleeding && this.bleeding.value,
|
||||||
exclude: this.bleeding ? this.bleeding.exclude : false
|
exclude: this.bleeding ? this.bleeding.exclude : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
symptomName = 'bleeding'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
if (typeof this.state.currentValue != 'number') {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry({
|
||||||
|
value: this.state.currentValue,
|
||||||
|
exclude: this.state.exclude
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
const bleedingRadioProps = [
|
const bleedingRadioProps = [
|
||||||
{ label: bleeding.labels[0], value: 0 },
|
{ label: bleeding.labels[0], value: 0 },
|
||||||
{ label: bleeding.labels[1], value: 1 },
|
{ label: bleeding.labels[1], value: 1 },
|
||||||
@@ -31,45 +41,30 @@ export default class Bleeding extends Component {
|
|||||||
{ label: bleeding.labels[3], value: 3 },
|
{ label: bleeding.labels[3], value: 3 },
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
header={bleeding.heaviness.header}
|
||||||
header={bleeding.heaviness.header}
|
explainer={bleeding.heaviness.explainer}
|
||||||
explainer={bleeding.heaviness.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={bleedingRadioProps}
|
||||||
buttons={bleedingRadioProps}
|
active={this.state.currentValue}
|
||||||
active={this.state.currentValue}
|
onSelect={val => this.setState({ currentValue: val })}
|
||||||
onSelect={val => this.setState({ currentValue: val })}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header={bleeding.exclude.header}
|
||||||
header={bleeding.exclude.header}
|
explainer={bleeding.exclude.explainer}
|
||||||
explainer={bleeding.exclude.explainer}
|
inline={true}
|
||||||
inline={true}
|
>
|
||||||
>
|
<Switch
|
||||||
<Switch
|
onValueChange={(val) => {
|
||||||
onValueChange={(val) => {
|
this.setState({ exclude: val })
|
||||||
this.setState({ exclude: val })
|
}}
|
||||||
}}
|
value={this.state.exclude}
|
||||||
value={this.state.exclude}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='bleeding'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.bleeding}
|
|
||||||
saveAction={() => {
|
|
||||||
saveSymptom('bleeding', this.props.date, {
|
|
||||||
value: this.state.currentValue,
|
|
||||||
exclude: this.state.exclude
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
saveDisabled={typeof this.state.currentValue != 'number'}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,40 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
Switch,
|
Switch,
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { cervix as labels } from '../../../i18n/en/cycle-day'
|
import { cervix as labels } from '../../../i18n/en/cycle-day'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
import SelectTabGroup from '../select-tab-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
import { ActionHint } from '../../app-text'
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Cervix extends Component {
|
export default class Cervix extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.cervix = cycleDay && cycleDay.cervix
|
this.cervix = cycleDay && cycleDay.cervix
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
this.state = this.cervix ? this.cervix : {}
|
this.state = this.cervix ? this.cervix : {}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
symptomName = 'cervix'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
const nothingEntered = ['opening', 'firmness', 'position'].every(val => typeof this.state[val] != 'number')
|
||||||
|
if (nothingEntered) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveSymptomEntry({
|
||||||
|
opening: this.state.opening,
|
||||||
|
firmness: this.state.firmness,
|
||||||
|
position: this.state.position,
|
||||||
|
exclude: Boolean(this.state.exclude)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
const cervixOpeningRadioProps = [
|
const cervixOpeningRadioProps = [
|
||||||
{ label: labels.opening.categories[0], value: 0 },
|
{ label: labels.opening.categories[0], value: 0 },
|
||||||
{ label: labels.opening.categories[1], value: 1 },
|
{ label: labels.opening.categories[1], value: 1 },
|
||||||
@@ -36,70 +49,53 @@ export default class Cervix extends Component {
|
|||||||
{ label: labels.position.categories[1], value: 1 },
|
{ label: labels.position.categories[1], value: 1 },
|
||||||
{ label: labels.position.categories[2], value: 2 }
|
{ label: labels.position.categories[2], value: 2 }
|
||||||
]
|
]
|
||||||
const mandatoryNotCompletedYet = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
|
// TODO saving this info for notice when leaving incomplete data
|
||||||
|
// const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
header="Opening"
|
||||||
header="Opening"
|
explainer={labels.opening.explainer}
|
||||||
explainer={labels.opening.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={cervixOpeningRadioProps}
|
||||||
buttons={cervixOpeningRadioProps}
|
active={this.state.opening}
|
||||||
active={this.state.opening}
|
onSelect={val => this.setState({ opening: val })}
|
||||||
onSelect={val => this.setState({ opening: val })}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header="Firmness"
|
||||||
header="Firmness"
|
explainer={labels.firmness.explainer}
|
||||||
explainer={labels.firmness.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={cervixFirmnessRadioProps}
|
||||||
buttons={cervixFirmnessRadioProps}
|
active={this.state.firmness}
|
||||||
active={this.state.firmness}
|
onSelect={val => this.setState({ firmness: val })}
|
||||||
onSelect={val => this.setState({ firmness: val })}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header="Position"
|
||||||
header="Position"
|
explainer={labels.position.explainer}
|
||||||
explainer={labels.position.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={cervixPositionRadioProps}
|
||||||
buttons={cervixPositionRadioProps}
|
active={this.state.position}
|
||||||
active={this.state.position}
|
onSelect={val => this.setState({ position: val })}
|
||||||
onSelect={val => this.setState({ position: val })}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header="Exclude"
|
||||||
header="Exclude"
|
explainer="You can exclude this value if you don't want to use it for fertility detection"
|
||||||
explainer="You can exclude this value if you don't want to use it for fertility detection"
|
inline={true}
|
||||||
inline={true}
|
>
|
||||||
>
|
<Switch
|
||||||
<Switch
|
onValueChange={(val) => {
|
||||||
onValueChange={(val) => {
|
this.setState({ exclude: val })
|
||||||
this.setState({ exclude: val })
|
}}
|
||||||
}}
|
value={this.state.exclude}
|
||||||
value={this.state.exclude}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
|
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='cervix'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.cervix}
|
|
||||||
saveAction={() => {
|
|
||||||
saveSymptom('cervix', this.props.date, {
|
|
||||||
opening: this.state.opening,
|
|
||||||
firmness: this.state.firmness,
|
|
||||||
position: this.state.position,
|
|
||||||
exclude: Boolean(this.state.exclude)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
saveDisabled={mandatoryNotCompletedYet}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,51 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { intensity, desire } from '../../../i18n/en/cycle-day'
|
import { intensity, desire } from '../../../i18n/en/cycle-day'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
import SelectTabGroup from '../select-tab-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Desire extends Component {
|
export default class Desire extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.desire = cycleDay && cycleDay.desire
|
this.desire = cycleDay && cycleDay.desire
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
const desireValue = this.desire && this.desire.value
|
const desireValue = this.desire && this.desire.value
|
||||||
this.state = { currentValue: desireValue }
|
this.state = { currentValue: desireValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
symptomName = 'desire'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
if (typeof this.state.currentValue != 'number') {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry({ value: this.state.currentValue })
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
const desireRadioProps = [
|
const desireRadioProps = [
|
||||||
{ label: intensity[0], value: 0 },
|
{ label: intensity[0], value: 0 },
|
||||||
{ label: intensity[1], value: 1 },
|
{ label: intensity[1], value: 1 },
|
||||||
{ label: intensity[2], value: 2 }
|
{ label: intensity[2], value: 2 }
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
header={desire.header}
|
||||||
header={desire.header}
|
explainer={desire.explainer}
|
||||||
explainer={desire.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={desireRadioProps}
|
||||||
buttons={desireRadioProps}
|
active={this.state.currentValue}
|
||||||
active={this.state.currentValue}
|
onSelect={val => this.setState({ currentValue: val })}
|
||||||
onSelect={val => this.setState({ currentValue: val })}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='desire'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.desire}
|
|
||||||
saveAction={() => {
|
|
||||||
saveSymptom('desire', this.props.date, { value: this.state.currentValue })
|
|
||||||
}}
|
|
||||||
saveDisabled={typeof this.state.currentValue != 'number'}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,22 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import { ScrollView } from 'react-native'
|
import { ScrollView, View, TouchableOpacity } from 'react-native'
|
||||||
|
import Icon from 'react-native-vector-icons/SimpleLineIcons'
|
||||||
import AppText from '../../app-text'
|
import AppText from '../../app-text'
|
||||||
import labels from '../../../i18n/en/symptom-info.js'
|
import labels from '../../../i18n/en/symptom-info.js'
|
||||||
import FramedSegment from '../../framed-segment'
|
import styles, {iconStyles} from '../../../styles/index'
|
||||||
import styles from '../../../styles/index'
|
|
||||||
|
|
||||||
export default class InfoSymptom extends Component {
|
export default function InfoSymptom(props) {
|
||||||
render() {
|
return (
|
||||||
const symptomView = this.props.symptomView
|
<View style={styles.infoPopUpWrapper}>
|
||||||
const symptomMapping = {
|
<View style={styles.dimmed}></View>
|
||||||
BleedingEditView: 'bleeding',
|
<View style={styles.infoPopUp}>
|
||||||
CervixEditView: 'cervix',
|
<TouchableOpacity onPress={props.close} style={styles.infoSymptomClose}>
|
||||||
DesireEditView: 'desire',
|
<Icon name='close' {...iconStyles.infoPopUpClose}/>
|
||||||
MoodEditView: 'mood',
|
</TouchableOpacity>
|
||||||
MucusEditView: 'mucus',
|
<ScrollView style={styles.infoSymptomText}>
|
||||||
NoteEditView: 'note',
|
<AppText>{labels[props.symptom].text}</AppText>
|
||||||
PainEditView: 'pain',
|
</ScrollView>
|
||||||
SexEditView: 'sex',
|
</View>
|
||||||
TemperatureEditView: 'temperature'
|
</View>
|
||||||
}
|
)
|
||||||
const currentSymptom = symptomMapping[symptomView]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView>
|
|
||||||
<FramedSegment
|
|
||||||
style={styles.framedSegmentLast}
|
|
||||||
title={labels[currentSymptom].title}
|
|
||||||
>
|
|
||||||
<AppText>{labels[currentSymptom].text}</AppText>
|
|
||||||
</FramedSegment>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
ScrollView,
|
ScrollView,
|
||||||
TextInput,
|
TextInput} from 'react-native'
|
||||||
View
|
|
||||||
} from 'react-native'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { mood as labels } from '../../../i18n/en/cycle-day'
|
import { mood as labels } from '../../../i18n/en/cycle-day'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
import SelectBoxGroup from '../select-box-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Mood extends Component {
|
export default class Mood extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
@@ -25,6 +22,21 @@ export default class Mood extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symptomName = "mood"
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
const nothingEntered = Object.values(this.state).every(val => !val)
|
||||||
|
if (nothingEntered) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const copyOfState = Object.assign({}, this.state)
|
||||||
|
if (!copyOfState.other) {
|
||||||
|
copyOfState.note = null
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry(copyOfState)
|
||||||
|
}
|
||||||
|
|
||||||
toggleState = (key) => {
|
toggleState = (key) => {
|
||||||
const curr = this.state[key]
|
const curr = this.state[key]
|
||||||
this.setState({[key]: !curr})
|
this.setState({[key]: !curr})
|
||||||
@@ -33,19 +45,18 @@ export default class Mood extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderContent() {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
explainer={labels.explainer}
|
||||||
explainer={labels.explainer}
|
>
|
||||||
>
|
<SelectBoxGroup
|
||||||
<SelectBoxGroup
|
labels={labels.categories}
|
||||||
labels={labels.categories}
|
onSelect={this.toggleState}
|
||||||
onSelect={this.toggleState}
|
optionsState={this.state}
|
||||||
optionsState={this.state}
|
/>
|
||||||
/>
|
{ this.state.other &&
|
||||||
{ this.state.other &&
|
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus={this.state.focusTextArea}
|
autoFocus={this.state.focusTextArea}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -55,24 +66,9 @@ export default class Mood extends Component {
|
|||||||
this.setState({note: val})
|
this.setState({note: val})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</SymptomSection>
|
</SymptomSection>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='mood'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.state}
|
|
||||||
saveAction={() => {
|
|
||||||
const copyOfState = Object.assign({}, this.state)
|
|
||||||
if (!copyOfState.other) {
|
|
||||||
copyOfState.note = null
|
|
||||||
}
|
|
||||||
saveSymptom('mood', this.props.date, copyOfState)
|
|
||||||
}}
|
|
||||||
saveDisabled={Object.values(this.state).every(value => !value)}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,43 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
Switch,
|
Switch,
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { mucus as labels } from '../../../i18n/en/cycle-day'
|
import { mucus as labels } from '../../../i18n/en/cycle-day'
|
||||||
import computeNfpValue from '../../../lib/nfp-mucus'
|
import computeNfpValue from '../../../lib/nfp-mucus'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
import SelectTabGroup from '../select-tab-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
import { ActionHint } from '../../app-text'
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Mucus extends Component {
|
export default class Mucus extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.mucus = cycleDay && cycleDay.mucus
|
this.mucus = cycleDay && cycleDay.mucus
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
this.state = this.mucus ? this.mucus : {}
|
this.state = this.mucus ? this.mucus : {}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
symptomName = 'mucus'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
const nothingEntered = ['feeling', 'texture'].every(val => typeof this.state[val] != 'number')
|
||||||
|
if (nothingEntered) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const feeling = this.state.feeling
|
||||||
|
const texture = this.state.texture
|
||||||
|
this.saveSymptomEntry({
|
||||||
|
feeling,
|
||||||
|
texture,
|
||||||
|
value: computeNfpValue(feeling, texture),
|
||||||
|
exclude: Boolean(this.state.exclude)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
const mucusFeeling = [
|
const mucusFeeling = [
|
||||||
{ label: labels.feeling.categories[0], value: 0 },
|
{ label: labels.feeling.categories[0], value: 0 },
|
||||||
{ label: labels.feeling.categories[1], value: 1 },
|
{ label: labels.feeling.categories[1], value: 1 },
|
||||||
@@ -34,62 +49,43 @@ export default class Mucus extends Component {
|
|||||||
{ label: labels.texture.categories[1], value: 1 },
|
{ label: labels.texture.categories[1], value: 1 },
|
||||||
{ label: labels.texture.categories[2], value: 2 }
|
{ label: labels.texture.categories[2], value: 2 }
|
||||||
]
|
]
|
||||||
const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
|
// TODO leaving this info for notice when leaving incomplete data
|
||||||
|
// const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
header='Feeling'
|
||||||
header='Feeling'
|
explainer={labels.feeling.explainer}
|
||||||
explainer={labels.feeling.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={mucusFeeling}
|
||||||
buttons={mucusFeeling}
|
onSelect={val => this.setState({ feeling: val })}
|
||||||
onSelect={val => this.setState({ feeling: val })}
|
active={this.state.feeling}
|
||||||
active={this.state.feeling}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header='Texture'
|
||||||
header='Texture'
|
explainer={labels.texture.explainer}
|
||||||
explainer={labels.texture.explainer}
|
>
|
||||||
>
|
<SelectTabGroup
|
||||||
<SelectTabGroup
|
buttons={mucusTexture}
|
||||||
buttons={mucusTexture}
|
onSelect={val => this.setState({ texture: val })}
|
||||||
onSelect={val => this.setState({ texture: val })}
|
active={this.state.texture}
|
||||||
active={this.state.texture}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header="Exclude"
|
||||||
header="Exclude"
|
explainer={labels.excludeExplainer}
|
||||||
explainer={labels.excludeExplainer}
|
inline={true}
|
||||||
inline={true}
|
>
|
||||||
>
|
<Switch
|
||||||
<Switch
|
onValueChange={(val) => {
|
||||||
onValueChange={(val) => {
|
this.setState({ exclude: val })
|
||||||
this.setState({ exclude: val })
|
}}
|
||||||
}}
|
value={this.state.exclude}
|
||||||
value={this.state.exclude}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
|
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='mucus'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.mucus}
|
|
||||||
saveAction={() => {
|
|
||||||
const feeling = this.state.feeling
|
|
||||||
const texture = this.state.texture
|
|
||||||
saveSymptom('mucus', this.props.date, {
|
|
||||||
feeling,
|
|
||||||
texture,
|
|
||||||
value: computeNfpValue(feeling, texture),
|
|
||||||
exclude: Boolean(this.state.exclude)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
saveDisabled={mandatoryNotCompletedYet}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,55 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
ScrollView,
|
ScrollView,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
import { noteExplainer } from '../../../i18n/en/cycle-day'
|
import { noteExplainer } from '../../../i18n/en/cycle-day'
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Note extends Component {
|
export default class Note extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.note = cycleDay && cycleDay.note
|
this.note = cycleDay && cycleDay.note
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentValue: this.note && this.note.value || ''
|
currentValue: this.note && this.note.value || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
symptomName = 'note'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
if (!this.state.currentValue) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry({
|
||||||
|
value: this.state.currentValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
explainer={noteExplainer}
|
||||||
explainer={noteExplainer}
|
>
|
||||||
>
|
<TextInput
|
||||||
<TextInput
|
autoFocus={!this.state.currentValue}
|
||||||
autoFocus={!this.state.currentValue}
|
multiline={true}
|
||||||
multiline={true}
|
placeholder={sharedLabels.enter}
|
||||||
placeholder={sharedLabels.enter}
|
onChangeText={(val) => {
|
||||||
onChangeText={(val) => {
|
this.setState({ currentValue: val })
|
||||||
this.setState({ currentValue: val })
|
}}
|
||||||
}}
|
value={this.state.currentValue}
|
||||||
value={this.state.currentValue}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
</ScrollView>
|
||||||
</ScrollView>
|
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='note'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.note}
|
|
||||||
saveAction={() => {
|
|
||||||
saveSymptom('note', this.props.date, {
|
|
||||||
value: this.state.currentValue
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
saveDisabled={!this.state.currentValue}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
ScrollView,
|
ScrollView,
|
||||||
TextInput,
|
TextInput,
|
||||||
View
|
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { pain as labels } from '../../../i18n/en/cycle-day'
|
import { pain as labels } from '../../../i18n/en/cycle-day'
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
import SelectBoxGroup from '../select-box-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Pain extends Component {
|
export default class Pain extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
@@ -26,6 +24,22 @@ export default class Pain extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symptomName = 'pain'
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
const nothingEntered = Object.values(this.state).every(val => !val)
|
||||||
|
if (nothingEntered) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyOfState = Object.assign({}, this.state)
|
||||||
|
if (!copyOfState.other) {
|
||||||
|
copyOfState.note = null
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry(copyOfState)
|
||||||
|
}
|
||||||
|
|
||||||
toggleState = (key) => {
|
toggleState = (key) => {
|
||||||
const curr = this.state[key]
|
const curr = this.state[key]
|
||||||
this.setState({[key]: !curr})
|
this.setState({[key]: !curr})
|
||||||
@@ -34,19 +48,18 @@ export default class Pain extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderContent() {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
explainer={labels.explainer}
|
||||||
explainer={labels.explainer}
|
>
|
||||||
>
|
<SelectBoxGroup
|
||||||
<SelectBoxGroup
|
labels={labels.categories}
|
||||||
labels={labels.categories}
|
onSelect={this.toggleState}
|
||||||
onSelect={this.toggleState}
|
optionsState={this.state}
|
||||||
optionsState={this.state}
|
/>
|
||||||
/>
|
{ this.state.other &&
|
||||||
{ this.state.other &&
|
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus={this.state.focusTextArea}
|
autoFocus={this.state.focusTextArea}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -56,24 +69,8 @@ export default class Pain extends Component {
|
|||||||
this.setState({note: val})
|
this.setState({note: val})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</SymptomSection>
|
</SymptomSection>
|
||||||
</ScrollView>
|
</ScrollView>)
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='pain'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.state}
|
|
||||||
saveAction={() => {
|
|
||||||
const copyOfState = Object.assign({}, this.state)
|
|
||||||
if (!copyOfState.other) {
|
|
||||||
copyOfState.note = null
|
|
||||||
}
|
|
||||||
saveSymptom('pain', this.props.date, copyOfState)
|
|
||||||
}}
|
|
||||||
saveDisabled={Object.values(this.state).every(value => !value)}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
TextInput,
|
TextInput,
|
||||||
View,
|
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
|
import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
import SelectBoxGroup from '../select-box-group'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
export default class Sex extends Component {
|
export default class Sex extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
@@ -26,6 +24,22 @@ export default class Sex extends Component {
|
|||||||
if (this.state.note) this.state.other = true
|
if (this.state.note) this.state.other = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symptomName = "sex"
|
||||||
|
|
||||||
|
autoSave = () => {
|
||||||
|
const nothingEntered = Object.values(this.state).every(val => !val)
|
||||||
|
if (nothingEntered) {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyOfState = Object.assign({}, this.state)
|
||||||
|
if (!copyOfState.other) {
|
||||||
|
copyOfState.note = null
|
||||||
|
}
|
||||||
|
this.saveSymptomEntry(copyOfState)
|
||||||
|
}
|
||||||
|
|
||||||
toggleState = (key) => {
|
toggleState = (key) => {
|
||||||
const curr = this.state[key]
|
const curr = this.state[key]
|
||||||
this.setState({[key]: !curr})
|
this.setState({[key]: !curr})
|
||||||
@@ -34,32 +48,31 @@ export default class Sex extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderContent() {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<SymptomSection
|
header={sexLabels.header}
|
||||||
header={sexLabels.header}
|
explainer={sexLabels.explainer}
|
||||||
explainer={sexLabels.explainer}
|
>
|
||||||
>
|
<SelectBoxGroup
|
||||||
<SelectBoxGroup
|
labels={sexLabels.categories}
|
||||||
labels={sexLabels.categories}
|
onSelect={this.toggleState}
|
||||||
onSelect={this.toggleState}
|
optionsState={this.state}
|
||||||
optionsState={this.state}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
<SymptomSection
|
||||||
<SymptomSection
|
header={contraceptivesLabels.header}
|
||||||
header={contraceptivesLabels.header}
|
explainer={contraceptivesLabels.explainer}
|
||||||
explainer={contraceptivesLabels.explainer}
|
>
|
||||||
>
|
<SelectBoxGroup
|
||||||
<SelectBoxGroup
|
labels={contraceptivesLabels.categories}
|
||||||
labels={contraceptivesLabels.categories}
|
onSelect={this.toggleState}
|
||||||
onSelect={this.toggleState}
|
optionsState={this.state}
|
||||||
optionsState={this.state}
|
/>
|
||||||
/>
|
</SymptomSection>
|
||||||
</SymptomSection>
|
|
||||||
|
|
||||||
{this.state.other &&
|
{this.state.other &&
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus={this.state.focusTextArea}
|
autoFocus={this.state.focusTextArea}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -69,23 +82,8 @@ export default class Sex extends Component {
|
|||||||
this.setState({ note: val })
|
this.setState({ note: val })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<ActionButtonFooter
|
|
||||||
symptom='sex'
|
|
||||||
date={this.props.date}
|
|
||||||
currentSymptomValue={this.state}
|
|
||||||
saveAction={() => {
|
|
||||||
const copyOfState = Object.assign({}, this.state)
|
|
||||||
if (!copyOfState.other) {
|
|
||||||
copyOfState.note = null
|
|
||||||
}
|
|
||||||
saveSymptom('sex', this.props.date, copyOfState)
|
|
||||||
}}
|
|
||||||
saveDisabled={Object.values(this.state).every(value => !value)}
|
|
||||||
navigate={this.props.navigate}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import AppText, { SymptomSectionHeader } from '../../app-text'
|
import AppText, { SymptomSectionHeader } from '../../app-text'
|
||||||
|
import styles from '../../../styles'
|
||||||
|
|
||||||
export default class SymptomSection extends Component {
|
export default class SymptomSection extends Component {
|
||||||
render() {
|
render() {
|
||||||
@@ -13,16 +14,20 @@ export default class SymptomSection extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={placeHeadingInline}>
|
<View style={[placeHeadingInline, styles.symptomSection]}>
|
||||||
<SymptomSectionHeader flex={1}>{p.header}</SymptomSectionHeader>
|
{ p.header &&
|
||||||
|
<SymptomSectionHeader flex={1}>{p.header}</SymptomSectionHeader>
|
||||||
|
}
|
||||||
<View
|
<View
|
||||||
flexDirection={p.inline ? 'row' : null}
|
flexDirection={p.inline ? 'row' : null}
|
||||||
flex={1}
|
flex={1}
|
||||||
alignItems={p.inline ? 'center' : null}
|
alignItems={p.inline ? 'center' : null}
|
||||||
>
|
>
|
||||||
<View flex={1}>
|
{ p.explainer && (
|
||||||
<AppText>{p.explainer}</AppText>
|
<View flex={1}>
|
||||||
</View>
|
<AppText>{p.explainer}</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{p.children}
|
{p.children}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import {
|
||||||
|
View, Alert, TouchableOpacity
|
||||||
|
} from 'react-native'
|
||||||
|
import { saveSymptom } from '../../../db'
|
||||||
|
import InfoPopUp from './info-symptom'
|
||||||
|
import Header from '../../header/symptom-view'
|
||||||
|
import { headerTitles } from '../../../i18n/en/labels'
|
||||||
|
import { sharedDialogs } from '../../../i18n/en/cycle-day'
|
||||||
|
import Icon from 'react-native-vector-icons/Entypo'
|
||||||
|
import styles, { iconStyles } from '../../../styles'
|
||||||
|
|
||||||
|
export default class SymptomView extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super()
|
||||||
|
this.date = props.date
|
||||||
|
this.navigate = props.navigate
|
||||||
|
this.state = {
|
||||||
|
showInfo: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.autoSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSymptomEntry(entry) {
|
||||||
|
saveSymptom(this.symptomName, this.date, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSymptomEntry() {
|
||||||
|
saveSymptom(this.symptomName, this.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeleteIconActive() {
|
||||||
|
const symptomValueHasBeenFilledOut = key => {
|
||||||
|
// the state tracks whether the symptom info should be shown,
|
||||||
|
// we ignore that property
|
||||||
|
if (key === 'showInfo') return
|
||||||
|
// is there any meaningful value in the current state?
|
||||||
|
return this.state[key] || this.state[key] === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const symptomValues = Object.keys(this.state)
|
||||||
|
|
||||||
|
return symptomValues.some(symptomValueHasBeenFilledOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<Header
|
||||||
|
title={headerTitles[this.symptomName].toLowerCase()}
|
||||||
|
date={this.date}
|
||||||
|
goBack={this.props.handleBackButtonPress}
|
||||||
|
deleteIconActive={this.isDeleteIconActive()}
|
||||||
|
deleteEntry={() => {
|
||||||
|
Alert.alert(
|
||||||
|
sharedDialogs.areYouSureTitle,
|
||||||
|
sharedDialogs.areYouSureToDelete,
|
||||||
|
[{
|
||||||
|
text: sharedDialogs.cancel,
|
||||||
|
style: 'cancel'
|
||||||
|
}, {
|
||||||
|
text: sharedDialogs.reallyDeleteData,
|
||||||
|
onPress: () => {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
this.props.handleBackButtonPress()
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<View flex={1}>
|
||||||
|
{ this.renderContent() }
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
this.setState({showInfo: true})
|
||||||
|
}}
|
||||||
|
style={styles.infoButtonSymptomView}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="info-with-circle"
|
||||||
|
style={iconStyles.info}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{ this.state.showInfo &&
|
||||||
|
<InfoPopUp
|
||||||
|
symptom={this.symptomName}
|
||||||
|
close={() => this.setState({showInfo: false})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
TextInput,
|
|
||||||
Switch,
|
Switch,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Alert,
|
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
||||||
import padWithZeros from '../../helpers/pad-time-with-zeros'
|
import padWithZeros from '../../helpers/pad-time-with-zeros'
|
||||||
|
|
||||||
import { getPreviousTemperature, saveSymptom } from '../../../db'
|
import { getPreviousTemperature } from '../../../db'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { LocalTime, ChronoUnit } from 'js-joda'
|
import { LocalTime, ChronoUnit } from 'js-joda'
|
||||||
import { temperature as labels } from '../../../i18n/en/cycle-day'
|
import { temperature as labels } from '../../../i18n/en/cycle-day'
|
||||||
import { scaleObservable } from '../../../local-storage'
|
import { scaleObservable } from '../../../local-storage'
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
import AppTextInput from '../../app-text-input'
|
||||||
|
import AppText from '../../app-text'
|
||||||
import SymptomSection from './symptom-section'
|
import SymptomSection from './symptom-section'
|
||||||
|
import SymptomView from './symptom-view'
|
||||||
|
|
||||||
const minutes = ChronoUnit.MINUTES
|
const minutes = ChronoUnit.MINUTES
|
||||||
|
|
||||||
export default class Temp extends Component {
|
export default class Temp extends SymptomView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
const cycleDay = props.cycleDay
|
const cycleDay = props.cycleDay
|
||||||
this.temperature = cycleDay && cycleDay.temperature
|
this.temperature = cycleDay && cycleDay.temperature
|
||||||
this.makeActionButtons = props.makeActionButtons
|
|
||||||
|
|
||||||
const temp = this.temperature
|
const temp = this.temperature
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ export default class Temp extends Component {
|
|||||||
exclude: temp ? temp.exclude : false,
|
exclude: temp ? temp.exclude : false,
|
||||||
time: temp ? temp.time : LocalTime.now().truncatedTo(minutes).toString(),
|
time: temp ? temp.time : LocalTime.now().truncatedTo(minutes).toString(),
|
||||||
isTimePickerVisible: false,
|
isTimePickerVisible: false,
|
||||||
outOfRange: null,
|
|
||||||
note: temp ? temp.note : null
|
note: temp ? temp.note : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,170 +42,154 @@ export default class Temp extends Component {
|
|||||||
if (temp.value === Math.floor(temp.value)) {
|
if (temp.value === Math.floor(temp.value)) {
|
||||||
this.state.temperature = `${this.state.temperature}.0`
|
this.state.temperature = `${this.state.temperature}.0`
|
||||||
}
|
}
|
||||||
|
this.state.outOfRangeWarning = makeOutOfRangeWarningMessage(this.state.temperature)
|
||||||
} else {
|
} else {
|
||||||
const prevTemp = getPreviousTemperature(this.props.date)
|
const prevTemp = getPreviousTemperature(props.date)
|
||||||
if (prevTemp) {
|
if (prevTemp) {
|
||||||
this.state.temperature = prevTemp.toString()
|
this.state.suggestedTemperature = prevTemp.toString()
|
||||||
this.state.isSuggestion = true
|
this.state.isSuggestion = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveTemperature = () => {
|
symptomName = 'temperature'
|
||||||
|
|
||||||
|
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 = () => {
|
||||||
|
if (typeof this.state.temperature != 'string' || this.state.temperature === '') {
|
||||||
|
this.deleteSymptomEntry()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const dataToSave = {
|
const dataToSave = {
|
||||||
value: Number(this.state.temperature),
|
value: Number(this.state.temperature),
|
||||||
exclude: this.state.exclude,
|
exclude: this.state.exclude,
|
||||||
time: this.state.time,
|
time: this.state.time,
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
}
|
}
|
||||||
saveSymptom('temperature', this.props.date, dataToSave)
|
|
||||||
this.props.navigate('CycleDay', {date: this.props.date})
|
|
||||||
}
|
|
||||||
|
|
||||||
checkRangeAndSave = () => {
|
|
||||||
const value = Number(this.state.temperature)
|
|
||||||
|
|
||||||
const absolute = {
|
|
||||||
min: config.temperatureScale.min,
|
|
||||||
max: config.temperatureScale.max
|
|
||||||
}
|
|
||||||
const scale = scaleObservable.value
|
|
||||||
let warningMsg
|
|
||||||
if (value < absolute.min || value > absolute.max) {
|
|
||||||
warningMsg = labels.outOfAbsoluteRangeWarning
|
|
||||||
} else if (value < scale.min || value > scale.max) {
|
|
||||||
warningMsg = labels.outOfRangeWarning
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warningMsg) {
|
|
||||||
Alert.alert(
|
|
||||||
sharedLabels.warning,
|
|
||||||
warningMsg,
|
|
||||||
[
|
|
||||||
{ text: sharedLabels.cancel },
|
|
||||||
{ text: sharedLabels.save, onPress: this.saveTemperature}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.saveTemperature()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.saveSymptomEntry(dataToSave)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
setTemperature = (temperature) => {
|
||||||
|
if (isNaN(Number(temperature))) return
|
||||||
|
this.setState({
|
||||||
|
temperature, isSuggestion: false,
|
||||||
|
outOfRangeWarning: makeOutOfRangeWarningMessage(temperature)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setNote = (note) => {
|
||||||
|
this.setState({ note })
|
||||||
|
}
|
||||||
|
|
||||||
|
showTimePicker = () => {
|
||||||
|
Keyboard.dismiss()
|
||||||
|
this.setState({ isTimePickerVisible: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
|
const inputStyle = [styles.temperatureTextInput]
|
||||||
|
if (this.state.isSuggestion) {
|
||||||
|
inputStyle.push(styles.temperatureTextInputSuggestion)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<ScrollView style={styles.page}>
|
||||||
<ScrollView style={styles.page}>
|
<SymptomSection
|
||||||
<View>
|
header={labels.temperature.header}
|
||||||
<SymptomSection
|
explainer={labels.temperature.explainer}
|
||||||
header={labels.temperature.header}
|
>
|
||||||
explainer={labels.temperature.explainer}
|
<View style={styles.framedSegmentInlineChildren}>
|
||||||
inline={true}
|
<AppTextInput
|
||||||
>
|
style={[inputStyle]}
|
||||||
<TempInput
|
autoFocus={true}
|
||||||
value={this.state.temperature}
|
value={this.state.temperature || this.state.suggestedTemperature}
|
||||||
setState={(val) => this.setState(val)}
|
onChangeText={this.setTemperature}
|
||||||
isSuggestion={this.state.isSuggestion}
|
keyboardType='numeric'
|
||||||
/>
|
maxLength={5}
|
||||||
</SymptomSection>
|
/>
|
||||||
<SymptomSection
|
<AppText style={{ marginLeft: 5 }}>°C</AppText>
|
||||||
header={labels.time}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
style={styles.temperatureTextInput}
|
|
||||||
onFocus={() => {
|
|
||||||
Keyboard.dismiss()
|
|
||||||
this.setState({ isTimePickerVisible: true })
|
|
||||||
}}
|
|
||||||
value={this.state.time}
|
|
||||||
/>
|
|
||||||
<DateTimePicker
|
|
||||||
mode="time"
|
|
||||||
isVisible={this.state.isTimePickerVisible}
|
|
||||||
onConfirm={jsDate => {
|
|
||||||
this.setState({
|
|
||||||
time: padWithZeros(jsDate),
|
|
||||||
isTimePickerVisible: false
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
onCancel={() => this.setState({ isTimePickerVisible: false })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={labels.note.header}
|
|
||||||
explainer={labels.note.explainer}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
multiline={true}
|
|
||||||
autoFocus={this.state.focusTextArea}
|
|
||||||
placeholder={sharedLabels.enter}
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
this.setState({ note: val })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={labels.exclude.header}
|
|
||||||
explainer={labels.exclude.explainer}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
onValueChange={(val) => {
|
|
||||||
this.setState({ exclude: val })
|
|
||||||
}}
|
|
||||||
value={this.state.exclude}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
{this.state.outOfRangeWarning &&
|
||||||
<ActionButtonFooter
|
<AppText style={styles.hint}>
|
||||||
symptom='temperature'
|
{this.state.outOfRangeWarning}
|
||||||
date={this.props.date}
|
</AppText>
|
||||||
currentSymptomValue={this.temperature}
|
|
||||||
saveAction={() => this.checkRangeAndSave()}
|
|
||||||
saveDisabled={
|
|
||||||
this.state.temperature === '' ||
|
|
||||||
isNaN(Number(this.state.temperature)) ||
|
|
||||||
isInvalidTime(this.state.time)
|
|
||||||
}
|
}
|
||||||
navigate={this.props.navigate}
|
</SymptomSection>
|
||||||
autoShowDayView={false}
|
<SymptomSection
|
||||||
/>
|
header={labels.time}
|
||||||
</View>
|
>
|
||||||
|
<View style={styles.framedSegmentInlineChildren}>
|
||||||
|
<AppTextInput
|
||||||
|
style={[styles.temperatureTextInput]}
|
||||||
|
onFocus={this.showTimePicker}
|
||||||
|
value={this.state.time}
|
||||||
|
/>
|
||||||
|
<DateTimePicker
|
||||||
|
mode="time"
|
||||||
|
isVisible={this.state.isTimePickerVisible}
|
||||||
|
onConfirm={jsDate => {
|
||||||
|
this.setState({
|
||||||
|
time: padWithZeros(jsDate),
|
||||||
|
isTimePickerVisible: false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onCancel={() => this.setState({ isTimePickerVisible: false })}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</SymptomSection>
|
||||||
|
<SymptomSection
|
||||||
|
header={labels.note.header}
|
||||||
|
explainer={labels.note.explainer}
|
||||||
|
>
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
autoFocus={this.state.focusTextArea}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
value={this.state.note}
|
||||||
|
onChangeText={this.setNote}
|
||||||
|
/>
|
||||||
|
</SymptomSection>
|
||||||
|
<SymptomSection
|
||||||
|
header={labels.exclude.header}
|
||||||
|
explainer={labels.exclude.explainer}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({ exclude: val })
|
||||||
|
}}
|
||||||
|
value={this.state.exclude}
|
||||||
|
/>
|
||||||
|
</SymptomSection>
|
||||||
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TempInput extends Component {
|
function makeOutOfRangeWarningMessage(temperature) {
|
||||||
render() {
|
if (temperature === '') return
|
||||||
const style = [styles.temperatureTextInput]
|
const value = Number(temperature)
|
||||||
if (this.props.isSuggestion) {
|
const { min, max } = config.temperatureScale
|
||||||
style.push(styles.temperatureTextInputSuggestion)
|
const range = { min, max }
|
||||||
}
|
const scale = scaleObservable.value
|
||||||
return (
|
let warningMsg
|
||||||
<TextInput
|
|
||||||
style={style}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
if (isNaN(Number(val))) return
|
|
||||||
this.props.setState({ temperature: val, isSuggestion: false })
|
|
||||||
}}
|
|
||||||
keyboardType='numeric'
|
|
||||||
value={this.props.value}
|
|
||||||
onBlur={this.checkRange}
|
|
||||||
autoFocus={true}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInvalidTime(timeString) {
|
if (value < range.min || value > range.max) {
|
||||||
try {
|
warningMsg = labels.outOfAbsoluteRangeWarning
|
||||||
LocalTime.parse(timeString)
|
} else if (value < scale.min || value > scale.max) {
|
||||||
} catch (err) {
|
warningMsg = labels.outOfRangeWarning
|
||||||
return true
|
} else {
|
||||||
|
warningMsg = null
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
return warningMsg
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import { View } from 'react-native'
|
|||||||
import AppText from './app-text'
|
import AppText from './app-text'
|
||||||
import styles from '../styles'
|
import styles from '../styles'
|
||||||
|
|
||||||
const FramedSegment = ({ children, ...props }) => {
|
const FramedSegment = ({children, ...props}) => {
|
||||||
const style = [styles.framedSegment, props.style]
|
const style = [styles.framedSegment, props.style]
|
||||||
if (props.last) style.push(styles.framedSegmentLast)
|
if (props.last) style.push(styles.framedSegmentLast)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles, { iconStyles } from '../../styles'
|
import styles from '../../styles'
|
||||||
import NavigationArrow from './navigation-arrow'
|
import NavigationArrow from './navigation-arrow'
|
||||||
import Icon from 'react-native-vector-icons/Entypo'
|
|
||||||
|
|
||||||
export default function BackButtonHeader(props) {
|
export default function BackButtonHeader(props) {
|
||||||
return (
|
return (
|
||||||
@@ -21,12 +19,6 @@ export default function BackButtonHeader(props) {
|
|||||||
{props.title}
|
{props.title}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity style={styles.hiddenIcon}>
|
|
||||||
<Icon
|
|
||||||
name={'chevron-thin-right'}
|
|
||||||
{...iconStyles.hiddenIcon}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Dimensions } from 'react-native'
|
|||||||
import CycleDayHeader from './cycle-day'
|
import CycleDayHeader from './cycle-day'
|
||||||
import DefaultHeader from './default'
|
import DefaultHeader from './default'
|
||||||
import BackButtonHeader from './back-button'
|
import BackButtonHeader from './back-button'
|
||||||
import SymptomViewHeader from './symptom-view'
|
|
||||||
|
|
||||||
export default function Header(p) {
|
export default function Header(p) {
|
||||||
const middle = Dimensions.get('window').width / 2
|
const middle = Dimensions.get('window').width / 2
|
||||||
@@ -11,11 +10,7 @@ export default function Header(p) {
|
|||||||
|
|
||||||
if (props.isCycleDayOverView) {
|
if (props.isCycleDayOverView) {
|
||||||
return (<CycleDayHeader {...props} />)
|
return (<CycleDayHeader {...props} />)
|
||||||
}
|
} else if (props.showBackButton) {
|
||||||
else if (props.isSymptomView) {
|
|
||||||
return (<SymptomViewHeader {...props} />)
|
|
||||||
}
|
|
||||||
else if (props.showBackButton) {
|
|
||||||
return (<BackButtonHeader {...props} />)
|
return (<BackButtonHeader {...props} />)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ export default function NavigationArrow(props) {
|
|||||||
left: 'chevron-thin-left',
|
left: 'chevron-thin-left',
|
||||||
right: 'chevron-thin-right'
|
right: 'chevron-thin-right'
|
||||||
}[props.direction]
|
}[props.direction]
|
||||||
|
const iconPosition = {
|
||||||
|
left: 'navigationArrowLeft',
|
||||||
|
right: 'navigationArrowRight'
|
||||||
|
}[props.direction]
|
||||||
let pressHandler
|
let pressHandler
|
||||||
if (props.goBack) {
|
if (props.goBack) {
|
||||||
pressHandler = () => props.goBack()
|
pressHandler = () => props.goBack()
|
||||||
@@ -19,7 +23,7 @@ export default function NavigationArrow(props) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.navigationArrow}
|
style={[styles.navigationArrow, styles[iconPosition]]}
|
||||||
onPress={pressHandler}
|
onPress={pressHandler}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ import React from 'react'
|
|||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity
|
TouchableOpacity,
|
||||||
|
Dimensions
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles, { iconStyles } from '../../styles'
|
import styles, { iconStyles } from '../../styles'
|
||||||
import FeatherIcon from 'react-native-vector-icons/Feather'
|
import Icon from 'react-native-vector-icons/AntDesign'
|
||||||
import NavigationArrow from './navigation-arrow'
|
import NavigationArrow from './navigation-arrow'
|
||||||
import formatDate from '../helpers/format-date'
|
import formatDate from '../helpers/format-date'
|
||||||
|
|
||||||
export default function SymptomViewHeader(props) {
|
export default function SymptomViewHeader(props) {
|
||||||
|
const middle = Dimensions.get('window').width / 2
|
||||||
return (
|
return (
|
||||||
<View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}>
|
<View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}>
|
||||||
<View
|
<View
|
||||||
style={styles.accentCircle}
|
style={styles.accentCircle}
|
||||||
left={props.middle - styles.accentCircle.width / 2}
|
left={middle - styles.accentCircle.width / 2}
|
||||||
/>
|
/>
|
||||||
<NavigationArrow
|
<NavigationArrow
|
||||||
direction='left'
|
direction='left'
|
||||||
@@ -28,16 +30,19 @@ export default function SymptomViewHeader(props) {
|
|||||||
{formatDate(props.date)}
|
{formatDate(props.date)}
|
||||||
</Text>
|
</Text>
|
||||||
</View >
|
</View >
|
||||||
<TouchableOpacity
|
{ props.deleteIconActive &&
|
||||||
onPress={() => props.goToSymptomInfo()}
|
<TouchableOpacity
|
||||||
style={styles.infoButton}
|
onPress={props.deleteEntry}
|
||||||
>
|
style={[
|
||||||
<FeatherIcon
|
styles.headerDeleteButton,
|
||||||
name="info"
|
]}
|
||||||
style={styles.symptomInfoIcon}
|
>
|
||||||
{...iconStyles.symptomHeaderIcons}
|
<Icon
|
||||||
/>
|
name="delete"
|
||||||
</TouchableOpacity>
|
{...iconStyles.symptomHeaderIcons}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,3 +7,7 @@ export default function (date) {
|
|||||||
const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY')
|
const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY')
|
||||||
return formattedDate.toLowerCase()
|
return formattedDate.toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDateForShortText (date) {
|
||||||
|
return moment(date.toString()).format('dddd, MMMM Do')
|
||||||
|
}
|
||||||
+54
-81
@@ -1,14 +1,12 @@
|
|||||||
import { ChronoUnit, LocalDate } from 'js-joda'
|
import { ChronoUnit, LocalDate } from 'js-joda'
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { Dimensions, ScrollView, TouchableHighlight, View } from 'react-native'
|
import { ScrollView, View } from 'react-native'
|
||||||
import Icon from 'react-native-vector-icons/Entypo'
|
|
||||||
|
|
||||||
import DripHomeIcon from '../assets/drip-home-icons'
|
import DripHomeIcon from '../assets/drip-home-icons'
|
||||||
import { getCycleDay } from '../db'
|
import { getCycleDay } from '../db'
|
||||||
import {
|
import {
|
||||||
bleedingPrediction as predictLabels,
|
bleedingPrediction as predictLabels,
|
||||||
home as labels,
|
home as labels
|
||||||
shared,
|
|
||||||
} from '../i18n/en/labels'
|
} from '../i18n/en/labels'
|
||||||
import links from '../i18n/en/links'
|
import links from '../i18n/en/links'
|
||||||
import cycleModule from '../lib/cycle'
|
import cycleModule from '../lib/cycle'
|
||||||
@@ -16,25 +14,7 @@ import { getFertilityStatusForDay } from '../lib/sympto-adapter'
|
|||||||
import styles, { cycleDayColor, periodColor, secondaryColor } from '../styles'
|
import styles, { cycleDayColor, periodColor, secondaryColor } from '../styles'
|
||||||
import AppText from './app-text'
|
import AppText from './app-text'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
|
import { formatDateForShortText } from './helpers/format-date'
|
||||||
const ShowMoreToggler = ({ isShowingMore, onToggle }) => {
|
|
||||||
const {height, width} = Dimensions.get('window')
|
|
||||||
const leftPosition = isShowingMore ? 10 : width - 40
|
|
||||||
const style = isShowingMore ? styles.showLess : styles.showMore
|
|
||||||
const topPosition = height / 2 - styles.header.height - 30
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableHighlight
|
|
||||||
onPress={onToggle}
|
|
||||||
style={[style, { top: topPosition, left: leftPosition}]}
|
|
||||||
>
|
|
||||||
<View style={{alignItems: 'center'}}>
|
|
||||||
<AppText>{isShowingMore ? shared.less : shared.more}</AppText>
|
|
||||||
<Icon name='chevron-thin-down' />
|
|
||||||
</View>
|
|
||||||
</TouchableHighlight>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const IconText = ({ children, wrapperStyles }) => {
|
const IconText = ({ children, wrapperStyles }) => {
|
||||||
return (
|
return (
|
||||||
@@ -50,15 +30,22 @@ const HomeElement = ({ children, onPress, buttonColor, buttonLabel }) => {
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
onPress={ onPress }
|
onPress={ onPress }
|
||||||
style={ styles.homeIconElement }
|
style={ styles.homeElement }
|
||||||
>
|
>
|
||||||
{ children }
|
<View style={styles.homeIconAndText}>
|
||||||
<Button
|
{children[0]}
|
||||||
style={styles.homeButton}
|
{children[1]}
|
||||||
onPress={ onPress }
|
</View>
|
||||||
backgroundColor={ buttonColor }>
|
|
||||||
{ buttonLabel }
|
<View style={{paddingLeft: 15}}>
|
||||||
</Button>
|
{children.slice(2)}
|
||||||
|
<Button
|
||||||
|
style={styles.homeButton}
|
||||||
|
onPress={ onPress }
|
||||||
|
backgroundColor={ buttonColor }>
|
||||||
|
{ buttonLabel }
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -74,7 +61,6 @@ export default class Home extends Component {
|
|||||||
const fertilityStatus = getFertilityStatusForDay(this.todayDateString)
|
const fertilityStatus = getFertilityStatusForDay(this.todayDateString)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isShowingMore: false,
|
|
||||||
cycleDayNumber: this.getCycleDayNumber(this.todayDateString),
|
cycleDayNumber: this.getCycleDayNumber(this.todayDateString),
|
||||||
predictionText: determinePredictionText(prediction),
|
predictionText: determinePredictionText(prediction),
|
||||||
bleedingPredictionRange: getBleedingPredictionRange(prediction),
|
bleedingPredictionRange: getBleedingPredictionRange(prediction),
|
||||||
@@ -90,12 +76,8 @@ export default class Home extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleShowingMore = () => {
|
|
||||||
this.setState({ isShowingMore: !this.state.isShowingMore })
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isShowingMore, cycleDayNumber, phase, status } = this.state
|
const { cycleDayNumber, phase, status } = this.state
|
||||||
const { navigate } = this.props
|
const { navigate } = this.props
|
||||||
const cycleDayMoreText = cycleDayNumber ?
|
const cycleDayMoreText = cycleDayNumber ?
|
||||||
labels.cycleDayKnown(cycleDayNumber) :
|
labels.cycleDayKnown(cycleDayNumber) :
|
||||||
@@ -116,13 +98,11 @@ export default class Home extends Component {
|
|||||||
<View>
|
<View>
|
||||||
<DripHomeIcon name="circle" size={80} color={cycleDayColor}/>
|
<DripHomeIcon name="circle" size={80} color={cycleDayColor}/>
|
||||||
</View>
|
</View>
|
||||||
<IconText wrapperStyles={styles.wrapperCycle}>
|
<IconText wrapperStyles={styles.wrapperIcon}>
|
||||||
{cycleDayNumber || labels.unknown}
|
{cycleDayNumber || labels.unknown}
|
||||||
</IconText>
|
</IconText>
|
||||||
|
|
||||||
{ isShowingMore &&
|
<AppText style={styles.homeDescriptionText}>{cycleDayMoreText}</AppText>
|
||||||
<AppText style={styles.paragraph}>{cycleDayMoreText}</AppText>
|
|
||||||
}
|
|
||||||
</HomeElement>
|
</HomeElement>
|
||||||
|
|
||||||
<HomeElement
|
<HomeElement
|
||||||
@@ -130,19 +110,15 @@ export default class Home extends Component {
|
|||||||
buttonColor={ periodColor }
|
buttonColor={ periodColor }
|
||||||
buttonLabel={ labels.trackPeriod }
|
buttonLabel={ labels.trackPeriod }
|
||||||
>
|
>
|
||||||
<View>
|
<DripHomeIcon name="drop" size={100} color={periodColor} />
|
||||||
<DripHomeIcon name="drop" size={105} color={periodColor} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<IconText wrapperStyles={styles.wrapperDrop}>
|
<IconText wrapperStyles={{top: '45%', ...styles.wrapperIcon}}>
|
||||||
{this.state.bleedingPredictionRange}
|
{this.state.bleedingPredictionRange}
|
||||||
</IconText>
|
</IconText>
|
||||||
|
|
||||||
{ isShowingMore &&
|
<AppText style={styles.homeDescriptionText}>
|
||||||
<AppText style={styles.paragraph}>
|
{this.state.predictionText}
|
||||||
{this.state.predictionText}
|
</AppText>
|
||||||
</AppText>
|
|
||||||
}
|
|
||||||
</HomeElement>
|
</HomeElement>
|
||||||
|
|
||||||
<HomeElement
|
<HomeElement
|
||||||
@@ -152,51 +128,50 @@ export default class Home extends Component {
|
|||||||
>
|
>
|
||||||
<View style={styles.homeCircle}/>
|
<View style={styles.homeCircle}/>
|
||||||
|
|
||||||
<IconText wrapperStyles={styles.wrapperCircle}>
|
<IconText wrapperStyles={styles.wrapperIcon}>
|
||||||
{ phase ? phase.toString() : labels.unknown }
|
{ phase ? phase.toString() : labels.unknown }
|
||||||
</IconText>
|
</IconText>
|
||||||
|
|
||||||
{ phase &&
|
{ phase &&
|
||||||
<AppText>{`${labels.phase(phase)} (${status})`}</AppText>
|
<AppText style={styles.homeDescriptionText}>
|
||||||
}
|
{`${labels.phase(phase)} (${status})`}
|
||||||
{ isShowingMore &&
|
</AppText>
|
||||||
<View>
|
|
||||||
<AppText styles={styles.paragraph}>
|
|
||||||
{ `${statusText} ${links.wiki.url}.` }
|
|
||||||
</AppText>
|
|
||||||
</View>
|
|
||||||
}
|
}
|
||||||
|
<AppText style={styles.homeDescriptionText}>
|
||||||
|
{ `${statusText} Visit ${links.wiki.url}.` }
|
||||||
|
</AppText>
|
||||||
</HomeElement>
|
</HomeElement>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<ShowMoreToggler
|
|
||||||
isShowingMore={isShowingMore}
|
|
||||||
onToggle={this.toggleShowingMore}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTimes(prediction) {
|
||||||
|
const todayDate = LocalDate.now()
|
||||||
|
const predictedBleedingStart = LocalDate.parse(prediction[0][0])
|
||||||
|
/* the range of predicted bleeding days can be either 3 or 5 */
|
||||||
|
const predictedBleedingEnd = LocalDate.parse(prediction[0][ prediction[0].length - 1 ])
|
||||||
|
const daysToEnd = todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)
|
||||||
|
return { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd }
|
||||||
|
}
|
||||||
|
|
||||||
function determinePredictionText(bleedingPrediction) {
|
function determinePredictionText(bleedingPrediction) {
|
||||||
if (!bleedingPrediction.length) return predictLabels.noPrediction
|
if (!bleedingPrediction.length) return predictLabels.noPrediction
|
||||||
const todayDate = LocalDate.now()
|
const { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd } = getTimes(bleedingPrediction)
|
||||||
const bleedingStart = LocalDate.parse(bleedingPrediction[0][0])
|
if (todayDate.isBefore(predictedBleedingStart)) {
|
||||||
const bleedingEnd = LocalDate.parse(
|
|
||||||
bleedingPrediction[0][ bleedingPrediction[0].length - 1 ]
|
|
||||||
)
|
|
||||||
if (todayDate.isBefore(bleedingStart)) {
|
|
||||||
return predictLabels.predictionInFuture(
|
return predictLabels.predictionInFuture(
|
||||||
todayDate.until(bleedingStart, ChronoUnit.DAYS),
|
todayDate.until(predictedBleedingStart, ChronoUnit.DAYS),
|
||||||
todayDate.until(bleedingEnd, ChronoUnit.DAYS)
|
todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (todayDate.isAfter(bleedingEnd)) {
|
if (todayDate.isAfter(predictedBleedingEnd)) {
|
||||||
return predictLabels.predictionInPast(
|
return predictLabels.predictionInPast(
|
||||||
bleedingStart.toString(), bleedingEnd.toString()
|
formatDateForShortText(predictedBleedingStart),
|
||||||
|
formatDateForShortText(predictedBleedingEnd)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const daysToEnd = todayDate.until(bleedingEnd, ChronoUnit.DAYS)
|
|
||||||
if (daysToEnd === 0) {
|
if (daysToEnd === 0) {
|
||||||
return predictLabels.predictionStartedNoDaysLeft
|
return predictLabels.predictionStartedNoDaysLeft
|
||||||
} else if (daysToEnd === 1) {
|
} else if (daysToEnd === 1) {
|
||||||
@@ -208,14 +183,12 @@ function determinePredictionText(bleedingPrediction) {
|
|||||||
|
|
||||||
function getBleedingPredictionRange(prediction) {
|
function getBleedingPredictionRange(prediction) {
|
||||||
if (!prediction.length) return labels.unknown
|
if (!prediction.length) return labels.unknown
|
||||||
const todayDate = LocalDate.now()
|
const { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd } = getTimes(prediction)
|
||||||
const bleedingStart = LocalDate.parse(prediction[0][0])
|
if (todayDate.isBefore(predictedBleedingStart)) {
|
||||||
const bleedingEnd = LocalDate.parse(prediction[0][ prediction[0].length - 1 ])
|
return `${todayDate.until(predictedBleedingStart, ChronoUnit.DAYS)}-${todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)}`
|
||||||
if (todayDate.isBefore(bleedingStart)) {
|
|
||||||
return `${todayDate.until(bleedingStart, ChronoUnit.DAYS)}-${todayDate.until(bleedingEnd, ChronoUnit.DAYS)}`
|
|
||||||
}
|
}
|
||||||
if (todayDate.isAfter(bleedingEnd)) {
|
if (todayDate.isAfter(predictedBleedingEnd)) {
|
||||||
return labels.unknown
|
return labels.unknown
|
||||||
}
|
}
|
||||||
return '0'
|
return (daysToEnd === 0 ? '0' : `0 - ${daysToEnd}`)
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View, TextInput, TouchableOpacity, Alert, Image } from 'react-native'
|
import { View, TextInput, TouchableOpacity, Alert } from 'react-native'
|
||||||
import nodejs from 'nodejs-mobile-react-native'
|
import nodejs from 'nodejs-mobile-react-native'
|
||||||
import { saveEncryptionFlag } from '../local-storage'
|
import { saveEncryptionFlag } from '../local-storage'
|
||||||
import AppText from './app-text'
|
import AppText from './app-text'
|
||||||
|
import Header from './header'
|
||||||
import styles from '../styles'
|
import styles from '../styles'
|
||||||
import { passwordPrompt as labels, shared } from '../i18n/en/labels'
|
import { passwordPrompt as labels, shared, menuTitles } from '../i18n/en/labels'
|
||||||
import { requestHash, deleteDbAndOpenNew, openDb } from '../db'
|
import { requestHash, deleteDbAndOpenNew, openDb } from '../db'
|
||||||
|
|
||||||
export default class PasswordPrompt extends Component {
|
export default class PasswordPrompt extends Component {
|
||||||
@@ -87,12 +88,10 @@ export default class PasswordPrompt extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View flex={1}>
|
<View flex={1}>
|
||||||
|
<Header title={menuTitles.PasswordPrompt.toLowerCase()} />
|
||||||
{this.state.showPasswordPrompt &&
|
{this.state.showPasswordPrompt &&
|
||||||
<View style={styles.passwordPromptPage}>
|
<View style={styles.passwordPromptPage}>
|
||||||
<Image
|
|
||||||
source={require('../assets/drip_small.png')}
|
|
||||||
style={styles.passwordPromptImage}
|
|
||||||
/>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
onChangeText={val => this.setState({ password: val })}
|
onChangeText={val => this.setState({ password: val })}
|
||||||
style={styles.passwordPromptField}
|
style={styles.passwordPromptField}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export default class AboutSection extends Component {
|
|||||||
<FramedSegment title={labels.credits.title}>
|
<FramedSegment title={labels.credits.title}>
|
||||||
<AppText>{labels.credits.note}</AppText>
|
<AppText>{labels.credits.note}</AppText>
|
||||||
</FramedSegment>
|
</FramedSegment>
|
||||||
|
<FramedSegment title={labels.donate.title}>
|
||||||
|
<AppText>{labels.donate.note}</AppText>
|
||||||
|
</FramedSegment>
|
||||||
<FramedSegment title={labels.website.title}>
|
<FramedSegment title={labels.website.title}>
|
||||||
<AppText>{links.website.url}</AppText>
|
<AppText>{links.website.url}</AppText>
|
||||||
</FramedSegment>
|
</FramedSegment>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import RNFS from 'react-native-fs'
|
import RNFS from 'react-native-fs'
|
||||||
import { Alert, ToastAndroid } from 'react-native'
|
import { Alert, ToastAndroid } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { clearDb, isDbEmpty } from '../../../db'
|
import { clearDb, isDbEmpty } from '../../../db'
|
||||||
import { hasEncryptionObservable } from '../../../local-storage'
|
import { hasEncryptionObservable } from '../../../local-storage'
|
||||||
@@ -24,6 +25,7 @@ export default class DeleteData extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAlertConfirmation = () => {
|
onAlertConfirmation = () => {
|
||||||
|
this.props.onStartDeletion()
|
||||||
if (this.state.isPasswordSet) {
|
if (this.state.isPasswordSet) {
|
||||||
this.setState({ isConfirmingWithPassword: true })
|
this.setState({ isConfirmingWithPassword: true })
|
||||||
} else {
|
} else {
|
||||||
@@ -78,8 +80,9 @@ export default class DeleteData extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isConfirmingWithPassword } = this.state
|
const { isConfirmingWithPassword } = this.state
|
||||||
|
const { isDeletingData } = this.props
|
||||||
|
|
||||||
if (isConfirmingWithPassword) {
|
if (isConfirmingWithPassword && isDeletingData) {
|
||||||
return (
|
return (
|
||||||
<ConfirmWithPassword
|
<ConfirmWithPassword
|
||||||
onSuccess={this.deleteAppData}
|
onSuccess={this.deleteAppData}
|
||||||
@@ -95,3 +98,8 @@ export default class DeleteData extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeleteData.propTypes = {
|
||||||
|
isDeletingData: PropTypes.bool,
|
||||||
|
onStartDeletion: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
@@ -6,23 +6,23 @@ import { shared as sharedLabels } from '../../../i18n/en/labels'
|
|||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
import alertError from '../shared/alert-error'
|
import alertError from '../shared/alert-error'
|
||||||
|
|
||||||
export default function openImportDialogAndImport() {
|
export function openImportDialog(onImportData) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
labels.import.title,
|
labels.import.title,
|
||||||
labels.import.message,
|
labels.import.message,
|
||||||
[{
|
[{
|
||||||
text: labels.import.replaceOption,
|
text: sharedLabels.cancel, style: 'cancel', onPress: () => { }
|
||||||
onPress: () => getFileContentAndImport({ deleteExisting: false })
|
|
||||||
}, {
|
}, {
|
||||||
text: labels.import.deleteOption,
|
text: labels.import.deleteOption,
|
||||||
onPress: () => getFileContentAndImport({ deleteExisting: true })
|
onPress: () => onImportData(true)
|
||||||
}, {
|
}, {
|
||||||
text: sharedLabels.cancel, style: 'cancel', onPress: () => { }
|
text: labels.import.replaceOption,
|
||||||
|
onPress: () => onImportData(false)
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFileContentAndImport({ deleteExisting }) {
|
export async function getFileContent() {
|
||||||
let fileInfo
|
let fileInfo
|
||||||
try {
|
try {
|
||||||
fileInfo = await new Promise((resolve, reject) => {
|
fileInfo = await new Promise((resolve, reject) => {
|
||||||
@@ -45,8 +45,13 @@ async function getFileContentAndImport({ deleteExisting }) {
|
|||||||
return importError(labels.import.errors.couldNotOpenFile)
|
return importError(labels.import.errors.couldNotOpenFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fileContent
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importData(shouldDeleteExistingData, fileContent) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await importCsv(fileContent, deleteExisting)
|
await importCsv(fileContent, shouldDeleteExistingData)
|
||||||
Alert.alert(sharedLabels.successTitle, labels.import.success.message)
|
Alert.alert(sharedLabels.successTitle, labels.import.success.message)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
importError(err.message)
|
importError(err.message)
|
||||||
|
|||||||
@@ -1,37 +1,91 @@
|
|||||||
import React from 'react'
|
import React, { Component } from 'react'
|
||||||
import { ScrollView } from 'react-native'
|
import { ScrollView, View } from 'react-native'
|
||||||
import AppText from '../../app-text'
|
import AppText from '../../app-text'
|
||||||
import FramedSegment from '../../framed-segment'
|
import FramedSegment from '../../framed-segment'
|
||||||
|
import AppLoadingView from '../../app-loading'
|
||||||
import SettingsButton from '../shared/settings-button'
|
import SettingsButton from '../shared/settings-button'
|
||||||
import openImportDialogAndImport from './import-dialog'
|
import { openImportDialog, getFileContent, importData } from './import-dialog'
|
||||||
import openShareDialogAndExport from './export-dialog'
|
import openShareDialogAndExport from './export-dialog'
|
||||||
import DeleteData from './delete-data'
|
import DeleteData from './delete-data'
|
||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
|
|
||||||
const DataManagement = () => {
|
export default class DataManagement extends Component {
|
||||||
return (
|
|
||||||
<ScrollView>
|
|
||||||
<FramedSegment title={labels.export.button}>
|
|
||||||
<AppText>{labels.export.segmentExplainer}</AppText>
|
|
||||||
<SettingsButton onPress={openShareDialogAndExport}>
|
|
||||||
{labels.export.button}
|
|
||||||
</SettingsButton>
|
|
||||||
</FramedSegment>
|
|
||||||
<FramedSegment title={labels.import.button}>
|
|
||||||
<AppText>{labels.import.segmentExplainer}</AppText>
|
|
||||||
<SettingsButton onPress={openImportDialogAndImport}>
|
|
||||||
{labels.import.button}
|
|
||||||
</SettingsButton>
|
|
||||||
</FramedSegment>
|
|
||||||
<FramedSegment
|
|
||||||
title={labels.deleteSegment.title}
|
|
||||||
last
|
|
||||||
>
|
|
||||||
<AppText>{labels.deleteSegment.explainer}</AppText>
|
|
||||||
<DeleteData />
|
|
||||||
</FramedSegment>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DataManagement
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
isLoading: false,
|
||||||
|
currentAction: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startLoading = () => {
|
||||||
|
this.setState({ isLoading: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
endLoading = () => {
|
||||||
|
this.setState({ isLoading: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
startImportFlow = async (shouldDeleteExistingData) => {
|
||||||
|
this.startLoading()
|
||||||
|
const fileContent = await getFileContent()
|
||||||
|
if (fileContent) {
|
||||||
|
await importData(shouldDeleteExistingData, fileContent)
|
||||||
|
}
|
||||||
|
this.endLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
startExport = () => {
|
||||||
|
this.setCurrentAction('export')
|
||||||
|
openShareDialogAndExport()
|
||||||
|
}
|
||||||
|
|
||||||
|
startImport = () => {
|
||||||
|
this.setCurrentAction('import')
|
||||||
|
openImportDialog(this.startImportFlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentAction = (action) => {
|
||||||
|
this.setState({ currentAction: action })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { currentAction } = this.state
|
||||||
|
return (
|
||||||
|
<View flex={1}>
|
||||||
|
{this.state.isLoading && <AppLoadingView />}
|
||||||
|
{!this.state.isLoading &&
|
||||||
|
<ScrollView>
|
||||||
|
<View>
|
||||||
|
<FramedSegment title={labels.export.button}>
|
||||||
|
<AppText>{labels.export.segmentExplainer}</AppText>
|
||||||
|
<SettingsButton onPress={this.startExport}>
|
||||||
|
{labels.export.button}
|
||||||
|
</SettingsButton>
|
||||||
|
</FramedSegment>
|
||||||
|
<FramedSegment title={labels.import.button}>
|
||||||
|
<AppText>{labels.import.segmentExplainer}</AppText>
|
||||||
|
<SettingsButton
|
||||||
|
onPress= {this.startImport}
|
||||||
|
>
|
||||||
|
{labels.import.button}
|
||||||
|
</SettingsButton>
|
||||||
|
</FramedSegment>
|
||||||
|
<FramedSegment
|
||||||
|
title={labels.deleteSegment.title}
|
||||||
|
last
|
||||||
|
>
|
||||||
|
<AppText>{labels.deleteSegment.explainer}</AppText>
|
||||||
|
<DeleteData
|
||||||
|
isDeletingData = { currentAction === 'delete' }
|
||||||
|
onStartDeletion = {() => this.setCurrentAction('delete')}
|
||||||
|
/>
|
||||||
|
</FramedSegment>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
|
|||||||
import {
|
import {
|
||||||
ScrollView, View
|
ScrollView, View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import styles, { iconStyles } from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
import AppText from '../../app-text'
|
import AppText from '../../app-text'
|
||||||
import FramedSegment from '../../framed-segment'
|
import FramedSegment from '../../framed-segment'
|
||||||
@@ -28,8 +28,8 @@ export default class Settings extends Component {
|
|||||||
</FramedSegment>
|
</FramedSegment>
|
||||||
<FramedSegment style={styles.framedSegmentLast} >
|
<FramedSegment style={styles.framedSegmentLast} >
|
||||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||||
<Icon name="info-with-circle" style={iconStyles.infoInHeading}/>
|
<Icon name="info-with-circle"/>
|
||||||
<AppText style={styles.framedSegmentTitle}>{`${labels.preOvu.title} `}</AppText>
|
<AppText style={styles.framedSegmentTitle}>{` ${labels.preOvu.title} `}</AppText>
|
||||||
</View>
|
</View>
|
||||||
<AppText>{labels.preOvu.note}</AppText>
|
<AppText>{labels.preOvu.note}</AppText>
|
||||||
</FramedSegment>
|
</FramedSegment>
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { TextInput } from 'react-native'
|
import PropTypes from 'prop-types'
|
||||||
import styles, {secondaryColor} from '../../../styles'
|
import AppTextInput from '../../app-text-input'
|
||||||
|
|
||||||
|
import styles from '../../../styles'
|
||||||
|
|
||||||
export default function PasswordField(props) {
|
export default function PasswordField(props) {
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<AppTextInput
|
||||||
style={styles.passwordField}
|
style={ styles.passwordField }
|
||||||
autoFocus={props.autoFocus === false ? false : true}
|
secureTextEntry
|
||||||
secureTextEntry={true}
|
{...props}
|
||||||
onChangeText={props.onChangeText}
|
|
||||||
value={props.value}
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
borderWidth={1}
|
|
||||||
borderColor={secondaryColor}
|
|
||||||
borderStyle={'solid'}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PasswordField.propTypes = {
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
onChangeText: PropTypes.func,
|
||||||
|
autoFocus: PropTypes.bool
|
||||||
|
}
|
||||||
|
|||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
const TemperatureSchema = {
|
||||||
|
name: 'Temperature',
|
||||||
|
properties: {
|
||||||
|
value: 'double',
|
||||||
|
exclude: 'bool',
|
||||||
|
time: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BleedingSchema = {
|
||||||
|
name: 'Bleeding',
|
||||||
|
properties: {
|
||||||
|
value: 'int',
|
||||||
|
exclude: 'bool'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MucusSchema = {
|
||||||
|
name: 'Mucus',
|
||||||
|
properties: {
|
||||||
|
feeling: { type: 'int', optional: true },
|
||||||
|
texture: { type: 'int', optional: true },
|
||||||
|
value: { type: 'int', optional: true },
|
||||||
|
exclude: 'bool'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CervixSchema = {
|
||||||
|
name: 'Cervix',
|
||||||
|
properties: {
|
||||||
|
opening: { type: 'int', optional: true },
|
||||||
|
firmness: { type: 'int', optional: true },
|
||||||
|
position: {type: 'int', optional: true },
|
||||||
|
exclude: 'bool'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoteSchema = {
|
||||||
|
name: 'Note',
|
||||||
|
properties: {
|
||||||
|
value: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DesireSchema = {
|
||||||
|
name: 'Desire',
|
||||||
|
properties: {
|
||||||
|
value: 'int'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SexSchema = {
|
||||||
|
name: 'Sex',
|
||||||
|
properties: {
|
||||||
|
solo: { type: 'bool', optional: true },
|
||||||
|
partner: { type: 'bool', optional: true },
|
||||||
|
condom: { type: 'bool', optional: true },
|
||||||
|
pill: { type: 'bool', optional: true },
|
||||||
|
iud: { type: 'bool', optional: true },
|
||||||
|
patch: { type: 'bool', optional: true },
|
||||||
|
ring: { type: 'bool', optional: true },
|
||||||
|
implant: { type: 'bool', optional: true },
|
||||||
|
diaphragm: { type: 'bool', optional: true },
|
||||||
|
none: { type: 'bool', optional: true },
|
||||||
|
other: { type: 'bool', optional: true },
|
||||||
|
note: { type: 'string', optional: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PainSchema = {
|
||||||
|
name: 'Pain',
|
||||||
|
properties: {
|
||||||
|
cramps: { type: 'bool', optional: true },
|
||||||
|
ovulationPain: { type: 'bool', optional: true },
|
||||||
|
headache: { type: 'bool', optional: true },
|
||||||
|
backache: { type: 'bool', optional: true },
|
||||||
|
nausea: { type: 'bool', optional: true },
|
||||||
|
tenderBreasts: { type: 'bool', optional: true },
|
||||||
|
migraine: { type: 'bool', optional: true },
|
||||||
|
other: { type: 'bool', optional: true },
|
||||||
|
note: { type: 'string', optional: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MoodSchema = {
|
||||||
|
name: 'Mood',
|
||||||
|
properties: {
|
||||||
|
happy: { type: 'bool', optional: true },
|
||||||
|
sad: { type: 'bool', optional: true },
|
||||||
|
stressed: { type: 'bool', optional: true },
|
||||||
|
balanced: { type: 'bool', optional: true },
|
||||||
|
fine: { type: 'bool', optional: true },
|
||||||
|
anxious: { type: 'bool', optional: true },
|
||||||
|
energetic: { type: 'bool', optional: true },
|
||||||
|
fatigue: { type: 'bool', optional: true },
|
||||||
|
angry: { type: 'bool', optional: true },
|
||||||
|
other: { type: 'bool', optional: true },
|
||||||
|
note: { type: 'string', optional: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CycleDaySchema = {
|
||||||
|
name: 'CycleDay',
|
||||||
|
primaryKey: 'date',
|
||||||
|
properties: {
|
||||||
|
date: 'string',
|
||||||
|
temperature: {
|
||||||
|
type: 'Temperature',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
isCycleStart: 'bool',
|
||||||
|
bleeding: {
|
||||||
|
type: 'Bleeding',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
mucus: {
|
||||||
|
type: 'Mucus',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
cervix: {
|
||||||
|
type: 'Cervix',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: 'Note',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
desire: {
|
||||||
|
type: 'Desire',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
sex: {
|
||||||
|
type: 'Sex',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
pain: {
|
||||||
|
type: 'Pain',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
mood: {
|
||||||
|
type: 'Mood',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: [
|
||||||
|
CycleDaySchema,
|
||||||
|
TemperatureSchema,
|
||||||
|
BleedingSchema,
|
||||||
|
MucusSchema,
|
||||||
|
CervixSchema,
|
||||||
|
NoteSchema,
|
||||||
|
DesireSchema,
|
||||||
|
SexSchema,
|
||||||
|
PainSchema,
|
||||||
|
MoodSchema
|
||||||
|
],
|
||||||
|
schemaVersion: 4,
|
||||||
|
migration: (oldRealm) => {
|
||||||
|
if (oldRealm.schemaVersion >= 4) return
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -2,5 +2,6 @@ import schema0 from './0.js'
|
|||||||
import schema1 from './1.js'
|
import schema1 from './1.js'
|
||||||
import schema2 from './2.js'
|
import schema2 from './2.js'
|
||||||
import schema3 from './3.js'
|
import schema3 from './3.js'
|
||||||
|
import schema4 from './4.js'
|
||||||
|
|
||||||
export default [schema0, schema1, schema2, schema3]
|
export default [schema0, schema1, schema2, schema3, schema4]
|
||||||
+39
-30
@@ -14,6 +14,11 @@ export const bleeding = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const cervix = {
|
export const cervix = {
|
||||||
|
subcategories: {
|
||||||
|
opening: 'opening',
|
||||||
|
firmness: 'firmness',
|
||||||
|
position: 'position'
|
||||||
|
},
|
||||||
opening: {
|
opening: {
|
||||||
categories: ['closed', 'medium', 'open'],
|
categories: ['closed', 'medium', 'open'],
|
||||||
explainer: 'Is your cervix open or closed?'
|
explainer: 'Is your cervix open or closed?'
|
||||||
@@ -30,6 +35,10 @@ export const cervix = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const mucus = {
|
export const mucus = {
|
||||||
|
subcategories: {
|
||||||
|
feeling: 'feeling',
|
||||||
|
texture: 'texture'
|
||||||
|
},
|
||||||
feeling: {
|
feeling: {
|
||||||
categories: ['dry', 'nothing', 'wet', 'slippery'],
|
categories: ['dry', 'nothing', 'wet', 'slippery'],
|
||||||
explainer: 'What does your vaginal entrance feel like?'
|
explainer: 'What does your vaginal entrance feel like?'
|
||||||
@@ -49,8 +58,8 @@ export const desire = {
|
|||||||
|
|
||||||
export const sex = {
|
export const sex = {
|
||||||
categories:{
|
categories:{
|
||||||
solo: 'Solo',
|
solo: 'solo',
|
||||||
partner: 'Partner',
|
partner: 'partner',
|
||||||
},
|
},
|
||||||
header: "Activity",
|
header: "Activity",
|
||||||
explainer: 'Were you sexually active today?',
|
explainer: 'Were you sexually active today?',
|
||||||
@@ -58,15 +67,15 @@ export const sex = {
|
|||||||
|
|
||||||
export const contraceptives = {
|
export const contraceptives = {
|
||||||
categories:{
|
categories:{
|
||||||
condom: 'Condom',
|
condom: 'condom',
|
||||||
pill: 'Pill',
|
pill: 'pill',
|
||||||
iud: 'IUD',
|
iud: 'iud',
|
||||||
patch: 'Patch',
|
patch: 'patch',
|
||||||
ring: 'Ring',
|
ring: 'ring',
|
||||||
implant: 'Implant',
|
implant: 'implant',
|
||||||
diaphragm: 'Diaphragm',
|
diaphragm: 'diaphragm',
|
||||||
none: 'None',
|
none: 'none',
|
||||||
other: 'Other',
|
other: 'other',
|
||||||
},
|
},
|
||||||
header: "Contraceptives",
|
header: "Contraceptives",
|
||||||
explainer: 'Did you use contraceptives?'
|
explainer: 'Did you use contraceptives?'
|
||||||
@@ -74,30 +83,30 @@ export const contraceptives = {
|
|||||||
|
|
||||||
export const pain = {
|
export const pain = {
|
||||||
categories: {
|
categories: {
|
||||||
cramps: 'Cramps',
|
cramps: 'cramps',
|
||||||
ovulationPain: 'Ovulation pain',
|
ovulationPain: 'ovulation pain',
|
||||||
headache: 'Headache',
|
headache: 'headache',
|
||||||
backache: 'Backache',
|
backache: 'backache',
|
||||||
nausea: 'Nausea',
|
nausea: 'nausea',
|
||||||
tenderBreasts: 'Tender breasts',
|
tenderBreasts: 'tender breasts',
|
||||||
migraine: 'Migraine',
|
migraine: 'migraine',
|
||||||
other: 'Other'
|
other: 'other'
|
||||||
},
|
},
|
||||||
explainer: 'How did your body feel today?'
|
explainer: 'How did your body feel today?'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mood = {
|
export const mood = {
|
||||||
categories: {
|
categories: {
|
||||||
happy: 'Happy',
|
happy: 'happy',
|
||||||
sad: 'Sad',
|
sad: 'sad',
|
||||||
stressed: 'Stressed',
|
stressed: 'stressed',
|
||||||
balanced: 'Balanced',
|
balanced: 'balanced',
|
||||||
fine: 'Fine',
|
fine: 'fine',
|
||||||
anxious: 'Anxious',
|
anxious: 'anxious',
|
||||||
energetic: 'Energetic',
|
energetic: 'energetic',
|
||||||
fatigue: 'Fatigue',
|
fatigue: 'fatigue',
|
||||||
angry: 'Angry',
|
angry: 'angry',
|
||||||
other: 'Other'
|
other: 'other'
|
||||||
},
|
},
|
||||||
explainer: 'How did you feel today?'
|
explainer: 'How did you feel today?'
|
||||||
}
|
}
|
||||||
@@ -107,7 +116,7 @@ export const temperature = {
|
|||||||
outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
|
outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
|
||||||
saveAnyway: 'Save anyway',
|
saveAnyway: 'Save anyway',
|
||||||
temperature: {
|
temperature: {
|
||||||
header: "Temperature (°C)",
|
header: "Temperature",
|
||||||
explainer: 'Take your temperature right after waking up, before getting out of bed'
|
explainer: 'Take your temperature right after waking up, before getting out of bed'
|
||||||
},
|
},
|
||||||
time: "Time",
|
time: "Time",
|
||||||
|
|||||||
+13
-14
@@ -15,8 +15,6 @@ export const shared = {
|
|||||||
date: 'Date',
|
date: 'Date',
|
||||||
cycleDayWithLinebreak: 'Cycle\nday',
|
cycleDayWithLinebreak: 'Cycle\nday',
|
||||||
loading: 'Loading ...',
|
loading: 'Loading ...',
|
||||||
more: 'more',
|
|
||||||
less: 'less',
|
|
||||||
enter: 'Enter'
|
enter: 'Enter'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,15 +30,15 @@ export const headerTitles = {
|
|||||||
Password: settingsTitles.password,
|
Password: settingsTitles.password,
|
||||||
About: settingsTitles.about,
|
About: settingsTitles.about,
|
||||||
License: settingsTitles.license,
|
License: settingsTitles.license,
|
||||||
BleedingEditView: 'Bleeding',
|
bleeding: 'Bleeding',
|
||||||
TemperatureEditView: 'Temperature',
|
temperature: 'Temperature',
|
||||||
MucusEditView: 'Mucus',
|
mucus: 'Cervical Mucus',
|
||||||
CervixEditView: 'Cervix',
|
cervix: 'Cervix',
|
||||||
NoteEditView: 'Note',
|
note: 'Note',
|
||||||
DesireEditView: 'Desire',
|
desire: 'Desire',
|
||||||
SexEditView: 'Sex',
|
sex: 'Sex',
|
||||||
PainEditView: 'Pain',
|
pain: 'Pain',
|
||||||
MoodEditView: 'Mood',
|
mood: 'Mood',
|
||||||
InfoSymptom: 'Info'
|
InfoSymptom: 'Info'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +48,7 @@ export const menuTitles = {
|
|||||||
Chart: 'Chart',
|
Chart: 'Chart',
|
||||||
Stats: 'Stats',
|
Stats: 'Stats',
|
||||||
Settings: 'Settings',
|
Settings: 'Settings',
|
||||||
|
PasswordPrompt: 'Drip'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stats = {
|
export const stats = {
|
||||||
@@ -110,12 +109,12 @@ export const fertilityStatus = {
|
|||||||
infertile: 'infertile',
|
infertile: 'infertile',
|
||||||
fertileUntilEvening: 'Fertile phase ends in the evening',
|
fertileUntilEvening: 'Fertile phase ends in the evening',
|
||||||
unknown: 'We cannot show any cycle information because no period data has been added.',
|
unknown: 'We cannot show any cycle information because no period data has been added.',
|
||||||
preOvuText: "With NFP rules, you may assume 5 days of infertility at the beginning of your cycle, provided you don't observe any fertile mucus or cervix values.",
|
preOvuText: "With NFP rules, you may assume 5 days of infertility at the beginning of your cycle, provided you don't observe any fertile cervical mucus or cervix values.",
|
||||||
periOvuText: "We have not been able to detect both a temperature shift and mucus or cervix shift. Please find more information on NFP rules here:",
|
periOvuText: "We have not been able to detect both a temperature shift and cervical mucus or cervix shift. Please find more information on NFP rules here:",
|
||||||
postOvuText: tempRule => {
|
postOvuText: tempRule => {
|
||||||
return (
|
return (
|
||||||
'We have detected a temperature shift (' + ['regular', '1st exception', '2nd exception'][tempRule] +
|
'We have detected a temperature shift (' + ['regular', '1st exception', '2nd exception'][tempRule] +
|
||||||
' temperature rule), as well as a mucus shift according to NFP rules. You may assume infertility, but always remember to ' +
|
' temperature rule), as well as a cervical mucus shift according to NFP rules. You may assume infertility, but always remember to ' +
|
||||||
'double-check for yourself. Make sure the data makes sense to you.'
|
'double-check for yourself. Make sure the data makes sense to you.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,8 @@ export default {
|
|||||||
website: {
|
website: {
|
||||||
url: 'https://bloodyhealth.gitlab.io/'
|
url: 'https://bloodyhealth.gitlab.io/'
|
||||||
},
|
},
|
||||||
|
donate: {
|
||||||
|
url: 'https://ko-fi.com/dripapp',
|
||||||
|
text: 'here'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -74,8 +74,8 @@ export default {
|
|||||||
},
|
},
|
||||||
useCervix: {
|
useCervix: {
|
||||||
title: 'Secondary symptom',
|
title: 'Secondary symptom',
|
||||||
cervixModeOn: 'Cervix values are being used for symptothermal fertility detection. You can switch here to use mucus values for symptothermal fertility detection',
|
cervixModeOn: 'Cervix values are being used for symptothermal fertility detection. You can switch here to use cervical mucus values for symptothermal fertility detection',
|
||||||
cervixModeOff: 'By default, mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection'
|
cervixModeOff: 'By default, cervical mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection'
|
||||||
},
|
},
|
||||||
passwordSettings: {
|
passwordSettings: {
|
||||||
title: 'App password',
|
title: 'App password',
|
||||||
@@ -123,5 +123,9 @@ You can contact us by bloodyhealth@mailbox.org.`
|
|||||||
credits: {
|
credits: {
|
||||||
title: 'Credits',
|
title: 'Credits',
|
||||||
note: 'Thanks and lots of <3 to all of our contributors as well as Susanne Umscheid for the wonderful design, and Paula Härtel for the symptom icons.'
|
note: 'Thanks and lots of <3 to all of our contributors as well as Susanne Umscheid for the wonderful design, and Paula Härtel for the symptom icons.'
|
||||||
|
},
|
||||||
|
donate: {
|
||||||
|
title: 'Buy us a coffee!',
|
||||||
|
note: `The Bloody Health team is always grateful for donations, big or small, that help us maintain this app and develop new features. You can donate ${links.donate.url}. Thank you! You're awesome.`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import links from './links'
|
import links from './links'
|
||||||
|
|
||||||
export const generalInfo = {
|
export const generalInfo = {
|
||||||
chartNfp: `On the chart, you can track fertility signs. When both a valid temperature shift and a mucus or cervix shift have been detected, an orange line will be displayed on the chart. This indicates the end of the peri-ovulatory and the beginning of the post-ovulatory phase.`,
|
chartNfp: `On the chart, you can track fertility signs. When both a valid temperature shift and a cervical mucus or cervix shift have been detected, an orange line will be displayed on the chart. This indicates the end of the peri-ovulatory and the beginning of the post-ovulatory phase.`,
|
||||||
curiousNfp: `If you are curious to learn more about the sympto-thermal method that is used for fertility tracking within the app, you can visit ${links.wiki.url}.`,
|
curiousNfp: `If you are curious to learn more about the sympto-thermal method that is used for fertility tracking within the app, you can visit ${links.wiki.url}.`,
|
||||||
cycleRelation: `It may be influenced by or have an impact on your menstrual cycles and its hormonal changes.`,
|
cycleRelation: `It may be influenced by or have an impact on your menstrual cycles and its hormonal changes.`,
|
||||||
excludeExplainer: `You can exclude these values, so they won't be taken into account for any fertility calculation.`,
|
excludeExplainer: `You can exclude these values, so they won't be taken into account for any fertility calculation.`,
|
||||||
@@ -85,9 +85,9 @@ From lowest to best quality:
|
|||||||
· S = (no OR wet feeling + creamy texture),
|
· S = (no OR wet feeling + creamy texture),
|
||||||
· S+ = (any feeling + egg white texture) OR (slippery feeling + any texture).
|
· S+ = (any feeling + egg white texture) OR (slippery feeling + any texture).
|
||||||
|
|
||||||
On the chart, mucus is colored in blue: the darker the shade of blue the better the quality of your mucus.
|
On the chart, cervical mucus is colored in blue: the darker the shade of blue the better the quality of your cervical mucus.
|
||||||
|
|
||||||
Please note that drip does not yet support "parenthesis values": According to NFP rules, you can qualify a mucus value by putting parentheses around it, to indicate that it doesn't fully meet the descriptors of one of the five categories, and instead is in between. This functionality will be supported in the future.
|
Please note that drip does not yet support "parenthesis values": According to NFP rules, you can qualify a cervical mucus value by putting parentheses around it, to indicate that it doesn't fully meet the descriptors of one of the five categories, and instead is in between. This functionality will be supported in the future.
|
||||||
|
|
||||||
${generalInfo.chartNfp}
|
${generalInfo.chartNfp}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
};
|
};
|
||||||
objectVersion = 46;
|
objectVersion = 46;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
||||||
@@ -72,6 +71,8 @@
|
|||||||
E09F3B05A4F84E9883101CC7 /* libRNNodeJsMobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */; };
|
E09F3B05A4F84E9883101CC7 /* libRNNodeJsMobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */; };
|
||||||
E43EF009AC8C4698AB322190 /* NodeMobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C225FC4966694B9FBD32E946 /* NodeMobile.framework */; };
|
E43EF009AC8C4698AB322190 /* NodeMobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C225FC4966694B9FBD32E946 /* NodeMobile.framework */; };
|
||||||
E4584E55EEC24302A3E84A23 /* nodejs-project in Resources */ = {isa = PBXBuildFile; fileRef = 6466AE2461BE4FA88B8372F0 /* nodejs-project */; };
|
E4584E55EEC24302A3E84A23 /* nodejs-project in Resources */ = {isa = PBXBuildFile; fileRef = 6466AE2461BE4FA88B8372F0 /* nodejs-project */; };
|
||||||
|
77500FAD5ADD402AAD2D9972 /* AntDesign.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3102FB76D69C42938E0E126D /* AntDesign.ttf */; };
|
||||||
|
AA800A96BB73482AA90E29B8 /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -515,6 +516,8 @@
|
|||||||
F5039D0A572B4BBCB7995891 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
|
F5039D0A572B4BBCB7995891 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
|
||||||
F59A471BDE4144A1A41D4B52 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; };
|
F59A471BDE4144A1A41D4B52 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; };
|
||||||
F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNNodeJsMobile.a; sourceTree = "<group>"; };
|
F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNNodeJsMobile.a; sourceTree = "<group>"; };
|
||||||
|
3102FB76D69C42938E0E126D /* AntDesign.ttf */ = {isa = PBXFileReference; name = "AntDesign.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
|
885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; name = "OpenSans-LightItalic.ttf"; path = "../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -606,6 +609,8 @@
|
|||||||
5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */,
|
5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */,
|
||||||
673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */,
|
673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */,
|
||||||
644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */,
|
644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */,
|
||||||
|
3102FB76D69C42938E0E126D /* AntDesign.ttf */,
|
||||||
|
885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */,
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -949,9 +954,9 @@
|
|||||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||||
2B572382D4504B8FB4B9D251 /* Embed Frameworks */,
|
2B572382D4504B8FB4B9D251 /* Embed Frameworks */,
|
||||||
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */,
|
D6AE48E8124449EE863F342D /* Build NodeJS Mobile Native Modules */,
|
||||||
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */,
|
7D43D3729FAE42C0859CDC36 /* Sign NodeJS Mobile Native Modules */,
|
||||||
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */,
|
54483AF1DA474A71931D6D6A /* Remove NodeJS Mobile Framework Simulator Strips */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -1510,6 +1515,8 @@
|
|||||||
B9A5B9946C4C456C823B7641 /* Prompt-ExtraLight.ttf in Resources */,
|
B9A5B9946C4C456C823B7641 /* Prompt-ExtraLight.ttf in Resources */,
|
||||||
5D921C348AC14944835A4D82 /* OpenSans-Light.ttf in Resources */,
|
5D921C348AC14944835A4D82 /* OpenSans-Light.ttf in Resources */,
|
||||||
71D0BCE4666A4AB8A0874B5A /* OpenSans-SemiBold.ttf in Resources */,
|
71D0BCE4666A4AB8A0874B5A /* OpenSans-SemiBold.ttf in Resources */,
|
||||||
|
77500FAD5ADD402AAD2D9972 /* AntDesign.ttf in Resources */,
|
||||||
|
AA800A96BB73482AA90E29B8 /* OpenSans-LightItalic.ttf in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1559,47 +1566,159 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
|
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
|
||||||
};
|
};
|
||||||
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
|
D6AE48E8124449EE863F342D /* Build NodeJS Mobile Native Modules */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Remove NodeJS Mobile Framework Simulator Strips";
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n";
|
|
||||||
};
|
|
||||||
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Sign NodeJS Mobile Native Modules";
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n \n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n";
|
|
||||||
};
|
|
||||||
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Build NodeJS Mobile Native Modules";
|
name = "Build NodeJS Mobile Native Modules";
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n";
|
shellScript = "
|
||||||
|
set -e
|
||||||
|
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
|
||||||
|
# If build native modules preference is not set, look for it in the project's
|
||||||
|
#nodejs-assets/BUILD_NATIVE_MODULES.txt file.
|
||||||
|
NODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"
|
||||||
|
PREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"
|
||||||
|
if [ -f \"$PREFERENCE_FILE_PATH\" ]; then
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
|
||||||
|
# If build native modules preference is not set, try to find .gyp files
|
||||||
|
#to turn it on.
|
||||||
|
gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))
|
||||||
|
if [ ${#gypfiles[@]} -gt 0 ]; then
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=1
|
||||||
|
else
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
|
||||||
|
# Delete object files that may already come from within the npm package.
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete
|
||||||
|
# Delete bundle contents that may be there from previous builds.
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
|
||||||
|
# Apply patches to the modules package.json
|
||||||
|
if [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then
|
||||||
|
PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"
|
||||||
|
NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"
|
||||||
|
node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR
|
||||||
|
fi
|
||||||
|
# Get the nodejs-mobile-gyp location
|
||||||
|
if [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then
|
||||||
|
NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"
|
||||||
|
else
|
||||||
|
NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"
|
||||||
|
fi
|
||||||
|
NODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js
|
||||||
|
# Rebuild modules with right environment
|
||||||
|
NODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"
|
||||||
|
pushd $CODESIGNING_FOLDER_PATH/nodejs-project/
|
||||||
|
if [ \"$PLATFORM_NAME\" == \"iphoneos\" ]
|
||||||
|
then
|
||||||
|
GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source
|
||||||
|
else
|
||||||
|
GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
";
|
||||||
|
};
|
||||||
|
7D43D3729FAE42C0859CDC36 /* Sign NodeJS Mobile Native Modules */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
name = "Sign NodeJS Mobile Native Modules";
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "
|
||||||
|
set -e
|
||||||
|
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
|
||||||
|
# If build native modules preference is not set, look for it in the project's
|
||||||
|
#nodejs-assets/BUILD_NATIVE_MODULES.txt file.
|
||||||
|
NODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"
|
||||||
|
PREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"
|
||||||
|
if [ -f \"$PREFERENCE_FILE_PATH\" ]; then
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
|
||||||
|
# If build native modules preference is not set, try to find .gyp files
|
||||||
|
#to turn it on.
|
||||||
|
gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))
|
||||||
|
if [ ${#gypfiles[@]} -gt 0 ]; then
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=1
|
||||||
|
else
|
||||||
|
NODEJS_MOBILE_BUILD_NATIVE_MODULES=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
|
||||||
|
# Delete object files
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete
|
||||||
|
# Create Info.plist for each framework built and loader override.
|
||||||
|
PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"
|
||||||
|
NODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"
|
||||||
|
node \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR
|
||||||
|
# Embed every resulting .framework in the application and delete them afterwards.
|
||||||
|
embed_framework()
|
||||||
|
{
|
||||||
|
FRAMEWORK_NAME=\"$(basename \"$1\")\"
|
||||||
|
cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"
|
||||||
|
|
||||||
|
/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"
|
||||||
|
}
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done
|
||||||
|
|
||||||
|
#Delete gyp temporary .deps dependency folders from the project structure.
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete
|
||||||
|
|
||||||
|
#Delete frameworks from their build paths
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete
|
||||||
|
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
|
||||||
|
";
|
||||||
|
};
|
||||||
|
54483AF1DA474A71931D6D6A /* Remove NodeJS Mobile Framework Simulator Strips */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
name = "Remove NodeJS Mobile Framework Simulator Strips";
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "
|
||||||
|
set -e
|
||||||
|
FRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"
|
||||||
|
FRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"
|
||||||
|
if [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then
|
||||||
|
if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then
|
||||||
|
lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"
|
||||||
|
rm \"$FRAMEWORK_BINARY_PATH\"
|
||||||
|
mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"
|
||||||
|
echo \"Removed simulator strip from NodeMobile.framework\"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,8 @@
|
|||||||
<string>OpenSans-Light.ttf</string>
|
<string>OpenSans-Light.ttf</string>
|
||||||
<string>OpenSans-Regular.ttf</string>
|
<string>OpenSans-Regular.ttf</string>
|
||||||
<string>OpenSans-SemiBold.ttf</string>
|
<string>OpenSans-SemiBold.ttf</string>
|
||||||
|
<string>AntDesign.ttf</string>
|
||||||
|
<string>OpenSans-LightItalic.ttf</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
export default function (feeling, texture) {
|
export default function (feeling, texture) {
|
||||||
|
|
||||||
|
if (typeof feeling != 'number' || typeof texture != 'number') return null
|
||||||
|
|
||||||
const feelingMapping = {
|
const feelingMapping = {
|
||||||
0: 0,
|
0: 0,
|
||||||
1: 1,
|
1: 1,
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ export default function setupNotifications(navigate) {
|
|||||||
function setupPeriodReminder() {
|
function setupPeriodReminder() {
|
||||||
const bleedingPrediction = cycleModule().getPredictedMenses()
|
const bleedingPrediction = cycleModule().getPredictedMenses()
|
||||||
if (bleedingPrediction.length > 0) {
|
if (bleedingPrediction.length > 0) {
|
||||||
const bleedingStart = Moment(bleedingPrediction[0][0], "YYYY-MM-DD")
|
const predictedBleedingStart = Moment(bleedingPrediction[0][0], "YYYY-MM-DD")
|
||||||
// 3 days before and at 6 am
|
// 3 days before and at 6 am
|
||||||
const reminderDate = bleedingStart
|
const reminderDate = predictedBleedingStart
|
||||||
.subtract(3, 'days')
|
.subtract(3, 'days')
|
||||||
.hours(6)
|
.hours(6)
|
||||||
.minutes(0)
|
.minutes(0)
|
||||||
|
|||||||
+13
-1
@@ -108,7 +108,15 @@ function formatCycleForSympto(cycle) {
|
|||||||
if (day[symptomName] && day[symptomName].exclude) {
|
if (day[symptomName] && day[symptomName].exclude) {
|
||||||
delete day[symptomName]
|
delete day[symptomName]
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
// remove days with incomplete cervix values
|
||||||
|
if (hasIncompleteCervixValue(day)) {
|
||||||
|
delete day.cervix
|
||||||
|
}
|
||||||
|
// remove days with incomplete mucus value (because nfp-mucus returns null when that's the case)
|
||||||
|
if (day.mucus && day.mucus.value === null) {
|
||||||
|
delete day.mucus
|
||||||
|
}
|
||||||
// change format
|
// change format
|
||||||
['bleeding', 'temperature', 'mucus'].forEach(symptomName => {
|
['bleeding', 'temperature', 'mucus'].forEach(symptomName => {
|
||||||
if (day[symptomName]) day[symptomName] = day[symptomName].value
|
if (day[symptomName]) day[symptomName] = day[symptomName].value
|
||||||
@@ -120,3 +128,7 @@ function formatCycleForSympto(cycle) {
|
|||||||
formatted.reverse()
|
formatted.reverse()
|
||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasIncompleteCervixValue(day) {
|
||||||
|
return day.cervix && (typeof day.cervix.opening != 'number' || typeof day.cervix.firmness != 'number')
|
||||||
|
}
|
||||||
Generated
+2931
-3378
File diff suppressed because it is too large
Load Diff
+21
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "drip",
|
"name": "drip",
|
||||||
"version": "0.0.2",
|
"version": "0.1905.29-beta",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Julia Friesel <julia.friesel@gmail.com>",
|
"Julia Friesel <julia.friesel@gmail.com>",
|
||||||
"Marie Kochsiek",
|
"Marie Kochsiek",
|
||||||
@@ -14,7 +14,12 @@
|
|||||||
"test": "mocha --recursive --require @babel/register test && npm run lint",
|
"test": "mocha --recursive --require @babel/register test && npm run lint",
|
||||||
"test-watch": "mocha --recursive --require @babel/register --watch test",
|
"test-watch": "mocha --recursive --require @babel/register --watch test",
|
||||||
"lint": "eslint components lib test",
|
"lint": "eslint components lib test",
|
||||||
"devtool": "adb shell input keyevent 82"
|
"devtool": "adb shell input keyevent 82",
|
||||||
|
"update-version": "./tools/update-version.js",
|
||||||
|
"build-android-release": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd ..",
|
||||||
|
"commit-release": "./tools/commit-release",
|
||||||
|
"update-changelog": "./tools/update-changelog.js",
|
||||||
|
"release": "npm run update-version && npm run build-android-release && npm run commit-release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ptomasroos/react-native-multi-slider": "^1.0.0",
|
"@ptomasroos/react-native-multi-slider": "^1.0.0",
|
||||||
@@ -25,12 +30,12 @@
|
|||||||
"js-base64": "^2.4.8",
|
"js-base64": "^2.4.8",
|
||||||
"js-joda": "^1.8.2",
|
"js-joda": "^1.8.2",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"nodejs-mobile-react-native": "^0.3.0",
|
"nodejs-mobile-react-native": "^0.4.1",
|
||||||
"object-path": "^0.11.4",
|
"object-path": "^0.11.4",
|
||||||
"obv": "0.0.1",
|
"obv": "0.0.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.6.3",
|
"react": "16.6.1",
|
||||||
"react-native": "^0.57.8",
|
"react-native": "0.58.0-rc.0",
|
||||||
"react-native-calendars": "^1.19.3",
|
"react-native-calendars": "^1.19.3",
|
||||||
"react-native-document-picker": "^2.1.0",
|
"react-native-document-picker": "^2.1.0",
|
||||||
"react-native-fs": "^2.13.3",
|
"react-native-fs": "^2.13.3",
|
||||||
@@ -39,20 +44,25 @@
|
|||||||
"react-native-push-notification": "github:jfr3000/react-native-push-notification",
|
"react-native-push-notification": "github:jfr3000/react-native-push-notification",
|
||||||
"react-native-restart": "0.0.7",
|
"react-native-restart": "0.0.7",
|
||||||
"react-native-share": "^1.1.3",
|
"react-native-share": "^1.1.3",
|
||||||
"react-native-vector-icons": "^5.0.0",
|
"react-native-vector-icons": "^6.4.2",
|
||||||
"realm": "^2.22.0",
|
"realm": "^2.22.0",
|
||||||
"sympto": "^1.0.0"
|
"sympto": "^1.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.0.1",
|
|
||||||
"@babel/register": "^7.0.0",
|
|
||||||
"@babel/core": "^7.2.2",
|
"@babel/core": "^7.2.2",
|
||||||
|
"@babel/register": "^7.0.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"basic-changelog": "gitlab:bloodyhealth/basic-changelog",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"eslint": "^4.19.1",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-react": "^7.8.2",
|
"eslint-plugin-react": "^7.8.2",
|
||||||
|
"jase": "^1.2.0",
|
||||||
|
"left-pad": "^1.3.0",
|
||||||
"metro-react-native-babel-preset": "^0.51.1",
|
"metro-react-native-babel-preset": "^0.51.1",
|
||||||
"mocha": "^5.2.0"
|
"mocha": "^5.2.0",
|
||||||
|
"react-native-version": "^3.1.0",
|
||||||
|
"readline": "^1.3.0"
|
||||||
},
|
},
|
||||||
"description": "A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle or for fertility awareness!",
|
"description": "A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle or for fertility awareness!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+86
-66
@@ -17,6 +17,7 @@ const headerFont = 'Prompt-ExtraLight'
|
|||||||
|
|
||||||
const textFont = 'OpenSans-Light'
|
const textFont = 'OpenSans-Light'
|
||||||
const textFontBold = 'OpenSans-SemiBold'
|
const textFontBold = 'OpenSans-SemiBold'
|
||||||
|
const textFontItalic = 'OpenSans-LightItalic'
|
||||||
|
|
||||||
const regularSize = 16
|
const regularSize = 16
|
||||||
const hintSize = 14
|
const hintSize = 14
|
||||||
@@ -43,12 +44,9 @@ export default StyleSheet.create({
|
|||||||
fontSize: regularSize,
|
fontSize: regularSize,
|
||||||
letterSpacing: 0.5
|
letterSpacing: 0.5
|
||||||
},
|
},
|
||||||
actionHint: {
|
hint: {
|
||||||
color: secondaryColor,
|
fontFamily: textFontItalic,
|
||||||
fontFamily: textFont,
|
|
||||||
fontSize: hintSize,
|
fontSize: hintSize,
|
||||||
fontWeight: 'bold',
|
|
||||||
margin: defaultIndentation
|
|
||||||
},
|
},
|
||||||
paragraph: {
|
paragraph: {
|
||||||
marginBottom: defaultBottomMargin
|
marginBottom: defaultBottomMargin
|
||||||
@@ -104,9 +102,6 @@ export default StyleSheet.create({
|
|||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
},
|
},
|
||||||
homeView: {
|
|
||||||
marginHorizontal: 50,
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
@@ -120,29 +115,26 @@ export default StyleSheet.create({
|
|||||||
homeButtonText: {
|
homeButtonText: {
|
||||||
color: fontOnPrimaryColor
|
color: fontOnPrimaryColor
|
||||||
},
|
},
|
||||||
homeIconElement: {
|
homeView: {
|
||||||
alignItems: 'center',
|
margin: 40,
|
||||||
marginTop: 15
|
},
|
||||||
|
homeDescriptionText: {
|
||||||
|
width: 200,
|
||||||
|
marginBottom: defaultBottomMargin,
|
||||||
|
},
|
||||||
|
homeElement: {
|
||||||
|
marginBottom: 30,
|
||||||
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
homeIconTextWrapper: {
|
homeIconTextWrapper: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginBottom: 10,
|
|
||||||
},
|
},
|
||||||
wrapperCycle: {
|
homeIconAndText: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
wrapperIcon: {
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 77,
|
|
||||||
position: 'absolute'
|
|
||||||
},
|
|
||||||
wrapperDrop: {
|
|
||||||
width: 81,
|
|
||||||
height: 85,
|
|
||||||
marginTop: 20,
|
|
||||||
position: 'absolute'
|
|
||||||
},
|
|
||||||
wrapperCircle: {
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
position: 'absolute'
|
position: 'absolute'
|
||||||
},
|
},
|
||||||
homeCircle: {
|
homeCircle: {
|
||||||
@@ -157,14 +149,6 @@ export default StyleSheet.create({
|
|||||||
iconText: {
|
iconText: {
|
||||||
fontSize: 25
|
fontSize: 25
|
||||||
},
|
},
|
||||||
showMore: {
|
|
||||||
transform: [{rotate: '90deg'}],
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
showLess: {
|
|
||||||
transform: [{rotate: '270deg'}],
|
|
||||||
position: 'absolute'
|
|
||||||
},
|
|
||||||
cycleDayNumber: {
|
cycleDayNumber: {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: fontOnPrimaryColor,
|
color: fontOnPrimaryColor,
|
||||||
@@ -173,12 +157,10 @@ export default StyleSheet.create({
|
|||||||
},
|
},
|
||||||
symptomViewHeading: {
|
symptomViewHeading: {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontFamily: textFontBold,
|
fontFamily: textFontBold
|
||||||
marginTop: 10
|
|
||||||
},
|
},
|
||||||
symptomInfoIcon: {
|
symptomSection: {
|
||||||
marginRight: 20,
|
marginBottom: 10
|
||||||
marginLeft: 20
|
|
||||||
},
|
},
|
||||||
symptomBoxImage: {
|
symptomBoxImage: {
|
||||||
width: 50,
|
width: 50,
|
||||||
@@ -236,16 +218,12 @@ export default StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 80
|
height: 80
|
||||||
},
|
},
|
||||||
headerCycleDay: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
navigationArrow: {
|
navigationArrow: {
|
||||||
padding: 20
|
padding: 20,
|
||||||
},
|
position: 'absolute'
|
||||||
hiddenIcon: {
|
|
||||||
padding: 20
|
|
||||||
},
|
},
|
||||||
|
navigationArrowLeft: { left: 0 },
|
||||||
|
navigationArrowRight: { right: 0 },
|
||||||
menu: {
|
menu: {
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -269,16 +247,12 @@ export default StyleSheet.create({
|
|||||||
temperatureTextInput: {
|
temperatureTextInput: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
color: 'black',
|
color: 'black',
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
|
width: '30%'
|
||||||
},
|
},
|
||||||
temperatureTextInputSuggestion: {
|
temperatureTextInputSuggestion: {
|
||||||
color: '#939393'
|
color: '#939393'
|
||||||
},
|
},
|
||||||
actionButtonRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-evenly',
|
|
||||||
marginTop: 50
|
|
||||||
},
|
|
||||||
symptomEditButton: {
|
symptomEditButton: {
|
||||||
width: 130
|
width: 130
|
||||||
},
|
},
|
||||||
@@ -299,10 +273,40 @@ export default StyleSheet.create({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontFamily: textFontBold
|
fontFamily: textFontBold
|
||||||
},
|
},
|
||||||
|
framedSegmentInlineChildren: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
infoPopUpWrapper: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
infoPopUp: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
padding: 15,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginTop: 20,
|
||||||
|
maxHeight: '92%'
|
||||||
|
},
|
||||||
|
dimmed: {
|
||||||
|
position: 'absolute',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
opacity: 0.5,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
infoSymptomClose: {
|
||||||
|
alignItems: 'flex-end'
|
||||||
|
},
|
||||||
|
infoSymptomText: {
|
||||||
|
marginTop: 10
|
||||||
|
},
|
||||||
settingsButton: {
|
settingsButton: {
|
||||||
padding: 10,
|
padding: 10,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
margin: 10
|
margin: 10,
|
||||||
|
borderRadius: 5,
|
||||||
},
|
},
|
||||||
settingsButtonAccent: {
|
settingsButtonAccent: {
|
||||||
backgroundColor: secondaryColor
|
backgroundColor: secondaryColor
|
||||||
@@ -374,7 +378,8 @@ export default StyleSheet.create({
|
|||||||
borderLeftWidth: null
|
borderLeftWidth: null
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
marginHorizontal: 10
|
marginHorizontal: 10,
|
||||||
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
calendarToday: {
|
calendarToday: {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@@ -383,13 +388,21 @@ export default StyleSheet.create({
|
|||||||
marginTop: 1
|
marginTop: 1
|
||||||
},
|
},
|
||||||
passwordField: {
|
passwordField: {
|
||||||
padding: 10,
|
|
||||||
marginTop: 10,
|
|
||||||
marginHorizontal: 10,
|
marginHorizontal: 10,
|
||||||
backgroundColor: 'white'
|
marginTop: 10
|
||||||
|
},
|
||||||
|
textInputField: {
|
||||||
|
padding: 10,
|
||||||
|
marginVertical: 10,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderColor: secondaryColor,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
passwordPromptPage: {
|
passwordPromptPage: {
|
||||||
padding: 30,
|
padding: 30,
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
passwordPromptField: {
|
passwordPromptField: {
|
||||||
@@ -418,8 +431,16 @@ export default StyleSheet.create({
|
|||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
color: 'grey'
|
color: 'grey'
|
||||||
},
|
},
|
||||||
infoButton: {
|
headerDeleteButton: {
|
||||||
paddingVertical: 20
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 20,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0
|
||||||
|
},
|
||||||
|
infoButtonSymptomView: {
|
||||||
|
position: 'absolute',
|
||||||
|
padding: 15,
|
||||||
|
right: 0
|
||||||
},
|
},
|
||||||
licensePage: {
|
licensePage: {
|
||||||
paddingVertical: 20,
|
paddingVertical: 20,
|
||||||
@@ -451,6 +472,10 @@ export const iconStyles = {
|
|||||||
symptomBoxActive: {
|
symptomBoxActive: {
|
||||||
color: fontOnPrimaryColor
|
color: fontOnPrimaryColor
|
||||||
},
|
},
|
||||||
|
info: {
|
||||||
|
color: secondaryColor,
|
||||||
|
fontSize: 25
|
||||||
|
},
|
||||||
menuIcon: {
|
menuIcon: {
|
||||||
size: 20,
|
size: 20,
|
||||||
color: fontOnPrimaryColor
|
color: fontOnPrimaryColor
|
||||||
@@ -458,12 +483,7 @@ export const iconStyles = {
|
|||||||
menuIconInactive: {
|
menuIconInactive: {
|
||||||
color: colorInActive,
|
color: colorInActive,
|
||||||
},
|
},
|
||||||
infoInHeading: {
|
infoPopUpClose: {
|
||||||
marginRight: 5,
|
size: 25
|
||||||
color: 'black'
|
|
||||||
},
|
|
||||||
hiddenIcon: {
|
|
||||||
size: 20,
|
|
||||||
display: 'none'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ chai.use(dirtyChai)
|
|||||||
import getSensiplanMucus from '../lib/nfp-mucus'
|
import getSensiplanMucus from '../lib/nfp-mucus'
|
||||||
|
|
||||||
describe('getSensiplanMucus', () => {
|
describe('getSensiplanMucus', () => {
|
||||||
|
it('returns null if there is no value for feeling or texture', () => {
|
||||||
|
expect(getSensiplanMucus()).to.be.null()
|
||||||
|
expect(getSensiplanMucus(undefined, 3)).to.be.null()
|
||||||
|
expect(getSensiplanMucus(2, undefined)).to.be.null()
|
||||||
|
})
|
||||||
|
|
||||||
describe('results in t for:', () => {
|
describe('results in t for:', () => {
|
||||||
it('dry feeling and no texture', function () {
|
it('dry feeling and no texture', function () {
|
||||||
|
|||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
|
||||||
|
set -eEu -o pipefail
|
||||||
|
shopt -s extdebug
|
||||||
|
IFS=$'\n\t'
|
||||||
|
trap 'onFailure $?' ERR
|
||||||
|
|
||||||
|
function onFailure() {
|
||||||
|
echo "Unhandled script error $1 at ${BASH_SOURCE[0]}:${BASH_LINENO[0]}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
git add -A
|
||||||
|
git commit -m "release: $(cat package.json | $(npm bin)/jase version)"
|
||||||
|
git tag v$(cat package.json | $(npm bin)/jase version)
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const updateChangelog = require('basic-changelog')
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
filterCommitsStartingWith: ['release:']
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChangelog('./CHANGELOG.md', opts, err => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Something went wrong trying to update the changelog:')
|
||||||
|
console.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('Changelog successfully updated')
|
||||||
|
})
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// from https://gitlab.com/staltz/manyverse/blob/master/tools/update-version.js
|
||||||
|
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const ReactNativeVersion = require('react-native-version')
|
||||||
|
const readline = require('readline')
|
||||||
|
const leftPad = require('left-pad')
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const currentVersion = JSON.parse(fs.readFileSync('./package.json')).version
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
})
|
||||||
|
|
||||||
|
function createTodaysVersion(attempt) {
|
||||||
|
const today = new Date()
|
||||||
|
const yy = today.getFullYear() - 2000 // So it's two digits
|
||||||
|
const mm = leftPad(today.getMonth() + 1, 2, '0')
|
||||||
|
const d = today.getDate()
|
||||||
|
if (attempt === 0) {
|
||||||
|
return `0.${yy}${mm}.${d}-beta`
|
||||||
|
} else {
|
||||||
|
const letter = String.fromCharCode(97 + attempt) // 0=a, 1=b, 2=c, ...
|
||||||
|
return `0.${yy}${mm}.${d}-beta.${letter}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextVersion
|
||||||
|
for (let i = 0 /* letter a */; i <= 25 /* letter z */; i++) {
|
||||||
|
nextVersion = createTodaysVersion(i)
|
||||||
|
if (nextVersion !== currentVersion) break
|
||||||
|
}
|
||||||
|
if (nextVersion === currentVersion) {
|
||||||
|
console.error('I dont know what else to generate beyong ' + nextVersion)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.question('Next version will be `' + nextVersion + '`, okay? y/n ', yn => {
|
||||||
|
if (yn !== 'y' && yn !== 'Y') {
|
||||||
|
console.log('Release cancelled.\n')
|
||||||
|
process.exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkgJSON = JSON.parse(fs.readFileSync('./package.json'))
|
||||||
|
const pkgLockJSON = JSON.parse(fs.readFileSync('./package-lock.json'))
|
||||||
|
pkgJSON.version = nextVersion
|
||||||
|
pkgLockJSON.version = nextVersion
|
||||||
|
fs.writeFileSync('./package.json', JSON.stringify(pkgJSON, null, 2))
|
||||||
|
fs.writeFileSync('./package-lock.json', JSON.stringify(pkgLockJSON, null, 2))
|
||||||
|
|
||||||
|
ReactNativeVersion.version(
|
||||||
|
{
|
||||||
|
neverAmend: true,
|
||||||
|
target: 'android',
|
||||||
|
},
|
||||||
|
path.resolve(__dirname, '../'),
|
||||||
|
).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.close()
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user