Compare commits

..

24 Commits

Author SHA1 Message Date
Sofiya Tepikin 9f3d115aca Use the date from context 2022-09-24 12:44:28 +02:00
Sofiya Tepikin 2f187b1007 Add date context and provide it to the app 2022-09-24 12:44:16 +02:00
Maria Zadnepryanets d78fbf74e3 Merge branch 'chore/add-translations-to-stats' into 'main'
Chore: add translations to stats

See merge request bloodyhealth/drip!545
2022-09-20 17:50:05 +00:00
Maria Zadnepryanets c9430439c5 Chore: add translations to stats 2022-09-20 17:50:05 +00:00
Maria Zadnepryanets 08712f460e Merge branch 'chore/add-footnote-component' into 'main'
Chore: add footnote component

See merge request bloodyhealth/drip!544
2022-09-20 16:43:21 +00:00
Maria Zadnepryanets 3447a0ea1e Chore: add footnote component 2022-09-20 16:43:20 +00:00
Sofiya Tepikin b4d92d0d7b Merge branch 'fix/chart-warning' into 'main'
Fix useEffect callback cannot be async

See merge request bloodyhealth/drip!540
2022-09-20 15:32:47 +00:00
Sofiya Tepikin d5f0e3532a Fix useEffect callback cannot be async 2022-09-20 15:32:47 +00:00
Maria Zadnepryanets 8774d251de Merge branch 'chore/add-statistics' into 'main'
Add more data to stats

Closes #264

See merge request bloodyhealth/drip!472
2022-09-19 14:23:56 +00:00
Maria Zadnepryanets 974d081f40 Add more data to stats 2022-09-19 14:23:55 +00:00
Sofiya Tepikin c847270159 Merge branch 'chore/extend-license-test' into 'main'
Chore/extend license test

See merge request bloodyhealth/drip!539
2022-09-19 10:54:34 +00:00
Sofiya Tepikin d58c230eda Chore/extend license test 2022-09-19 10:54:33 +00:00
Sofiya Tepikin 0747ea8a5f Merge branch 'dependabot-npm_and_yarn-babel-eslint-parser-7.19.1' into 'main'
Chore(deps-dev): bump @babel/eslint-parser from 7.16.3 to 7.19.1

See merge request bloodyhealth/drip!533
2022-09-19 09:46:54 +00:00
Sofiya Tepikin 0bba7afc6f Merge branch 'chore/retire-hyperlink' into 'main'
Chore/Retire hyperlink library

See merge request bloodyhealth/drip!538
2022-09-19 09:25:16 +00:00
Sofiya Tepikin f957553026 Chore/Retire hyperlink library 2022-09-19 09:25:16 +00:00
Maria Zadnepryanets 0597540b88 Merge branch 'fix/stop-keyboard-from-overlapping-with-inputs' into 'main'
Fix: Define isKeyboardOffset based on Platform

See merge request bloodyhealth/drip!523
2022-09-19 09:12:32 +00:00
Maria Zadnepryanets 75823ed750 Fix: Define isKeyboardOffset based on Platform 2022-09-19 09:12:31 +00:00
Sofiya Tepikin dd1c2cd96d Merge branch 'fix/license' into 'main'
Bring back License

See merge request bloodyhealth/drip!537
2022-09-18 13:54:56 +00:00
Sofiya Tepikin 06d346ee46 Bring back License 2022-09-18 15:51:39 +02:00
Sofiya Tepikin d4bd576cc9 Delete license.js 2022-09-18 13:49:24 +00:00
Sofiya Tepikin 85b3a8b4b6 Delete License.js 2022-09-18 13:46:21 +00:00
Sofiya Tepikin 206d4b06fa Merge branch 'fix/bring-back-license-test' into 'main'
Bring back the test

