Compare commits

..

18 Commits

Author SHA1 Message Date
MariaZ bee6b145ed Add test checking if StatsTable is shown on button click 2022-09-20 13:42:21 +02:00
MariaZ 57c8e31a9a Fix image import mocking 2022-09-20 13:36:05 +02:00
MariaZ 726b65914b Move react-i18next mock to jest-setup.js 2022-09-20 12:37:41 +02:00
MariaZ 82b6a6b603 Move jest-setup.js to ./test folder 2022-09-20 12:37:13 +02:00
MariaZ 5f61f37d2f Add tests for Stats component 2022-09-20 12:31:54 +02:00
MariaZ 1b6d21f730 Add check for undefined & null data in Stats component 2022-09-20 12:31:27 +02:00
MariaZ be0c11abfe Add tests for StatsTabe component 2022-09-20 11:58:34 +02:00
MariaZ e2e320927b Add tests for StatsOverview component 2022-09-20 10:59:28 +02:00
MariaZ 5c2a80c5c2 Add tests for AppHelp component 2022-09-20 10:48:31 +02:00
MariaZ 2e060d3261 Recreate components folder structure for tests 2022-09-20 10:35:28 +02:00
MariaZ c267e80424 Remove unnecessary import of labels 2022-09-20 10:29:40 +02:00
MariaZ c95e25a9b2 Remove onClose required prop from AppHelp 2022-09-19 21:31:35 +02:00
MariaZ 6c1ee3e5e2 Add explainer text to stats page and move more stats to modal 2022-09-19 21:31:17 +02:00
MariaZ 499e2d0628 Add en translations for stats page 2022-09-19 21:30:51 +02:00
MariaZ 951fb778d4 Add * to cell with standard deviation 2022-09-19 21:23:03 +02:00
MariaZ 6d6473ca78 Add translations to StatsTable 2022-09-19 21:17:09 +02:00
MariaZ 5a0321c5e5 Update styling & add onClose to StatsTable to make it modal 2022-09-19 21:14:06 +02:00
MariaZ 0c6c706274 Add AppHelp component 2022-09-19 20:52:44 +02:00
67 changed files with 3148 additions and 1731 deletions
+5
View File
@@ -73,6 +73,11 @@ ios/Podfile.lock
android/app/src/main/res/drawable-* android/app/src/main/res/drawable-*
android/app/src/main/assets/* android/app/src/main/assets/*
# nodejs-mobile creates these with every npm install
nodejs-assets/nodejs-project/sample-*
nodejs-assets/build-native-modules-MacOS-helper-script-node.sh
nodejs-assets/build-native-modules-MacOS-helper-script-npm.sh
# yarn # yarn
.yarn/* .yarn/*
yarn-error.log yarn-error.log
+1 -1
View File
@@ -11,7 +11,7 @@ updates:
- dependency-name: '*' - dependency-name: '*'
update-types: ['version-update:semver-patch'] update-types: ['version-update:semver-patch']
- dependency-name: 'realm' - dependency-name: 'realm'
- dependency-name: 'nodejs-mobile-react-native'
- dependency-name: 'react' - dependency-name: 'react'
- dependency-name: 'react-native' - dependency-name: 'react-native'
- dependency-name: 'react-native-push-notifications' - dependency-name: 'react-native-push-notifications'
- dependency-name: '@babel/core'
+5 -1
View File
@@ -50,7 +50,7 @@ Install [Android Studio](https://developer.android.com/studio/) - you'll need it
### 3.2 More requirements from Android Studio ### 3.2 More requirements from Android Studio
Open Android Studio and click on "Open an existing Android Studio project". Navigate to the drip repository you cloned and double click the android folder. It detects, downloads and cofigures requirements that might be missing, like the NDK and CMake to build the native code part of the project. Open Android Studio and click on "Open an existing Android Studio project". Navigate to the drip repository you cloned and double click the android folder. It detects, downloads and cofigures requirements that might be missing, like the NDK and CMake to build the native code part of the project. Also see the [nodejs-mobile repository](https://github.com/janeasystems/nodejs-mobile) for the necessary prerequisites for your system.
### 3.3 Run the app on Android ### 3.3 Run the app on Android
@@ -201,3 +201,7 @@ More information about how the app calculates fertility status and bleeding pred
react-native link react-native link
5. You should be able to use the icon now within drip, e.g. in Cycle Day Overview and on the chart. 5. You should be able to use the icon now within drip, e.g. in Cycle Day Overview and on the chart.
## Translation
We are using [Weblate](https://weblate.org/) as translation software.
+28 -3
View File
@@ -4,9 +4,7 @@ import PropTypes from 'prop-types'
import moment from 'moment' import moment from 'moment'
import AppText from './common/app-text' import AppText from './common/app-text'
import Asterisk from './common/Asterisk'
import Button from './common/button' import Button from './common/button'
import Footnote from './common/Footnote'
import cycleModule from '../lib/cycle' import cycleModule from '../lib/cycle'
import { getFertilityStatusForDay } from '../lib/sympto-adapter' import { getFertilityStatusForDay } from '../lib/sympto-adapter'
@@ -71,12 +69,26 @@ const Home = ({ navigate, setDate }) => {
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}> <Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')} {t('labels.home.addDataForToday')}
</Button> </Button>
{phase && <Footnote colorLabel="greyLight">{statusText}</Footnote>} {phase && (
<View style={styles.asteriskLine}>
<Asterisk />
<AppText linkStyle={styles.whiteText} style={styles.greyText}>
{statusText}
</AppText>
</View>
)}
</ScrollView> </ScrollView>
) )
} }
const Asterisk = () => {
return <AppText style={styles.asterisk}>*</AppText>
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
asterisk: {
color: Colors.orange,
},
container: { container: {
backgroundColor: Colors.purple, backgroundColor: Colors.purple,
flex: 1, flex: 1,
@@ -92,6 +104,12 @@ const styles = StyleSheet.create({
marginBottom: Spacing.tiny, marginBottom: Spacing.tiny,
marginTop: Spacing.small, marginTop: Spacing.small,
}, },
asteriskLine: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
title: { title: {
color: Colors.purpleLight, color: Colors.purpleLight,
fontFamily: Fonts.bold, fontFamily: Fonts.bold,
@@ -106,6 +124,13 @@ const styles = StyleSheet.create({
color: 'white', color: 'white',
fontSize: Sizes.subtitle, fontSize: Sizes.subtitle,
}, },
whiteText: {
color: 'white',
},
greyText: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
},
}) })
Home.propTypes = { Home.propTypes = {
+3 -1
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import nodejs from 'nodejs-mobile-react-native'
import { getLicenseFlag, saveEncryptionFlag } from '../local-storage' import { getLicenseFlag, saveEncryptionFlag } from '../local-storage'
import { openDb } from '../db' import { openDb } from '../db'
@@ -7,7 +8,7 @@ import App from './app'
import AppLoadingView from './common/app-loading' import AppLoadingView from './common/app-loading'
import AppStatusBar from './common/app-status-bar' import AppStatusBar from './common/app-status-bar'
import AcceptLicense from './AcceptLicense' import AcceptLicense from './AcceptLicense'
import PasswordPrompt from './PasswordPrompt' import PasswordPrompt from './password-prompt'
export default function AppWrapper() { export default function AppWrapper() {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@@ -27,6 +28,7 @@ export default function AppWrapper() {
} }
useEffect(() => { useEffect(() => {
nodejs.start('main.js')
checkIsLicenseAccepted() checkIsLicenseAccepted()
checkIsDbEncrypted() checkIsDbEncrypted()
}, []) }, [])
+2 -14
View File
@@ -9,7 +9,7 @@ import HorizontalGrid from './horizontal-grid'
import MainGrid from './main-grid' import MainGrid from './main-grid'
import NoData from './no-data' import NoData from './no-data'
import NoTemperature from './no-temperature' import NoTemperature from './no-temperature'
import Tutorial from './Tutorial' import Tutorial from './tutorial'
import YAxis from './y-axis' import YAxis from './y-axis'
import { getCycleDaysSortedByDate } from '../../db' import { getCycleDaysSortedByDate } from '../../db'
@@ -31,21 +31,9 @@ const getSymptomsFromCycleDays = (cycleDays) =>
const CycleChart = ({ navigate, setDate }) => { const CycleChart = ({ navigate, setDate }) => {
const [shouldShowHint, setShouldShowHint] = useState(true) const [shouldShowHint, setShouldShowHint] = useState(true)
useEffect(() => { useEffect(async () => {
let isMounted = true
async function checkShouldShowHint() {
const flag = await getChartFlag() const flag = await getChartFlag()
if (isMounted) {
setShouldShowHint(flag === 'true') setShouldShowHint(flag === 'true')
}
}
checkShouldShowHint()
return () => {
isMounted = false
}
}, []) }, [])
const hideHint = () => { const hideHint = () => {
@@ -6,17 +6,16 @@ import AppText from '../common/app-text'
import CloseIcon from '../common/close-icon' import CloseIcon from '../common/close-icon'
import { Containers, Spacing } from '../../styles' import { Containers, Spacing } from '../../styles'
import { useTranslation } from 'react-i18next' import { chart } from '../../i18n/en/labels'
const image = require('../../assets/swipe.png') const image = require('../../assets/swipe.png')
const Tutorial = ({ onClose }) => { const Tutorial = ({ onClose }) => {
const { t } = useTranslation()
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Image resizeMode="contain" source={image} style={styles.image} /> <Image resizeMode="contain" source={image} style={styles.image} />
<View style={styles.textContainer}> <View style={styles.textContainer}>
<AppText>{t('chart.tutorial')}</AppText> <AppText>{chart.tutorial}</AppText>
</View> </View>
<CloseIcon onClose={onClose} /> <CloseIcon onClose={onClose} />
</View> </View>
+32
View File
@@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import { Containers, Spacing, Typography } from '../../styles'
const AppHelp = ({ text }) => (
<View style={styles.container}>
<AppText style={styles.accentPurple}>*</AppText>
<AppText>{text}</AppText>
</View>
)
AppHelp.propTypes = {
text: PropTypes.string.isRequired,
}
const styles = StyleSheet.create({
accentPurple: {
...Typography.accentPurple,
alignSelf: 'flex-start',
paddingRight: Spacing.base,
},
container: {
...Containers.rowContainer,
padding: Spacing.base,
},
})
export default AppHelp
-16
View File
@@ -1,16 +0,0 @@
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
-50
View File
@@ -1,50 +0,0 @@
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, colorLabel }) => {
if (!children) return false
return (
<View style={styles.container}>
<Asterisk />
<AppText
linkStyle={styles.link}
style={{ ...styles.text, color: Colors[colorLabel] }}
>
{children}
</AppText>
</View>
)
}
Footnote.propTypes = {
children: PropTypes.node,
colorLabel: PropTypes.string,
}
Footnote.defaultProps = {
colorLabel: 'greyDark',
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.base,
},
link: {
color: 'white',
},
text: {
paddingLeft: Spacing.small,
},
})
export default Footnote
@@ -2,7 +2,7 @@ import React from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import AppText from '../common/app-text' import AppText from './app-text'
import { Sizes, Spacing, Typography } from '../../styles' import { Sizes, Spacing, Typography } from '../../styles'
@@ -15,12 +15,12 @@ StatsOverview.propTypes = {
} }
const Row = ({ rowContent }) => { const Row = ({ rowContent }) => {
const isStandardDeviation = rowContent[1].includes('deviation') const showHelp = rowContent[1].includes('deviation') ? true : false
return ( return (
<View style={styles.row}> <View style={styles.row}>
<Cell content={rowContent[0]} isLeft /> <Cell content={rowContent[0]} isLeft />
<Cell content={rowContent[1]} hasAsterisk={isStandardDeviation} /> <Cell content={rowContent[1]} showHelp={showHelp} />
</View> </View>
) )
} }
@@ -29,7 +29,7 @@ Row.propTypes = {
rowContent: PropTypes.array.isRequired, rowContent: PropTypes.array.isRequired,
} }
const Cell = ({ content, isLeft, hasAsterisk }) => { const Cell = ({ content, isLeft, showHelp }) => {
const styleContainer = isLeft ? styles.cellLeft : styles.cellRight const styleContainer = isLeft ? styles.cellLeft : styles.cellRight
const styleText = isLeft ? styles.accentPurpleBig : styles.accentOrange const styleText = isLeft ? styles.accentPurpleBig : styles.accentOrange
const numberOfLines = isLeft ? 1 : 2 const numberOfLines = isLeft ? 1 : 2
@@ -43,8 +43,8 @@ const Cell = ({ content, isLeft, hasAsterisk }) => {
style={styleText} style={styleText}
> >
{content} {content}
{hasAsterisk && <AppText style={styles.accentOrange}>*</AppText>}
</AppText> </AppText>
{showHelp && <AppText style={styles.accentPurpleBig}>*</AppText>}
</View> </View>
) )
} }
@@ -52,7 +52,7 @@ const Cell = ({ content, isLeft, hasAsterisk }) => {
Cell.propTypes = { Cell.propTypes = {
content: PropTypes.node.isRequired, content: PropTypes.node.isRequired,
isLeft: PropTypes.bool, isLeft: PropTypes.bool,
hasAsterisk: PropTypes.bool, showHelp: PropTypes.bool,
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -70,7 +70,10 @@ const styles = StyleSheet.create({
flex: 3, flex: 3,
justifyContent: 'center', justifyContent: 'center',
}, },
cellRight: { flex: 5 }, cellRight: {
flex: 5,
flexDirection: 'row',
},
row: { flexDirection: 'row' }, row: { flexDirection: 'row' },
}) })
@@ -1,13 +1,13 @@
import React from 'react' import React from 'react'
import { FlatList, StyleSheet, View } from 'react-native' import { Dimensions, FlatList, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import AppModal from '../common/app-modal' import AppText from './app-text'
import AppText from '../common/app-text' import CloseIcon from './close-icon'
import cycleModule from '../../lib/cycle' import cycleModule from '../../lib/cycle'
import { Spacing, Typography, Colors } from '../../styles' import { Sizes, Spacing, Typography, Colors } from '../../styles'
import { humanizeDate } from '../helpers/format-date' import { humanizeDate } from '../helpers/format-date'
const Item = ({ data }) => { const Item = ({ data }) => {
@@ -36,15 +36,17 @@ Item.propTypes = {
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
} }
const PeriodDetailsModal = ({ onClose }) => { const StatsTable = ({ onClose }) => {
const renderItem = ({ item }) => <Item data={item} /> const renderItem = ({ item }) => <Item data={item} />
const data = cycleModule().getStats() const data = cycleModule().getStats()
if (!data || data.length === 0) return false if (!data || data.length === 0) return false
return ( return (
<AppModal onClose={onClose}> <View style={styles.modalContainer}>
<View> <View style={styles.headerContainer}>
<CloseIcon onClose={onClose} />
</View>
<FlatList <FlatList
data={data} data={data}
renderItem={renderItem} renderItem={renderItem}
@@ -56,31 +58,45 @@ const PeriodDetailsModal = ({ onClose }) => {
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
/> />
</View> </View>
</AppModal>
) )
} }
PeriodDetailsModal.propTypes = { StatsTable.propTypes = {
onClose: PropTypes.func, onClose: PropTypes.func,
} }
const ItemDivider = () => <View style={styles.divider} /> const ItemDivider = () => <View style={styles.divider} />
const FlatListHeader = () => ( const FlatListHeader = () => {
const { t } = useTranslation(null, { keyPrefix: 'stats' })
return (
<View style={styles.row}> <View style={styles.row}>
<View style={styles.accentCell}> <View style={styles.accentCell}>
<AppText style={styles.header}>{'Cycle Start'}</AppText> <AppText style={styles.header}>{t('cycle_start')}</AppText>
</View> </View>
<View style={styles.cell}> <View style={styles.cell}>
<AppText style={styles.header}>{'Cycle Length'}</AppText> <AppText style={styles.header}>{t('cycle_length')}</AppText>
</View> </View>
<View style={styles.cell}> <View style={styles.cell}>
<AppText style={styles.header}>{'Bleeding'}</AppText> <AppText style={styles.header}>{t('bleeding')}</AppText>
</View> </View>
</View> </View>
) )
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
accentCell: {
flex: 3,
justifyContent: 'center',
},
cell: {
flex: 2,
justifyContent: 'center',
},
container: {
paddingHorizontal: Spacing.base,
},
divider: { divider: {
height: 1, height: 1,
width: '100%', width: '100%',
@@ -90,29 +106,31 @@ const styles = StyleSheet.create({
...Typography.accentOrange, ...Typography.accentOrange,
paddingVertical: Spacing.small, paddingVertical: Spacing.small,
}, },
headerContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingTop: Spacing.base,
paddingRight: Spacing.base,
},
headerDivider: { headerDivider: {
borderBottomColor: Colors.purple, borderBottomColor: Colors.purple,
borderBottomWidth: 2, borderBottomWidth: 2,
}, },
modalContainer: {
alignSelf: 'center',
backgroundColor: Colors.turquoiseLight,
marginTop: Sizes.huge * 2,
maxHeight: Dimensions.get('window').height * 0.7,
minHeight: '40%',
position: 'absolute',
width: '100%',
},
row: { row: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingVertical: Spacing.tiny, paddingVertical: Spacing.tiny,
backgroundColor: 'white', backgroundColor: Colors.turquoiseLight,
},
cell: {
flex: 2,
justifyContent: 'center',
},
accentCell: {
flex: 3,
justifyContent: 'center',
},
container: {
minHeight: '40%',
minWidth: '95%',
paddingHorizontal: Spacing.base,
}, },
}) })
export default PeriodDetailsModal export default StatsTable
+5 -40
View File
@@ -1,18 +1,9 @@
import React from 'react' import React from 'react'
import { import { Modal, StyleSheet, TouchableOpacity } from 'react-native'
Dimensions,
Modal,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import CloseIcon from './close-icon' const AppModal = ({ children, onClose }) => {
return (
import { Sizes, Spacing } from '../../styles'
const AppModal = ({ children, onClose }) => (
<Modal <Modal
animationType="fade" animationType="fade"
onRequestClose={onClose} onRequestClose={onClose}
@@ -20,14 +11,10 @@ const AppModal = ({ children, onClose }) => (
visible={true} visible={true}
> >
<TouchableOpacity onPress={onClose} style={styles.blackBackground} /> <TouchableOpacity onPress={onClose} style={styles.blackBackground} />
<View style={styles.modalWindow}>
<View style={styles.headerContainer}>
<CloseIcon onClose={onClose} />
</View>
{children} {children}
</View>
</Modal> </Modal>
) )
}
AppModal.propTypes = { AppModal.propTypes = {
children: PropTypes.node, children: PropTypes.node,
@@ -40,28 +27,6 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
opacity: 0.5, opacity: 0.5,
}, },
headerContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingTop: Spacing.base,
paddingHorizontal: Spacing.base,
position: 'absolute',
width: '100%',
zIndex: 3, // works on ios
elevation: 3, // works on android
},
modalWindow: {
alignSelf: 'center',
backgroundColor: 'white',
borderRadius: 10,
marginTop: Sizes.huge * 2,
paddingVertical: Spacing.large * 2,
position: 'absolute',
maxHeight: Dimensions.get('window').height * 0.7,
zIndex: 2, // works on ios
elevation: 2, // works on android
minWidth: '80%',
},
}) })
export default AppModal export default AppModal
+4 -3
View File
@@ -10,7 +10,7 @@ import SymptomEditView from './symptom-edit-view'
import { isDateInFuture } from '../helpers/cycle-day' import { isDateInFuture } from '../helpers/cycle-day'
import { Colors, Sizes, Spacing } from '../../styles' import { Colors, Sizes, Spacing } from '../../styles'
import { useTranslation } from 'react-i18next' import { headerTitles as symptomTitles } from '../../i18n/en/labels'
const SymptomBox = ({ const SymptomBox = ({
date, date,
@@ -20,7 +20,6 @@ const SymptomBox = ({
editedSymptom, editedSymptom,
setEditedSymptom, setEditedSymptom,
}) => { }) => {
const { t } = useTranslation(null, { keyPrefix: 'cycleDay.symptomBox' })
const isSymptomEdited = editedSymptom === symptom const isSymptomEdited = editedSymptom === symptom
const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note' const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
const isExcluded = symptomData !== null ? symptomData.exclude : false const isExcluded = symptomData !== null ? symptomData.exclude : false
@@ -62,7 +61,9 @@ const SymptomBox = ({
size={Sizes.icon} size={Sizes.icon}
/> />
<View style={styles.textContainer}> <View style={styles.textContainer}>
<AppText style={symptomNameStyle}>{t(symptom)}</AppText> <AppText style={symptomNameStyle}>
{symptomTitles[symptom].toLowerCase()}
</AppText>
{symptomDataToDisplay && ( {symptomDataToDisplay && (
<AppText style={textStyle} numberOfLines={4}> <AppText style={textStyle} numberOfLines={4}>
{symptomDataToDisplay} {symptomDataToDisplay}
+27 -1
View File
@@ -1,12 +1,13 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { ScrollView, StyleSheet, View } from 'react-native' import { Dimensions, ScrollView, StyleSheet, View } from 'react-native'
import AppModal from '../common/app-modal' import AppModal from '../common/app-modal'
import AppSwitch from '../common/app-switch' import AppSwitch from '../common/app-switch'
import AppText from '../common/app-text' import AppText from '../common/app-text'
import AppTextInput from '../common/app-text-input' import AppTextInput from '../common/app-text-input'
import Button from '../common/button' import Button from '../common/button'
import CloseIcon from '../common/close-icon'
import Segment from '../common/segment' import Segment from '../common/segment'
import SelectBoxGroup from './select-box-group' import SelectBoxGroup from './select-box-group'
import SelectTabGroup from './select-tab-group' import SelectTabGroup from './select-tab-group'
@@ -118,6 +119,10 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
return ( return (
<AppModal onClose={onSave}> <AppModal onClose={onSave}>
<View style={styles.modalWindow}>
<View style={styles.headerContainer}>
<CloseIcon onClose={onSave} />
</View>
<ScrollView <ScrollView
contentContainerStyle={styles.modalContainer} contentContainerStyle={styles.modalContainer}
keyboardDismissMode="on-drag" keyboardDismissMode="on-drag"
@@ -206,6 +211,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
</Segment> </Segment>
)} )}
</ScrollView> </ScrollView>
</View>
</AppModal> </AppModal>
) )
} }
@@ -223,12 +229,32 @@ const styles = StyleSheet.create({
paddingHorizontal: Spacing.base, paddingHorizontal: Spacing.base,
paddingBottom: Spacing.base, paddingBottom: Spacing.base,
}, },
headerContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingTop: Spacing.base,
paddingHorizontal: Spacing.base,
position: 'absolute',
width: '100%',
zIndex: 3, // works on ios
elevation: 3, // works on android
},
input: { input: {
height: Sizes.base * 5, height: Sizes.base * 5,
}, },
modalContainer: { modalContainer: {
paddingHorizontal: Spacing.base, paddingHorizontal: Spacing.base,
}, },
modalWindow: {
alignSelf: 'center',
backgroundColor: 'white',
borderRadius: 10,
marginTop: Sizes.huge * 2,
paddingTop: Spacing.large * 2,
position: 'absolute',
minHeight: '40%',
maxHeight: Dimensions.get('window').height * 0.7,
},
segmentBorder: { segmentBorder: {
borderBottomColor: Colors.greyLight, borderBottomColor: Colors.greyLight,
}, },
+21 -24
View File
@@ -7,28 +7,28 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import AppText from '../common/app-text'
import AppIcon from '../common/app-icon' import AppIcon from '../common/app-icon'
import CloseIcon from '../common/close-icon' import CloseIcon from '../common/close-icon'
import MenuItem from './menu-item'
import { Colors, Sizes, Typography } from '../../styles' import { Colors, Sizes } from '../../styles'
import settingsLabels from '../../i18n/en/settings'
import { HIT_SLOP } from '../../config' import { HIT_SLOP } from '../../config'
import { useTranslation } from 'react-i18next'
const { menuItems } = settingsLabels
const settingsMenuItems = [ const settingsMenuItems = [
{ labelKey: 'settings', componentName: 'SettingsMenu' }, { name: menuItems.settings, component: 'SettingsMenu' },
{ labelKey: 'about', componentName: 'About' }, { name: menuItems.about, component: 'About' },
{ labelKey: 'license', componentName: 'License' }, { name: menuItems.license, component: 'License' },
{ labelKey: 'privacyPolicy', componentName: 'PrivacyPolicy' }, { name: menuItems.privacyPolicy, component: 'PrivacyPolicy' },
] ]
const HamburgerMenu = ({ navigate }) => { const HamburgerMenu = ({ navigate }) => {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const closeMenu = () => setIsOpen(false) const closeMenu = () => setIsOpen(false)
const { t } = useTranslation(null, { keyPrefix: 'hamburgerMenu.menuMain' })
if (!isOpen) if (!isOpen)
return ( return (
<TouchableOpacity onPress={() => setIsOpen(true)} hitSlop={HIT_SLOP}> <TouchableOpacity onPress={() => setIsOpen(true)} hitSlop={HIT_SLOP}>
@@ -36,25 +36,23 @@ const HamburgerMenu = ({ navigate }) => {
</TouchableOpacity> </TouchableOpacity>
) )
function onPress(componentName) {
closeMenu()
navigate(componentName)
}
return ( return (
<Modal animationType="fade" onRequestClose={closeMenu} transparent> <Modal animationType="fade" onRequestClose={closeMenu} transparent={true}>
<TouchableOpacity onPress={closeMenu} style={styles.blackBackground} /> <TouchableOpacity
onPress={closeMenu}
style={styles.blackBackground}
></TouchableOpacity>
<View style={styles.menu}> <View style={styles.menu}>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<CloseIcon color={'black'} onClose={closeMenu} /> <CloseIcon color={'black'} onClose={closeMenu} />
</View> </View>
{settingsMenuItems.map(({ labelKey, componentName }) => ( {settingsMenuItems.map((item) => (
<TouchableOpacity <MenuItem
onPress={() => onPress(componentName)} item={item}
key={labelKey} key={item.name}
> closeMenu={closeMenu}
<AppText style={styles.menuItem}>{t(labelKey)}</AppText> navigate={navigate}
</TouchableOpacity> />
))} ))}
</View> </View>
</Modal> </Modal>
@@ -86,5 +84,4 @@ const styles = StyleSheet.create({
position: 'absolute', position: 'absolute',
width: '60%', width: '60%',
}, },
menuItem: Typography.subtitle,
}) })
+35
View File
@@ -0,0 +1,35 @@
import React from 'react'
import { StyleSheet, TouchableOpacity } from 'react-native'
import PropTypes from 'prop-types'
import AppText from '../common/app-text'
import { Typography } from '../../styles'
const MenuItem = ({ item, navigate, closeMenu }) => {
const { component, name } = item
const onPress = () => {
closeMenu()
navigate(component)
}
return (
<TouchableOpacity onPress={onPress}>
<AppText style={styles.text}>{name}</AppText>
</TouchableOpacity>
)
}
MenuItem.propTypes = {
item: PropTypes.object.isRequired,
navigate: PropTypes.func.isRequired,
closeMenu: PropTypes.func.isRequired,
}
const styles = StyleSheet.create({
text: {
...Typography.subtitle,
},
})
export default MenuItem
+3 -6
View File
@@ -6,23 +6,20 @@ import MenuItem from './menu-item'
import { Containers } from '../../styles' import { Containers } from '../../styles'
import { pages } from '../pages' import { pages } from '../pages'
import { useTranslation } from 'react-i18next'
const Menu = ({ currentPage, navigate }) => { const Menu = ({ currentPage, navigate }) => {
const menuItems = pages.filter((page) => page.isInMenu) const menuItems = pages.filter((page) => page.isInMenu)
const { t } = useTranslation(null, { keyPrefix: 'bottomMenu' })
return ( return (
<View style={styles.container}> <View style={styles.container}>
{menuItems.map(({ icon, labelKey, component }) => { {menuItems.map(({ icon, label, component }) => {
return ( return (
<MenuItem <MenuItem
isActive={component === currentPage} isActive={component === currentPage}
onPress={() => navigate(component)} onPress={() => navigate(component)}
icon={icon} icon={icon}
key={labelKey} key={label}
label={t(labelKey)} label={label}
/> />
) )
})} })}
+15 -3
View File
@@ -1,63 +1,75 @@
import settingsViews from './settings' import settingsViews from './settings'
import settingsLabels from '../i18n/en/settings'
const labels = settingsLabels.menuItems
export const pages = [ export const pages = [
{ {
component: 'Home', component: 'Home',
icon: 'home', icon: 'home',
label: 'Home',
}, },
{ {
component: 'Calendar', component: 'Calendar',
icon: 'calendar', icon: 'calendar',
isInMenu: true, isInMenu: true,
labelKey: 'calendar', label: 'Calendar',
parent: 'Home', parent: 'Home',
}, },
{ {
component: 'Chart', component: 'Chart',
icon: 'chart', icon: 'chart',
isInMenu: true, isInMenu: true,
labelKey: 'chart', label: 'Chart',
parent: 'Home', parent: 'Home',
}, },
{ {
component: 'Stats', component: 'Stats',
icon: 'statistics', icon: 'statistics',
isInMenu: true, isInMenu: true,
labelKey: 'stats', label: 'Stats',
parent: 'Home', parent: 'Home',
}, },
{ {
children: Object.keys(settingsViews), children: Object.keys(settingsViews),
component: 'SettingsMenu', component: 'SettingsMenu',
icon: 'settings', icon: 'settings',
label: 'Settings',
parent: 'Home', parent: 'Home',
}, },
{ {
component: 'Reminders', component: 'Reminders',
label: labels.reminders.name,
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'NfpSettings', component: 'NfpSettings',
label: labels.nfpSettings.name,
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'DataManagement', component: 'DataManagement',
label: labels.dataManagement.name,
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'Password', component: 'Password',
label: labels.password.name,
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'About', component: 'About',
label: 'About',
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'License', component: 'License',
label: 'License',
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
component: 'PrivacyPolicy', component: 'PrivacyPolicy',
label: 'PrivacyPolicy',
parent: 'SettingsMenu', parent: 'SettingsMenu',
}, },
{ {
@@ -1,7 +1,7 @@
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native' import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native'
import { SHA512 } from 'jshashes' import nodejs from 'nodejs-mobile-react-native'
import AppPage from './common/app-page' import AppPage from './common/app-page'
import AppTextInput from './common/app-text-input' import AppTextInput from './common/app-text-input'
@@ -9,40 +9,38 @@ import Button from './common/button'
import Header from './header' import Header from './header'
import { saveEncryptionFlag } from '../local-storage' import { saveEncryptionFlag } from '../local-storage'
import { deleteDbAndOpenNew, openDb } from '../db' import { requestHash, deleteDbAndOpenNew, openDb } from '../db'
import { passwordPrompt as labels, shared } from '../i18n/en/labels'
import { Containers, Spacing } from '../styles' import { Containers, Spacing } from '../styles'
import { useTranslation } from 'react-i18next'
const cancelButton = { text: shared.cancel, style: 'cancel' }
const PasswordPrompt = ({ enableShowApp }) => { const PasswordPrompt = ({ enableShowApp }) => {
const [password, setPassword] = useState(null) const [password, setPassword] = useState(null)
const unlockApp = () => requestHash('check-pw', password)
const { t } = useTranslation(null, { keyPrefix: 'password' }) const isPasswordEntered = Boolean(password)
const passHashToDb = async (hash) => {
const cancelButton = {
text: t('forgotPasswordDialog.cancel'),
style: 'cancel',
}
const unlockApp = async () => {
const hash = new SHA512().hex(password)
const connected = await openDb(hash) const connected = await openDb(hash)
if (!connected) { if (!connected) {
Alert.alert( Alert.alert(shared.incorrectPassword, shared.incorrectPasswordMessage, [
t('incorrectPasswordDialog.incorrectPassword'),
t('incorrectPasswordDialog.incorrectPasswordMessage'),
[
{ {
text: t('incorrectPasswordDialog.tryAgain'), text: shared.tryAgain,
onPress: () => setPassword(null), onPress: () => setPassword(null),
}, },
] ])
)
return return
} }
enableShowApp() enableShowApp()
} }
useEffect(() => {
const listener = nodejs.channel.addListener('check-pw', passHashToDb, this)
return () => listener.remove()
}, [])
const onDeleteDataConfirmation = async () => { const onDeleteDataConfirmation = async () => {
await deleteDbAndOpenNew() await deleteDbAndOpenNew()
await saveEncryptionFlag(false) await saveEncryptionFlag(false)
@@ -50,22 +48,19 @@ const PasswordPrompt = ({ enableShowApp }) => {
} }
const onDeleteData = () => { const onDeleteData = () => {
Alert.alert(t('confirmationDialog.title'), t('confirmationDialog.text'), [ Alert.alert(labels.areYouSureTitle, labels.areYouSure, [
cancelButton, cancelButton,
{ {
text: t('confirmationDialog.confirm'), text: labels.reallyDeleteData,
onPress: onDeleteDataConfirmation, onPress: onDeleteDataConfirmation,
}, },
]) ])
} }
const onConfirmDeletion = async () => { const onConfirmDeletion = async () => {
Alert.alert(t('forgotPassword'), t('forgotPasswordDialog.text'), [ Alert.alert(labels.deleteDatabaseTitle, labels.deleteDatabaseExplainer, [
cancelButton, cancelButton,
{ { text: labels.deleteData, onPress: onDeleteData },
text: t('forgotPasswordDialog.confirm'),
onPress: onDeleteData,
},
]) ])
} }
@@ -76,13 +71,17 @@ const PasswordPrompt = ({ enableShowApp }) => {
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={150}> <KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={150}>
<AppTextInput <AppTextInput
onChangeText={setPassword} onChangeText={setPassword}
secureTextEntry secureTextEntry={true}
placeholder={t('enterPassword')} placeholder={labels.enterPassword}
/> />
<View style={styles.containerButtons}> <View style={styles.containerButtons}>
<Button onPress={onConfirmDeletion}>{t('forgotPassword')}</Button> <Button onPress={onConfirmDeletion}>{labels.forgotPassword}</Button>
<Button disabled={!password} isCTA={!!password} onPress={unlockApp}> <Button
{t('unlockApp')} disabled={!isPasswordEntered}
isCTA={isPasswordEntered}
onPress={unlockApp}
>
{labels.title}
</Button> </Button>
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
+1 -1
View File
@@ -12,7 +12,7 @@ import links from '../../i18n/en/links'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const AboutSection = () => { const AboutSection = () => {
const { t } = useTranslation(null, { keyPrefix: 'hamburgerMenu.about' }) const { t } = useTranslation(null, { keyPrefix: 'settings.about' })
return ( return (
<AppPage title={t('title')}> <AppPage title={t('title')}>
+1 -1
View File
@@ -8,7 +8,7 @@ import AppLink from '../common/AppLink'
import Segment from '../common/segment' import Segment from '../common/segment'
const License = ({ children }) => { const License = ({ children }) => {
const { t } = useTranslation(null, { keyPrefix: 'hamburgerMenu.license' }) const { t } = useTranslation(null, { keyPrefix: 'settings.license' })
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
const link = 'https://www.gnu.org/licenses/gpl-3.0.html' const link = 'https://www.gnu.org/licenses/gpl-3.0.html'
return ( return (
@@ -1,12 +1,12 @@
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native' import { Alert, KeyboardAvoidingView, StyleSheet, View } from 'react-native'
import { SHA512 } from 'jshashes' import nodejs from 'nodejs-mobile-react-native'
import AppTextInput from '../../common/app-text-input' import AppTextInput from '../../common/app-text-input'
import Button from '../../common/button' import Button from '../../common/button'
import { openDb } from '../../../db' import { requestHash, openDb } from '../../../db'
import { Containers } from '../../../styles' import { Containers } from '../../../styles'
import settings from '../../../i18n/en/settings' import settings from '../../../i18n/en/settings'
import { shared } from '../../../i18n/en/labels' import { shared } from '../../../i18n/en/labels'
@@ -14,8 +14,7 @@ import { shared } from '../../../i18n/en/labels'
const ConfirmWithPassword = ({ onSuccess, onCancel }) => { const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
const [password, setPassword] = useState(null) const [password, setPassword] = useState(null)
const checkPassword = async () => { const checkPassword = async (hash) => {
const hash = new SHA512().hex(password)
try { try {
await openDb(hash) await openDb(hash)
onSuccess() onSuccess()
@@ -24,6 +23,15 @@ const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
} }
} }
useEffect(() => {
const listener = nodejs.channel.addListener(
'password-check',
checkPassword,
this
)
return () => listener.remove()
}, [])
const onIncorrectPassword = () => { const onIncorrectPassword = () => {
Alert.alert(shared.incorrectPassword, shared.incorrectPasswordMessage, [ Alert.alert(shared.incorrectPassword, shared.incorrectPasswordMessage, [
{ {
@@ -37,6 +45,10 @@ const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
]) ])
} }
const initPasswordCheck = () => {
requestHash('password-check', password)
}
const labels = settings.passwordSettings const labels = settings.passwordSettings
const isPassword = password !== null const isPassword = password !== null
@@ -53,7 +65,7 @@ const ConfirmWithPassword = ({ onSuccess, onCancel }) => {
<Button <Button
disabled={!isPassword} disabled={!isPassword}
isCTA={isPassword} isCTA={isPassword}
onPress={checkPassword} onPress={initPasswordCheck}
> >
{shared.confirmToProceed} {shared.confirmToProceed}
</Button> </Button>
@@ -1,97 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Alert } from 'react-native'
import DocumentPicker from 'react-native-document-picker'
import rnfs from 'react-native-fs'
import importCsv from '../../../lib/import-export/import-from-csv'
import alertError from '../common/alert-error'
import Segment from '../../common/segment'
import AppText from '../../common/app-text'
import Button from '../../common/button'
import { useTranslation } from 'react-i18next'
export default function ImportData({ resetIsDeletingData, setIsLoading }) {
const { t } = useTranslation(null, {
keyPrefix: 'hamburgerMenu.settings.data.import',
})
async function startImport(shouldDeleteExistingData) {
setIsLoading(true)
await importData(shouldDeleteExistingData)
setIsLoading(false)
}
async function getFileInfo() {
try {
const fileInfo = await DocumentPicker.pickSingle({
type: [DocumentPicker.types.csv, 'text/comma-separated-values'],
})
return fileInfo
} catch (error) {
if (DocumentPicker.isCancel(error)) return // User cancelled the picker, exit any dialogs or menus and move on
showImportErrorAlert(error)
}
}
async function getFileContent() {
const fileInfo = await getFileInfo()
if (!fileInfo) return null
try {
const fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
return fileContent
} catch (err) {
return showImportErrorAlert(t('error.couldNotOpenFile'))
}
}
async function importData(shouldDeleteExistingData) {
const fileContent = await getFileContent()
if (!fileContent) return
try {
await importCsv(fileContent, shouldDeleteExistingData)
Alert.alert(t('success.title'), t('success.message'))
} catch (err) {
showImportErrorAlert(err.message)
}
}
function openImportDialog() {
resetIsDeletingData()
Alert.alert(t('dialog.title'), t('dialog.message'), [
{
text: t('dialog.cancel'),
style: 'cancel',
onPress: () => {},
},
{
text: t('dialog.replace'),
onPress: () => startImport(false),
},
{
text: t('dialog.delete'),
onPress: () => startImport(true),
},
])
}
function showImportErrorAlert(message) {
const errorMessage = t('error.noDataImported', { message })
alertError(errorMessage)
}
return (
<Segment title={t('button')}>
<AppText>{t('segmentExplainer')}</AppText>
<Button isCTA onPress={openImportDialog}>
{t('button')}
</Button>
</Segment>
)
}
ImportData.propTypes = {
resetIsDeletingData: PropTypes.func.isRequired,
setIsLoading: PropTypes.func.isRequired,
}
@@ -0,0 +1,64 @@
import { Alert } from 'react-native'
import DocumentPicker 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 labels from '../../../i18n/en/settings'
import alertError from '../common/alert-error'
export function openImportDialog(onImportData) {
Alert.alert(labels.import.title, labels.import.message, [
{
text: sharedLabels.cancel,
style: 'cancel',
onPress: () => {},
},
{
text: labels.import.replaceOption,
onPress: () => onImportData(false),
},
{
text: labels.import.deleteOption,
onPress: () => onImportData(true),
},
])
}
export async function getFileContent() {
let fileInfo
try {
fileInfo = await DocumentPicker.pickSingle({
type: [DocumentPicker.types.csv, 'text/comma-separated-values'],
})
} catch (error) {
if (DocumentPicker.isCancel(error)) {
// User cancelled the picker, exit any dialogs or menus and move on
return
} else {
importError(error)
}
}
let fileContent
try {
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
} catch (err) {
return importError(labels.import.errors.couldNotOpenFile)
}
return fileContent
}
export async function importData(shouldDeleteExistingData, fileContent) {
try {
await importCsv(fileContent, shouldDeleteExistingData)
Alert.alert(sharedLabels.successTitle, labels.import.success.message)
} catch (err) {
importError(err.message)
}
}
function importError(msg) {
const postFixed = `${msg}\n\n${labels.import.errors.postFix}`
alertError(postFixed)
}
@@ -6,23 +6,40 @@ import AppText from '../../common/app-text'
import Button from '../../common/button' import Button from '../../common/button'
import Segment from '../../common/segment' import Segment from '../../common/segment'
import { openImportDialog, getFileContent, importData } from './import-dialog'
import openShareDialogAndExport from './export-dialog' import openShareDialogAndExport from './export-dialog'
import DeleteData from './delete-data' import DeleteData from './delete-data'
import labels from '../../../i18n/en/settings' import labels from '../../../i18n/en/settings'
import ImportData from './ImportData' import { ACTION_DELETE, ACTION_EXPORT, ACTION_IMPORT } from '../../../config'
const DataManagement = () => { const DataManagement = () => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isDeletingData, setIsDeletingData] = useState(false) const [currentAction, setCurrentAction] = useState(null)
const startImportFlow = async (shouldDeleteExistingData) => {
setIsLoading(true)
const fileContent = await getFileContent()
if (fileContent) {
await importData(shouldDeleteExistingData, fileContent)
}
setIsLoading(false)
}
const startExport = () => { const startExport = () => {
setIsDeletingData(false) setCurrentAction(ACTION_EXPORT)
openShareDialogAndExport() openShareDialogAndExport()
} }
const startImport = () => {
setCurrentAction(ACTION_IMPORT)
openImportDialog(startImportFlow)
}
if (isLoading) return <AppLoadingView /> if (isLoading) return <AppLoadingView />
const isDeletingData = currentAction === ACTION_DELETE
return ( return (
<AppPage> <AppPage>
<Segment title={labels.export.button}> <Segment title={labels.export.button}>
@@ -31,15 +48,17 @@ const DataManagement = () => {
{labels.export.button} {labels.export.button}
</Button> </Button>
</Segment> </Segment>
<ImportData <Segment title={labels.import.button}>
resetIsDeletingData={() => setIsDeletingData(false)} <AppText>{labels.import.segmentExplainer}</AppText>
setIsLoading={setIsLoading} <Button isCTA onPress={startImport}>
/> {labels.import.button}
</Button>
</Segment>
<Segment title={labels.deleteSegment.title} last> <Segment title={labels.deleteSegment.title} last>
<AppText>{labels.deleteSegment.explainer}</AppText> <AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData <DeleteData
isDeletingData={isDeletingData} isDeletingData={isDeletingData}
onStartDeletion={() => setIsDeletingData(true)} onStartDeletion={() => setCurrentAction(ACTION_DELETE)}
/> />
</Segment> </Segment>
</AppPage> </AppPage>
+1 -1
View File
@@ -1,6 +1,6 @@
import Reminders from './reminders/reminders' import Reminders from './reminders/reminders'
import NfpSettings from './nfp-settings' import NfpSettings from './nfp-settings'
import DataManagement from './data-management/DataManagement' import DataManagement from './data-management'
import Password from './password' import Password from './password'
import About from './About' import About from './About'
import License from './License' import License from './License'
+4 -8
View File
@@ -7,22 +7,18 @@ import AppText from '../common/app-text'
import Segment from '../common/segment' import Segment from '../common/segment'
import { Colors, Containers, Sizes } from '../../styles' import { Colors, Containers, Sizes } from '../../styles'
import { useTranslation } from 'react-i18next'
const MenuItem = ({ item, last, navigate }) => { const MenuItem = ({ item, last, navigate }) => {
const { t } = useTranslation(null, {
keyPrefix: 'hamburgerMenu.settings.menuItem',
})
return ( return (
<Segment last={last}> <Segment last={last}>
<TouchableOpacity <TouchableOpacity
style={styles.container} style={styles.container}
key={item.label} key={item.name}
onPress={() => navigate(item.componentName)} onPress={() => navigate(item.component)}
> >
<View> <View>
<AppText style={styles.title}>{t(`${item.label}.name`)}</AppText> <AppText style={styles.title}>{item.name}</AppText>
{!!item.label && <AppText>{t(`${item.label}.text`)}</AppText>} {item.text.length > 0 && <AppText>{item.text}</AppText>}
</View> </View>
<AppIcon name="chevron-right" color={Colors.orange} /> <AppIcon name="chevron-right" color={Colors.orange} />
</TouchableOpacity> </TouchableOpacity>
@@ -1,24 +1,35 @@
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import { KeyboardAvoidingView, StyleSheet } from 'react-native' import { KeyboardAvoidingView, StyleSheet } from 'react-native'
import nodejs from 'nodejs-mobile-react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { SHA512 } from 'jshashes'
import AppText from '../../common/app-text' import AppText from '../../common/app-text'
import AppTextInput from '../../common/app-text-input' import AppTextInput from '../../common/app-text-input'
import Button from '../../common/button' import Button from '../../common/button'
import { requestHash } from '../../../db'
import { Colors, Spacing } from '../../../styles' import { Colors, Spacing } from '../../../styles'
import settings from '../../../i18n/en/settings' import settings from '../../../i18n/en/settings'
const LISTENER_TYPE = 'create-or-change-pw'
const EnterNewPassword = ({ changeEncryptionAndRestart }) => { const EnterNewPassword = ({ changeEncryptionAndRestart }) => {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [passwordConfirmation, setPasswordConfirmation] = useState('') const [passwordConfirmation, setPasswordConfirmation] = useState('')
const [shouldShowErrorMessage, setShouldShowErrorMessage] = useState(false) const [shouldShowErrorMessage, setShouldShowErrorMessage] = useState(false)
useEffect(() => {
const listener = nodejs.channel.addListener(
LISTENER_TYPE,
changeEncryptionAndRestart,
this
)
return () => listener.remove()
}, [])
const savePassword = () => { const savePassword = () => {
if (comparePasswords()) { if (comparePasswords()) {
const hash = new SHA512().hex(password) requestHash(LISTENER_TYPE, password)
changeEncryptionAndRestart(hash)
} else { } else {
setShouldShowErrorMessage(true) setShouldShowErrorMessage(true)
} }
+1 -3
View File
@@ -9,9 +9,7 @@ import Segment from '../common/segment'
import { Colors, Sizes } from '../../styles' import { Colors, Sizes } from '../../styles'
const PrivacyPolicy = () => { const PrivacyPolicy = () => {
const { t } = useTranslation(null, { const { t } = useTranslation(null, { keyPrefix: 'settings.privacyPolicy' })
keyPrefix: 'hamburgerMenu.privacyPolicy',
})
const sections = ['intro', 'dataUse', 'permissions', 'transparency'] const sections = ['intro', 'dataUse', 'permissions', 'transparency']
return ( return (
+10 -10
View File
@@ -4,21 +4,21 @@ import PropTypes from 'prop-types'
import AppPage from '../common/app-page' import AppPage from '../common/app-page'
import MenuItem from './menu-item' import MenuItem from './menu-item'
import { useTranslation } from 'react-i18next' import settingsLabels from '../../i18n/en/settings'
const menuItems = [ const { menuItems } = settingsLabels
{ label: 'reminders', componentName: 'Reminders' }, const menu = [
{ label: 'nfpSettings', componentName: 'NfpSettings' }, { ...menuItems.reminders, component: 'Reminders' },
{ label: 'dataManagement', componentName: 'DataManagement' }, { ...menuItems.nfpSettings, component: 'NfpSettings' },
{ label: 'password', componentName: 'Password' }, { ...menuItems.dataManagement, component: 'DataManagement' },
{ ...menuItems.password, component: 'Password' },
] ]
const SettingsMenu = ({ navigate }) => { const SettingsMenu = ({ navigate }) => {
const { t } = useTranslation()
return ( return (
<AppPage title={t('hamburgerMenu.settings.title')}> <AppPage title={settingsLabels.title}>
{menuItems.map((menuItem, i) => { {menu.map((menuItem, i) => {
const last = menuItems.length === i + 1 const last = menu.length === i + 1
return ( return (
<MenuItem item={menuItem} key={i} last={last} navigate={navigate} /> <MenuItem item={menuItem} key={i} last={last} navigate={navigate} />
@@ -3,18 +3,19 @@ import { ImageBackground, SafeAreaView, ScrollView, View } from 'react-native'
import { ScaledSheet } from 'react-native-size-matters' import { ScaledSheet } from 'react-native-size-matters'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import AppText from '../common/app-text' import Button from './common/button'
import Button from '../common/button' import AppHelp from './common/AppHelp'
import Footnote from '../common/Footnote' import AppModal from './common/app-modal'
import StatsOverview from './StatsOverview' import AppText from './common/app-text'
import PeriodDetailsModal from './PeriodDetailsModal' import StatsOverview from './common/StatsOverview'
import StatsTable from './common/StatsTable'
import cycleModule from '../../lib/cycle' import cycleModule from '../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../../lib/cycle-length' import { getCycleLengthStats as getCycleInfo } from '../lib/cycle-length'
import { Containers, 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 Stats = () => {
const [isStatsVisible, setIsStatsVisible] = useState(false) const [isStatsVisible, setIsStatsVisible] = useState(false)
@@ -22,28 +23,27 @@ const Stats = () => {
const { t } = useTranslation(null, { keyPrefix: 'stats' }) const { t } = useTranslation(null, { keyPrefix: 'stats' })
const cycleLengths = cycleModule().getAllCycleLengths() const cycleLengths = cycleModule().getAllCycleLengths()
const numberOfCycles = cycleLengths.length const numberOfCycles = cycleLengths?.length
const cycleData = const hasAtLeastOneCycle = numberOfCycles >= 1
numberOfCycles > 0 const cycleData = hasAtLeastOneCycle
? getCycleInfo(cycleLengths) ? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' } : { minimum: '—', maximum: '—', stdDeviation: '—' }
const standardDeviation = cycleData.stdDeviation
? cycleData.stdDeviation
: '—'
const statsData = [ const statsData = [
[cycleData.minimum, t('overview.min')], [cycleData.minimum, t('min')],
[cycleData.maximum, t('overview.max')], [cycleData.maximum, t('max')],
[standardDeviation, t('overview.standardDeviation')], [
[numberOfCycles, t('overview.completedCycles')], cycleData.stdDeviation ? cycleData.stdDeviation : '—',
t('standard_deviation'),
],
[numberOfCycles, t('completed_cycles')],
] ]
return ( return (
<SafeAreaView style={styles.pageContainer}> <SafeAreaView style={styles.pageContainer}>
<ScrollView contentContainerStyle={styles.overviewContainer}> <ScrollView contentContainerStyle={styles.overviewContainer}>
<AppText>{t('intro')}</AppText> <AppText>{t('cycle_length_explainer')}</AppText>
{numberOfCycles === 0 ? ( {!hasAtLeastOneCycle && <AppText>{t('no_data')}</AppText>}
<AppText>{t('noData')}</AppText> {hasAtLeastOneCycle && (
) : (
<> <>
<View style={styles.container}> <View style={styles.container}>
<View style={styles.columnLeft}> <View style={styles.columnLeft}>
@@ -59,28 +59,30 @@ const Stats = () => {
> >
{cycleData.mean} {cycleData.mean}
</AppText> </AppText>
<AppText style={styles.accentPurpleHuge}> <AppText style={styles.accentPurpleHuge}>{t('days')}</AppText>
{t('overview.days')}
</AppText>
</ImageBackground> </ImageBackground>
<AppText style={styles.accentOrange}> <AppText style={styles.accentOrange}>{t('average')}</AppText>
{t('overview.average')}
</AppText>
</View> </View>
<View style={styles.columnRight}> <View style={styles.columnRight}>
<StatsOverview data={statsData} /> <StatsOverview data={statsData} />
</View> </View>
</View> </View>
<Button isCTA onPress={() => setIsStatsVisible(true)}> <Button isCTA onPress={() => setIsStatsVisible(true)}>
{t('showStats')} {t('show_stats')}
</Button> </Button>
{isStatsVisible && ( <AppHelp text={t('standard_deviation_help')} />
<PeriodDetailsModal onClose={() => setIsStatsVisible(false)} />
)}
<Footnote>{t('footnote')}</Footnote>
</> </>
)} )}
</ScrollView> </ScrollView>
{isStatsVisible && (
<AppModal onClose={() => setIsStatsVisible(false)}>
<StatsTable
onClose={() => setIsStatsVisible(false)}
testID="statsTable"
/>
</AppModal>
)}
</SafeAreaView> </SafeAreaView>
) )
} }
+5 -1
View File
@@ -1,6 +1,10 @@
import { PixelRatio, StatusBar } from 'react-native' import { PixelRatio, StatusBar } from 'react-native'
import { scale, verticalScale } from 'react-native-size-matters' import { scale, verticalScale } from 'react-native-size-matters'
export const ACTION_DELETE = 'delete'
export const ACTION_EXPORT = 'export'
export const ACTION_IMPORT = 'import'
export const SYMPTOMS = [ export const SYMPTOMS = [
'bleeding', 'bleeding',
'temperature', 'temperature',
@@ -36,7 +40,7 @@ export const HIT_SLOP = {
top: verticalScale(20), top: verticalScale(20),
bottom: verticalScale(20), bottom: verticalScale(20),
left: scale(20), left: scale(20),
right: scale(20), right: scale(20)
} }
export const STATUSBAR_HEIGHT = StatusBar.currentHeight export const STATUSBAR_HEIGHT = StatusBar.currentHeight
+11
View File
@@ -1,5 +1,6 @@
import Realm from 'realm' import Realm from 'realm'
import { LocalDate, ChronoUnit } from '@js-joda/core' import { LocalDate, ChronoUnit } from '@js-joda/core'
import nodejs from 'nodejs-mobile-react-native'
import fs from 'react-native-fs' import fs from 'react-native-fs'
import schemas from './schemas' import schemas from './schemas'
@@ -183,6 +184,16 @@ export function tryToImportWithoutDelete(cycleDays) {
}) })
} }
export function requestHash(type, pw) {
nodejs.channel.post(
'request-SHA512',
JSON.stringify({
type: type,
message: pw,
})
)
}
export async function changeDbEncryption(hash) { export async function changeDbEncryption(hash) {
let key let key
if (hash) key = hashToInt8Array(hash) if (hash) key = hashToInt8Array(hash)
+1
View File
@@ -0,0 +1 @@
{}
+16 -111
View File
@@ -1,25 +1,4 @@
{ {
"bottomMenu": {
"calendar": "Calendar",
"chart": "Chart",
"stats": "Stats"
},
"chart": {
"tutorial": "You can swipe the chart to view more dates."
},
"cycleDay": {
"symptomBox": {
"bleeding": "Bleeding",
"temperature": "Temperature",
"mucus": "Cervical Mucus",
"cervix": "Cervix",
"note": "Note",
"desire": "Desire",
"sex": "Sex",
"pain": "Pain",
"mood": "Mood"
}
},
"labels": { "labels": {
"bleedingPrediction": { "bleedingPrediction": {
"noPrediction": "As soon as you have tracked 3 menstrual cycles, drip. will make predictions for the next ones." "noPrediction": "As soon as you have tracked 3 menstrual cycles, drip. will make predictions for the next ones."
@@ -34,7 +13,7 @@
"ok": "OK" "ok": "OK"
} }
}, },
"hamburgerMenu": { "settings": {
"about": { "about": {
"credits": { "credits": {
"title": "Credits", "title": "Credits",
@@ -62,12 +41,6 @@
"title": "drip. an open-source cycle tracking app", "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:" "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:"
}, },
"menuMain": {
"about": "About",
"license": "License",
"settings": "Settings",
"privacyPolicy": "Privacy Policy"
},
"privacyPolicy": { "privacyPolicy": {
"title": "Privacy Policy", "title": "Privacy Policy",
"intro": { "intro": {
@@ -86,93 +59,25 @@
"title": "Transparency", "title": "Transparency",
"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!" "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!"
} }
},
"settings": {
"data": {
"import": {
"button": "Import data",
"dialog": {
"cancel": "Cancel",
"delete": "Import and delete existing",
"message": "There are two options for the import:\n\n1. Keep existing cycle days and replace only the ones in the import file.\n\n2. Delete all existing cycle days and import cycle days from file",
"replace": "Import and replace",
"title": "Keep existing data?"
},
"error": {
"couldNotOpenFile": "Could not open file",
"futureEdit": "Future dates may only contain a note, no other symptoms",
"incorrectColumns": "Expected CSV column titles to be {{incorrectColumns}}",
"noDataImported": "{{message}}\n\nNo data was imported or changed"
},
"segmentExplainer": "Import data in CSV format",
"success": {
"message": "Data successfully imported",
"title": "Success"
} }
}
},
"menuItem": {
"dataManagement": {
"name": "Data",
"text": "import, export or delete your data"
},
"nfpSettings": {
"name": "NFP settings",
"text": "define how you want to use NFP"
},
"password": {
"name": "Password",
"text": ""
},
"reminders": {
"name": "Reminders",
"text": "turn on/off reminders"
}
},
"title": "Settings"
}
},
"password": {
"confirmationDialog": {
"confirm": "Yes, I am sure",
"text": "Are you absolutely sure you want to permanently delete all your data?",
"title": "Are you sure?"
},
"enterPassword": "Enter password here",
"forgotPassword": "Forgot your password?",
"forgotPasswordDialog": {
"cancel": "Cancel",
"confirm": "Yes, delete all my data",
"text": "If you've forgotten your password, unfortunately, there is nothing we can do to recover your data, because it is encrypted with the password only you know. You can, however, delete all your encrypted data and start fresh. Once all data has been erased, you can set a new password in the settings, if you like."
},
"incorrectPasswordDialog": {
"incorrectPassword": "Password incorrect",
"incorrectPasswordMessage": "That password is incorrect.",
"tryAgain": "Try again"
},
"unlockApp": "Unlock app"
},
"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": { "plurals": {
"day": "{{count}} day", "day": "{{count}} day",
"day_plural": "{{count}} days" "day_plural": "{{count}} days"
},
"stats": {
"show_stats": "Show period details",
"cycle_start": "Cycle start",
"cycle_length": "Cycle length",
"bleeding": "Bleeding",
"cycle_length_explainer": "Basic statistics about the length of your cycles.",
"no_data": "At least one completed cycle is needed to display stats.",
"days": "days",
"completed_cycles": "completed\ncycles",
"average": "Average cycle",
"min": "Shortest",
"max": "Longest",
"standard_deviation": "Standard\ndeviation",
"standard_deviation_help": "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."
} }
} }
+56
View File
@@ -1,8 +1,15 @@
import labels from './settings'
const settingsTitles = labels.menuItems
export const home = { export const home = {
unknown: '?', unknown: '?',
phase: (n) => `${['1st', '2nd', '3rd'][n - 1]} cycle phase`, phase: (n) => `${['1st', '2nd', '3rd'][n - 1]} cycle phase`,
} }
export const chart = {
tutorial: 'You can swipe the chart to view more dates.',
}
export const shared = { export const shared = {
cancel: 'Cancel', cancel: 'Cancel',
save: 'Save', save: 'Save',
@@ -26,6 +33,41 @@ export const shared = {
learnMore: 'Learn more', learnMore: 'Learn more',
} }
export const headerTitles = {
Home: 'Home',
Calendar: 'Calendar',
Chart: 'Chart',
Stats: 'Statistics',
SettingsMenu: 'Settings',
Reminders: settingsTitles.reminders.name,
NfpSettings: settingsTitles.nfpSettings.name,
DataManagement: settingsTitles.dataManagement.name,
Password: settingsTitles.password.name,
About: 'About',
License: 'License',
PrivacyPolicy: 'Privacy Policy',
bleeding: 'Bleeding',
temperature: 'Temperature',
mucus: 'Cervical Mucus',
cervix: 'Cervix',
note: 'Note',
desire: 'Desire',
sex: 'Sex',
pain: 'Pain',
mood: 'Mood',
}
export const stats = {
cycleLengthExplainer: 'Basic statistics about the length of your cycles.',
emptyStats: 'At least one completed cycle is needed to display stats.',
daysLabel: 'days',
basisOfStatsEnd: 'completed\ncycles',
averageLabel: 'Average cycle',
minLabel: `Shortest`,
maxLabel: `Longest`,
stdLabel: `Standard\ndeviation`,
}
export const bleedingPrediction = { export const bleedingPrediction = {
predictionInFuture: (startDays, endDays) => predictionInFuture: (startDays, endDays) =>
`Your next period is likely to start in ${startDays} to ${endDays} days.`, `Your next period is likely to start in ${startDays} to ${endDays} days.`,
@@ -38,6 +80,20 @@ export const bleedingPrediction = {
`Based on your documented data, your period was likely to start between ${startDate} and ${endDate}.`, `Based on your documented data, your period was likely to start between ${startDate} and ${endDate}.`,
} }
export const passwordPrompt = {
title: 'Unlock app',
enterPassword: 'Enter password here',
deleteDatabaseExplainer:
"If you've forgotten your password, unfortunately, there is nothing we can do to recover your data, because it is encrypted with the password only you know. You can, however, delete all your encrypted data and start fresh. Once all data has been erased, you can set a new password in the settings, if you like.",
forgotPassword: 'Forgot your password?',
deleteDatabaseTitle: 'Forgot your password?',
deleteData: 'Yes, delete all my data',
areYouSureTitle: 'Are you sure?',
areYouSure:
'Are you absolutely sure you want to permanently delete all your data?',
reallyDeleteData: 'Yes, I am sure',
}
export const fertilityStatus = { export const fertilityStatus = {
fertile: 'fertile', fertile: 'fertile',
infertile: 'infertile', infertile: 'infertile',
+41
View File
@@ -1,6 +1,29 @@
import links from './links' import links from './links'
export default { export default {
title: 'Settings',
menuItems: {
reminders: {
name: 'Reminders',
text: 'turn on/off reminders',
},
nfpSettings: {
name: 'NFP settings',
text: 'define how you want to use NFP',
},
dataManagement: {
name: 'Data',
text: 'import, export or delete your data',
},
password: {
name: 'Password',
text: '',
},
about: 'About',
license: 'License',
settings: 'Settings',
privacyPolicy: 'Privacy Policy',
},
export: { export: {
errors: { errors: {
noData: 'There is no data to export', noData: 'There is no data to export',
@@ -13,6 +36,24 @@ export default {
segmentExplainer: segmentExplainer:
'Export data in CSV format for backup or so you can use it elsewhere', 'Export data in CSV format for backup or so you can use it elsewhere',
}, },
import: {
button: 'Import data',
title: 'Keep existing data?',
message: `There are two options for the import:
1. Keep existing cycle days and replace only the ones in the import file.
2. Delete all existing cycle days and import cycle days from file.`,
replaceOption: 'Import and replace',
deleteOption: 'Import and delete existing',
errors: {
couldNotOpenFile: 'Could not open file',
postFix: 'No data was imported or changed',
futureEdit: 'Future dates may only contain a note, no other symptoms',
},
success: {
message: 'Data successfully imported',
},
segmentExplainer: 'Import data in CSV format',
},
deleteSegment: { deleteSegment: {
title: 'Delete app data', title: 'Delete app data',
explainer: 'Delete app data from this phone', explainer: 'Delete app data from this phone',
+1
View File
@@ -17,6 +17,7 @@ i18n
compatibilityJSON: 'v3', // TODO: migrate json to v4 and afterwards remove it compatibilityJSON: 'v3', // TODO: migrate json to v4 and afterwards remove it
resources, resources,
fallbackLng: 'en', fallbackLng: 'en',
debug: true,
interpolation: { interpolation: {
escapeValue: false, // not needed for react as it escapes by default escapeValue: false, // not needed for react as it escapes by default
+159 -2
View File
@@ -36,9 +36,12 @@
54DFE73D25D94DED0025C3FC /* cycle-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54DFE73C25D94DED0025C3FC /* cycle-icon.png */; }; 54DFE73D25D94DED0025C3FC /* cycle-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54DFE73C25D94DED0025C3FC /* cycle-icon.png */; };
54DFE73E25D94DED0025C3FC /* cycle-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54DFE73C25D94DED0025C3FC /* cycle-icon.png */; }; 54DFE73E25D94DED0025C3FC /* cycle-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54DFE73C25D94DED0025C3FC /* cycle-icon.png */; };
54E1D49923E7588F003FA37B /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54E1D49823E7588F003FA37B /* JavaScriptCore.framework */; }; 54E1D49923E7588F003FA37B /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54E1D49823E7588F003FA37B /* JavaScriptCore.framework */; };
5C4C9DDE2824847500B72CBE /* NodeMobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C225FC4966694B9FBD32E946 /* NodeMobile.framework */; };
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEBF0735214455AAEDF56D5 /* libc++.tbd */; }; 62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEBF0735214455AAEDF56D5 /* libc++.tbd */; };
A16B351C3F3644CF95F104D2 /* builtin_modules in Resources */ = {isa = PBXBuildFile; fileRef = 36F1B55D0DEE47AA9AF4BBDD /* builtin_modules */; };
BD7041F2826E4A2CBE6CB87D /* RealmJSTests.xctest in Frameworks */ = {isa = PBXBuildFile; fileRef = F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */; }; BD7041F2826E4A2CBE6CB87D /* RealmJSTests.xctest in Frameworks */ = {isa = PBXBuildFile; fileRef = F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */; };
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CD8C8B91E0A747B3883A0D56 /* libz.tbd */; }; D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CD8C8B91E0A747B3883A0D56 /* libz.tbd */; };
E4584E55EEC24302A3E84A23 /* nodejs-project in Resources */ = {isa = PBXBuildFile; fileRef = 6466AE2461BE4FA88B8372F0 /* nodejs-project */; };
E545887DBE87912F11770AB9 /* libPods-drip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 699D3B3789FC2E5185CB9894 /* libPods-drip.a */; }; E545887DBE87912F11770AB9 /* libPods-drip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 699D3B3789FC2E5185CB9894 /* libPods-drip.a */; };
FAC423E577F555F66C9891E4 /* libPods-drip-dripTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F33CCAAB670FD0D98C5C72DF /* libPods-drip-dripTests.a */; }; FAC423E577F555F66C9891E4 /* libPods-drip-dripTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F33CCAAB670FD0D98C5C72DF /* libPods-drip-dripTests.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -66,6 +69,7 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = drip/main.m; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = drip/main.m; sourceTree = "<group>"; };
2B1578D5817F46EE9BFC9BAF /* Pods-drip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip.debug.xcconfig"; path = "Target Support Files/Pods-drip/Pods-drip.debug.xcconfig"; sourceTree = "<group>"; }; 2B1578D5817F46EE9BFC9BAF /* Pods-drip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip.debug.xcconfig"; path = "Target Support Files/Pods-drip/Pods-drip.debug.xcconfig"; sourceTree = "<group>"; };
2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; 2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
36F1B55D0DEE47AA9AF4BBDD /* builtin_modules */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = builtin_modules; path = "../node_modules/nodejs-mobile-react-native/install/resources/nodejs-modules/builtin_modules"; sourceTree = "<group>"; };
4A9B2D77CAC90DFEB5C4565A /* Pods-dripTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dripTests.debug.xcconfig"; path = "Target Support Files/Pods-dripTests/Pods-dripTests.debug.xcconfig"; sourceTree = "<group>"; }; 4A9B2D77CAC90DFEB5C4565A /* Pods-dripTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dripTests.debug.xcconfig"; path = "Target Support Files/Pods-dripTests/Pods-dripTests.debug.xcconfig"; sourceTree = "<group>"; };
5409189625AB725F00086AE1 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-LightItalic.ttf"; path = "../../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; }; 5409189625AB725F00086AE1 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-LightItalic.ttf"; path = "../../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; };
540918A225AB725F00086AE1 /* Prompt-ExtraLight.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Prompt-ExtraLight.ttf"; path = "../../assets/fonts/Prompt-ExtraLight.ttf"; sourceTree = "<group>"; }; 540918A225AB725F00086AE1 /* Prompt-ExtraLight.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Prompt-ExtraLight.ttf"; path = "../../assets/fonts/Prompt-ExtraLight.ttf"; sourceTree = "<group>"; };
@@ -93,6 +97,7 @@
54E1D49823E7588F003FA37B /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 54E1D49823E7588F003FA37B /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
5ABC2C1190B4D25AC0398D09 /* Pods-dripTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dripTests.release.xcconfig"; path = "Target Support Files/Pods-dripTests/Pods-dripTests.release.xcconfig"; sourceTree = "<group>"; }; 5ABC2C1190B4D25AC0398D09 /* Pods-dripTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dripTests.release.xcconfig"; path = "Target Support Files/Pods-dripTests/Pods-dripTests.release.xcconfig"; sourceTree = "<group>"; };
5C649EDC281151BC005FED46 /* dripRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = dripRelease.entitlements; path = drip/dripRelease.entitlements; sourceTree = "<group>"; }; 5C649EDC281151BC005FED46 /* dripRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = dripRelease.entitlements; path = drip/dripRelease.entitlements; sourceTree = "<group>"; };
6466AE2461BE4FA88B8372F0 /* nodejs-project */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "nodejs-project"; path = "../nodejs-assets/nodejs-project"; sourceTree = "<group>"; };
699D3B3789FC2E5185CB9894 /* libPods-drip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 699D3B3789FC2E5185CB9894 /* libPods-drip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6B7C2A0A7AAA83BBEFBD0B6A /* libPods-drip-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6B7C2A0A7AAA83BBEFBD0B6A /* libPods-drip-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
72DDDE8D34518ED64FD7EBBA /* Pods-drip-dripTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip-dripTests.release.xcconfig"; path = "Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests.release.xcconfig"; sourceTree = "<group>"; }; 72DDDE8D34518ED64FD7EBBA /* Pods-drip-dripTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip-dripTests.release.xcconfig"; path = "Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests.release.xcconfig"; sourceTree = "<group>"; };
@@ -103,6 +108,7 @@
A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNShare.a; sourceTree = "<group>"; }; A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNShare.a; sourceTree = "<group>"; };
AB636AA0286D45CE9B23B2C3 /* libRCTRestart.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTRestart.a; sourceTree = "<group>"; }; AB636AA0286D45CE9B23B2C3 /* libRCTRestart.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTRestart.a; sourceTree = "<group>"; };
B6FD0A300273E09D74C14C19 /* Pods-drip-dripTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip-dripTests.debug.xcconfig"; path = "Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests.debug.xcconfig"; sourceTree = "<group>"; }; B6FD0A300273E09D74C14C19 /* Pods-drip-dripTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drip-dripTests.debug.xcconfig"; path = "Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests.debug.xcconfig"; sourceTree = "<group>"; };
C225FC4966694B9FBD32E946 /* NodeMobile.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = NodeMobile.framework; path = "../node_modules/nodejs-mobile-react-native/ios/NodeMobile.framework"; sourceTree = "<group>"; };
CD8C8B91E0A747B3883A0D56 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; CD8C8B91E0A747B3883A0D56 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDocumentPicker.a; sourceTree = "<group>"; }; D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDocumentPicker.a; sourceTree = "<group>"; };
E086AB579387F878A2CBCFEB /* libPods-drip-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E086AB579387F878A2CBCFEB /* libPods-drip-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-drip-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -111,6 +117,7 @@
F5039D0A572B4BBCB7995891 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; }; F5039D0A572B4BBCB7995891 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
F710D85E391D4094816E1B62 /* libRealmJS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRealmJS.a; sourceTree = "<group>"; }; F710D85E391D4094816E1B62 /* libRealmJS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRealmJS.a; sourceTree = "<group>"; };
F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = RealmJSTests.xctest; sourceTree = "<group>"; }; F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = RealmJSTests.xctest; sourceTree = "<group>"; };
F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNNodeJsMobile.a; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -130,6 +137,7 @@
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */, 62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */,
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */, D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
BD7041F2826E4A2CBE6CB87D /* RealmJSTests.xctest in Frameworks */, BD7041F2826E4A2CBE6CB87D /* RealmJSTests.xctest in Frameworks */,
5C4C9DDE2824847500B72CBE /* NodeMobile.framework in Frameworks */,
E545887DBE87912F11770AB9 /* libPods-drip.a in Frameworks */, E545887DBE87912F11770AB9 /* libPods-drip.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -196,6 +204,7 @@
2D16E6891FA4F8E400B85C8A /* libReact.a */, 2D16E6891FA4F8E400B85C8A /* libReact.a */,
9AEBF0735214455AAEDF56D5 /* libc++.tbd */, 9AEBF0735214455AAEDF56D5 /* libc++.tbd */,
CD8C8B91E0A747B3883A0D56 /* libz.tbd */, CD8C8B91E0A747B3883A0D56 /* libz.tbd */,
C225FC4966694B9FBD32E946 /* NodeMobile.framework */,
6B7C2A0A7AAA83BBEFBD0B6A /* libPods-drip-tvOS.a */, 6B7C2A0A7AAA83BBEFBD0B6A /* libPods-drip-tvOS.a */,
E086AB579387F878A2CBCFEB /* libPods-drip-tvOSTests.a */, E086AB579387F878A2CBCFEB /* libPods-drip-tvOSTests.a */,
699D3B3789FC2E5185CB9894 /* libPods-drip.a */, 699D3B3789FC2E5185CB9894 /* libPods-drip.a */,
@@ -213,6 +222,7 @@
D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */, D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */,
84CCEBD3B2C44758853BC941 /* libRNFS.a */, 84CCEBD3B2C44758853BC941 /* libRNFS.a */,
AB636AA0286D45CE9B23B2C3 /* libRCTRestart.a */, AB636AA0286D45CE9B23B2C3 /* libRCTRestart.a */,
F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */,
90224CB4571D41C4969E9722 /* libGCDWebServers.a */, 90224CB4571D41C4969E9722 /* libGCDWebServers.a */,
F710D85E391D4094816E1B62 /* libRealmJS.a */, F710D85E391D4094816E1B62 /* libRealmJS.a */,
F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */, F79F72C5390646E0A06AAE68 /* RealmJSTests.xctest */,
@@ -257,6 +267,8 @@
00E356EF1AD99517003FC87E /* dripTests */, 00E356EF1AD99517003FC87E /* dripTests */,
83CBBA001A601CBA00E9B192 /* Products */, 83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */, 2D16E6871FA4F8E400B85C8A /* Frameworks */,
6466AE2461BE4FA88B8372F0 /* nodejs-project */,
36F1B55D0DEE47AA9AF4BBDD /* builtin_modules */,
42C7F9942202468200F22656 /* Recovered References */, 42C7F9942202468200F22656 /* Recovered References */,
6817ABC38854EEB6D3EE933A /* Pods */, 6817ABC38854EEB6D3EE933A /* Pods */,
006C39A0B9774387BC5ACA43 /* Resources */, 006C39A0B9774387BC5ACA43 /* Resources */,
@@ -286,6 +298,7 @@
00E356EA1AD99517003FC87E /* Sources */, 00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */, 00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */, 00E356EC1AD99517003FC87E /* Resources */,
22A5D3F7F84BDF2020F07B0E /* [CP] Embed Pods Frameworks */,
46313D848A7A3E69E5ED05E7 /* [CP] Copy Pods Resources */, 46313D848A7A3E69E5ED05E7 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
@@ -307,6 +320,14 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */, 13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
DE9DD9049EBE47E5A1EA3F5D /* Build NodeJS Mobile Native Modules */,
6C10CEE2CE544C7F9400F1B4 /* Sign NodeJS Mobile Native Modules */,
B93B657279074F0DB95BCDE2 /* Remove NodeJS Mobile Framework Simulator Strips */,
CD43B73558845DFAF38EE039 /* [CP] Embed Pods Frameworks */,
935E6C85F581DC5BB536B833 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */,
FCE9B437260EC219FF32251C /* [CP-User] [NODEJS MOBILE] Build Native Modules */,
2A3F66C5C345646DE949E1FF /* [CP-User] [NODEJS MOBILE] Sign Native Modules */,
CD7DBF41D9E2377DC61F80D8 /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */,
616E70055A3365D214C7B201 /* [CP] Copy Pods Resources */, 616E70055A3365D214C7B201 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
@@ -391,6 +412,8 @@
54DFE73A25D94D6E0025C3FC /* swipe.png in Resources */, 54DFE73A25D94D6E0025C3FC /* swipe.png in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
5472A44A25BB7806005E81DE /* drip-home-icons.ttf in Resources */, 5472A44A25BB7806005E81DE /* drip-home-icons.ttf in Resources */,
E4584E55EEC24302A3E84A23 /* nodejs-project in Resources */,
A16B351C3F3644CF95F104D2 /* builtin_modules in Resources */,
5472A45225BB7807005E81DE /* Jost-700-Bold.otf in Resources */, 5472A45225BB7807005E81DE /* Jost-700-Bold.otf in Resources */,
5472A44E25BB7807005E81DE /* Prompt-ExtraLight.ttf in Resources */, 5472A44E25BB7807005E81DE /* Prompt-ExtraLight.ttf in Resources */,
); );
@@ -413,6 +436,34 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
}; };
22A5D3F7F84BDF2020F07B0E /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests-frameworks.sh",
"${PODS_ROOT}/../../node_modules/nodejs-mobile-react-native/ios/NodeMobile.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NodeMobile.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-drip-dripTests/Pods-drip-dripTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
2A3F66C5C345646DE949E1FF /* [CP-User] [NODEJS MOBILE] Sign Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "[CP-User] [NODEJS MOBILE] Sign Native Modules";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n";
};
46313D848A7A3E69E5ED05E7 /* [CP] Copy Pods Resources */ = { 46313D848A7A3E69E5ED05E7 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -513,6 +564,20 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-drip/Pods-drip-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-drip/Pods-drip-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
6C10CEE2CE544C7F9400F1B4 /* Sign NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Sign NodeJS Mobile Native Modules";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n \n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n";
};
852A2E0CD5D0F561CE2927A3 /* [CP] Check Pods Manifest.lock */ = { 852A2E0CD5D0F561CE2927A3 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -535,6 +600,48 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
935E6C85F581DC5BB536B833 /* [CP-User] [NODEJS MOBILE] Copy Node.js Project files */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "[CP-User] [NODEJS MOBILE] Copy Node.js Project files";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\nset -e\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nNODEJS_BUILT_IN_MODULES_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/install/resources/nodejs-modules/ && pwd )\"\nif [ -d \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/nodejs-project/\"\nfi\nif [ -d \"$CODESIGNING_FOLDER_PATH/builtin_modules/\" ]\nthen\nrm -rf \"$CODESIGNING_FOLDER_PATH/builtin_modules/\"\nfi\nrsync -av --delete \"$NODEJS_ASSETS_DIR/nodejs-project\" \"$CODESIGNING_FOLDER_PATH\"\nrsync -av --delete \"$NODEJS_BUILT_IN_MODULES_DIR/builtin_modules\" \"$CODESIGNING_FOLDER_PATH\"\n";
};
B93B657279074F0DB95BCDE2 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Remove NodeJS Mobile Framework Simulator Strips";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n";
};
CD43B73558845DFAF38EE039 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-drip/Pods-drip-frameworks.sh",
"${PODS_ROOT}/../../node_modules/nodejs-mobile-react-native/ios/NodeMobile.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NodeMobile.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-drip/Pods-drip-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
CD68B565C5F4A7A674494D02 /* [CP] Check Pods Manifest.lock */ = { CD68B565C5F4A7A674494D02 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -557,6 +664,40 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
CD7DBF41D9E2377DC61F80D8 /* [CP-User] [NODEJS MOBILE] Remove Simulator Strip */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "[CP-User] [NODEJS MOBILE] Remove Simulator Strip";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n";
};
DE9DD9049EBE47E5A1EA3F5D /* Build NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Build NodeJS Mobile Native Modules";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n";
};
FCE9B437260EC219FF32251C /* [CP-User] [NODEJS MOBILE] Build Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "[CP-User] [NODEJS MOBILE] Build Native Modules";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Support building neon-bindings (Rust) native modules\nif [ -f ~/.cargo/env ]; then\n source ~/.cargo/env;\nfi\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"aarch64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" CARGO_BUILD_TARGET=\"x86_64-apple-ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n";
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@@ -596,6 +737,8 @@
ENABLE_BITCODE = "$(inherited)"; ENABLE_BITCODE = "$(inherited)";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
"$(SRCROOT)/../node_modules/react-native-svg/ios/**", "$(SRCROOT)/../node_modules/react-native-svg/ios/**",
); );
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@@ -610,6 +753,7 @@
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker", "$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**", "$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**", "$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
); );
INFOPLIST_FILE = dripTests/Info.plist; INFOPLIST_FILE = dripTests/Info.plist;
@@ -641,6 +785,8 @@
ENABLE_BITCODE = "$(inherited)"; ENABLE_BITCODE = "$(inherited)";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
"$(SRCROOT)/../node_modules/react-native-svg/ios/**", "$(SRCROOT)/../node_modules/react-native-svg/ios/**",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
@@ -651,6 +797,7 @@
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker", "$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**", "$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**", "$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
); );
INFOPLIST_FILE = dripTests/Info.plist; INFOPLIST_FILE = dripTests/Info.plist;
@@ -685,7 +832,10 @@
ENABLE_BITCODE = "$(inherited)"; ENABLE_BITCODE = "$(inherited)";
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=*]" = ""; "EXCLUDED_ARCHS[sdk=*]" = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
);
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"COCOAPODS=1", "COCOAPODS=1",
"$(inherited)", "$(inherited)",
@@ -698,6 +848,7 @@
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker", "$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**", "$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**", "$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/@react-native-community/push-notification-ios/ios", "$(SRCROOT)/../node_modules/@react-native-community/push-notification-ios/ios",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
); );
@@ -732,6 +883,7 @@
"\"${PODS_CONFIGURATION_BUILD_DIR}/RealmJS\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/RealmJS\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/nodejs-mobile-react-native\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-document-picker\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-document-picker\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-restart\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-restart\"",
"\"${PODS_ROOT}/../../node_modules/realm/vendor/realm-ios\"", "\"${PODS_ROOT}/../../node_modules/realm/vendor/realm-ios\"",
@@ -763,7 +915,10 @@
DEVELOPMENT_TEAM = 6AD72X6W26; DEVELOPMENT_TEAM = 6AD72X6W26;
ENABLE_BITCODE = "$(inherited)"; ENABLE_BITCODE = "$(inherited)";
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"../node_modules/nodejs-mobile-react-native/ios\"",
);
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"COCOAPODS=1", "COCOAPODS=1",
"$(inherited)", "$(inherited)",
@@ -776,6 +931,7 @@
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker", "$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**", "$(SRCROOT)/../node_modules/react-native-fs/**",
"$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**", "$(SRCROOT)/../node_modules/react-native-restart/ios/RCTRestart/**",
"$(SRCROOT)/../node_modules/nodejs-mobile-react-native/ios/**",
"$(SRCROOT)/../node_modules/@react-native-community/push-notification-ios/ios", "$(SRCROOT)/../node_modules/@react-native-community/push-notification-ios/ios",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
); );
@@ -810,6 +966,7 @@
"\"${PODS_CONFIGURATION_BUILD_DIR}/RealmJS\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/RealmJS\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/nodejs-mobile-react-native\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-document-picker\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-document-picker\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-restart\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-restart\"",
"\"${PODS_ROOT}/../../node_modules/realm/vendor/realm-ios\"", "\"${PODS_ROOT}/../../node_modules/realm/vendor/realm-ios\"",
-2
View File
@@ -1,2 +0,0 @@
// Import Jest Native matchers
import '@testing-library/jest-native/extend-expect'
+4 -5
View File
@@ -1,12 +1,11 @@
module.exports = { module.exports = {
preset: '@testing-library/react-native', preset: '@testing-library/react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
setupFilesAfterEnv: ['./jest-setup.js'], moduleNameMapper: {
'\\.(png)$': require.resolve('./test/file-mock.js'),
},
setupFilesAfterEnv: ['./test/jest-setup.js'],
transformIgnorePatterns: [ transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)/)', 'node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)/)',
], ],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
} }
+5 -13
View File
@@ -8,7 +8,7 @@ import {
import getColumnNamesForCsv from './get-csv-column-names' import getColumnNamesForCsv from './get-csv-column-names'
import replaceWithNullIfAllPropertiesAreNull from './replace-with-null' import replaceWithNullIfAllPropertiesAreNull from './replace-with-null'
import { LocalDate } from '@js-joda/core' import { LocalDate } from '@js-joda/core'
import i18next from 'i18next' import labels from '../../i18n/en/settings'
export default async function importCsv(csv, deleteFirst) { export default async function importCsv(csv, deleteFirst) {
const parseFuncs = { const parseFuncs = {
@@ -46,10 +46,7 @@ export default async function importCsv(csv, deleteFirst) {
const cycleDays = await csvParser(config) const cycleDays = await csvParser(config)
.fromString(csv) .fromString(csv)
.on('header', (headers) => validateHeaders(headers)) .on('header', validateHeaders)
.on('error', (error) => {
throw error
})
//remove symptoms where all fields are null //remove symptoms where all fields are null
putNullForEmptySymptoms(cycleDays) putNullForEmptySymptoms(cycleDays)
@@ -70,11 +67,8 @@ function validateHeaders(headers) {
return expectedHeaders.indexOf(header) > -1 return expectedHeaders.indexOf(header) > -1
}) })
) { ) {
throw new Error( const msg = `Expected CSV column titles to be ${expectedHeaders.join()}`
i18next.t('hamburgerMenu.settings.data.import.error.incorrectColumns', { throw new Error(msg)
incorrectColumns: expectedHeaders.join(),
})
)
} }
} }
@@ -98,9 +92,7 @@ function throwIfFutureData(cycleDays) {
day.date > today && day.date > today &&
Object.keys(day).some((symptom) => symptom != 'date' && symptom != 'note') Object.keys(day).some((symptom) => symptom != 'date' && symptom != 'note')
) { ) {
throw new Error( throw new Error(labels.import.errors.futureEdit)
i18next.t('hamburgerMenu.settings.data.import.error.futureEdit')
)
} }
} }
} }
+4 -3
View File
@@ -1,16 +1,17 @@
export default function getSensiplanMucus(feeling, texture) { export default function (feeling, texture) {
if (typeof feeling != 'number' || typeof texture != 'number') return null if (typeof feeling != 'number' || typeof texture != 'number') return null
const feelingMapping = { const feelingMapping = {
0: 0, 0: 0,
1: 1, 1: 1,
2: 2, 2: 2,
3: 4, 3: 4
} }
const textureMapping = { const textureMapping = {
0: 0, 0: 0,
1: 3, 1: 3,
2: 4, 2: 4
} }
const nfpFeelingValue = feelingMapping[feeling] const nfpFeelingValue = feelingMapping[feeling]
const nfpTextureValue = textureMapping[texture] const nfpTextureValue = textureMapping[texture]
+12
View File
@@ -0,0 +1,12 @@
// too see stdout / stderr from this process, run
// adb logcat | grep "NODEJS-MOBILE"
const rnBridge = require('rn-bridge')
const crypto = require('crypto')
rnBridge.channel.on('request-SHA512', (msg) => {
msg = JSON.parse(msg)
const hash = crypto.createHash('sha512')
hash.update(msg.message)
const result = hash.digest('hex')
rnBridge.channel.post(msg.type, result)
})
@@ -0,0 +1,4 @@
{
"dependencies": {},
"main": "main.js"
}
+8 -9
View File
@@ -36,14 +36,14 @@
"@react-native-community/datetimepicker": "^6.3.1", "@react-native-community/datetimepicker": "^6.3.1",
"@react-native-community/push-notification-ios": "^1.8.0", "@react-native-community/push-notification-ios": "^1.8.0",
"csvtojson": "^2.0.8", "csvtojson": "^2.0.8",
"i18next": "^22.0.2", "i18next": "^21.9.0",
"jshashes": "^1.0.8",
"moment": "^2.29.4", "moment": "^2.29.4",
"nodejs-mobile-react-native": "^0.8.2",
"object-path": "^0.11.4", "object-path": "^0.11.4",
"obv": "0.0.1", "obv": "0.0.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "17.0.2", "react": "17.0.2",
"react-i18next": "^12.0.0", "react-i18next": "^11.18.3",
"react-native": "0.67.4", "react-native": "0.67.4",
"react-native-calendars": "^1.1287.0", "react-native-calendars": "^1.1287.0",
"react-native-document-picker": "^8.1.1", "react-native-document-picker": "^8.1.1",
@@ -58,18 +58,17 @@
"sympto": "3.0.1" "sympto": "3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.2", "@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.19.1", "@babel/eslint-parser": "^7.19.1",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@testing-library/jest-native": "^4.0.12", "@testing-library/jest-native": "^4.0.12",
"@testing-library/react-native": "^11.1.0", "@testing-library/react-native": "^11.1.0",
"basic-changelog": "gitlab:bloodyhealth/basic-changelog", "basic-changelog": "gitlab:bloodyhealth/basic-changelog",
"eslint": "^7.32.0", "eslint": "7.14.0",
"eslint-plugin-react": "^7.31.10", "eslint-plugin-react": "^7.8.2",
"husky": "^8.0.0", "husky": "^8.0.0",
"jest": "^29.1.2", "jest": "^28.1.3",
"jest-watch-typeahead": "^2.2.0",
"jetifier": "^1.6.6", "jetifier": "^1.6.6",
"metro-react-native-babel-preset": "^0.66.2", "metro-react-native-babel-preset": "^0.66.2",
"prettier": "2.4.0", "prettier": "2.4.0",
+13
View File
@@ -0,0 +1,13 @@
// https://github.com/janeasystems/nodejs-mobile-react-native#duplicate-module-name
const blacklist = require('metro-config/src/defaults/blacklist')
module.exports = {
resolver:{
blacklistRE: blacklist([
/nodejs-assets\/.*/,
/android\/.*/,
/ios\/.*/
])
},
}
+19 -15
View File
@@ -6,7 +6,7 @@ import Spacing from './spacing'
export const fonts = { export const fonts = {
main: Platform.OS === 'ios' ? 'Jost-Book' : 'Jost-400-Book', main: Platform.OS === 'ios' ? 'Jost-Book' : 'Jost-400-Book',
bold : Platform.OS === 'ios' ? 'Jost-Bold' : 'Jost-700-Bold', bold: Platform.OS === 'ios' ? 'Jost-Bold' : 'Jost-700-Bold',
} }
export const sizes = { export const sizes = {
@@ -23,7 +23,7 @@ export const sizes = {
const accentText = { const accentText = {
fontFamily: fonts.bold, fontFamily: fonts.bold,
textAlignVertical: 'center', textAlignVertical: 'center',
textTransform: 'uppercase' textTransform: 'uppercase',
} }
const accentTextBig = { const accentTextBig = {
@@ -43,47 +43,51 @@ const accentTextHuge = {
const accentTextSmall = { const accentTextSmall = {
...accentText, ...accentText,
fontSize: sizes.small fontSize: sizes.small,
} }
const title = { const title = {
color: Colors.purple, color: Colors.purple,
marginVertical: Spacing.large marginVertical: Spacing.large,
} }
const label = { const label = {
fontSize: sizes.small, fontSize: sizes.small,
textTransform: 'uppercase' textTransform: 'uppercase',
} }
export default { export default {
accentOrange: { accentOrange: {
...accentTextSmall, ...accentTextSmall,
color: Colors.orange color: Colors.orange,
},
accentPurple: {
...accentTextSmall,
color: Colors.purple,
}, },
accentPurpleBig: { accentPurpleBig: {
...accentTextBig, ...accentTextBig,
color: Colors.purple color: Colors.purple,
}, },
accentPurpleGiant: { accentPurpleGiant: {
...accentTextGiant, ...accentTextGiant,
color: Colors.purple color: Colors.purple,
}, },
accentPurpleHuge: { accentPurpleHuge: {
...accentTextHuge, ...accentTextHuge,
color: Colors.purple color: Colors.purple,
}, },
mainText: { mainText: {
fontFamily: fonts.main, fontFamily: fonts.main,
fontSize: sizes.base fontSize: sizes.base,
}, },
label: { label: {
...label ...label,
}, },
labelBold: { labelBold: {
color: Colors.greyDark, color: Colors.greyDark,
fontWeight: 'bold', fontWeight: 'bold',
...label ...label,
}, },
labelLight: { labelLight: {
color: Colors.grey, color: Colors.grey,
@@ -91,7 +95,7 @@ export default {
}, },
subtitle: { subtitle: {
fontSize: sizes.subtitle, fontSize: sizes.subtitle,
...title ...title,
}, },
title: { title: {
alignSelf: 'center', alignSelf: 'center',
@@ -99,7 +103,7 @@ export default {
fontWeight: '700', fontWeight: '700',
fontSize: sizes.title, fontSize: sizes.title,
marginHorizontal: Spacing.base, marginHorizontal: Spacing.base,
...title ...title,
}, },
titleWithoutMargin: { titleWithoutMargin: {
alignSelf: 'center', alignSelf: 'center',
@@ -107,5 +111,5 @@ export default {
fontFamily: fonts.bold, fontFamily: fonts.bold,
fontWeight: '700', fontWeight: '700',
fontSize: sizes.title, fontSize: sizes.title,
} },
} }
-29
View File
@@ -1,29 +0,0 @@
import React from 'react'
import AcceptLicense from '../components/AcceptLicense'
import { saveLicenseFlag } from '../local-storage'
import { render, screen, fireEvent } from './test-utils'
jest.mock('../local-storage', () => ({
saveLicenseFlag: jest.fn(() => Promise.resolve()),
}))
describe('AcceptLicense', () => {
test('should accept license when clicking ok button', async () => {
const mockedSetLicense = jest.fn()
render(<AcceptLicense setLicense={mockedSetLicense} />)
const okButton = screen.getByText('OK')
fireEvent(okButton, 'click')
await expect(saveLicenseFlag).toHaveBeenCalled()
expect(mockedSetLicense).toHaveBeenCalled()
})
test('should render cancel button', async () => {
render(<AcceptLicense setLicense={jest.fn()} />)
screen.getByText('Cancel')
})
})
+30
View File
@@ -0,0 +1,30 @@
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()),
}))
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 })
})
})
@@ -0,0 +1,395 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Stats screen when provided data, renders stats 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Object {
"paddingHorizontal": 34.285714285714285,
"paddingTop": 34.285714285714285,
}
}
>
<View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
cycle_length_explainer
</Text>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<View
style={
Object {
"flex": 3,
"flexDirection": "column",
}
}
>
<View
accessibilityIgnoresInvertColors={true}
style={
Object {
"marginBottom": 42.857142857142854,
"paddingTop": 107.14285714285714,
}
}
>
<Image
source={123}
style={
Array [
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
Object {
"height": undefined,
"width": undefined,
},
Object {
"resizeMode": "contain",
},
]
}
/>
<Text
ellipsizeMode="clip"
numberOfLines={1}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 85.71428571428571,
"marginTop": -68.57142857142857,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
30.33
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 68.57142857142857,
"marginTop": -34.285714285714285,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
days
</Text>
</View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
average
</Text>
</View>
<View
style={
Object {
"flex": 5,
"flexDirection": "column",
"paddingTop": 21.428571428571427,
}
}
>
<StatsOverview
data={
Array [
Array [
30,
"min",
],
Array [
31,
"max",
],
Array [
0.58,
"standard_deviation",
],
Array [
3,
"completed_cycles",
],
]
}
/>
</View>
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"alignSelf": "center",
"backgroundColor": "#F38337",
"borderRadius": 25,
"flexDirection": "row",
"justifyContent": "center",
"marginTop": 34.285714285714285,
"minWidth": "15%",
"opacity": 1,
"paddingHorizontal": 8.571428571428571,
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Array [
Object {
"color": "white",
"fontFamily": "Jost-Bold",
},
Object {
"fontSize": 27.857142857142858,
"padding": 21.428571428571427,
"textTransform": "uppercase",
},
],
]
}
>
show_stats
</Text>
</View>
<AppHelp
text="standard_deviation_help"
/>
</View>
</RCTScrollView>
</RCTSafeAreaView>
`;
exports[`Stats screen when provided no data, renders no_data text 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Object {
"paddingHorizontal": 34.285714285714285,
"paddingTop": 34.285714285714285,
}
}
>
<View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
cycle_length_explainer
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
no_data
</Text>
</View>
</RCTScrollView>
</RCTSafeAreaView>
`;
exports[`Stats screen when provided null, renders no_data text 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Object {
"paddingHorizontal": 34.285714285714285,
"paddingTop": 34.285714285714285,
}
}
>
<View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
cycle_length_explainer
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
no_data
</Text>
</View>
</RCTScrollView>
</RCTSafeAreaView>
`;
exports[`Stats screen when provided undefined, renders no_data text 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
}
>
<RCTScrollView
contentContainerStyle={
Object {
"paddingHorizontal": 34.285714285714285,
"paddingTop": 34.285714285714285,
}
}
>
<View>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
cycle_length_explainer
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
no_data
</Text>
</View>
</RCTScrollView>
</RCTSafeAreaView>
`;
+13
View File
@@ -0,0 +1,13 @@
import React from 'react'
import { render } from '@testing-library/react-native'
import AppHelp from '../../../components/common/AppHelp'
describe('AppHelp screen', () => {
test('when provided text, should render it', async () => {
const text = 'Some help test'
const { toJSON } = render(<AppHelp text={text} />)
expect(toJSON()).toMatchSnapshot()
})
})
-19
View File
@@ -1,19 +0,0 @@
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,25 @@
import React from 'react'
import { render } from '@testing-library/react-native'
import StatsOverview from '../../../components/common/StatsOverview'
describe('StatsOverview screen', () => {
test('when provided correct, renders it', async () => {
const data = [
[21, 'shortest'],
[21, 'longest'],
[0, 'standard deviation'],
[2, 'completed cycles'],
]
const { toJSON } = render(<StatsOverview data={data} />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided empty data, renders nothing (does not break)', async () => {
const data = []
const { toJSON } = render(<StatsOverview data={data} />)
expect(toJSON()).toMatchSnapshot()
})
})
+47
View File
@@ -0,0 +1,47 @@
import React from 'react'
import { render } from '@testing-library/react-native'
import StatsTable from '../../../components/common/StatsTable'
const mockGetStats = jest
.fn()
.mockImplementationOnce(() => [
{ date: '2022-07-01', cycleLength: 31, bleedingLength: 5 },
{ date: '2022-06-01', cycleLength: 31, bleedingLength: 5 },
])
.mockImplementationOnce(() => [])
.mockImplementationOnce(() => null)
.mockImplementationOnce(() => undefined)
jest.mock('../../../lib/cycle', () => ({
__esModule: true,
default: () => ({
getStats: mockGetStats,
}),
}))
describe('StatsTable screen', () => {
test('when provided correct data set, renders it', async () => {
const { toJSON } = render(<StatsTable onClose={jest.fn()} />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided no data, renders nothing', async () => {
const { toJSON } = render(<StatsTable onClose={jest.fn()} />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided null, renders nothing', async () => {
const { toJSON } = render(<StatsTable onClose={jest.fn()} />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided undefined, renders nothing', async () => {
const { toJSON } = render(<StatsTable onClose={jest.fn()} />)
expect(toJSON()).toMatchSnapshot()
})
})
@@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AppHelp screen when provided text, should render it 1`] = `
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
"padding": 34.285714285714285,
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"alignSelf": "flex-start",
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"paddingRight": 34.285714285714285,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
*
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
Some help test
</Text>
</View>
`;
@@ -1,55 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footnote component when children are present, renders them 1`] = `
<View
style={
{
"alignContent": "flex-start",
"flexDirection": "row",
"marginBottom": 8.571428571428571,
"marginTop": 34.285714285714285,
}
}
>
<Text
style={
[
{
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
{
"color": "#F38337",
},
]
}
>
*
</Text>
<Text
linkStyle={
{
"color": "white",
}
}
style={
[
{
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
{
"color": "#555",
"paddingLeft": 21.428571428571427,
},
]
}
>
Some footnote text
</Text>
</View>
`;
exports[`Footnote component when no children, renders nothing 1`] = `null`;
@@ -0,0 +1,321 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StatsOverview screen when provided correct, renders it 1`] = `
Array [
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "flex-end",
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
ellipsizeMode="clip"
numberOfLines={1}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 64.28571428571428,
"marginRight": 8.571428571428571,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
21
</Text>
</View>
<View
style={
Object {
"flex": 5,
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"margin": 15,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
shortest
</Text>
</View>
</View>,
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "flex-end",
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
ellipsizeMode="clip"
numberOfLines={1}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 64.28571428571428,
"marginRight": 8.571428571428571,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
21
</Text>
</View>
<View
style={
Object {
"flex": 5,
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"margin": 15,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
longest
</Text>
</View>
</View>,
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "flex-end",
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
ellipsizeMode="clip"
numberOfLines={1}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 64.28571428571428,
"marginRight": 8.571428571428571,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
0
</Text>
</View>
<View
style={
Object {
"flex": 5,
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"margin": 15,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
standard deviation
</Text>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 64.28571428571428,
"marginRight": 8.571428571428571,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
*
</Text>
</View>
</View>,
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "flex-end",
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
ellipsizeMode="clip"
numberOfLines={1}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#3A2671",
"fontFamily": "Jost-Bold",
"fontSize": 64.28571428571428,
"marginRight": 8.571428571428571,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
2
</Text>
</View>
<View
style={
Object {
"flex": 5,
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"margin": 15,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
completed cycles
</Text>
</View>
</View>,
]
`;
exports[`StatsOverview screen when provided empty data, renders nothing (does not break) 1`] = `null`;
@@ -0,0 +1,433 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StatsTable screen when provided correct data set, renders it 1`] = `
<View
style={
Object {
"alignSelf": "center",
"backgroundColor": "#E9F2ED",
"marginTop": 137.14285714285714,
"maxHeight": 933.8,
"minHeight": "40%",
"position": "absolute",
"width": "100%",
}
}
>
<View
style={
Object {
"flexDirection": "row",
"justifyContent": "flex-end",
"paddingRight": 34.285714285714285,
"paddingTop": 34.285714285714285,
}
}
>
<View
accessible={true}
collapsable={false}
focusable={true}
hitSlop={
Object {
"bottom": 39.23529411764706,
"left": 42.857142857142854,
"right": 42.857142857142854,
"top": 39.23529411764706,
}
}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignSelf": "flex-start",
"marginBottom": 34.285714285714285,
"opacity": 1,
}
}
>
<Text
allowFontScaling={false}
selectable={false}
style={
Array [
Object {
"color": undefined,
"fontSize": 12,
},
Array [
Object {
"fontSize": 47.14285714285714,
},
undefined,
Object {
"color": "#F38337",
},
],
Object {
"fontFamily": "Entypo",
"fontStyle": "normal",
"fontWeight": "normal",
},
Object {},
]
}
>
</Text>
</View>
</View>
<RCTScrollView
ItemSeparatorComponent={[Function]}
ListHeaderComponent={[Function]}
ListHeaderComponentStyle={
Object {
"borderBottomColor": "#3A2671",
"borderBottomWidth": 2,
}
}
contentContainerStyle={
Object {
"paddingHorizontal": 34.285714285714285,
}
}
data={
Array [
Object {
"bleedingLength": 5,
"cycleLength": 31,
"date": "2022-07-01",
},
Object {
"bleedingLength": 5,
"cycleLength": 31,
"date": "2022-06-01",
},
]
}
getItem={[Function]}
getItemCount={[Function]}
keyExtractor={[Function]}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={
Array [
0,
]
}
viewabilityConfigCallbackPairs={Array []}
>
<View>
<View
onLayout={[Function]}
style={
Object {
"borderBottomColor": "#3A2671",
"borderBottomWidth": 2,
}
}
>
<View
style={
Object {
"backgroundColor": "#E9F2ED",
"flexDirection": "row",
"justifyContent": "space-between",
"paddingVertical": 8.571428571428571,
}
}
>
<View
style={
Object {
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"paddingVertical": 21.428571428571427,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
cycle_start
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"paddingVertical": 21.428571428571427,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
cycle_length
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
"fontFamily": "Jost-Bold",
"fontSize": 27.857142857142858,
"paddingVertical": 21.428571428571427,
"textAlignVertical": "center",
"textTransform": "uppercase",
},
]
}
>
bleeding
</Text>
</View>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"backgroundColor": "#E9F2ED",
"flexDirection": "row",
"justifyContent": "space-between",
"paddingVertical": 8.571428571428571,
}
}
>
<View
style={
Object {
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
01. Jul 22
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
day{"count":31}
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
day{"count":5}
</Text>
</View>
</View>
<View
style={
Object {
"backgroundColor": "#888",
"height": 1,
"width": "100%",
}
}
/>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"backgroundColor": "#E9F2ED",
"flexDirection": "row",
"justifyContent": "space-between",
"paddingVertical": 8.571428571428571,
}
}
>
<View
style={
Object {
"flex": 3,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
01. Jun 22
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
day{"count":31}
</Text>
</View>
<View
style={
Object {
"flex": 2,
"justifyContent": "center",
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
undefined,
]
}
>
day{"count":5}
</Text>
</View>
</View>
</View>
</View>
</RCTScrollView>
</View>
`;
exports[`StatsTable screen when provided no data, renders nothing 1`] = `null`;
exports[`StatsTable screen when provided null, renders nothing 1`] = `null`;
exports[`StatsTable screen when provided undefined, renders nothing 1`] = `null`;
@@ -1,16 +1,17 @@
import React from 'react' import React from 'react'
import License from '../components/settings/License' import { render, screen } from '@testing-library/react-native'
import { render, screen } from './test-utils'
import License from '../../../components/settings/License'
describe('License screen', () => { describe('License screen', () => {
test('should display license text with correct year', async () => { test('It should have a correct year', async () => {
render(<License />) render(<License />)
const year = new Date().getFullYear().toString() const year = new Date().getFullYear().toString()
screen.getByText(year, { exact: false }) screen.getByText(year, { exact: false })
}) })
test('should match the snapshot', async () => { test('It should match the snapshot', async () => {
const licenseScreen = render(<License />) const licenseScreen = render(<License />)
expect(licenseScreen).toMatchSnapshot() expect(licenseScreen).toMatchSnapshot()
@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`License screen should match the snapshot 1`] = ` exports[`License screen It should match the snapshot 1`] = `
<View <View
style={ style={
{ Object {
"backgroundColor": "#E9F2ED", "backgroundColor": "#E9F2ED",
"flex": 1, "flex": 1,
} }
@@ -11,8 +11,8 @@ exports[`License screen should match the snapshot 1`] = `
> >
<RCTScrollView <RCTScrollView
contentContainerStyle={ contentContainerStyle={
[ Array [
{ Object {
"backgroundColor": "#E9F2ED", "backgroundColor": "#E9F2ED",
"flexGrow": 1, "flexGrow": 1,
}, },
@@ -23,13 +23,13 @@ exports[`License screen should match the snapshot 1`] = `
<View> <View>
<Text <Text
style={ style={
[ Array [
{ Object {
"color": "#555", "color": "#555",
"fontFamily": "Jost-Book", "fontFamily": "Jost-Book",
"fontSize": 34.285714285714285, "fontSize": 34.285714285714285,
}, },
{ Object {
"alignSelf": "center", "alignSelf": "center",
"color": "#3A2671", "color": "#3A2671",
"fontFamily": "Jost-Bold", "fontFamily": "Jost-Bold",
@@ -41,11 +41,11 @@ exports[`License screen should match the snapshot 1`] = `
] ]
} }
> >
drip. an open-source cycle tracking app title
</Text> </Text>
<View <View
style={ style={
{ Object {
"marginBottom": 34.285714285714285, "marginBottom": 34.285714285714285,
"marginHorizontal": 34.285714285714285, "marginHorizontal": 34.285714285714285,
} }
@@ -53,8 +53,8 @@ exports[`License screen should match the snapshot 1`] = `
> >
<Text <Text
style={ style={
[ Array [
{ Object {
"color": "#555", "color": "#555",
"fontFamily": "Jost-Book", "fontFamily": "Jost-Book",
"fontSize": 34.285714285714285, "fontSize": 34.285714285714285,
@@ -63,14 +63,12 @@ exports[`License screen should match the snapshot 1`] = `
] ]
} }
> >
Copyright (C) 2022 Heart of Code e.V. text{"currentYear":2022}
This 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:
</Text> </Text>
<Text <Text
onPress={[Function]} onPress={[Function]}
style={ style={
{ Object {
"color": "#3A2671", "color": "#3A2671",
"fontFamily": "Jost-Book", "fontFamily": "Jost-Book",
"fontSize": 34.285714285714285, "fontSize": 34.285714285714285,
+58
View File
@@ -0,0 +1,58 @@
import React from 'react'
import { fireEvent, render } from '@testing-library/react-native'
import Stats from '../../components/stats'
jest.mock('../../components/common/AppHelp', () => 'AppHelp')
jest.mock('../../components/common/StatsOverview', () => 'StatsOverview')
jest.mock('../../components/common/StatsTable', () => 'StatsTable')
const mockGetAllCycleLengths = jest
.fn()
.mockImplementationOnce(() => [])
.mockImplementationOnce(() => [30, 31, 30])
.mockImplementationOnce(() => null)
.mockImplementationOnce(() => undefined)
.mockImplementationOnce(() => [30, 31, 30])
jest.mock('../../lib/cycle', () => ({
__esModule: true,
default: () => ({
getAllCycleLengths: mockGetAllCycleLengths,
}),
}))
describe('Stats screen', () => {
test('when provided no data, renders no_data text', async () => {
const { toJSON } = render(<Stats />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided data, renders stats', async () => {
const { toJSON } = render(<Stats />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided null, renders no_data text', async () => {
const { toJSON } = render(<Stats />)
expect(toJSON()).toMatchSnapshot()
})
test('when provided undefined, renders no_data text', async () => {
const { toJSON } = render(<Stats />)
expect(toJSON()).toMatchSnapshot()
})
test('when button is clicked, StatsTable is rendered', async () => {
const { getByText, findByTestId } = render(<Stats />)
const button = getByText('show_stats')
fireEvent(button, 'click')
await expect(findByTestId('statsTable')).toBeTruthy()
})
})
+1
View File
@@ -0,0 +1 @@
module.exports = 123
+8
View File
@@ -0,0 +1,8 @@
// Import Jest Native matchers
import '@testing-library/jest-native/extend-expect'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (str, options) => str + (options ? JSON.stringify(options) : ''),
}),
}))
-10
View File
@@ -1,10 +0,0 @@
import { render } from '@testing-library/react-native'
import '../i18n/i18n'
const customRender = (ui, options) => render(ui, { ...options })
// re-export everything
export * from '@testing-library/react-native'
// override render method
export { customRender as render }
+815 -911
View File
File diff suppressed because it is too large Load Diff