diff --git a/.buckconfig b/.buckconfig deleted file mode 100644 index 934256cb29d4a3616c740861c6af35ff6a165917..0000000000000000000000000000000000000000 --- a/.buckconfig +++ /dev/null @@ -1,6 +0,0 @@ - -[android] - target = Google Inc.:Google APIs:23 - -[maven_repositories] - central = https://repo1.maven.org/maven2 diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 7d5e2d3362ba40d068cb6a03d0e3772931f94a35..0000000000000000000000000000000000000000 --- a/.flowconfig +++ /dev/null @@ -1,54 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -<PROJECT_ROOT>/\.buckd/ - -; Ignore unexpected extra "@providesModule" -.*/node_modules/.*/node_modules/fbjs/.* - -; Ignore duplicate module providers -; For RN Apps installed via npm, "Libraries" folder is inside -; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js - -; Ignore polyfills -.*/Libraries/polyfills/.* - -; Ignore metro -.*/node_modules/metro/.* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow/ -node_modules/react-native/flow-github/ - -[options] -emoji=true - -module.system=haste - -munge_underscores=true - -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' - -module.file_ext=.js -module.file_ext=.jsx -module.file_ext=.json -module.file_ext=.native.js - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FlowFixMeProps -suppress_type=$FlowFixMeState - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - -[version] -^0.67.0 diff --git a/README.md b/README.md index 33c2411de527d38a07dd89bf42f0e40a111f1618..a7b475487457aea1241013d416dfcb66af740a05 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,7 @@ Unfortunately, the react native version we use doesn't work on Windows 10 it see You can run the tests with `npm test`. ## Debugging -When running into an old version of the app try to run the following command first: -`react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res` +In order to see logging output from the app, run `npm run log` in a separate terminal. ## NFP rules More information about how the app calculates fertility status and bleeding predictions in the [wiki on Gitlab](https://gitlab.com/bloodyhealth/drip/wikis/home) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 207d90aa0bc07961fc8de65cd883b8580827c910..5c5243a37f829634ee88a512927239a4b3f37519 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,8 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.drip"> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.drip" +> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> @@ -36,9 +39,11 @@ android:grantUriPermissions="true" android:exported="false"> <meta-data + tools:replace="android:resource" android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> + <meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_name" android:value="drip-notification"/> <meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_description" diff --git a/android/app/src/main/res/xml/filepaths.xml b/android/app/src/main/res/xml/filepaths.xml index ce1ddcf6528f92651c013e6de8165f069d0dc575..48a8aef8eceb9af05eb484eb985e6ae0c9a0c1aa 100644 --- a/android/app/src/main/res/xml/filepaths.xml +++ b/android/app/src/main/res/xml/filepaths.xml @@ -1,4 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="Downloads" path="Download/" /> + <root-path name="root" path="" /> + <files-path + name="files-path" + path="." /> <!-- Used to access into application data files --> </paths> \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 3d21e10bbf01a85adc729617fce4a5ee71a08156..12fbc47123f43551ab239fc48ff730b50c14175e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -35,3 +35,11 @@ ext { targetSdkVersion = 26 supportLibVersion = "26.1.0" } + +subprojects {project -> + buildscript { + repositories { + maven { url = 'https://dl.bintray.com/android/android-tools/' } + } + } +} diff --git a/components/app-text.js b/components/app-text.js index eabab32a2d21ff9c6a762d4626171867c6801568..6e996da5873a31e77e0cd1a018c0b5457a10e95b 100644 --- a/components/app-text.js +++ b/components/app-text.js @@ -1,33 +1,30 @@ -import React, { Component } from 'react' +import React from 'react' import { Text } from 'react-native' import styles from "../styles" -export default class AppText extends Component { - render() { - return ( - <Text style={[styles.appText, this.props.style]}> - {this.props.children} - </Text> - ) - } +export default function AppText(props) { + return ( + <Text + style={[styles.appText, props.style]} + onPress={props.onPress} + > + {props.children} + </Text> + ) } -export class AppTextLight extends Component { - render() { - return ( - <Text style={[styles.appTextLight, this.props.style]}> - {this.props.children} - </Text> - ) - } +export function AppTextLight(props) { + return ( + <Text style={[styles.appTextLight, props.style]}> + {props.children} + </Text> + ) } -export class SymptomSectionHeader extends Component { - render() { - return ( - <AppText style={styles.symptomViewHeading}> - {this.props.children} - </AppText> - ) - } +export function SymptomSectionHeader(props) { + return ( + <AppText style={styles.symptomViewHeading}> + {props.children} + </AppText> + ) } \ No newline at end of file diff --git a/components/app.js b/components/app.js index b3218a5fc10ecd81d17d27efbe5dc362001d2365..39318f37460e06e93a1bd612ae4dfacb249a0c3f 100644 --- a/components/app.js +++ b/components/app.js @@ -7,7 +7,8 @@ import Calendar from './calendar' import CycleDay from './cycle-day/cycle-day-overview' import symptomViews from './cycle-day/symptoms' import Chart from './chart/chart' -import Settings from './settings' +import SettingsMenu from './settings/settings-menu' +import settingsViews from './settings' import Stats from './stats' import {headerTitles, menuTitles} from '../i18n/en/labels' import setupNotifications from '../lib/notifications' @@ -24,6 +25,7 @@ const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => { }, {}) const isSymptomView = name => Object.keys(symptomViews).includes(name) +const isSettingsView = name => Object.keys(settingsViews).includes(name) const isMenuItem = name => Object.keys(menuTitles).includes(name) export default class App extends Component { @@ -58,6 +60,8 @@ export default class App extends Component { this.navigate( this.originForSymptomView, { date: this.state.currentProps.date } ) + } else if (isSettingsView(this.state.currentPage)) { + this.navigate('SettingsMenu') } else if(this.state.currentPage === 'CycleDay') { this.navigate(this.menuOrigin) } else { @@ -68,7 +72,7 @@ export default class App extends Component { render() { const page = { - Home, Calendar, CycleDay, Chart, Settings, Stats, ...symptomViews + Home, Calendar, CycleDay, Chart, SettingsMenu, ...settingsViews, Stats, ...symptomViews }[this.state.currentPage] return ( <View style={{flex: 1}}> @@ -81,6 +85,7 @@ export default class App extends Component { title={headerTitlesLowerCase[this.state.currentPage]} isSymptomView={true} goBack={this.handleBackButtonPress} + date={this.state.currentProps.date} />} diff --git a/components/chart/chart.js b/components/chart/chart.js index c11db6bfee008d2722a9f97314109ab41358a94f..b8d2142abfd77a83f6fe4434c2de28b005b61a60 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -81,7 +81,7 @@ export default class CycleChart extends Component { this.chartSymptoms.push('temperature') } - const columnData = this.makeColumnInfo(nfpLines(), this.chartSymptoms) + const columnData = this.makeColumnInfo() this.setState({ columns: columnData, chartHeight: height diff --git a/components/chart/day-column.js b/components/chart/day-column.js index fe4b82c39bdd411d17478d175afd7c077b93346e..e9c6f76056435937f2a1c5d815b5a86d6e172007 100644 --- a/components/chart/day-column.js +++ b/components/chart/day-column.js @@ -52,7 +52,7 @@ export default class DayColumn extends Component { this.fhmAndLtl = props.getFhmAndLtlInfo( props.dateString, - props.temp, + this.data.temperature, props.columnHeight ) } diff --git a/components/cycle-day/symptoms/action-button-footer.js b/components/cycle-day/symptoms/action-button-footer.js index 076b0bb827c5d9eca7d07c5ef0804dfa5f92699b..9da25e27be9febaae58f4f7ef056d917b1c918db 100644 --- a/components/cycle-day/symptoms/action-button-footer.js +++ b/components/cycle-day/symptoms/action-button-footer.js @@ -1,12 +1,13 @@ import React, { Component } from 'react' import { - View, TouchableOpacity, Text, Alert + 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 { @@ -43,19 +44,26 @@ export default class ActionButtonFooter extends Component { }, { title: labels.save, action: () => { - saveAction() - if (autoShowDayView) navigateToOverView() + if(saveDisabled) { + ToastAndroid.show(labels.disabledInfo, ToastAndroid.LONG); + console.log() + } 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) + if (disabledCondition) { + textStyle.push(styles.menuTextInActive); + } const iconStyle = disabledCondition ? Object.assign( {}, @@ -68,7 +76,6 @@ export default class ActionButtonFooter extends Component { <TouchableOpacity onPress={action} style={styles.menuItem} - disabled={disabledCondition} key={i.toString()} > <Icon name={icon} {...iconStyle} /> @@ -76,9 +83,10 @@ export default class ActionButtonFooter extends Component { {title.toLowerCase()} </Text> </TouchableOpacity> + ) })} </View> ) } -} \ No newline at end of file +} diff --git a/components/cycle-day/symptoms/mucus.js b/components/cycle-day/symptoms/mucus.js index f2c268dc6fe9328c3c1597951810e1ce53d9d192..7de989695c7beae20f89b86f823ba765517edd2a 100644 --- a/components/cycle-day/symptoms/mucus.js +++ b/components/cycle-day/symptoms/mucus.js @@ -7,7 +7,7 @@ import { import styles from '../../../styles' import { saveSymptom } from '../../../db' import { mucus as labels } from '../../../i18n/en/cycle-day' -import computeSensiplanValue from '../../../lib/sensiplan-mucus' +import computeNfpValue from '../../../lib/nfp-mucus' import ActionButtonFooter from './action-button-footer' import SelectTabGroup from '../select-tab-group' import SymptomSection from './symptom-section' @@ -80,7 +80,7 @@ export default class Mucus extends Component { saveSymptom('mucus', this.props.date, { feeling, texture, - value: computeSensiplanValue(feeling, texture), + value: computeNfpValue(feeling, texture), exclude: Boolean(this.state.exclude) }) }} diff --git a/components/header/cycle-day.js b/components/header/cycle-day.js index b5dbbdfb2b15d615aad69c5c3144eb09950c59a4..0cb32526084b8563f85038dd4be9d1a18ccf907d 100644 --- a/components/header/cycle-day.js +++ b/components/header/cycle-day.js @@ -2,17 +2,9 @@ import React from 'react' import { View, Text} from 'react-native' -import { LocalDate } from 'js-joda' -import moment from 'moment' import styles from '../../styles' import NavigationArrow from './navigation-arrow' - -const FormattedDate = ({ date }) => { - const today = LocalDate.now() - const dateToDisplay = LocalDate.parse(date) - const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY') - return formattedDate.toLowerCase() -} +import formatDate from '../helpers/format-date' export default function CycleDayHeader({ date, ...props }) { return (<View style={[styles.header, styles.headerCycleDay]}> @@ -23,7 +15,7 @@ export default function CycleDayHeader({ date, ...props }) { <NavigationArrow direction='left' {...props}/> <View> <Text style={styles.dateHeader}> - <FormattedDate date={date} /> + {formatDate(date)} </Text> {props.cycleDayNumber && <Text style={styles.cycleDayNumber}> @@ -33,4 +25,4 @@ export default function CycleDayHeader({ date, ...props }) { <NavigationArrow direction='right' {...props}/> </View> ) -} \ No newline at end of file +} diff --git a/components/header/symptom-view.js b/components/header/symptom-view.js index 3f6a21f16e7bd78c5e14f2f0a009cfd664c6783b..20817767aa12f10dc784f098fe2141fc63b4ca2d 100644 --- a/components/header/symptom-view.js +++ b/components/header/symptom-view.js @@ -5,6 +5,7 @@ import { import styles, { iconStyles } from '../../styles' import FeatherIcon from 'react-native-vector-icons/Feather' import NavigationArrow from './navigation-arrow' +import formatDate from '../helpers/format-date' export default function SymptomViewHeader(props) { return ( @@ -21,6 +22,9 @@ export default function SymptomViewHeader(props) { <Text style={styles.dateHeader}> {props.title} </Text> + <Text style={styles.cycleDayNumber}> + {formatDate(props.date)} + </Text> </View > <FeatherIcon name='info' @@ -29,4 +33,4 @@ export default function SymptomViewHeader(props) { /> </View> ) -} \ No newline at end of file +} diff --git a/components/helpers/format-date.js b/components/helpers/format-date.js new file mode 100644 index 0000000000000000000000000000000000000000..079ea4d434d7b87d0fd6575e013441094dec8f02 --- /dev/null +++ b/components/helpers/format-date.js @@ -0,0 +1,9 @@ +import { LocalDate } from 'js-joda' +import moment from 'moment' + +export default function (date) { + const today = LocalDate.now() + const dateToDisplay = LocalDate.parse(date) + const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY') + return formattedDate.toLowerCase() +} diff --git a/components/home.js b/components/home.js index ad4dce1985653f0d58412a62860bb5d5a408897f..e57633d23288180fb07d521f8a5392974f7020dd 100644 --- a/components/home.js +++ b/components/home.js @@ -9,7 +9,6 @@ import { getCycleDaysSortedByDate } from '../db' import { getFertilityStatusForDay } from '../lib/sympto-adapter' import styles from '../styles' import AppText, { AppTextLight } from './app-text' -import nothingChanged from '../db/db-unchanged' import DripHomeIcon from '../assets/drip-home-icons' const HomeButton = ({ backgroundColor, children }) => { @@ -42,23 +41,6 @@ export default class Home extends Component { } this.cycleDays = getCycleDaysSortedByDate() - this.cycleDays.addListener(this.updateState) - } - - updateState = (_, changes) => { - if (nothingChanged(changes)) return - const prediction = this.getBleedingPrediction() - const fertilityStatus = getFertilityStatusForDay(this.todayDateString) - this.setState({ - cycleDayNumber: this.getCycleDayNumber(this.todayDateString), - predictionText: determinePredictionText(prediction), - bleedingPredictionRange: getBleedingPredictionRange(prediction), - ...fertilityStatus - }) - } - - componentWillUnmount() { - this.cycleDays.removeListener(this.updateState) } passTodayTo(componentName) { diff --git a/components/link.js b/components/link.js new file mode 100644 index 0000000000000000000000000000000000000000..f5d2c10c2b02ab900195177fdf1d54496c140088 --- /dev/null +++ b/components/link.js @@ -0,0 +1,13 @@ +import React from 'react' +import { Linking } from 'react-native' +import AppText from "./app-text" +import styles from '../styles'; + +export default function Link(props) { + return ( + <AppText + style={styles.link} + onPress={() => Linking.openURL(props.href)} + >{props.text}</AppText> + ) +} \ No newline at end of file diff --git a/components/menu.js b/components/menu.js index 4e80cbbc79552689f066ed786b3df1ec0516d87a..7a692e03701dbce763da6694213cd7556e2a048e 100644 --- a/components/menu.js +++ b/components/menu.js @@ -38,7 +38,7 @@ export default class Menu extends Component { { title: t.Calendar, icon: 'calendar-range', onPress: () => this.goTo('Calendar') }, { title: t.Chart, icon: 'chart-line', onPress: () => this.goTo('Chart') }, { title: t.Stats, icon: 'chart-pie', onPress: () => this.goTo('Stats') }, - { title: t.Settings, icon: 'settings', onPress: () => this.goTo('Settings') }, + { title: t.Settings, icon: 'settings', onPress: () => this.goTo('SettingsMenu') }, ].map(this.makeMenuItem)} </View > ) diff --git a/components/settings/about.js b/components/settings/about.js new file mode 100644 index 0000000000000000000000000000000000000000..5d7cba1cc442f36fef2191ee2a4f4573e518f5c7 --- /dev/null +++ b/components/settings/about.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { View, ScrollView } from 'react-native' +import AppText from '../app-text' +import styles from '../../styles/index' +import labels from '../../i18n/en/settings' +export default class AboutSection extends Component { + render() { + return ( + <ScrollView> + <View style={styles.settingsSegment}> + <AppText style={styles.settingsSegmentTitle}>{`${labels.aboutSection.title} `}</AppText> + <AppText>{`${labels.aboutSection.segmentExplainer} `}</AppText> + </View> + <View style={[styles.settingsSegment, styles.settingsSegmentLast]}> + <AppText style={styles.settingsSegmentTitle}>{`${labels.credits.title} `}</AppText> + <AppText>{`${labels.credits.note}`}</AppText> + </View> + </ScrollView> + ) + } +} diff --git a/components/settings/export-dialog.js b/components/settings/export-dialog.js deleted file mode 100644 index e87b9d20300e07d57292078ab21606e8d8c94b12..0000000000000000000000000000000000000000 --- a/components/settings/export-dialog.js +++ /dev/null @@ -1,31 +0,0 @@ -import Share from 'react-native-share' -import getDataAsCsvDataUri from '../../lib/import-export/export-to-csv' -import alertError from './alert-error' -import { settings as labels } from '../../i18n/en/settings' - -export default async function openShareDialogAndExport() { - let data - try { - data = getDataAsCsvDataUri() - if (!data) { - return alertError(labels.errors.noData) - } - } catch (err) { - console.error(err) - return alertError(labels.errors.couldNotConvert) - } - - try { - await Share.open({ - title: labels.export.title, - url: data, - subject: labels.export.subject, - type: 'text/csv', - showAppsToView: true - }) - } catch (err) { - console.error(err) - return alertError(labels.export.errors.problemSharing) - } -} - diff --git a/components/settings/import-export/export-dialog.js b/components/settings/import-export/export-dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..58301cf56042a89a49c7a0bd5ad6698c147b5163 --- /dev/null +++ b/components/settings/import-export/export-dialog.js @@ -0,0 +1,43 @@ +import Share from 'react-native-share' + +import { getCycleDaysSortedByDate } from '../../../db' +import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv' +import alertError from '../alert-error' +import settings from '../../../i18n/en/settings' +import RNFS from 'react-native-fs' + +export default async function exportData() { + let data + const labels = settings.export + const cycleDaysByDate = getCycleDaysSortedByDate() + + if (!cycleDaysByDate.length) return alertError(labels.errors.noData) + + try { + data = getDataAsCsvDataUri(cycleDaysByDate) + if (!data) { + return alertError(labels.errors.noData) + } + } catch (err) { + console.error(err) + return alertError(labels.errors.couldNotConvert) + } + + try { + const path = RNFS.DocumentDirectoryPath + '/data.csv' + await RNFS.writeFile(path, data) + + await Share.open({ + title: labels.title, + url: `file://${path}`, + subject: labels.subject, + type: 'text/csv', + showAppsToView: true + }) + + } catch (err) { + console.error(err) + return alertError(labels.errors.problemSharing) + } +} + diff --git a/components/settings/import-dialog.js b/components/settings/import-export/import-dialog.js similarity index 86% rename from components/settings/import-dialog.js rename to components/settings/import-export/import-dialog.js index 5701b0876c43349fa5556c746290b1a6258a9982..2ff977e27e44fc71c9b696fd72e037971b98e92f 100644 --- a/components/settings/import-dialog.js +++ b/components/settings/import-export/import-dialog.js @@ -1,10 +1,10 @@ import { Alert } from 'react-native' import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker' import rnfs from 'react-native-fs' -import importCsv from '../../lib/import-export/import-from-csv' -import { shared as sharedLabels } from '../../i18n/en/labels' -import { settings as labels } from '../../i18n/en/settings' -import alertError from './alert-error' +import importCsv from '../../../lib/import-export/import-from-csv' +import { shared as sharedLabels } from '../../../i18n/en/labels' +import labels from '../../../i18n/en/settings' +import alertError from '../alert-error' export default function openImportDialogAndImport() { Alert.alert( diff --git a/components/settings/import-export/index.js b/components/settings/import-export/index.js new file mode 100644 index 0000000000000000000000000000000000000000..430f29b50dae183b962da3a07df9af8413795bbb --- /dev/null +++ b/components/settings/import-export/index.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react' +import { + View, ScrollView, + TouchableOpacity, +} from 'react-native' +import styles from '../../../styles/index' +import labels from '../../../i18n/en/settings' +import AppText from '../../app-text' +import openImportDialogAndImport from './import-dialog' +import openShareDialogAndExport from './export-dialog' + +export default class Settings extends Component { + constructor(props) { + super(props) + this.state = {} + } + + render() { + return ( + <ScrollView> + <View style={styles.settingsSegment}> + <AppText style={styles.settingsSegmentTitle}> + {labels.export.button} + </AppText> + <AppText>{labels.export.segmentExplainer}</AppText> + <TouchableOpacity + onPress={openShareDialogAndExport} + style={styles.settingsButton}> + <AppText style={styles.settingsButtonText}> + {labels.export.button} + </AppText> + </TouchableOpacity> + </View> + <View style={styles.settingsSegment}> + <AppText style={styles.settingsSegmentTitle}> + {labels.import.button} + </AppText> + <AppText>{labels.import.segmentExplainer}</AppText> + <TouchableOpacity + onPress={openImportDialogAndImport} + style={styles.settingsButton}> + <AppText style={styles.settingsButtonText}> + {labels.import.button} + </AppText> + </TouchableOpacity> + </View> + </ScrollView> + ) + } +} diff --git a/components/settings/index.js b/components/settings/index.js index b91acbc9aef59de4430bc8f67018556552640a13..a1eaa03ee5bc9f1218da31b773e16c208679689f 100644 --- a/components/settings/index.js +++ b/components/settings/index.js @@ -1,67 +1,9 @@ -import React, { Component } from 'react' -import { - View, - TouchableOpacity, - ScrollView, -} from 'react-native' -import styles from '../../styles/index' -import { settings as labels } from '../../i18n/en/settings' -import AppText from '../app-text' -import TempReminderPicker from './temp-reminder-picker' -import PeriodReminderPicker from './period-reminder' -import TempSlider from './temp-slider' -import openImportDialogAndImport from './import-dialog' -import openShareDialogAndExport from './export-dialog' -import PasswordSetting from './password' -import UseCervixSetting from './use-cervix' +import Reminders from './reminders' +import NfpSettings from './nfp-settings' +import ImportExport from './import-export' +import Password from './password' +import About from './about' -export default class Settings extends Component { - constructor(props) { - super(props) - this.state = {} - } - - render() { - return ( - <ScrollView> - <TempReminderPicker/> - <UseCervixSetting/> - <View style={styles.settingsSegment}> - <AppText style={styles.settingsSegmentTitle}> - {labels.tempScale.segmentTitle} - </AppText> - <AppText>{labels.tempScale.segmentExplainer}</AppText> - <TempSlider/> - </View> - <PeriodReminderPicker/> - <PasswordSetting /> - <View style={styles.settingsSegment}> - <AppText style={styles.settingsSegmentTitle}> - {labels.export.button} - </AppText> - <AppText>{labels.export.segmentExplainer}</AppText> - <TouchableOpacity - onPress={openShareDialogAndExport} - style={styles.settingsButton}> - <AppText style={styles.settingsButtonText}> - {labels.export.button} - </AppText> - </TouchableOpacity> - </View> - <View style={styles.settingsSegment}> - <AppText style={styles.settingsSegmentTitle}> - {labels.import.button} - </AppText> - <AppText>{labels.import.segmentExplainer}</AppText> - <TouchableOpacity - onPress={openImportDialogAndImport} - style={styles.settingsButton}> - <AppText style={styles.settingsButtonText}> - {labels.import.button} - </AppText> - </TouchableOpacity> - </View> - </ScrollView> - ) - } +export default { + Reminders, NfpSettings, ImportExport, Password, About } diff --git a/components/settings/nfp-settings/index.js b/components/settings/nfp-settings/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e975dc746635fe8acacd002ffbfd0991c8cb329a --- /dev/null +++ b/components/settings/nfp-settings/index.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react' +import { + ScrollView, View +} from 'react-native' +import styles, { iconStyles } from '../../../styles' +import labels from '../../../i18n/en/settings' +import AppText from '../../app-text' +import TempSlider from './temp-slider' +import UseCervixSetting from './use-cervix' +import Icon from 'react-native-vector-icons/Entypo' +import Link from '../../link' + +export default class Settings extends Component { + constructor(props) { + super(props) + this.state = {} + } + + render() { + return ( + <ScrollView> + <UseCervixSetting/> + <View style={styles.settingsSegment}> + <AppText style={styles.settingsSegmentTitle}> + {labels.tempScale.segmentTitle} + </AppText> + <AppText>{labels.tempScale.segmentExplainer}</AppText> + <TempSlider/> + </View> + <View style={[styles.settingsSegment, styles.settingsSegmentLast]}> + <View style={{flexDirection: 'row', alignItems: 'center'}}> + <Icon name="info-with-circle" style={iconStyles.infoInHeading}/> + <AppText style={styles.settingsSegmentTitle}>{`${labels.preOvu.title} `}</AppText> + </View> + <AppText> + {labels.preOvu.note1} + <Link text={labels.preOvu.link} href="https://gitlab.com/bloodyhealth/drip/wikis/home" /> + {labels.preOvu.note2} + </AppText> + </View> + </ScrollView> + ) + } +} diff --git a/components/settings/temp-slider.js b/components/settings/nfp-settings/temp-slider.js similarity index 86% rename from components/settings/temp-slider.js rename to components/settings/nfp-settings/temp-slider.js index 70522994eaa3300e642a9558a82163b1b068f5a5..9dcdfb9824722bd0511e7a9453f3e79e737520c5 100644 --- a/components/settings/temp-slider.js +++ b/components/settings/nfp-settings/temp-slider.js @@ -1,15 +1,15 @@ import React, { Component } from 'react' import { View } from 'react-native' import Slider from '@ptomasroos/react-native-multi-slider' -import AppText from '../app-text' +import AppText from '../../app-text' import { scaleObservable, saveTempScale, -} from '../../local-storage' -import { secondaryColor } from '../../styles/index' -import { settings as labels } from '../../i18n/en/settings' -import config from '../../config' -import alertError from './alert-error' +} from '../../../local-storage' +import { secondaryColor } from '../../../styles/index' +import labels from '../../../i18n/en/settings' +import config from '../../../config' +import alertError from '../alert-error' export default class TempSlider extends Component { constructor(props) { diff --git a/components/settings/use-cervix.js b/components/settings/nfp-settings/use-cervix.js similarity index 87% rename from components/settings/use-cervix.js rename to components/settings/nfp-settings/use-cervix.js index 668a0e8268065f8bd5cfd187db8b6d9d2c500081..ecfe5d3825786a0c4803ff58a91f9b8201ac3bf2 100644 --- a/components/settings/use-cervix.js +++ b/components/settings/nfp-settings/use-cervix.js @@ -4,13 +4,13 @@ import { TouchableOpacity, Switch } from 'react-native' -import AppText from '../app-text' +import AppText from '../../app-text' import { useCervixObservable, saveUseCervix -} from '../../local-storage' -import styles from '../../styles/index' -import { settings as labels } from '../../i18n/en/settings' +} from '../../../local-storage' +import styles from '../../../styles/index' +import labels from '../../../i18n/en/settings' export default class UseCervixSetting extends Component { constructor() { diff --git a/components/settings/password/create.js b/components/settings/password/create.js index 68b7b2d2f014bbca23ec6047a23391934bd68608..bda688f83ebbb02cf5961b0a98bcb4382069f2cb 100644 --- a/components/settings/password/create.js +++ b/components/settings/password/create.js @@ -1,59 +1,15 @@ import React, { Component } from 'react' -import { - View, - TouchableOpacity, -} from 'react-native' -import nodejs from 'nodejs-mobile-react-native' -import AppText from '../../app-text' -import styles from '../../../styles' -import { settings } from '../../../i18n/en/settings' -import { requestHash, changeEncryptionAndRestartApp } from '../../../db' -import PasswordField from './password-field' +import { View } from 'react-native' +import settings from '../../../i18n/en/settings' +import EnterNewPassword from './enter-new-password' +import SettingsButton from './settings-button' import showBackUpReminder from './show-backup-reminder' -const SettingsButton = ({ children, ...props }) => { - return ( - <TouchableOpacity - style={[ - styles.settingsButton, - props.disabled ? styles.settingsButtonDisabled : null - ]} - { ...props } - > - <AppText style={styles.settingsButtonText}> - {children} - </AppText> - </TouchableOpacity> - ) -} - export default class CreatePassword extends Component { constructor() { super() this.state = { - isSettingPassword: false, - password: '', - passwordConfirmation: '', - shouldShowErrorMessage: false, - } - nodejs.channel.addListener( - 'create-pw-hash', - changeEncryptionAndRestartApp, - this - ) - } - - componentWillUnmount() { - nodejs.channel.removeListener('create-pw-hash', changeEncryptionAndRestartApp) - } - - savePassword = () => { - if (this.comparePasswords()) { - requestHash('create-pw-hash', this.state.password) - } else { - this.setState({ - shouldShowErrorMessage: true - }) + isSettingPassword: false } } @@ -66,35 +22,12 @@ export default class CreatePassword extends Component { showBackUpReminder(this.toggleSettingPassword) } - comparePasswords = () => { - return this.state.password === this.state.passwordConfirmation - } - - handlePasswordInput = (password) => { - this.setState({ password }) - } - - handleConfirmationInput = (passwordConfirmation) => { - const { password } = this.state - this.setState({ - passwordConfirmation, - isPasswordsMatch: passwordConfirmation === password - }) - } - render () { const { - isSettingPassword, - password, - passwordConfirmation, - shouldShowErrorMessage, + isSettingPassword } = this.state const labels = settings.passwordSettings - const isSaveButtonDisabled = - !password.length || - !passwordConfirmation.length - if (!isSettingPassword) { return ( <View> @@ -104,33 +37,7 @@ export default class CreatePassword extends Component { </View> ) } else { - return ( - <View> - <PasswordField - placeholder={labels.enterNew} - value={password} - onChangeText={this.handlePasswordInput} - /> - <PasswordField - autoFocus={false} - placeholder={labels.confirmPassword} - value={passwordConfirmation} - onChangeText={this.handleConfirmationInput} - /> - { - shouldShowErrorMessage && - <AppText style={styles.errorMessage}> - {labels.passwordsDontMatch} - </AppText> - } - <SettingsButton - onPress={this.savePassword} - disabled={isSaveButtonDisabled} - > - {labels.savePassword} - </SettingsButton> - </View> - ) + return <EnterNewPassword /> } } diff --git a/components/settings/password/delete.js b/components/settings/password/delete.js index d75f2de62916cfb9f5a2a94e49bce08b870ffd63..6cef844e5c3896e85c46017ebd0a36e8e7893fe8 100644 --- a/components/settings/password/delete.js +++ b/components/settings/password/delete.js @@ -6,7 +6,7 @@ import { import nodejs from 'nodejs-mobile-react-native' import AppText from '../../app-text' import styles from '../../../styles' -import { settings as labels } from '../../../i18n/en/settings' +import labels from '../../../i18n/en/settings' import { requestHash, changeEncryptionAndRestartApp } from '../../../db' import PasswordField from './password-field' import showBackUpReminder from './show-backup-reminder' diff --git a/components/settings/password/enter-new-password.js b/components/settings/password/enter-new-password.js new file mode 100644 index 0000000000000000000000000000000000000000..ed09ead3328b7bf56e153cfb7d7750c5e5b0c96f --- /dev/null +++ b/components/settings/password/enter-new-password.js @@ -0,0 +1,97 @@ +import React, { Component } from 'react' +import { View } from 'react-native' +import nodejs from 'nodejs-mobile-react-native' + +import { requestHash, changeEncryptionAndRestartApp } from '../../../db' +import AppText from '../../app-text' +import PasswordField from './password-field' +import SettingsButton from './settings-button' + +import styles from '../../../styles' +import settings from '../../../i18n/en/settings' + +const LISTENER_TYPE = 'create-or-change-pw' + +export default class EnterNewPassword extends Component { + + constructor() { + super() + this.state = { + password: '', + passwordConfirmation: '', + shouldShowErrorMessage: false, + } + nodejs.channel.addListener( + LISTENER_TYPE, + changeEncryptionAndRestartApp, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener(LISTENER_TYPE, changeEncryptionAndRestartApp) + } + + savePassword = () => { + if (this.comparePasswords()) { + requestHash(LISTENER_TYPE, this.state.password) + } else { + this.setState({ + shouldShowErrorMessage: true + }) + } + } + + comparePasswords = () => { + return this.state.password === this.state.passwordConfirmation + } + + handlePasswordInput = (password) => { + this.setState({ password }) + } + + handleConfirmationInput = (passwordConfirmation) => { + this.setState({ passwordConfirmation }) + } + + render () { + const { + password, + passwordConfirmation, + shouldShowErrorMessage, + } = this.state + const labels = settings.passwordSettings + + const isSaveButtonDisabled = + !password.length || + !passwordConfirmation.length + + return ( + <View> + <PasswordField + placeholder={labels.enterNew} + value={password} + onChangeText={this.handlePasswordInput} + /> + <PasswordField + autoFocus={false} + placeholder={labels.confirmPassword} + value={passwordConfirmation} + onChangeText={this.handleConfirmationInput} + /> + { + shouldShowErrorMessage && + <AppText style={styles.errorMessage}> + {labels.passwordsDontMatch} + </AppText> + } + <SettingsButton + onPress={this.savePassword} + disabled={isSaveButtonDisabled} + > + {labels.savePassword} + </SettingsButton> + </View> + ) + } +} \ No newline at end of file diff --git a/components/settings/password/index.js b/components/settings/password/index.js index 47e484866df58479c97f1ef7a76feb43ba28de86..f5741e3f372027f471bba9687a25501753edc193 100644 --- a/components/settings/password/index.js +++ b/components/settings/password/index.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import { View } from 'react-native' +import { View, ScrollView } from 'react-native' import CreatePassword from './create' import ChangePassword from './update' import DeletePassword from './delete' @@ -8,7 +8,7 @@ import { hasEncryptionObservable } from '../../../local-storage' import styles from '../../../styles/index' -import { settings as labels } from '../../../i18n/en/settings' +import labels from '../../../i18n/en/settings' export default class PasswordSetting extends Component { constructor(props) { @@ -21,30 +21,32 @@ export default class PasswordSetting extends Component { render() { return ( - <View style={styles.settingsSegment}> + <ScrollView> + <View style={styles.settingsSegment}> - <AppText style={styles.settingsSegmentTitle}> - {labels.passwordSettings.title} - </AppText> + <AppText style={styles.settingsSegmentTitle}> + {labels.passwordSettings.title} + </AppText> - {this.state.showUpdateAndDelete ? - <AppText>{labels.passwordSettings.explainerEnabled}</AppText> - : - <AppText>{labels.passwordSettings.explainerDisabled}</AppText> - } + {this.state.showUpdateAndDelete ? + <AppText>{labels.passwordSettings.explainerEnabled}</AppText> + : + <AppText>{labels.passwordSettings.explainerDisabled}</AppText> + } - {this.state.showUpdateAndDelete && + {this.state.showUpdateAndDelete && <View> <ChangePassword/> <DeletePassword/> </View> - } + } - {this.state.showCreate && + {this.state.showCreate && <CreatePassword/> - } + } - </View> + </View> + </ScrollView> ) } } \ No newline at end of file diff --git a/components/settings/password/settings-button.js b/components/settings/password/settings-button.js new file mode 100644 index 0000000000000000000000000000000000000000..97272bb2bb6cb603d97fcd37b97408d808ca7582 --- /dev/null +++ b/components/settings/password/settings-button.js @@ -0,0 +1,29 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { TouchableOpacity } from 'react-native' +import AppText from '../../app-text' +import styles from '../../../styles' + +const SettingsButton = ({ children, ...props }) => { + return ( + <TouchableOpacity + style={[ + styles.settingsButton, + props.disabled ? styles.settingsButtonDisabled : null + ]} + { ...props } + > + <AppText style={styles.settingsButtonText}> + {children} + </AppText> + </TouchableOpacity> + ) +} + +SettingsButton.propTypes = { + onPress: PropTypes.func.isRequired, + disabled: PropTypes.bool +} + +export default SettingsButton \ No newline at end of file diff --git a/components/settings/password/show-backup-reminder.js b/components/settings/password/show-backup-reminder.js index 708b81c5edc5e7112abbc6a146fd0a2f62edf2f6..f61e5a479b5c1efcc11ae1cf17f11c2f8a715e96 100644 --- a/components/settings/password/show-backup-reminder.js +++ b/components/settings/password/show-backup-reminder.js @@ -1,6 +1,6 @@ import { Alert } from 'react-native' import { shared } from '../../../i18n/en/labels' -import { settings as labels } from '../../../i18n/en/settings' +import labels from '../../../i18n/en/settings' export default function showBackUpReminder(okHandler, isDelete) { let title, message diff --git a/components/settings/password/update.js b/components/settings/password/update.js index fc10cca5a4e509cc82f3ae04aec0cb86b9be7986..a246c0ea6dc6c8e3d83bc96b377bbe26a2a17148 100644 --- a/components/settings/password/update.js +++ b/components/settings/password/update.js @@ -1,26 +1,23 @@ - import React, { Component } from 'react' -import { - View, - TouchableOpacity} from 'react-native' +import { View } from 'react-native' import nodejs from 'nodejs-mobile-react-native' -import AppText from '../../app-text' -import styles from '../../../styles' -import { shared } from '../../../i18n/en/labels' -import { settings as labels } from '../../../i18n/en/settings' -import { requestHash, changeEncryptionAndRestartApp } from '../../../db' +import { shared as sharedLabels } from '../../../i18n/en/labels' +import settings from '../../../i18n/en/settings' +import { requestHash } from '../../../db' +import EnterNewPassword from './enter-new-password' import PasswordField from './password-field' +import SettingsButton from './settings-button' import showBackUpReminder from './show-backup-reminder' import checkCurrentPassword from './check-current-password' + export default class ChangePassword extends Component { constructor() { super() this.state = { - enteringCurrentPassword: false, currentPassword: null, - enteringNewPassword: false, - newPassword: null + enteringCurrentPassword: false, + enteringNewPassword: false } nodejs.channel.addListener( @@ -28,17 +25,10 @@ export default class ChangePassword extends Component { this.openNewPasswordField, this ) - - nodejs.channel.addListener( - 'change-pw', - changeEncryptionAndRestartApp, - this - ) } componentWillUnmount() { nodejs.channel.removeListener('pre-change-pw-check', this.openNewPasswordField) - nodejs.channel.removeListener('change-pw', changeEncryptionAndRestartApp) } openNewPasswordField = async hash => { @@ -53,77 +43,67 @@ export default class ChangePassword extends Component { if (passwordCorrect) { this.setState({ - enteringCurrentPassword: false, currentPassword: null, - enteringNewPassword: true + enteringNewPassword: true, + enteringCurrentPassword: false }) } } + startChangingPassword = () => { + showBackUpReminder(() => { + this.setState({ enteringCurrentPassword: true }) + }) + } + + handleCurrentPasswordInput = (currentPassword) => { + this.setState({ currentPassword }) + } + + checkCurrentPassword = () => { + requestHash('pre-change-pw-check', this.state.currentPassword) + } + render() { - return ( - <View> - {!this.state.enteringCurrentPassword && - !this.state.enteringNewPassword && - <TouchableOpacity - onPress={() => showBackUpReminder(() => { - this.setState({ enteringCurrentPassword: true }) - })} - disabled={this.state.currentPassword} - style={styles.settingsButton}> - <AppText style={styles.settingsButtonText}> - {labels.passwordSettings.changePassword} - </AppText> - </TouchableOpacity> - } - {this.state.enteringCurrentPassword && - <View> - <PasswordField - onChangeText={val => { - this.setState({ - currentPassword: val, - wrongPassword: false - }) - }} - value={this.state.currentPassword} - placeholder={labels.passwordSettings.enterCurrent} - /> - <TouchableOpacity - onPress={() => requestHash('pre-change-pw-check', this.state.currentPassword)} - disabled={!this.state.currentPassword} - style={styles.settingsButton}> - <AppText style={styles.settingsButtonText}> - {shared.unlock} - </AppText> - </TouchableOpacity> - </View> - } + const { + enteringCurrentPassword, + enteringNewPassword, + currentPassword + } = this.state + + const labels = settings.passwordSettings - {this.state.enteringNewPassword && + if (enteringCurrentPassword) { + return ( <View> <PasswordField - style={styles.passwordField} - onChangeText={val => { - this.setState({ - newPassword: val - }) - }} - value={this.state.changedPassword} - placeholder={labels.passwordSettings.enterNew} + placeholder={labels.enterCurrent} + value={currentPassword} + onChangeText={this.handleCurrentPasswordInput} /> - - <TouchableOpacity - onPress={() => requestHash('change-pw', this.state.newPassword)} - disabled={ !this.state.newPassword } - style={styles.settingsButton}> - <AppText style={styles.settingsButtonText}> - {labels.passwordSettings.changePassword} - </AppText> - </TouchableOpacity> + <SettingsButton + onPress={this.checkCurrentPassword} + disabled={!currentPassword} + > + {sharedLabels.unlock} + </SettingsButton> </View> - } + ) + } + + if (enteringNewPassword) { + return <EnterNewPassword /> + } + return ( + <View> + <SettingsButton + onPress={this.startChangingPassword} + disabled={currentPassword} + > + {labels.changePassword} + </SettingsButton> </View> ) } diff --git a/components/settings/reminders/index.js b/components/settings/reminders/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ca234d1345afa26d25118453d0510da75cf4e3e1 --- /dev/null +++ b/components/settings/reminders/index.js @@ -0,0 +1,22 @@ +import React, { Component } from 'react' +import { + ScrollView, +} from 'react-native' +import TempReminderPicker from './temp-reminder-picker' +import PeriodReminderPicker from './period-reminder' + +export default class Settings extends Component { + constructor(props) { + super(props) + this.state = {} + } + + render() { + return ( + <ScrollView> + <TempReminderPicker/> + <PeriodReminderPicker/> + </ScrollView> + ) + } +} diff --git a/components/settings/period-reminder.js b/components/settings/reminders/period-reminder.js similarity index 85% rename from components/settings/period-reminder.js rename to components/settings/reminders/period-reminder.js index 1bc41cbe885f8fea50321af9c9f70d7ef285095a..3c07c1028634ba27146ea2a3964a88d01135e1bc 100644 --- a/components/settings/period-reminder.js +++ b/components/settings/reminders/period-reminder.js @@ -3,13 +3,13 @@ import { View, Switch } from 'react-native' -import AppText from '../app-text' +import AppText from '../../app-text' import { periodReminderObservable, savePeriodReminder -} from '../../local-storage' -import styles from '../../styles/index' -import { settings as labels } from '../../i18n/en/settings' +} from '../../../local-storage' +import styles from '../../../styles/index' +import labels from '../../../i18n/en/settings' export default class PeriodReminderPicker extends Component { constructor(props) { diff --git a/components/settings/temp-reminder-picker.js b/components/settings/reminders/temp-reminder-picker.js similarity index 90% rename from components/settings/temp-reminder-picker.js rename to components/settings/reminders/temp-reminder-picker.js index 48a7611bac78b5c5bdc14f75e4b1d8eca094e8c9..09dda4255458150b300530f713f4a6dc28405cee 100644 --- a/components/settings/temp-reminder-picker.js +++ b/components/settings/reminders/temp-reminder-picker.js @@ -5,14 +5,14 @@ import { Switch } from 'react-native' import DateTimePicker from 'react-native-modal-datetime-picker-nevo' -import AppText from '../app-text' +import AppText from '../../app-text' import { tempReminderObservable, saveTempReminder -} from '../../local-storage' -import styles from '../../styles/index' -import { settings as labels } from '../../i18n/en/settings' -import padWithZeros from '../helpers/pad-time-with-zeros' +} from '../../../local-storage' +import styles from '../../../styles/index' +import labels from '../../../i18n/en/settings' +import padWithZeros from '../../helpers/pad-time-with-zeros' export default class TempReminderPicker extends Component { constructor(props) { diff --git a/components/settings/settings-menu.js b/components/settings/settings-menu.js new file mode 100644 index 0000000000000000000000000000000000000000..4152c11f6a56558cadc8dbb452b860db80f56223 --- /dev/null +++ b/components/settings/settings-menu.js @@ -0,0 +1,40 @@ +import React from 'react' +import { + TouchableOpacity, + ScrollView, +} from 'react-native' +import styles from '../../styles/index' +import settingsLabels from '../../i18n/en/settings' +import AppText from '../app-text' + +console.log(settingsLabels.menuTitles) +const labels = settingsLabels.menuTitles +console.log(settingsLabels.menuTitles) + +const menu = [ + {title: labels.reminders, component: 'Reminders'}, + {title: labels.nfpSettings, component: 'NfpSettings'}, + {title: labels.importExport, component: 'ImportExport'}, + {title: labels.password, component: 'Password'}, + {title: labels.about, component: 'About'} +] + +export default function SettingsMenu(props) { + return ( + <ScrollView> + { menu.map(menuItem)} + </ScrollView> + ) + + function menuItem(item) { + return ( + <TouchableOpacity + style={styles.settingsSegment} + key={item.title} + onPress={() => props.navigate(item.component)} + > + <AppText>{item.title}</AppText> + </TouchableOpacity> + ) + } +} \ No newline at end of file diff --git a/i18n/en/cycle-day.js b/i18n/en/cycle-day.js index ff7ed56975f830e98b0a8595a9a337016c74bd8a..78cd4e24f0e009ae7b2ea96b619861a5e2a4d52d 100644 --- a/i18n/en/cycle-day.js +++ b/i18n/en/cycle-day.js @@ -92,5 +92,6 @@ export const sharedDialogs = { areYouSureToUnset: 'Are you sure you want to unset all entered data?', reallyUnsetData: 'Yes, I am sure', save: 'Save', - unset: 'Unset' + unset: 'Unset', + disabledInfo: 'There is some data missing' } diff --git a/i18n/en/labels.js b/i18n/en/labels.js index 059e2c0c1abbc03afc24d1f4f7b691f084fed376..743f4c12023c3a0791922237674c6988cd005e2d 100644 --- a/i18n/en/labels.js +++ b/i18n/en/labels.js @@ -1,3 +1,6 @@ +import labels from './settings' +const settingsTitles = labels.menuTitles + export const shared = { cancel: 'Cancel', save: 'Save', @@ -21,7 +24,12 @@ export const headerTitles = { Calendar: 'Calendar', Chart: 'Chart', Stats: 'Statistics', - Settings: 'Settings', + SettingsMenu: 'Settings', + Reminders: settingsTitles.reminders, + NfpSettings: settingsTitles.nfpSettings, + ImportExport: settingsTitles.importExport, + Password: settingsTitles.password, + About: settingsTitles.about, BleedingEditView: 'Bleeding', TemperatureEditView: 'Temperature', MucusEditView: 'Mucus', diff --git a/i18n/en/settings.js b/i18n/en/settings.js index eef6615c83f7e59a6502eda61208ab1d828aa811..9d46eb45942cc4227f0a92b21e5d199e44532873 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -1,4 +1,12 @@ -export const settings = { + +export default { + menuTitles: { + reminders: 'Reminders', + importExport: 'Import and Export', + nfpSettings: 'NFP settings', + password: 'Password', + about: 'About' + }, export: { errors: { noData: 'There is no data to export', @@ -67,5 +75,19 @@ export const settings = { backupReminder: 'Just to be safe, please backup your data using the export function before making changes to your password.\n\nLonger passwords are better! Consider using a passphrase.\n\nPlease also make sure you do not lose your password. There is no way to recover your data if you do.\n\nMaking any changes to your password setting will keep your data as it was before and restart the app.', deleteBackupReminderTitle: 'Read this before deleting your password', deleteBackupReminder: 'Deleting your password means your data will no longer be encrypted.\n\nJust to be safe, please backup your data using the export function before deleting your password.\n\nMaking any changes to your password setting will keep your data as it was before and restart the app.', + }, + aboutSection: { + title: 'About', + segmentExplainer: 'Please note that your data is stored locally on your phone and not on a server. We want to ensure that you stay in control of those sensitive information. If you are planning to switch or reset your phone, please remember to export your data before doing so. You can reinstall the app afterwards and import your data.\n\nIf you encounter any technical issues, don\'t hesitate to contact us via email (bl00dyhealth@mailbox.org). You can also contribute to the code base on GitLab (https://gitlab.com/bloodyhealth/drip/).', + }, + preOvu: { + title: 'Infertile days at cycle start', + note1: "drip applies NFP's rules for calculating infertile days at the start of the cycle (see the ", + link: 'wiki', + note2: " for more info). However, drip does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past symptothermal info is available." + }, + credits: { + title: 'Credits', + note: 'Thanks and lots of <3 to all of our contributors, as well as, and especially, Susanne Umscheid for the wonderful visual and logo design, and Paula Härtel for the symptom icons' } } \ No newline at end of file diff --git a/lib/cycle.js b/lib/cycle.js index 0f47690e47be58d5e80cee659278be01ede69bec..b675f52b9b221c2afc60aeb140b9f2b1bd517a00 100644 --- a/lib/cycle.js +++ b/lib/cycle.js @@ -39,7 +39,8 @@ export default function config(opts) { const targetDate = LocalDate.parse(targetDateString) const lastMensesLocalDate = LocalDate.parse(lastMensesStart.date) const diffInDays = lastMensesLocalDate.until(targetDate, DAYS) - + // take maxCycleLength into account (we don't display cycle day numbers higher than 99 at the moment) + if (diffInDays >= 99) return null // cycle starts at day 1 return diffInDays + 1 } diff --git a/lib/import-export/export-to-csv.js b/lib/import-export/export-to-csv.js index 619c5e2b4bcc6c2e1a8bb5e497e8571543348985..cb731265d3f7ea3296ac86d7d3db58fb1bd55668 100644 --- a/lib/import-export/export-to-csv.js +++ b/lib/import-export/export-to-csv.js @@ -1,19 +1,8 @@ import objectPath from 'object-path' -import { Base64 } from 'js-base64' -import { getCycleDaysSortedByDate } from '../../db' import getColumnNamesForCsv from './get-csv-column-names' -export default function makeDataURI() { - const cycleDaysSortedByDate = getCycleDaysSortedByDate() - if (!cycleDaysSortedByDate.length) return null - const csv = transformToCsv(cycleDaysSortedByDate) - const encoded = Base64.encodeURI(csv) - // this is the MIME type android/libcore/MimeUtils expects, so we oblige - return `data:text/comma-separated-values;base64,${encoded}` -} - -function transformToCsv(cycleDays) { +export default function transformToCsv(cycleDays) { const columnNames = getColumnNamesForCsv() const rows = cycleDays .map(day => { @@ -23,7 +12,6 @@ function transformToCsv(cycleDays) { }) }) .map(row => row.join(',')) - rows.unshift(columnNames.join(',')) return rows.join('\n') } diff --git a/lib/sensiplan-mucus.js b/lib/nfp-mucus.js similarity index 100% rename from lib/sensiplan-mucus.js rename to lib/nfp-mucus.js diff --git a/lib/sympto-adapter.js b/lib/sympto-adapter.js index 628ea2b97137cd1c07aa87477af406637eb260ab..980cbfe7c79aa4efd3c71d8791fd5e22104f28f3 100644 --- a/lib/sympto-adapter.js +++ b/lib/sympto-adapter.js @@ -1,16 +1,17 @@ import getFertilityStatus from './sympto' import cycleModule from './cycle' -import { fertilityStatus } from '../i18n/en/labels' import { useCervixObservable } from '../local-storage' +import { fertilityStatus as labels } from '../i18n/en/labels' export function getFertilityStatusForDay(dateString) { const status = getCycleStatusForDay(dateString) if (!status) return { - status: fertilityStatus.fertile, + status: labels.fertile, phase: null } - const phaseNameForDay = Object.keys(status.phases).find(phaseName => { + const phases = Object.keys(status.phases) + const phaseNameForDay = phases.find(phaseName => { const phase = status.phases[phaseName] const dayIsAfterPhaseStart = dateString >= phase.start.date let dayIsBeforePhaseEnd @@ -22,6 +23,12 @@ export function getFertilityStatusForDay(dateString) { return dayIsAfterPhaseStart && dayIsBeforePhaseEnd }) + // if there's only cycle data for the pre phase and the target day is after its end, + // the day is in the peri phase + if (phases.length === 1 && phases[0] === 'preOvulatory' && !phaseNameForDay) { + return formatStatus('periOvulatory', dateString, {phases: {periOvulatory: {}}}) + } + return formatStatus(phaseNameForDay, dateString, status) } @@ -58,27 +65,32 @@ function formatStatus(phaseNameForDay, dateString, status) { const mapping = { preOvulatory: () => { return { - status: fertilityStatus.infertile, + status: labels.infertile, phase: 1, - statusText: fertilityStatus.preOvuText + statusText: labels.preOvuText } }, periOvulatory: (dateString, status) => { - const phaseEnd = status.phases.periOvulatory.end + //there might not actually be any data for the phase + const peri = status.phases.periOvulatory + const phaseEnd = peri && peri.end + let s if (phaseEnd && phaseEnd.date === dateString) { - return fertilityStatus.fertileUntilEvening + s = labels.fertileUntilEvening + } else { + s = labels.fertile } return { - status: fertilityStatus.fertile, + status: s, phase: 2, - statusText: fertilityStatus.periOvuText + statusText: labels.periOvuText } }, postOvulatory: (dateString, status) => { return { - status: fertilityStatus.infertile, + status: labels.infertile, phase: 3, - statusText: fertilityStatus.postOvuText(status.temperatureShift.rule) + statusText: labels.postOvuText(status.temperatureShift.rule) } } } diff --git a/lib/sympto/temperature.js b/lib/sympto/temperature.js index d738ba34e017bd530dc1f1381b5ccd79ed54fbc5..1e8c87ed52cb9de2b9f80fb23ece2f5d4d713f2d 100644 --- a/lib/sympto/temperature.js +++ b/lib/sympto/temperature.js @@ -51,7 +51,7 @@ function checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl) { function getResultForRegularRule(nextDaysAfterPotentialFhm, ltl) { if (!nextDaysAfterPotentialFhm.every(day => day.temp > ltl)) return false const thirdDay = nextDaysAfterPotentialFhm[1] - if (rounded(thirdDay.temp - ltl, 0.1) < 0.2) return false + if (isLessThan0Point2(thirdDay.temp - ltl)) return false return { detected: true, rule: 0, @@ -77,7 +77,7 @@ function getResultForSecondExceptionRule(nextDaysAfterPotentialFhm, ltl) { if (nextDaysAfterPotentialFhm.length < 3) return false if (secondOrThirdTempIsAtOrBelowLtl(nextDaysAfterPotentialFhm, ltl)) { const fourthDay = nextDaysAfterPotentialFhm[2] - if (rounded(fourthDay.temp - ltl, 0.1) >= 0.2) { + if (isBiggerOrEqual0Point2(fourthDay.temp - ltl)) { return { detected: true, rule: 2, @@ -104,3 +104,19 @@ function rounded(val, step) { // we round the difference because of JS decimal weirdness return Math.round(val * inverted) / inverted } + + +// since we're dealing with floats, there is some imprecision in comparisons, +// so we add an error margin that is definitely much smaller than any possible +// actual difference between values +// see https://floating-point-gui.de/errors/comparison/ for background + +const errorMargin = 0.0001 + +function isLessThan0Point2(val) { + return val < (0.2 - errorMargin) +} + +function isBiggerOrEqual0Point2(val) { + return val >= (0.2 - errorMargin) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0964c8a86817d0052bd316c058b0e0f4deb32c79..9ae02fcc6b7db175411e9e3c5f3bdc272a429973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -771,12 +771,26 @@ } }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "ajv-keywords": { @@ -1248,9 +1262,9 @@ } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -1287,9 +1301,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.7.0", @@ -2218,18 +2232,11 @@ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "requires": { - "safe-buffer": "5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - } + "safe-buffer": "5.1.2" } }, "bcrypt-pbkdf": { @@ -2255,27 +2262,11 @@ "safe-buffer": "^5.1.1" } }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "~2.0.0" - } - }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.x.x" - } - }, "bplist-creator": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", @@ -2721,14 +2712,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.x.x" - } - }, "csvtojson": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.8.tgz", @@ -3640,12 +3623,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -3677,6 +3660,14 @@ "klaw": "^1.0.0" } }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "requires": { + "minipass": "^2.2.1" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4144,27 +4135,6 @@ } } }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "requires": { - "fstream": "^1.0.0", - "inherits": "2", - "minimatch": "^3.0.0" - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4313,17 +4283,17 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, "has": { @@ -4400,28 +4370,12 @@ } } }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -4482,11 +4436,11 @@ "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^0.2.0", + "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } @@ -4510,6 +4464,14 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "image-size": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", @@ -5184,9 +5146,9 @@ } }, "merge": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", - "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" }, "merge-stream": { "version": "1.0.1", @@ -5629,13 +5591,13 @@ "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "requires": { "basic-auth": "~2.0.0", "debug": "2.6.9", - "depd": "~1.1.1", + "depd": "~1.1.2", "on-finished": "~2.3.0", "on-headers": "~1.0.1" } @@ -5653,8 +5615,7 @@ "nan": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", - "optional": true + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" }, "nanomatch": { "version": "1.2.13", @@ -5719,6 +5680,23 @@ "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" }, + "needle": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", + "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + } + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -5738,6 +5716,11 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" }, + "node-machine-id": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.10.tgz", + "integrity": "sha512-6SVxo3Ic2Qc09z1rCJh3No7ubizPLszImsMQnZZWfzeOC6SYU4orN214++c3ikB8uaP/A6dwSlO88A3ohI5oNA==" + }, "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", @@ -5755,21 +5738,20 @@ } }, "node-pre-gyp": { - "version": "0.6.39", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", - "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", "requires": { "detect-libc": "^1.0.2", - "hawk": "3.1.3", "mkdirp": "^0.5.1", + "needle": "^2.2.1", "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", - "rc": "^1.1.7", - "request": "2.81.0", + "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^2.2.1", - "tar-pack": "^3.4.0" + "tar": "^4" }, "dependencies": { "gauge": { @@ -5806,35 +5788,6 @@ "set-blocking": "~2.0.0" } }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -5937,6 +5890,20 @@ "remove-trailing-separator": "^1.0.1" } }, + "npm-bundled": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" + }, + "npm-packlist": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz", + "integrity": "sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -6258,9 +6225,9 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "3.0.0", @@ -6387,20 +6354,25 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", + "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==" }, "randomatic": { "version": "3.1.0", @@ -6746,26 +6718,32 @@ } }, "realm": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/realm/-/realm-2.14.0.tgz", - "integrity": "sha512-HiCj/ZE3iZNOyWexVkhDetNEfcTtSdgsg5lop7g7rcCsE+4NM3hDRxvBjrBSk/mgs6HXBAhHohzGmREbs6piRg==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/realm/-/realm-2.21.0.tgz", + "integrity": "sha512-2XxkVogKOObhwBUcP7NPvyA9kU/HIeopVbAGgKanJWYw5z09J+I+q1CN2gCVR5EC4+H55Ht4loEhjDud5+LEYQ==", "requires": { "command-line-args": "^4.0.6", "decompress": "^4.2.0", "deepmerge": "2.1.0", "fs-extra": "^4.0.2", "ini": "^1.3.4", - "nan": "2.8.0", + "nan": "^2.10.0", "node-fetch": "^1.6.3", - "node-pre-gyp": "^0.6.36", + "node-machine-id": "^1.1.10", + "node-pre-gyp": "^0.11.0", "progress": "^2.0.0", "prop-types": "^15.5.10", - "request": "^2.78.0", + "request": "^2.88.0", "stream-counter": "^1.0.0", "sync-request": "^3.0.1", "url-parse": "^1.2.0" }, "dependencies": { + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", @@ -6784,10 +6762,46 @@ "graceful-fs": "^4.1.6" } }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } } } }, @@ -7698,14 +7712,6 @@ } } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.x.x" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7891,11 +7897,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -8000,41 +8001,50 @@ } }, "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "tar-pack": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", - "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", - "requires": { - "debug": "^2.2.0", - "fstream": "^1.0.10", - "fstream-ignore": "^1.0.5", - "once": "^1.3.3", - "readable-stream": "^2.1.4", - "rimraf": "^2.5.1", - "tar": "^2.2.1", - "uid-number": "^0.0.6" + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } } }, "tar-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", - "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "requires": { "bl": "^1.0.0", - "buffer-alloc": "^1.1.0", + "buffer-alloc": "^1.2.0", "end-of-stream": "^1.0.0", "fs-constants": "^1.0.0", "readable-stream": "^2.3.0", - "to-buffer": "^1.1.0", + "to-buffer": "^1.1.1", "xtend": "^4.0.0" } }, @@ -8299,20 +8309,15 @@ } } }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" - }, "ultron": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" }, "unbzip2-stream": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz", - "integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz", + "integrity": "sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==", "requires": { "buffer": "^3.0.1", "through": "^2.3.6" @@ -8425,15 +8430,30 @@ } } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url-parse": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", - "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", "requires": { "querystringify": "^2.0.0", "requires-port": "^1.0.0" diff --git a/package.json b/package.json index e01b525e81ae36917a00fe8d5fe19c8c5e279b43..f50ced907d664eb1f2b1aac44d6304d91620550e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "log": "react-native log-android", "test": "mocha --recursive --require @babel/register test && npm run lint", "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" }, "dependencies": { "@ptomasroos/react-native-multi-slider": "^1.0.0", @@ -26,6 +27,7 @@ "nodejs-mobile-react-native": "^0.3.0", "object-path": "^0.11.4", "obv": "0.0.1", + "prop-types": "^15.6.2", "react": "16.4.1", "react-native": "~0.56.0", "react-native-calendars": "^1.19.3", @@ -36,7 +38,7 @@ "react-native-restart": "0.0.7", "react-native-share": "^1.1.3", "react-native-vector-icons": "^5.0.0", - "realm": "^2.7.1" + "realm": "^2.21.0" }, "devDependencies": { "@babel/register": "^7.0.0-beta.55", diff --git a/styles/index.js b/styles/index.js index 36393dfaaee2f67fb411315a44cf6f02d2bcbbc5..118e1ce2017ddb7d5dfe80df58fe7630cb0f22d7 100644 --- a/styles/index.js +++ b/styles/index.js @@ -43,6 +43,10 @@ export default StyleSheet.create({ fontWeight: 'bold', fontFamily: textFont }, + link: { + color: cycleDayColor, + textDecorationLine: 'underline' + }, title: { fontSize: 18, color: 'black', @@ -263,6 +267,9 @@ export default StyleSheet.create({ padding: 7, fontFamily: 'textFont' }, + settingsSegmentLast: { + marginBottom: defaultTopMargin, + }, settingsSegmentTitle: { fontWeight: 'bold', fontFamily: 'textFont' @@ -414,4 +421,8 @@ export const iconStyles = { menuIconInactive: { color: colorInActive, }, + infoInHeading: { + marginRight: 5, + color: 'black' + } } diff --git a/test/sensiplan-mucus.spec.js b/test/sensiplan-mucus.spec.js index df1c461e8b9cbec78fac968399a63cc38c555a19..accbfe3060888a4ccc31a963162e2f668fded93c 100644 --- a/test/sensiplan-mucus.spec.js +++ b/test/sensiplan-mucus.spec.js @@ -4,7 +4,7 @@ import dirtyChai from 'dirty-chai' const expect = chai.expect chai.use(dirtyChai) -import getSensiplanMucus from '../lib/sensiplan-mucus' +import getSensiplanMucus from '../lib/nfp-mucus' describe('getSensiplanMucus', () => {