See merge request bloodyhealth/drip!536
2022-09-18 13:30:18 +00:00
Sofiya Tepikin 2669738c9d Bring back the test 2022-09-18 15:25:51 +02:00
Sofiya Tepikin 1eadd1c5d6 Chore(deps-dev): bump @babel/eslint-parser from 7.16.3 to 7.19.1
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.16.3 to 7.19.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.19.1/eslint/babel-eslint-parser)
2022-09-18 09:07:33 +00:00
40 changed files with 702 additions and 295 deletions
+1 -1
View File
@@ -33,7 +33,7 @@
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="sensorPortrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+6 -30
View File
@@ -4,7 +4,9 @@ import PropTypes from 'prop-types'
import moment from 'moment'
import AppText from './common/app-text'
import Asterisk from './common/Asterisk'
import Button from './common/button'
import Footnote from './common/Footnote'
import cycleModule from '../lib/cycle'
import { getFertilityStatusForDay } from '../lib/sympto-adapter'
@@ -16,9 +18,11 @@ import {
import { Colors, Fonts, Sizes, Spacing } from '../styles'
import { LocalDate } from '@js-joda/core'
import { useTranslation } from 'react-i18next'
import { useDate } from '../hooks/useDate'
const Home = ({ navigate, setDate }) => {
const Home = ({ navigate }) => {
const { t } = useTranslation()
const { setDate } = useDate()
function navigateToCycleDayView() {
setDate(todayDateString)
@@ -69,26 +73,12 @@ const Home = ({ navigate, setDate }) => {
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')}
</Button>
{phase && (
<View style={styles.asteriskLine}>
<Asterisk />
<AppText linkStyle={styles.whiteText} style={styles.greyText}>
{statusText}
</AppText>
</View>
)}
{phase && <Footnote>{statusText}</Footnote>}
</ScrollView>
)
}
const Asterisk = () => {
return <AppText style={styles.asterisk}>*</AppText>
}
const styles = StyleSheet.create({
asterisk: {
color: Colors.orange,
},
container: {
backgroundColor: Colors.purple,
flex: 1,
@@ -104,12 +94,6 @@ const styles = StyleSheet.create({
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
asteriskLine: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
title: {
color: Colors.purpleLight,
fontFamily: Fonts.bold,
@@ -124,18 +108,10 @@ const styles = StyleSheet.create({
color: 'white',
fontSize: Sizes.subtitle,
},
whiteText: {
color: 'white',
},
greyText: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
},
})
Home.propTypes = {
navigate: PropTypes.func,
setDate: PropTypes.func,
}
export default Home
+5 -1
View File
@@ -10,6 +10,8 @@ import AppStatusBar from './common/app-status-bar'
import AcceptLicense from './AcceptLicense'
import PasswordPrompt from './password-prompt'
import { DateProvider } from '../hooks/useDate'
export default function AppWrapper() {
const [isLoading, setIsLoading] = useState(true)
const [isLicenseAccepted, setIsLicenseAccepted] = useState(false)
@@ -47,7 +49,9 @@ export default function AppWrapper() {
{isDbEncrypted ? (
<PasswordPrompt enableShowApp={() => setIsDbEncrypted(false)} />
) : (
<App restartApp={() => checkIsDbEncrypted()} />
<DateProvider>
<App restartApp={() => checkIsDbEncrypted()} />
</DateProvider>
)}
</>
)
+3 -4
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
import { BackHandler, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { LocalDate } from '@js-joda/core'
import { useDate } from '../hooks/useDate'
import Header from './header'
import Menu from './menu'
@@ -13,7 +13,8 @@ import setupNotifications from '../lib/notifications'
import { closeDb } from '../db'
const App = ({ restartApp }) => {
const [date, setDate] = useState(LocalDate.now().toString())
const { setDate } = useDate()
const [currentPage, setCurrentPage] = useState('Home')
const goBack = () => {
if (currentPage === 'Home') {
@@ -43,8 +44,6 @@ const App = ({ restartApp }) => {
const isTemperatureEditView = currentPage === 'TemperatureEditView'
const headerProps = { navigate: setCurrentPage }
const pageProps = {
date,
setDate,
isTemperatureEditView,
navigate: setCurrentPage,
}
+4 -2
View File
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import { StyleSheet, View } from 'react-native'
import { CalendarList } from 'react-native-calendars'
import { useDate } from '../hooks/useDate'
import { getBleedingDaysSortedByDate } from '../db'
import cycleModule from '../lib/cycle'
import {
@@ -12,7 +14,8 @@ import {
todayToCalFormat,
} from './helpers/calendar'
const CalendarView = ({ setDate, navigate }) => {
const CalendarView = ({ navigate }) => {
const { setDate } = useDate()
const bleedingDays = getBleedingDaysSortedByDate()
const predictedMenses = cycleModule().getPredictedMenses()
@@ -49,7 +52,6 @@ const styles = StyleSheet.create({
})
CalendarView.propTypes = {
setDate: PropTypes.func.isRequired,
navigate: PropTypes.func.isRequired,
}
+16 -6
View File
@@ -28,12 +28,24 @@ import { Spacing } from '../../styles'
const getSymptomsFromCycleDays = (cycleDays) =>
SYMPTOMS.filter((symptom) => cycleDays.some((cycleDay) => cycleDay[symptom]))
const CycleChart = ({ navigate, setDate }) => {
const CycleChart = ({ navigate }) => {
const [shouldShowHint, setShouldShowHint] = useState(true)
useEffect(async () => {
const flag = await getChartFlag()
setShouldShowHint(flag === 'true')
useEffect(() => {
let isMounted = true
async function checkShouldShowHint() {
const flag = await getChartFlag()
if (isMounted) {
setShouldShowHint(flag === 'true')
}
}
checkShouldShowHint()
return () => {
isMounted = false
}
}, [])
const hideHint = () => {
@@ -72,7 +84,6 @@ const CycleChart = ({ navigate, setDate }) => {
const renderColumn = ({ item }) => {
return (
<DayColumn
setDate={setDate}
dateString={item}
navigate={navigate}
symptomHeight={symptomHeight}
@@ -124,7 +135,6 @@ const CycleChart = ({ navigate, setDate }) => {
CycleChart.propTypes = {
navigate: PropTypes.func,
setDate: PropTypes.func,
}
const styles = StyleSheet.create({
+3 -2
View File
@@ -8,6 +8,8 @@ import SymptomCell from './symptom-cell'
import TemperatureColumn from './temperature-column'
import CycleDayLabel from './cycle-day-label'
import { useDate } from '../../hooks/useDate'
import {
symptomColorMethods,
getTemperatureProps,
@@ -19,13 +21,13 @@ const DayColumn = ({
dateString,
chartSymptoms,
columnHeight,
setDate,
navigate,
shouldShowTemperatureColumn,
symptomHeight,
symptomRowSymptoms,
xAxisHeight,
}) => {
const { setDate } = useDate()
const cycleDayData = getCycleDay(dateString)
let data = {}
@@ -105,7 +107,6 @@ DayColumn.propTypes = {
chartSymptoms: PropTypes.array,
columnHeight: PropTypes.number.isRequired,
navigate: PropTypes.func.isRequired,
setDate: PropTypes.func.isRequired,
shouldShowTemperatureColumn: PropTypes.bool,
symptomHeight: PropTypes.number.isRequired,
symptomRowSymptoms: PropTypes.array,
+28
View File
@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, Text, Linking } from 'react-native'
import { Colors, Typography } from '../../styles'
const AppLink = ({ children, url, ...props }) => {
return (
<Text style={styles.link} {...props} onPress={() => Linking.openURL(url)}>
{children}
</Text>
)
}
AppLink.propTypes = {
children: PropTypes.node,
url: PropTypes.string,
}
const styles = StyleSheet.create({
link: {
...Typography.mainText,
color: Colors.purple,
textDecorationLine: 'underline',
},
})
export default AppLink
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import { StyleSheet } from 'react-native'
import AppText from './app-text'
import { Colors } from '../../styles'
const Asterisk = () => <AppText style={styles.asterisk}>*</AppText>
const styles = StyleSheet.create({
asterisk: {
color: Colors.orange,
},
})
export default Asterisk
+43
View File
@@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import Asterisk from '../common/Asterisk'
import { Colors, Spacing } from '../../styles'
const Footnote = ({ children }) => {
if (!children) return false
return (
<View style={styles.container}>
<Asterisk />
<AppText linkStyle={styles.link} style={styles.text}>
{children}
</AppText>
</View>
)
}
Footnote.propTypes = {
children: PropTypes.node,
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
link: {
color: 'white',
},
text: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
},
})
export default Footnote
+2 -5
View File
@@ -4,7 +4,7 @@ import { ScrollView, StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import { Colors, Typography } from '../../styles'
import { Colors, Containers, Typography } from '../../styles'
const AppPage = ({
children,
@@ -35,10 +35,7 @@ AppPage.propTypes = {
}
const styles = StyleSheet.create({
container: {
backgroundColor: Colors.turquoiseLight,
flex: 1,
},
container: { ...Containers.pageContainer },
scrollView: {
backgroundColor: Colors.turquoiseLight,
flexGrow: 1,
+4 -19
View File
@@ -1,30 +1,15 @@
import React from 'react'
import { KeyboardAvoidingView, StyleSheet, TextInput } from 'react-native'
import { StyleSheet, TextInput } from 'react-native'
import PropTypes from 'prop-types'
import { Colors, Spacing, Typography } from '../../styles'
const AppTextInput = ({ style, isKeyboardOffset, ...props }) => {
const behavior = isKeyboardOffset ? 'padding' : 'height'
const keyboardVerticalOffset = isKeyboardOffset ? 300 : 0
return (
<KeyboardAvoidingView
behavior={behavior}
keyboardVerticalOffset={keyboardVerticalOffset}
>
<TextInput style={[styles.input, style]} {...props} />
</KeyboardAvoidingView>
)
}
const AppTextInput = ({ style, ...props }) => (
<TextInput style={[styles.input, style]} {...props} />
)
AppTextInput.propTypes = {
style: PropTypes.object,
isKeyboardOffset: PropTypes.bool,
}
AppTextInput.defultProps = {
isKeyboardOffset: true,
}
const styles = StyleSheet.create({
+4 -10
View File
@@ -2,24 +2,18 @@ import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, Text } from 'react-native'
import Link from './link'
import { Colors, Typography } from '../../styles'
const AppText = ({ children, linkStyle, style, ...props }) => {
// we parse for links in case the text contains any
const AppText = ({ children, style, ...props }) => {
return (
<Link style={linkStyle}>
<Text style={[styles.text, style]} {...props}>
{children}
</Text>
</Link>
<Text style={[styles.text, style]} {...props}>
{children}
</Text>
)
}
AppText.propTypes = {
children: PropTypes.node,
linkStyle: PropTypes.object,
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}
-40
View File
@@ -1,40 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Hyperlink from 'react-native-hyperlink'
import { StyleSheet } from 'react-native'
import { Colors, Typography } from '../../styles'
import links from '../../i18n/en/links'
const Link = ({ children, style }) => {
return (
<Hyperlink
linkStyle={[styles.link, style]}
linkText={replaceUrlWithText}
linkDefault
>
{children}
</Hyperlink>
)
}
Link.propTypes = {
children: PropTypes.node,
style: PropTypes.object,
}
const styles = StyleSheet.create({
link: {
color: Colors.purple,
textDecorationLine: 'underline',
...Typography.mainText,
},
})
function replaceUrlWithText(url) {
const link = Object.values(links).find((l) => l.url === url)
return (link && link.text) || url
}
export default Link
+3 -8
View File
@@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native'
import AppText from './app-text'
import { Colors, Spacing, Typography } from '../../styles'
import { Colors, Containers, Spacing, Typography } from '../../styles'
const Segment = ({ children, last, title }) => {
const containerStyle = last ? styles.containerLast : styles.container
@@ -25,21 +25,16 @@ Segment.propTypes = {
title: PropTypes.string,
}
const segmentContainer = {
marginHorizontal: Spacing.base,
marginBottom: Spacing.base,
}
const styles = StyleSheet.create({
container: {
borderStyle: 'solid',
borderBottomWidth: 1,
borderBottomColor: Colors.greyLight,
paddingBottom: Spacing.base,
...segmentContainer,
...Containers.segmentContainer,
},
containerLast: {
...segmentContainer,
...Containers.segmentContainer,
},
title: {
...Typography.subtitle,
+4 -3
View File
@@ -9,10 +9,13 @@ import SymptomPageTitle from './symptom-page-title'
import { getCycleDay } from '../../db'
import { getData, nextDate, prevDate } from '../helpers/cycle-day'
import { useDate } from '../../hooks/useDate'
import { Spacing } from '../../styles'
import { SYMPTOMS } from '../../config'
const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
const CycleDayOverView = ({ isTemperatureEditView }) => {
const { date, setDate } = useDate()
const cycleDay = getCycleDay(date)
const [editedSymptom, setEditedSymptom] = useState(
@@ -58,8 +61,6 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
CycleDayOverView.propTypes = {
cycleDay: PropTypes.object,
date: PropTypes.string,
setDate: PropTypes.func,
isTemperatureEditView: PropTypes.bool,
}
+12 -3
View File
@@ -109,6 +109,13 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
}
const iconName = shouldShowInfo ? 'chevron-up' : 'chevron-down'
const noteText = symptom === 'note' ? data.value : data.note
const inputProps = {
multiline: true,
numberOfLines: 3,
scrollEnabled: false,
style: styles.input,
textAlignVertical: 'top',
}
return (
<AppModal onClose={onSave}>
@@ -157,7 +164,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
/>
{isOtherSelected && (
<AppTextInput
multiline={true}
{...inputProps}
placeholder={sharedLabels.enter}
value={data.note}
onChangeText={(value) => onSelectBoxNote(value)}
@@ -179,8 +186,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
<Segment style={styles.segmentBorder}>
<AppText>{symtomPage[symptom].note}</AppText>
<AppTextInput
multiline={true}
numberOfLines={3}
{...inputProps}
onChangeText={onEditNote}
placeholder={sharedLabels.enter}
testID="noteInput"
@@ -233,6 +239,9 @@ const styles = StyleSheet.create({
zIndex: 3, // works on ios
elevation: 3, // works on android
},
input: {
height: Sizes.base * 5,
},
modalContainer: {
paddingHorizontal: Spacing.base,
},
+15
View File
@@ -22,3 +22,18 @@ export function dateToTitle(dateString) {
? labels.today
: moment(dateString).format('ddd DD. MMM YY')
}
export function humanizeDate(dateString) {
if (!dateString) return ''
const today = LocalDate.now()
try {
const dateToDisplay = LocalDate.parse(dateString)
return today.equals(dateToDisplay)
? labels.today
: moment(dateString).format('DD. MMM YY')
} catch (e) {
return ''
}
}
+18 -17
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { Alert, StyleSheet, View } from 'react-native'
import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native'
import nodejs from 'nodejs-mobile-react-native'
import AppPage from './common/app-page'
@@ -68,22 +68,23 @@ const PasswordPrompt = ({ enableShowApp }) => {
<>
<Header isStatic />
<AppPage contentContainerStyle={styles.contentContainer}>
<AppTextInput
isKeyboardOffset={false}
onChangeText={setPassword}
secureTextEntry={true}
placeholder={labels.enterPassword}
/>
<View style={styles.containerButtons}>
<Button onPress={onConfirmDeletion}>{labels.forgotPassword}</Button>
<Button
disabled={!isPasswordEntered}
isCTA={isPasswordEntered}
onPress={unlockApp}
>
{labels.title}
</Button>
</View>
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={150}>
<AppTextInput
onChangeText={setPassword}
secureTextEntry={true}
placeholder={labels.enterPassword}
/>
<View style={styles.containerButtons}>
<Button onPress={onConfirmDeletion}>{labels.forgotPassword}</Button>
<Button
disabled={!isPasswordEntered}
isCTA={isPasswordEntered}
onPress={unlockApp}
>
{labels.title}
</Button>
</View>
</KeyboardAvoidingView>
</AppPage>
</>
)
@@ -3,6 +3,7 @@ import { Platform, Linking } from 'react-native'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import AppLink from '../common/AppLink'
import Segment from '../common/segment'
import Button from '../common/button'
import ButtonRow from '../common/button-row'
@@ -35,13 +36,15 @@ const AboutSection = () => {
</Segment>
<Segment title={t('credits.title')}>
<AppText>
{t('credits.text', {
smashicons: links.smashicons.url,
pause08: links.pause08.url,
kazachek: links.kazachek.url,
freepik: links.freepik.url,
flaticon: links.flaticon.url,
})}
{t('credits.text')}{' '}
<AppLink url={links.flaticon.url}>flaticon</AppLink>.{' '}
</AppText>
<AppText>
{t('credits.madeBy')}{' '}
<AppLink url={links.smashicons.url}>smashicons</AppLink>,{' '}
<AppLink url={links.pause08.url}>pause08</AppLink>,{' '}
<AppLink url={links.kazachek.url}>kazachek</AppLink>,{' '}
<AppLink url={links.freepik.url}>freepik</AppLink>.
</AppText>
</Segment>
<Segment title={t('donate.title')}>
+6 -4
View File
@@ -4,16 +4,18 @@ import { useTranslation } from 'react-i18next'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import AppLink from '../common/AppLink'
import Segment from '../common/segment'
const License = ({ children }) => {
const { t } = useTranslation()
const { t } = useTranslation(null, { keyPrefix: 'settings.license' })
const currentYear = new Date().getFullYear()
const link = 'https://www.gnu.org/licenses/gpl-3.0.html'
return (
<AppPage title={t('settings.license.title')}>
<AppPage title={t('title')}>
<Segment last>
<AppText>{t('settings.license.text', { currentYear })}</AppText>
<AppText>{t('text', { currentYear })}</AppText>
<AppLink url={link}>{link}</AppLink>
{children}
</Segment>
</AppPage>
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Alert, StyleSheet, View } from 'react-native'
import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native'
import nodejs from 'nodejs-mobile-react-native'
import AppTextInput from '../../common/app-text-input'
@@ -53,7 +53,7 @@ const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
const isPassword = password !== null
return (
<>
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={150}>
<AppTextInput
onChangeText={setPassword}
placeholder={labels.enterCurrent}
@@ -70,7 +70,7 @@ const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
{shared.confirmToProceed}
</Button>
</View>
</>
</KeyboardAvoidingView>
)
}
+1 -1
View File
@@ -2,7 +2,7 @@ import Reminders from './reminders/reminders'
import NfpSettings from './nfp-settings'
import DataManagement from './data-management'
import Password from './password'
import About from './about'
import About from './About'
import License from './License'
import PrivacyPolicy from './privacy-policy'
-27
View File
@@ -1,27 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import Segment from '../common/segment'
const License = ({ children }) => {
const { t } = useTranslation()
const currentYear = new Date().getFullYear()
return (
<AppPage title={t('settings.license.title')}>
<Segment last>
<AppText>{t('settings.license.text', { currentYear })}</AppText>
{children}
</Segment>
</AppPage>
)
}
License.propTypes = {
children: PropTypes.node,
}
export default License
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { StyleSheet } from 'react-native'
import { KeyboardAvoidingView, StyleSheet } from 'react-native'
import nodejs from 'nodejs-mobile-react-native'
import PropTypes from 'prop-types'
@@ -41,9 +41,8 @@ const EnterNewPassword = ({ changeEncryptionAndRestart }) => {
const isButtonActive = password.length > 0 && passwordConfirmation.length > 0
return (
<>
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={150}>
<AppTextInput
isKeyboardOffset={false}
onChangeText={setPassword}
placeholder={labels.enterNew}
textContentType="password"
@@ -51,7 +50,6 @@ const EnterNewPassword = ({ changeEncryptionAndRestart }) => {
secureTextEntry={true}
/>
<AppTextInput
isKeyboardOffset={false}
onChangeText={setPasswordConfirmation}
placeholder={labels.confirmPassword}
textContentType="password"
@@ -68,7 +66,7 @@ const EnterNewPassword = ({ changeEncryptionAndRestart }) => {
>
{labels.savePassword}
</Button>
</>
</KeyboardAvoidingView>
)
}
@@ -2,18 +2,16 @@ import React from 'react'
import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import AppText from './app-text'
import AppText from '../common/app-text'
import { Sizes, Spacing, Typography } from '../../styles'
const Table = ({ tableContent }) => {
return tableContent.map((rowContent, i) => (
<Row key={i} rowContent={rowContent} />
))
const StatsOverview = ({ data }) => {
return data.map((rowContent, i) => <Row key={i} rowContent={rowContent} />)
}
Table.propTypes = {
tableContent: PropTypes.array.isRequired,
StatsOverview.propTypes = {
data: PropTypes.array.isRequired,
}
const Row = ({ rowContent }) => {
@@ -65,18 +63,11 @@ const styles = StyleSheet.create({
},
cellLeft: {
alignItems: 'flex-end',
flex: 5,
flex: 3,
justifyContent: 'center',
},
cellRight: {
flex: 5,
justifyContent: 'center',
},
row: {
flexDirection: 'row',
marginBottom: Spacing.tiny,
marginLeft: Spacing.tiny,
},
cellRight: { flex: 5 },
row: { flexDirection: 'row' },
})
export default Table
export default StatsOverview
+107
View File
@@ -0,0 +1,107 @@
import React from 'react'
import { FlatList, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import AppText from '../common/app-text'
import cycleModule from '../../lib/cycle'
import { Spacing, Typography, Colors } from '../../styles'
import { humanizeDate } from '../helpers/format-date'
const Item = ({ data }) => {
const { t } = useTranslation(null, { keyPrefix: 'plurals' })
if (!data) return false
const { date, cycleLength, bleedingLength } = data
return (
<View style={styles.row}>
<View style={styles.accentCell}>
<AppText>{humanizeDate(date)}</AppText>
</View>
<View style={styles.cell}>
<AppText>{t('day', { count: cycleLength })}</AppText>
</View>
<View style={styles.cell}>
<AppText>{t('day', { count: bleedingLength })}</AppText>
</View>
</View>
)
}
Item.propTypes = {
data: PropTypes.object.isRequired,
}
const StatsTable = () => {
const renderItem = ({ item }) => <Item data={item} />
const data = cycleModule().getStats()
if (!data || data.length === 0) return false
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.date}
ItemSeparatorComponent={ItemDivider}
ListHeaderComponent={FlatListHeader}
ListHeaderComponentStyle={styles.headerDivider}
stickyHeaderIndices={[0]}
contentContainerStyle={styles.container}
/>
)
}
const ItemDivider = () => <View style={styles.divider} />
const FlatListHeader = () => (
<View style={styles.row}>
<View style={styles.accentCell}>
<AppText style={styles.header}>{'Cycle Start'}</AppText>
</View>
<View style={styles.cell}>
<AppText style={styles.header}>{'Cycle Length'}</AppText>
</View>
<View style={styles.cell}>
<AppText style={styles.header}>{'Bleeding'}</AppText>
</View>
</View>
)
const styles = StyleSheet.create({
divider: {
height: 1,
width: '100%',
backgroundColor: Colors.grey,
},
header: {
...Typography.accentOrange,
paddingVertical: Spacing.small,
},
headerDivider: {
borderBottomColor: Colors.purple,
borderBottomWidth: 2,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: Spacing.tiny,
backgroundColor: Colors.turquoiseLight,
},
cell: {
flex: 2,
justifyContent: 'center',
},
accentCell: {
flex: 3,
justifyContent: 'center',
},
container: {
paddingHorizontal: Spacing.base,
},
})
export default StatsTable
@@ -1,39 +1,45 @@
import React from 'react'
import { ImageBackground, View } from 'react-native'
import { ScaledSheet } from 'react-native-size-matters'
import { useTranslation } from 'react-i18next'
import AppPage from './common/app-page'
import AppText from './common/app-text'
import Segment from './common/segment'
import Table from './common/table'
import AppText from '../common/app-text'
import StatsOverview from './StatsOverview'
import StatsTable from './StatsTable'
import cycleModule from '../lib/cycle'
import {getCycleLengthStats as getCycleInfo} from '../lib/cycle-length'
import {stats as labels} from '../i18n/en/labels'
import cycleModule from '../../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../../lib/cycle-length'
import { Sizes, Spacing, Typography } from '../styles'
import { Containers, Sizes, Spacing, Typography } from '../../styles'
const image = require('../assets/cycle-icon.png')
const image = require('../../assets/cycle-icon.png')
const Stats = () => {
const { t } = useTranslation(null, { keyPrefix: 'stats' })
const cycleLengths = cycleModule().getAllCycleLengths()
const numberOfCycles = cycleLengths.length
const hasAtLeastOneCycle = numberOfCycles >= 1
const cycleData = hasAtLeastOneCycle ? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const cycleData =
numberOfCycles > 0
? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const standardDeviation = cycleData.stdDeviation
? cycleData.stdDeviation
: '—'
const statsData = [
[cycleData.minimum, labels.minLabel],
[cycleData.maximum, labels.maxLabel],
[cycleData.stdDeviation ? cycleData.stdDeviation : '—', labels.stdLabel],
[numberOfCycles, labels.basisOfStatsEnd]
[cycleData.minimum, t('overview.min')],
[cycleData.maximum, t('overview.max')],
[standardDeviation, t('overview.standardDeviation')],
[numberOfCycles, t('overview.completedCycles')],
]
return (
<AppPage contentContainerStyle={styles.pageContainer}>
<Segment last style={styles.pageContainer}>
<AppText>{labels.cycleLengthExplainer}</AppText>
{!hasAtLeastOneCycle && <AppText>{labels.emptyStats}</AppText>}
{hasAtLeastOneCycle &&
<View style={styles.pageContainer}>
<View style={styles.overviewContainer}>
<AppText>{t('intro')}</AppText>
{numberOfCycles === 0 ? (
<AppText>{t('noData')}</AppText>
) : (
<View style={styles.container}>
<View style={styles.columnLeft}>
<ImageBackground
@@ -49,20 +55,21 @@ const Stats = () => {
{cycleData.mean}
</AppText>
<AppText style={styles.accentPurpleHuge}>
{labels.daysLabel}
{t('overview.days')}
</AppText>
</ImageBackground>
<AppText style={styles.accentOrange}>
{labels.averageLabel}
{t('overview.average')}
</AppText>
</View>
<View style={styles.columnRight}>
<Table tableContent={statsData} />
<StatsOverview data={statsData} />
</View>
</View>
}
</Segment>
</AppPage>
)}
</View>
<StatsTable />
</View>
)
}
@@ -77,25 +84,24 @@ const styles = ScaledSheet.create({
},
accentPurpleGiant: {
...Typography.accentPurpleGiant,
marginTop: Spacing.base * (-2),
marginTop: Spacing.base * -2,
},
accentPurpleHuge: {
...Typography.accentPurpleHuge,
marginTop: Spacing.base * (-1),
marginTop: Spacing.base * -1,
},
container: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
paddingTop: Spacing.base
},
columnLeft: {
...column,
flex: 2,
flex: 3,
},
columnRight: {
...column,
flex: 3,
flex: 5,
paddingTop: Spacing.small,
},
image: {
@@ -105,9 +111,13 @@ const styles = ScaledSheet.create({
paddingTop: Spacing.large * 2.5,
marginBottom: Spacing.large,
},
overviewContainer: {
paddingHorizontal: Spacing.base,
paddingTop: Spacing.base,
},
pageContainer: {
marginTop: Spacing.base * 2,
}
...Containers.pageContainer,
},
})
export default Stats
+24
View File
@@ -0,0 +1,24 @@
import React, { createContext, useContext, useState } from 'react'
import PropTypes from 'prop-types'
import { LocalDate } from '@js-joda/core'
const DateContext = createContext()
export const DateProvider = ({ children }) => {
const [date, setDate] = useState(LocalDate.now().toString())
return (
<DateContext.Provider value={{ date, setDate }}>
{children}
</DateContext.Provider>
)
}
DateProvider.propTypes = {
children: PropTypes.node,
}
export const useDate = () => {
const { date, setDate } = useContext(DateContext)
return { date, setDate }
}
+26 -2
View File
@@ -17,7 +17,8 @@
"about": {
"credits": {
"title": "Credits",
"text": "We love the drip. team. Thanks and lots of <3 to all of our condriputors. Thanks to Paula Härtel for the symptom tracking icons. All the other icons are made by {{smashicons}}, {{pause08}}, {{kazachek}} & {{freepik}} from {{flaticon}}."
"text": "We love the drip. team. Thanks and lots of <3 to all of our condriputors. Thanks to Paula Härtel for the symptom tracking icons. All the other icons from:",
"madeBy": "Made by:"
},
"donate": {
"button": "Donate here",
@@ -38,7 +39,7 @@
},
"license": {
"title": "drip. an open-source cycle tracking app",
"text": "Copyright (C) {{currentYear}} Heart of Code e.V.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details: https://www.gnu.org/licenses/gpl-3.0.html."
"text": "Copyright (C) {{currentYear}} Heart of Code e.V.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details:"
},
"privacyPolicy": {
"title": "Privacy Policy",
@@ -59,5 +60,28 @@
"text": "You can read through the source code of drip. to ensure the given information is correct. The source code is like a recipe: It tells you how much and what kind of ingredients you need and how you prepare them to cook a tasty meal or program a funky app.\n\nBuon appetito!"
}
}
},
"stats": {
"noData": "At least one completed cycle is needed to display stats.",
"intro": "Basic statistics about the length of your cycles.",
"overview": {
"average": "Average cycle",
"days": "days",
"min": "Shortest",
"max": "Longest",
"standardDeviation": "Standard\ndeviation",
"completedCycles": "completed\ncycles"
},
"showStats": "Show period details",
"details": {
"cycleStart": "Cycle start",
"cycleLength": "Cycle length",
"bleedingDays": "Bleeding"
},
"footnote": "Based on the standard deviation of all your tracked periods drip. calculates a range for the starting day of the upcoming 3 periods. The range will be 3 days if your standard deviation is smaller than 1.5 and 5 days if the value is bigger.\n\nThe standard deviation tells you how much the length of your periods vary, 0 means all your periods are exactly the same length and the bigger the value the more the period length varies."
},
"plurals": {
"day": "{{count}} day",
"day_plural": "{{count}} days"
}
}
+23 -3
View File
@@ -3,6 +3,8 @@ import { getCycleLengthStats } from './cycle-length'
const LocalDate = joda.LocalDate
const DAYS = joda.ChronoUnit.DAYS
const toJSON = (realmObj) => JSON.parse(JSON.stringify(realmObj))
export default function config(opts) {
let bleedingDaysSortedByDate
let cycleStartsSortedByDate
@@ -14,9 +16,13 @@ export default function config(opts) {
if (!opts) {
// we only want to require (and run) the db module
// when not running the tests
bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
cycleStartsSortedByDate = require('../db').getCycleStartsSortedByDate()
cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
bleedingDaysSortedByDate = toJSON(
require('../db').getBleedingDaysSortedByDate()
)
cycleStartsSortedByDate = toJSON(
require('../db').getCycleStartsSortedByDate()
)
cycleDaysSortedByDate = toJSON(require('../db').getCycleDaysSortedByDate())
maxBreakInBleeding = 1
maxCycleLength = 99
minCyclesForPrediction = 3
@@ -222,6 +228,19 @@ export default function config(opts) {
return predictedMenses
}
const getStats = () =>
cycleStartsSortedByDate.map((day, i) => {
const today = getTodayDate()
const cycleLength =
i === 0 ? getCycleDayNumber(today) : getAllCycleLengths()[i - 1]
return {
date: day.date,
cycleLength,
bleedingLength: ++getMensesDaysRightAfter(day).length,
}
})
return {
getCycleDayNumber,
getCycleForDay,
@@ -232,5 +251,6 @@ export default function config(opts) {
isMensesStart,
getMensesDaysRightAfter,
getCycleByStartDay,
getStats,
}
}
+1 -2
View File
@@ -48,7 +48,6 @@
"react-native-calendars": "^1.1287.0",
"react-native-document-picker": "^8.1.1",
"react-native-fs": "^2.20.0",
"react-native-hyperlink": "0.0.19",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-push-notification": "3.2.1",
"react-native-share": "^7.9.0",
@@ -60,7 +59,7 @@
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.16.3",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.12.5",
"@testing-library/jest-native": "^4.0.12",
+14 -6
View File
@@ -9,7 +9,7 @@ export default {
marginTop: Spacing.small,
marginRight: Spacing.small,
paddingHorizontal: Spacing.small,
paddingVertical: Spacing.tiny
paddingVertical: Spacing.tiny,
},
boxActive: {
backgroundColor: Colors.orange,
@@ -17,17 +17,25 @@ export default {
centerItems: {
alignItems: 'center',
flex: 1,
justifyContent: 'center'
justifyContent: 'center',
},
pageContainer: {
backgroundColor: Colors.turquoiseLight,
flex: 1,
},
rowContainer: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between'
justifyContent: 'space-between',
},
selectGroupContainer: {
alignItems: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
marginVertical: Spacing.small
}
}
marginVertical: Spacing.small,
},
segmentContainer: {
marginHorizontal: Spacing.base,
marginBottom: Spacing.base,
},
}
+37
View File
@@ -0,0 +1,37 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react-native'
import AcceptLicense from '../components/AcceptLicense'
import { saveLicenseFlag } from '../local-storage'
jest.mock('../local-storage', () => ({
saveLicenseFlag: jest.fn(() => Promise.resolve()),
}))
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (str, options) => {
return str + (options ? JSON.stringify(options) : '')
},
}),
}))
describe('AcceptLicense', () => {
test('On clicking OK button, the license is accepted', async () => {
const mockedSetLicense = jest.fn()
render(<AcceptLicense setLicense={mockedSetLicense} />)
const okButton = screen.getByText('ok', { exact: false })
fireEvent(okButton, 'click')
await expect(saveLicenseFlag).toHaveBeenCalled()
expect(mockedSetLicense).toHaveBeenCalled()
})
test('There is a Cancel button', async () => {
render(<AcceptLicense setLicense={jest.fn()} />)
screen.getByText('cancel', { exact: false })
})
})
+8 -1
View File
@@ -12,8 +12,15 @@ jest.mock('react-i18next', () => ({
describe('License screen', () => {
test('It should have a correct year', async () => {
render(<License setLicense={() => {}} />)
render(<License />)
const year = new Date().getFullYear().toString()
screen.getByText(year, { exact: false })
})
test('It should match the snapshot', async () => {
const licenseScreen = render(<License />)
expect(licenseScreen).toMatchSnapshot()
})
})
+85
View File
@@ -0,0 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`License screen It should match the snapshot 1`] = `
<View
style={
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Array [
Object {
"backgroundColor": "#E9F2ED",
"flexGrow": 1,
},
undefined,
]
}
>
<View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"alignSelf": "center",
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 51.42857142857143,
"fontWeight": "700",
"marginHorizontal": 34.285714285714285,
"marginVertical": 42.857142857142854,
},
]
}
>
title
</Text>
<View
style={
Object {
"marginBottom": 34.285714285714285,
"marginHorizontal": 34.285714285714285,
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
text{"currentYear":2022}
</Text>
<Text
onPress={[Function]}
style={
Object {
"color": "#3A2671",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
"textDecorationLine": "underline",
}
}
>
https://www.gnu.org/licenses/gpl-3.0.html
</Text>
</View>
</View>
</RCTScrollView>
</View>
`;
+19
View File
@@ -0,0 +1,19 @@
import React from 'react'
import { render } from '@testing-library/react-native'
import Footnote from '../../../components/common/Footnote'
describe('Footnote component', () => {
test('when children are present, renders them', () => {
const text = 'Some footnote text'
const { toJSON } = render(<Footnote>{text}</Footnote>)
expect(toJSON()).toMatchSnapshot()
})
test('when no children, renders nothing', () => {
const { toJSON } = render(<Footnote></Footnote>)
expect(toJSON()).toMatchSnapshot()
})
})
@@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footnote component when children are present, renders them 1`] = `
<View
style={
Object {
"alignContent": "flex-start",
"flexDirection": "row",
"marginBottom": 8.571428571428571,
"marginTop": 21.428571428571427,
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
},
]
}
>
*
</Text>
<Text
linkStyle={
Object {
"color": "white",
}
}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#CCC",
"paddingLeft": 34.285714285714285,
},
]
}
>
Some footnote text
</Text>
</View>
`;
exports[`Footnote component when no children, renders nothing 1`] = `null`;
+27
View File
@@ -0,0 +1,27 @@
import { humanizeDate } from '../../components/helpers/format-date'
describe('humanizeDate', () => {
test('if receives null, returns empty string', () => {
const result = humanizeDate(null)
expect(result).toEqual('')
})
test('if receives undefined, returns empty string', () => {
const result = humanizeDate(undefined)
expect(result).toEqual('')
})
test('if receives incorrectly formatted date, returns empty string', () => {
const result = humanizeDate('abc')
expect(result).toEqual('')
})
test('if receives correct date string, returns date in humanized format', () => {
const result = humanizeDate('2022-01-07')
expect(result).toEqual('07. Jan 22')
})
})
+13 -31
View File
@@ -102,12 +102,12 @@
json5 "^2.2.1"
semver "^6.3.0"
"@babel/eslint-parser@^7.16.3":
version "7.16.3"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.16.3.tgz#2a6b1702f3f5aea48e00cea5a5bcc241c437e459"
integrity sha512-iB4ElZT0jAt7PKVaeVulOECdGe6UnmA/O0P9jlF5g5GBOwDVbna8AXhHRu4s27xQf6OkveyA8iTDv1jHdDejgQ==
"@babel/eslint-parser@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz#4f68f6b0825489e00a24b41b6a1ae35414ecd2f4"
integrity sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==
dependencies:
eslint-scope "^5.1.1"
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
eslint-visitor-keys "^2.1.0"
semver "^6.3.0"
@@ -1515,6 +1515,13 @@
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.3.0.tgz#b6f01fa510c9d360134a77f7a2820bdca3a396bf"
integrity sha512-3uObVJ08i0vSbtsTWQ8omy8XUlVDnoest5MOLp6delLUZev8bu++S+3Aua7xWPPWzQt9pcuwDqjEOKslQVDj8g==
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==
dependencies:
eslint-scope "5.1.1"
"@ptomasroos/react-native-multi-slider@^2.2.0":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@ptomasroos/react-native-multi-slider/-/react-native-multi-slider-2.2.2.tgz#35a97fb8c355627c6a2ded010b360ac5728b44ad"
@@ -3404,7 +3411,7 @@ eslint-plugin-react@^7.8.2:
resolve "^2.0.0-next.3"
string.prototype.matchall "^4.0.5"
eslint-scope@^5.1.1:
eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -5429,13 +5436,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
linkify-it@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -5580,11 +5580,6 @@ mdast-util-compact@^1.0.0:
dependencies:
unist-util-visit "^1.1.0"
mdurl@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
memoize-one@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
@@ -6903,14 +6898,6 @@ react-native-fs@^2.20.0:
base-64 "^0.1.0"
utf8 "^3.0.0"
react-native-hyperlink@0.0.19:
version "0.0.19"
resolved "https://registry.yarnpkg.com/react-native-hyperlink/-/react-native-hyperlink-0.0.19.tgz#ca375cde1d244bb94c551736852ee0d688c8bb1f"
integrity sha512-x4wuRGDMnnpWcRr5MCK1D2UcEuzD9IHK8lfjEhO/+QqXNaX31HdeD3ss3BXXZgHxpRYtLbTB0TuFcl1HHANp3w==
dependencies:
linkify-it "^2.2.0"
mdurl "^1.0.0"
react-native-modal-datetime-picker@14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-14.0.0.tgz#ca2c81a275ee3a23d9ad02113e76ed243c90781e"
@@ -8280,11 +8267,6 @@ ua-parser-js@^0.7.30:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
uc.micro@^1.0.1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-es@^3.1.9:
version "3.3.9"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"