Compare commits

..

8 Commits

Author SHA1 Message Date
Lisa Hillebrand 56c783020d 622 Rename component to delete data 2022-10-16 14:54:05 +02:00
Lisa Hillebrand 43106f0081 622 Use translation library for delete settings 2022-10-16 14:54:05 +02:00
Lisa Hillebrand df6bc575d0 621 Use translation library for data export 2022-10-14 12:11:03 +02:00
Lisa Hillebrand d77cb17f1d 618 Rename function to start import 2022-10-14 12:08:29 +02:00
Lisa Hillebrand bcc8ebd7e2 618 Remove old file with import functions 2022-09-30 19:52:56 +02:00
Lisa Hillebrand a54dad9b1d 618 Remove unused constants 2022-09-30 19:52:56 +02:00
Lisa Hillebrand bb83785e56 618 Create dedicated component for importing data 2022-09-30 19:52:56 +02:00
Lisa Hillebrand 0a80b30388 618 Rename index.js to DataManagement.js 2022-09-30 12:44:41 +02:00
47 changed files with 3391 additions and 2905 deletions
-1
View File
@@ -14,4 +14,3 @@ updates:
- dependency-name: 'react'
- dependency-name: 'react-native'
- dependency-name: 'react-native-push-notifications'
- dependency-name: '@babel/core'
-26
View File
@@ -1,26 +0,0 @@
## oh no a bug 🐛
### Description what has happened
Short overview how the bug manifests.
### which OS + version is your device
[ ] Android _number_
[ ] iOS _number_
### which drip version number are you using
_On your phone go to ➞ menu on the top right ➞ about, scroll to the very bottom and find the version number_
### how did it happen
_what triggered the bug/behavior, always/sometimes, is it reproducible(how)?_
### describe how it looks or add screenshot
feel free to attach a file 📎
### any idea to solve it
💡
-22
View File
@@ -1,22 +0,0 @@
## This has to be done 🪠
### Description what has to be done
Short overview
### is it urgent? ⏳
[ ] Yes
[ ] No
[ ] something in between
_Explain the urgency if possible, e.g. is it a security vulnerability for potentially everyone?_
### which OS
[ ] Android
[ ] iOS
### what shall be the ideal outcome 🎆
_You can e.g. specify here the version number for a library update_
@@ -1,19 +0,0 @@
## Yeah a feature idea 🧩
### what should this feature do or solve? 🪄
Please give a short overview so as many people as possible would be able to understand.
### what is particularly important to the people who would use this feature?
Do you have certain user groups in mind?
### Any idea where it shall be placed in the app?
### is it connected with or dependent on some other feature?
### any idea how it shall look (sketch?)
feel free to attach a file 📎
### what could be difficulties (with other components) 🪆
+1 -1
View File
@@ -1,6 +1,6 @@
## Why this change?
Closes #
Closes ticket #
## Description
+2 -70
View File
@@ -2,80 +2,12 @@
All notable changes to this project will be documented in this file.
## v1.2311.14
### Changes
- Make the app compatible with Android 13
- Update Android's targetSdkVersion to 33
- Update buildToolsVersion to 33.0.2
- Update Android Gradle plugin to 7.0.3
- Update Gradle to 7.3.3
- Update kotlinVersion to 1.3.40
- Chart: Improved readability
- Finer temperature lines and dots
- Enlarge screen space for temperature chart
- A very light grey background color for weekend days on the whole chart screen
- Reminders:
- Use new fork of react-native-push-notification: <https://github.com/github:bl00dymarie/react-native-push-notification> without google services
- Adding channels after breaking changes in react-native-push-notification
- Homescreen: date displayed in new format
- Minor changes in "about" section
- Updated dependencies:
- moment ^2.29.4,
- prop-types ^15.8.1,
- react v17.0.2,
- react-native v0.67.4,
- react-native-calendars ^1.1287.0,
- react-native-document-picker ^8.1.1,
- react-native-fs ^2.20.0,
- react-native-modal-datetime-picker v14.0.0,
- react-native-share ^7.9.0,
- react-native-vector-icons ^9.2.0,
- realm ^10.16.0,
- sympto v3.0.1
### Adds
- Stats: Show period details, including cycle start, cycle length and amount of days with bleeding
- Stats: Explainer text for standard deviation
- Settings: Privacy Policy
- App asks for permissions for notifications right at the start, which allows you to set reminders (this is a new requirement for Android 13)
- Buttons can now be displayed as row
- Added dependencies:
- @js-joda/core ^5.3.0,
- @react-native-async-storage/async-storage ^1.17.9,
- @react-native-community/art ^1.2.0,
- @react-native-community/datetimepicker ^6.3.1,
- @react-native-community/push-notification-ios ^1.11.0,
- i18next ^22.0.2,
- react-i18next ^12.0.0,
- jshashes ^1.0.8,
- react-native-permissions ^3.10.0,
- react-native-push-notification: github:bl00dymarie/react-native-push-notification,
- react-native-simple-toast ^1.1.3,
- react-native-size-matters ^0.4.0,
### Fixed
- Password: Disable setting empty passwords
- After updating the password the app will do a full restart
- Chart: Grid for symptoms
- Chart: Horizontal lines in temperature chart
## Unreleased
- Partially implemented translations with react-i18next
## v1.2102.28
### Changes
- Temperature range is now between 35 - 39°C and its default values are now set to 35.5 - 37.5°C
### Fixed
- Blocks invalid input of temperature value
- Error message for incorrect password on login screen
- Phase text on home screen for last fertile day
@@ -129,7 +61,7 @@ All notable changes to this project will be documented in this file.
### Adds
- Allows chart not to show temperature part, when temperature is not tracked and corresponding refactoring
- Detox support for e2e testing and addition of the e2e tests
- Detox support for e2e testing and addition of the e2e tests
- Introduces Redux global state (date and navigation are stored locally now)
- Introduces clear.sh script to the project automising clearing project caches and packages reinstallation
@@ -148,7 +80,7 @@ All notable changes to this project will be documented in this file.
- Fixed adding notes to the future dates
- Fixed app exiting with error when hitting back button on device
- Fixed Sex symptom showing on y axis of chart even though the contraception method was deleted
- Fixed Sex symptom showing on y axis of chart even though the contraception method was deleted
- Fixed of the clear.sh file name in package.json
- Fixed of navigation from chart to the cycle day overview
- Bug fix for maximum value of mucus not showing on chart
+4
View File
@@ -201,3 +201,7 @@ More information about how the app calculates fertility status and bleeding pred
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.
## Translation
We are using [Weblate](https://weblate.org/) as translation software.
+7 -8
View File
@@ -134,10 +134,10 @@ android {
applicationId "com.drip"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 25
versionName "1.2311.14"
versionCode 8
versionName "1.2102.28"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
abiFilters "armeabi-v7a", "x86", "arm64-v8a"
}
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -198,7 +198,7 @@ android {
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def versionCodes = ["armeabi-v7a": 1, "x86": 2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
@@ -211,12 +211,11 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "androidx.appcompat:appcompat:1.0.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.work:work-runtime-ktx:2.7.1"
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
+50 -57
View File
@@ -5,81 +5,74 @@
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission tools:node="remove" android:name="android.permission.READ_PHONE_STATE" />
<uses-permission tools:node="remove" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission tools:node="remove" android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:name=".MainApplication"
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<meta-data
android:name="com.dieam.reactnativepushnotification.notification_foreground"
android:value="false" />
<meta-data
android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@color/purple" />
<meta-data
android:name="com.dieam.reactnativepushnotification.default_notification_channel_id"
android:value="..." />
<receiver
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions"
android:exported="false"
tools:ignore="MissingClass" />
<receiver
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher"
android:exported="false" />
<receiver
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"
android:exported="false" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="sensorPortrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="sensorPortrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.drip.provider"
android:grantUriPermissions="true"
android:exported="false" >
android:exported="false">
<meta-data
tools:replace="android:resource"
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</provider>
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground"
android:value="false"/>
<!-- Change the resource name to your App's accent color - or any other color you want -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@android:color/white"/> <!-- or @android:color/{name} to use a standard color -->
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
@@ -14,5 +14,4 @@
<color name="grey">#A5A5A5</color>
<color name="orange">#F38337</color>
<color name="purple">#3A2671</color>
<color name="turquoiseDark">#69CBC1</color>
</resources>
+11 -8
View File
@@ -5,13 +5,13 @@ buildscript {
google()
mavenCentral()
}
ext.kotlinVersion = '1.3.40'
ext.kotlinVersion = "1.3.10"
dependencies {
classpath('com.android.tools.build:gradle:7.0.3')
classpath("com.android.tools.build:gradle:4.2.2")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
@@ -46,9 +46,12 @@ allprojects {
}
ext {
buildToolsVersion = "33.0.2"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
ndkVersion = "21.4.7075529"
googlePlayServicesVersion = "+" // default: "+"
firebaseMessagingVersion = "21.1.0" // default: "+"
buildToolsVersion = "30.0.2"
minSdkVersion = 23
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "21.4.7075529"
}
+2 -3
View File
@@ -1,6 +1,5 @@
#Wed Oct 11 14:45:21 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3 -12
View File
@@ -4,19 +4,15 @@ import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import { Sizes, Typography } from '../../styles'
import { Typography } from '../../styles'
import { CHART_YAXIS_WIDTH } from '../../config'
import { shared as labels } from '../../i18n/en/labels'
const ChartLegend = ({ height }) => {
return (
<View style={[styles.container, { height }]}>
<View style={[styles.singleLabelContainer, { height: height / 2 }]}>
<AppText style={styles.textBold}>#</AppText>
</View>
<View style={[styles.singleLabelContainer, { height: height / 2 }]}>
<AppText style={styles.text}>{labels.date}</AppText>
</View>
<AppText style={styles.textBold}>#</AppText>
<AppText style={styles.text}>{labels.date}</AppText>
</View>
)
}
@@ -31,13 +27,8 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end',
width: CHART_YAXIS_WIDTH,
},
singleLabelContainer: {
justifyContent: 'space-around',
alignItems: 'center',
},
text: {
...Typography.label,
fontSize: Sizes.footnote,
},
textBold: {
...Typography.labelBold,
+2 -2
View File
@@ -9,7 +9,7 @@ import HorizontalGrid from './horizontal-grid'
import MainGrid from './main-grid'
import NoData from './no-data'
import NoTemperature from './no-temperature'
import Tutorial from './Tutorial'
import Tutorial from './tutorial'
import YAxis from './y-axis'
import { getCycleDaysSortedByDate } from '../../db'
@@ -29,7 +29,7 @@ const getSymptomsFromCycleDays = (cycleDays) =>
SYMPTOMS.filter((symptom) => cycleDays.some((cycleDay) => cycleDay[symptom]))
const CycleChart = ({ navigate, setDate }) => {
const [shouldShowHint, setShouldShowHint] = useState(false)
const [shouldShowHint, setShouldShowHint] = useState(true)
useEffect(() => {
let isMounted = true
+7 -20
View File
@@ -19,20 +19,11 @@ const CycleDayLabel = ({ height, date }) => {
return (
<View style={[styles.container, { height }]}>
<View style={{ ...styles.labelRow, height: height / 2 }}>
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
</View>
<View style={{ ...styles.labelRow, height: height / 2 }}>
{isFirstDayOfMonth && (
<AppText style={styles.textFootnote}>
{momentDate.format('MMM')}
</AppText>
)}
{!isFirstDayOfMonth && (
<AppText style={styles.textSmall}>{dayOfMonth}</AppText>
)}
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
<View style={styles.dateLabel}>
<AppText style={styles.text}>
{isFirstDayOfMonth ? momentDate.format('MMM') : dayOfMonth}
</AppText>
{!isFirstDayOfMonth && (
<AppText style={styles.textLight}>
{getOrdinalSuffix(dayOfMonth)}
@@ -54,21 +45,17 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end',
left: 4,
},
textSmall: {
text: {
...Typography.label,
fontSize: Sizes.small,
},
textFootnote: {
...Typography.label,
fontSize: Sizes.footnote,
},
textBold: {
...Typography.labelBold,
},
textLight: {
...Typography.labelLight,
},
labelRow: {
dateLabel: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
-5
View File
@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { TouchableOpacity } from 'react-native'
import moment from 'moment'
import { getCycleDay } from '../../db'
@@ -27,8 +26,6 @@ const DayColumn = ({
symptomRowSymptoms,
xAxisHeight,
}) => {
const momentDate = moment(dateString)
const isWeekend = momentDate.day() == 0 || momentDate.day() == 6
const cycleDayData = getCycleDay(dateString)
let data = {}
@@ -76,7 +73,6 @@ const DayColumn = ({
isVerticalLine={fhmAndLtl.drawFhmLine}
data={data && data.temperature}
columnHeight={columnHeight}
isWeekend={isWeekend}
/>
)}
@@ -96,7 +92,6 @@ const DayColumn = ({
isSymptomDataComplete={
hasSymptomData && isSymptomDataComplete(symptom, dateString)
}
isWeekend={isWeekend}
height={symptomHeight}
/>
)
+6 -7
View File
@@ -7,8 +7,7 @@ import { Colors } from '../../styles'
import {
CHART_COLUMN_WIDTH,
CHART_COLUMN_MIDDLE,
CHART_DOT_RADIUS_SYMPTOM,
CHART_DOT_RADIUS_TEMPERATURE,
CHART_DOT_RADIUS,
CHART_STROKE_WIDTH,
} from '../../config'
@@ -36,9 +35,9 @@ const DotAndLine = ({
}
const dot = new Path()
.moveTo(CHART_COLUMN_MIDDLE, y - CHART_DOT_RADIUS_TEMPERATURE)
.arc(0, CHART_DOT_RADIUS_TEMPERATURE * 2, CHART_DOT_RADIUS_TEMPERATURE)
.arc(0, CHART_DOT_RADIUS_TEMPERATURE * -2, CHART_DOT_RADIUS_TEMPERATURE)
.moveTo(CHART_COLUMN_MIDDLE, y - CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
const dotColor = exclude ? Colors.turquoise : Colors.turquoiseDark
const lineColorLeft = excludeLeftLine
? Colors.turquoise
@@ -59,13 +58,13 @@ const DotAndLine = ({
d={lineRight}
stroke={lineColorRight}
strokeWidth={CHART_STROKE_WIDTH}
key={y + CHART_DOT_RADIUS_SYMPTOM}
key={y + CHART_DOT_RADIUS}
/>
<Shape
d={dot}
stroke={dotColor}
strokeWidth={CHART_STROKE_WIDTH}
fill={Colors.turquoiseDark}
fill="white"
key="dot"
/>
</React.Fragment>
+7 -24
View File
@@ -5,7 +5,7 @@ import { StyleSheet, View } from 'react-native'
import { Colors, Containers } from '../../styles'
import {
CHART_COLUMN_WIDTH,
CHART_DOT_RADIUS_SYMPTOM,
CHART_DOT_RADIUS,
CHART_GRID_LINE_HORIZONTAL_WIDTH,
} from '../../config'
@@ -15,31 +15,14 @@ const SymptomCell = ({
symptom,
symptomValue,
isSymptomDataComplete,
isWeekend,
}) => {
const shouldDrawDot = symptomValue !== false
// Determine the background color based on isWeekend prop
const backgroundColor = isWeekend ? Colors.greyVeryLight : 'white'
const styleCell =
index !== 0
? [
styles.cell,
{
height,
width: CHART_COLUMN_WIDTH,
backgroundColor: backgroundColor,
},
]
: [
styles.cell,
{
height,
width: CHART_COLUMN_WIDTH,
backgroundColor: backgroundColor,
},
styles.topBorder,
]
? [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
: [styles.cell, { height, width: CHART_COLUMN_WIDTH }, styles.topBorder]
let styleDot
if (shouldDrawDot) {
const styleSymptom = Colors.iconColors[symptom]
const symptomColor = styleSymptom.shades[symptomValue]
@@ -64,11 +47,11 @@ SymptomCell.propTypes = {
symptom: PropTypes.string,
symptomValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
isSymptomDataComplete: PropTypes.bool,
isWeekend: PropTypes.bool,
}
const styles = StyleSheet.create({
cell: {
backgroundColor: 'white',
borderBottomColor: Colors.grey,
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
borderLeftColor: Colors.grey,
@@ -80,8 +63,8 @@ const styles = StyleSheet.create({
borderTopWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
},
dot: {
width: CHART_DOT_RADIUS_SYMPTOM * 2,
height: CHART_DOT_RADIUS_SYMPTOM * 2,
width: CHART_DOT_RADIUS * 2,
height: CHART_DOT_RADIUS * 2,
borderRadius: 50,
},
})
+8 -5
View File
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Colors } from '../../styles'
import { StyleSheet } from 'react-native'
import { Surface, Path } from '@react-native-community/art'
@@ -14,16 +14,14 @@ const TemperatureColumn = ({
isVerticalLine,
data,
columnHeight,
isWeekend,
}) => {
const x = CHART_STROKE_WIDTH / 2
const backgroundColor = isWeekend ? Colors.greyVeryLight : 'white'
return (
<Surface
width={CHART_COLUMN_WIDTH}
height={columnHeight}
style={{ backgroundColor: backgroundColor }}
style={styles.container}
>
<ChartLine path={new Path().lineTo(0, columnHeight)} />
@@ -65,7 +63,12 @@ TemperatureColumn.propTypes = {
isVerticalLine: PropTypes.bool,
data: PropTypes.object,
columnHeight: PropTypes.number,
isWeekend: PropTypes.bool,
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
})
export default TemperatureColumn
+2 -5
View File
@@ -8,15 +8,13 @@ import { Sizes } from '../../styles'
import { CHART_TICK_WIDTH } from '../../config'
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
const top = yPosition - height / 2 - 4
const top = yPosition - height / 2
const containerStyle = [styles.container, { flexBasis: height, height, top }]
const textStyle = isBold ? styles.textBold : styles.textNormal
if (!shouldShowLabel) return null
return (
<View style={containerStyle}>
<AppText style={textStyle}>{label}</AppText>
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
</View>
)
}
@@ -38,7 +36,6 @@ const styles = StyleSheet.create({
position: 'absolute',
right: 0,
width: CHART_TICK_WIDTH,
minHeight: Sizes.base + 2,
},
textBold: {
fontSize: Sizes.base,
@@ -6,17 +6,16 @@ import AppText from '../common/app-text'
import CloseIcon from '../common/close-icon'
import { Containers, Spacing } from '../../styles'
import { useTranslation } from 'react-i18next'
import { chart } from '../../i18n/en/labels'
const image = require('../../assets/swipe.png')
const Tutorial = ({ onClose }) => {
const { t } = useTranslation()
return (
<View style={styles.container}>
<Image resizeMode="contain" source={image} style={styles.image} />
<View style={styles.textContainer}>
<AppText>{t('chart.tutorial')}</AppText>
<AppText>{chart.tutorial}</AppText>
</View>
<CloseIcon onClose={onClose} />
</View>
+3 -6
View File
@@ -6,23 +6,20 @@ import MenuItem from './menu-item'
import { Containers } from '../../styles'
import { pages } from '../pages'
import { useTranslation } from 'react-i18next'
const Menu = ({ currentPage, navigate }) => {
const menuItems = pages.filter((page) => page.isInMenu)
const { t } = useTranslation(null, { keyPrefix: 'bottomMenu' })
return (
<View style={styles.container}>
{menuItems.map(({ icon, labelKey, component }) => {
{menuItems.map(({ icon, label, component }) => {
return (
<MenuItem
isActive={component === currentPage}
onPress={() => navigate(component)}
icon={icon}
key={labelKey}
label={t(labelKey)}
key={label}
label={label}
/>
)
})}
+15 -3
View File
@@ -1,63 +1,75 @@
import settingsViews from './settings'
import settingsLabels from '../i18n/en/settings'
const labels = settingsLabels.menuItems
export const pages = [
{
component: 'Home',
icon: 'home',
label: 'Home',
},
{
component: 'Calendar',
icon: 'calendar',
isInMenu: true,
labelKey: 'calendar',
label: 'Calendar',
parent: 'Home',
},
{
component: 'Chart',
icon: 'chart',
isInMenu: true,
labelKey: 'chart',
label: 'Chart',
parent: 'Home',
},
{
component: 'Stats',
icon: 'statistics',
isInMenu: true,
labelKey: 'stats',
label: 'Stats',
parent: 'Home',
},
{
children: Object.keys(settingsViews),
component: 'SettingsMenu',
icon: 'settings',
label: 'Settings',
parent: 'Home',
},
{
component: 'Reminders',
label: labels.reminders.name,
parent: 'SettingsMenu',
},
{
component: 'NfpSettings',
label: labels.nfpSettings.name,
parent: 'SettingsMenu',
},
{
component: 'DataManagement',
label: labels.dataManagement.name,
parent: 'SettingsMenu',
},
{
component: 'Password',
label: labels.password.name,
parent: 'SettingsMenu',
},
{
component: 'About',
label: 'About',
parent: 'SettingsMenu',
},
{
component: 'License',
label: 'License',
parent: 'SettingsMenu',
},
{
component: 'PrivacyPolicy',
label: 'PrivacyPolicy',
parent: 'SettingsMenu',
},
{
@@ -2,46 +2,30 @@ import React, { useState } from 'react'
import AppLoadingView from '../../common/app-loading'
import AppPage from '../../common/app-page'
import AppText from '../../common/app-text'
import Button from '../../common/button'
import Segment from '../../common/segment'
import openShareDialogAndExport from './export-dialog'
import DeleteData from './delete-data'
import labels from '../../../i18n/en/settings'
import DeleteData from './DeleteData'
import ImportData from './ImportData'
import ExportData from './ExportData'
const DataManagement = () => {
const [isLoading, setIsLoading] = useState(false)
const [isDeletingData, setIsDeletingData] = useState(false)
const startExport = () => {
setIsDeletingData(false)
openShareDialogAndExport()
}
if (isLoading) return <AppLoadingView />
return (
<AppPage>
<Segment title={labels.export.button}>
<AppText>{labels.export.segmentExplainer}</AppText>
<Button isCTA onPress={startExport}>
{labels.export.button}
</Button>
</Segment>
<ExportData
resetIsDeletingData={() => setIsDeletingData(false)}
setIsLoading={setIsLoading}
/>
<ImportData
resetIsDeletingData={() => setIsDeletingData(false)}
setIsLoading={setIsLoading}
/>
<Segment title={labels.deleteSegment.title} last>
<AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData
isDeletingData={isDeletingData}
onStartDeletion={() => setIsDeletingData(true)}
/>
</Segment>
<DeleteData
isDeletingData={isDeletingData}
onStartDeletion={() => setIsDeletingData(true)}
/>
</AppPage>
)
}
@@ -11,9 +11,10 @@ import alertError from '../common/alert-error'
import { clearDb, isDbEmpty } from '../../../db'
import { showToast } from '../../helpers/general'
import { hasEncryptionObservable } from '../../../local-storage'
import settings from '../../../i18n/en/settings'
import { shared as sharedLabels } from '../../../i18n/en/labels'
import { EXPORT_FILE_NAME } from './constants'
import Segment from '../../common/segment'
import AppText from '../../common/app-text'
import { useTranslation } from 'react-i18next'
const exportedFilePath = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}`
@@ -22,6 +23,10 @@ const DeleteData = ({ onStartDeletion, isDeletingData }) => {
const [isConfirmingWithPassword, setIsConfirmingWithPassword] =
useState(false)
const { t } = useTranslation(null, {
keyPrefix: 'hamburgerMenu.settings.data.delete',
})
const onAlertConfirmation = () => {
onStartDeletion()
if (isPasswordSet) {
@@ -32,17 +37,16 @@ const DeleteData = ({ onStartDeletion, isDeletingData }) => {
}
const alertBeforeDeletion = async () => {
const { question, message, confirmation, errors } = settings.deleteSegment
if (isDbEmpty() && !(await RNFS.exists(exportedFilePath))) {
alertError(errors.noData)
alertError(t('error.noData'))
} else {
Alert.alert(question, message, [
Alert.alert(t('dialog.title'), t('dialog.message'), [
{
text: confirmation,
text: t('dialog.delete'),
onPress: onAlertConfirmation,
},
{
text: sharedLabels.cancel,
text: t('dialog.cancel'),
style: 'cancel',
onPress: cancelConfirmationWithPassword,
},
@@ -57,16 +61,14 @@ const DeleteData = ({ onStartDeletion, isDeletingData }) => {
}
const deleteAppData = async () => {
const { errors, success } = settings.deleteSegment
try {
if (!isDbEmpty()) {
clearDb()
}
await deleteExportedFile()
showToast(success.message)
showToast(t('success.message'))
} catch (err) {
alertError(errors.couldNotDeleteFile)
alertError(t('error.delete'))
}
cancelConfirmationWithPassword()
}
@@ -85,9 +87,12 @@ const DeleteData = ({ onStartDeletion, isDeletingData }) => {
}
return (
<Button isCTA onPress={alertBeforeDeletion}>
{settings.deleteSegment.title}
</Button>
<Segment title={t('title')} last>
<AppText>{t('subTitle')}</AppText>
<Button isCTA onPress={alertBeforeDeletion}>
{t('title')}
</Button>
</Segment>
)
}
@@ -0,0 +1,77 @@
import React from 'react'
import PropTypes from 'prop-types'
import { getCycleDaysSortedByDate, mapRealmObjToJsObj } from '../../../db'
import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv'
import alertError from '../common/alert-error'
import { EXPORT_FILE_NAME } from './constants'
import RNFS from 'react-native-fs'
import { useTranslation } from 'react-i18next'
import AppText from '../../common/app-text'
import Button from '../../common/button'
import Segment from '../../common/segment'
import Share from 'react-native-share'
export default function ExportData({ setIsLoading, resetIsDeletingData }) {
const { t } = useTranslation(null, {
keyPrefix: 'hamburgerMenu.settings.data.export',
})
async function startExport() {
resetIsDeletingData()
setIsLoading(true)
await exportData()
setIsLoading(false)
}
async function getData() {
const cycleDaysByDate = mapRealmObjToJsObj(getCycleDaysSortedByDate())
try {
return cycleDaysByDate.length
? getDataAsCsvDataUri(cycleDaysByDate)
: null
} catch (err) {
alertError(t('error.convert'))
return null
}
}
async function exportData() {
const data = await getData()
if (!data) {
alertError(t('error.data'))
return
}
try {
const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}`
await RNFS.writeFile(path, data)
await Share.open({
title: t('title'),
url: `file://${path}`,
subject: t('title'),
type: 'text/csv',
showAppsToView: true,
failOnCancel: false,
})
} catch (err) {
return alertError(t('error.share'))
}
}
return (
<Segment title={t('button')}>
<AppText>{t('text')}</AppText>
<Button isCTA onPress={startExport}>
{t('button')}
</Button>
</Segment>
)
}
ExportData.propTypes = {
resetIsDeletingData: PropTypes.func.isRequired,
setIsLoading: PropTypes.func.isRequired,
}
@@ -41,7 +41,7 @@ export default function ImportData({ resetIsDeletingData, setIsLoading }) {
const fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
return fileContent
} catch (err) {
return showImportErrorAlert(t('error.couldNotOpenFile'))
return showImportErrorAlert(t('errors.couldNotOpenFile'))
}
}
@@ -65,19 +65,19 @@ export default function ImportData({ resetIsDeletingData, setIsLoading }) {
style: 'cancel',
onPress: () => {},
},
{
text: t('dialog.delete'),
onPress: () => startImport(true),
},
{
text: t('dialog.replace'),
onPress: () => startImport(false),
},
{
text: t('dialog.delete'),
onPress: () => startImport(true),
},
])
}
function showImportErrorAlert(message) {
const errorMessage = t('error.noDataImported', { message })
const errorMessage = t('errors.noDataImported', { message })
alertError(errorMessage)
}
@@ -1,43 +0,0 @@
import Share from 'react-native-share'
import { getCycleDaysSortedByDate, mapRealmObjToJsObj } from '../../../db'
import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv'
import alertError from '../common/alert-error'
import settings from '../../../i18n/en/settings'
import { EXPORT_FILE_NAME } from './constants'
import RNFS from 'react-native-fs'
export default async function exportData() {
let data
const labels = settings.export
const cycleDaysByDate = mapRealmObjToJsObj(getCycleDaysSortedByDate())
if (!cycleDaysByDate.length) return alertError(labels.errors.noData)
try {
data = getDataAsCsvDataUri(cycleDaysByDate)
if (!data) {
return alertError(labels.errors.noData)
}
} catch (err) {
console.error(err)
return alertError(labels.errors.couldNotConvert)
}
try {
const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}`
await RNFS.writeFile(path, data)
await Share.open({
title: labels.title,
url: `file://${path}`,
subject: labels.subject,
type: 'text/csv',
showAppsToView: true,
failOnCancel: false,
})
} catch (err) {
console.error(err)
return alertError(labels.errors.problemSharing)
}
}
+3 -4
View File
@@ -15,13 +15,12 @@ export const SYMPTOMS = [
export const CHART_COLUMN_WIDTH = 32
export const CHART_COLUMN_MIDDLE = CHART_COLUMN_WIDTH / 2
export const CHART_DOT_RADIUS_SYMPTOM = scale(6)
export const CHART_DOT_RADIUS_TEMPERATURE = scale(4)
export const CHART_DOT_RADIUS = scale(6)
export const CHART_GRID_LINE_HORIZONTAL_WIDTH =
PixelRatio.roundToNearestPixel(0.3)
export const CHART_ICON_SIZE = scale(20)
export const CHART_STROKE_WIDTH = scale(1.5)
export const CHART_SYMPTOM_HEIGHT_RATIO = scale(0.06)
export const CHART_STROKE_WIDTH = scale(3)
export const CHART_SYMPTOM_HEIGHT_RATIO = scale(0.08)
export const CHART_XAXIS_HEIGHT_RATIO = scale(0.1)
export const CHART_YAXIS_WIDTH = scale(32)
export const CHART_TICK_WIDTH = scale(44)
+37 -19
View File
@@ -1,23 +1,15 @@
{
"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"
"bleeding": "Bleeding",
"temperature": "Temperature",
"mucus": "Cervical Mucus",
"cervix": "Cervix",
"note": "Note",
"desire": "Desire",
"sex": "Sex",
"pain": "Pain",
"mood": "Mood"
}
},
"labels": {
@@ -89,6 +81,33 @@
},
"settings": {
"data": {
"delete": {
"dialog": {
"cancel": "Cancel",
"delete": "Delete app data permanently",
"message": "Please note that deletion of the app data is permanent and irreversible. We recommend exporting existing data before deletion.",
"title": "Do you want to delete app data from this phone?"
},
"error": {
"delete": "Could not delete data",
"noData": "There is no data to delete"
},
"subTitle": "Delete app data from this phone",
"success": {
"message": "App data successfully deleted"
},
"title": "Delete app data"
},
"export": {
"button": "Export data",
"error": {
"convert": "Could not convert data to CSV",
"data": "There is no data to export",
"share": "There was a problem sharing the data export file"
},
"text": "Export data in CSV format for backup or so you can use it elsewhere",
"title": "My drip. data export"
},
"import": {
"button": "Import data",
"dialog": {
@@ -98,10 +117,9 @@
"replace": "Import and replace",
"title": "Keep existing data?"
},
"error": {
"errors": {
"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",
+4
View File
@@ -3,6 +3,10 @@ export const home = {
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 = {
cancel: 'Cancel',
save: 'Save',
+14 -24
View File
@@ -1,32 +1,22 @@
import links from './links'
export default {
export: {
errors: {
noData: 'There is no data to export',
couldNotConvert: 'Could not convert data to CSV',
problemSharing: 'There was a problem sharing the data export file',
menuItems: {
reminders: {
name: 'Reminders',
text: 'turn on/off reminders',
},
title: 'My drip. data export',
subject: 'My drip. data export',
button: 'Export data',
segmentExplainer:
'Export data in CSV format for backup or so you can use it elsewhere',
},
deleteSegment: {
title: 'Delete app data',
explainer: 'Delete app data from this phone',
question: 'Do you want to delete app data from this phone?',
message:
'Please note that deletion of the app data is permanent and irreversible. We recommend exporting existing data before deletion.',
confirmation: 'Delete app data permanently',
errors: {
couldNotDeleteFile: 'Could not delete data',
postFix: 'No data was deleted or changed',
noData: 'There is no data to delete',
nfpSettings: {
name: 'NFP settings',
text: 'define how you want to use NFP',
},
success: {
message: 'App data successfully deleted',
dataManagement: {
name: 'Data',
text: 'import, export or delete your data',
},
password: {
name: 'Password',
text: '',
},
},
tempScale: {
+1
View File
@@ -17,6 +17,7 @@ i18n
compatibilityJSON: 'v3', // TODO: migrate json to v4 and afterwards remove it
resources,
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
-4
View File
@@ -5,8 +5,4 @@ module.exports = {
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?)/)',
],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
}
+9 -4
View File
@@ -3,6 +3,8 @@ import { getCycleLengthStats } from './cycle-length'
const LocalDate = joda.LocalDate
const DAYS = joda.ChronoUnit.DAYS
const toJSON = (realmObj) => JSON.parse(JSON.stringify(realmObj))
export default function config(opts) {
let bleedingDaysSortedByDate
let cycleStartsSortedByDate
@@ -14,10 +16,13 @@ export default function config(opts) {
if (!opts) {
// we only want to require (and run) the db module
// when not running the tests
bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
cycleStartsSortedByDate = require('../db').getCycleStartsSortedByDate()
cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
bleedingDaysSortedByDate = toJSON(
require('../db').getBleedingDaysSortedByDate()
)
cycleStartsSortedByDate = toJSON(
require('../db').getCycleStartsSortedByDate()
)
cycleDaysSortedByDate = toJSON(require('../db').getCycleDaysSortedByDate())
maxBreakInBleeding = 1
maxCycleLength = 99
minCyclesForPrediction = 3
+5 -13
View File
@@ -8,7 +8,7 @@ import {
import getColumnNamesForCsv from './get-csv-column-names'
import replaceWithNullIfAllPropertiesAreNull from './replace-with-null'
import { LocalDate } from '@js-joda/core'
import i18next from 'i18next'
import labels from '../../i18n/en/settings'
export default async function importCsv(csv, deleteFirst) {
const parseFuncs = {
@@ -46,10 +46,7 @@ export default async function importCsv(csv, deleteFirst) {
const cycleDays = await csvParser(config)
.fromString(csv)
.on('header', (headers) => validateHeaders(headers))
.on('error', (error) => {
throw error
})
.on('header', validateHeaders)
//remove symptoms where all fields are null
putNullForEmptySymptoms(cycleDays)
@@ -70,11 +67,8 @@ function validateHeaders(headers) {
return expectedHeaders.indexOf(header) > -1
})
) {
throw new Error(
i18next.t('hamburgerMenu.settings.data.import.error.incorrectColumns', {
incorrectColumns: expectedHeaders.join(),
})
)
const msg = `Expected CSV column titles to be ${expectedHeaders.join()}`
throw new Error(msg)
}
}
@@ -98,9 +92,7 @@ function throwIfFutureData(cycleDays) {
day.date > today &&
Object.keys(day).some((symptom) => symptom != 'date' && symptom != 'note')
) {
throw new Error(
i18next.t('hamburgerMenu.settings.data.import.error.futureEdit')
)
throw new Error(labels.import.errors.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
const feelingMapping = {
0: 0,
1: 1,
2: 2,
3: 4,
3: 4
}
const textureMapping = {
0: 0,
1: 3,
2: 4,
2: 4
}
const nfpFeelingValue = feelingMapping[feeling]
const nfpTextureValue = textureMapping[texture]
+7 -21
View File
@@ -1,10 +1,8 @@
import { Platform } from 'react-native'
import {
tempReminderObservable,
periodReminderObservable,
} from '../local-storage'
import * as PN from 'react-native-push-notification'
import { requestNotifications } from 'react-native-permissions'
import Notification from 'react-native-push-notification'
import Moment from 'moment'
import { LocalDate } from '@js-joda/core'
@@ -14,16 +12,7 @@ import cycleModule from './cycle'
import nothingChanged from '../db/db-unchanged'
export default function setupNotifications(navigate, setDate) {
requestNotifications()
const PushNotification = Platform.OS === 'ios' ? PN : PN.default
PushNotification.createChannel({
channelId: 'drip-channel-id', // (required)
channelName: 'drip reminder', // (required)
playSound: false, // (optional) default: true
})
PushNotification.configure({
Notification.configure({
onNotification: (notification) => {
// https://github.com/zo0r/react-native-push-notification/issues/966#issuecomment-479069106
if (notification.data?.id === '1' || notification.id === '1') {
@@ -37,7 +26,7 @@ export default function setupNotifications(navigate, setDate) {
})
tempReminderObservable((reminder) => {
PushNotification.cancelLocalNotification({ id: '1' })
Notification.cancelLocalNotifications({ id: '1' })
if (reminder.enabled) {
const [hours, minutes] = reminder.time.split(':')
let target = new Moment()
@@ -49,33 +38,31 @@ export default function setupNotifications(navigate, setDate) {
target = target.add(1, 'd')
}
PushNotification.localNotificationSchedule({
Notification.localNotificationSchedule({
id: '1',
userInfo: { id: '1' },
message: labels.tempReminder.notification,
date: target.toDate(),
vibrate: false,
repeatType: 'day',
channelId: 'drip-channel-id',
})
}
}, false)
periodReminderObservable((reminder) => {
PushNotification.cancelLocalNotification({ id: '2' })
Notification.cancelLocalNotifications({ id: '2' })
if (reminder.enabled) setupPeriodReminder()
}, false)
getBleedingDaysSortedByDate().addListener((_, changes) => {
// the listener fires on setup, so we check if there were actually any changes
if (nothingChanged(changes)) return
PushNotification.cancelLocalNotification({ id: '2' })
Notification.cancelLocalNotifications({ id: '2' })
if (periodReminderObservable.value.enabled) setupPeriodReminder()
})
}
function setupPeriodReminder() {
const PushNotification = Platform.OS === 'ios' ? PN : PN.default
const bleedingPrediction = cycleModule().getPredictedMenses()
if (bleedingPrediction.length > 0) {
const predictedBleedingStart = Moment(
@@ -93,13 +80,12 @@ function setupPeriodReminder() {
// period is likely to start in 3 to 3 + (length of prediction - 1) days
const daysToEndOfPrediction = bleedingPrediction[0].length + 2
PushNotification.localNotificationSchedule({
Notification.localNotificationSchedule({
id: '2',
userInfo: { id: '2' },
message: labels.periodReminder.notification(daysToEndOfPrediction),
date: reminderDate.toDate(),
vibrate: false,
channelId: 'drip-channel-id',
})
}
}
+12 -13
View File
@@ -1,6 +1,6 @@
{
"name": "drip.",
"version": "1.2311.14",
"version": "1.2208.11",
"contributors": [
"Julia Friesel <julia.friesel@gmail.com>",
"Marie Kochsiek",
@@ -34,23 +34,22 @@
"@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-community/art": "^1.2.0",
"@react-native-community/datetimepicker": "^6.3.1",
"@react-native-community/push-notification-ios": "^1.11.0",
"@react-native-community/push-notification-ios": "^1.8.0",
"csvtojson": "^2.0.8",
"i18next": "^22.0.2",
"i18next": "^21.9.0",
"jshashes": "^1.0.8",
"moment": "^2.29.4",
"object-path": "^0.11.4",
"obv": "0.0.1",
"prop-types": "^15.8.1",
"react": "17.0.2",
"react-i18next": "^12.0.0",
"react-i18next": "^11.18.3",
"react-native": "0.67.4",
"react-native-calendars": "^1.1287.0",
"react-native-document-picker": "^8.1.1",
"react-native-fs": "^2.20.0",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-permissions": "^3.10.0",
"react-native-push-notification": "github:bl00dymarie/react-native-push-notification",
"react-native-push-notification": "3.2.1",
"react-native-share": "^7.9.0",
"react-native-simple-toast": "^1.1.3",
"react-native-size-matters": "^0.4.0",
@@ -59,18 +58,18 @@
"sympto": "3.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-react": "^7.18.6",
"@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.12.5",
"@testing-library/jest-native": "^4.0.12",
"@testing-library/react-native": "^11.1.0",
"eslint": "^7.32.0",
"eslint-plugin-react": "^7.31.10",
"basic-changelog": "gitlab:bloodyhealth/basic-changelog",
"eslint": "7.14.0",
"eslint-plugin-react": "^7.8.2",
"husky": "^8.0.0",
"jest": "^29.1.2",
"jest-watch-typeahead": "^2.2.0",
"jetifier": "^2.0.0",
"jest": "^28.1.3",
"jetifier": "^1.6.6",
"metro-react-native-babel-preset": "^0.66.2",
"prettier": "2.4.0",
"pretty-quick": "^3.1.1",
+10 -18
View File
@@ -1,14 +1,7 @@
const redColor = '#c3000d'
export const shadesOfRed = ['#e7999e', '#db666d', '#cf323d', '#c3000d'] // light to dark
const violetColor = '#6a7b98'
const shadesOfViolet = [
'#e3e7ed',
'#c8cfdc',
'#acb8cb',
'#91a0ba',
'#7689a9',
violetColor,
] // light to dark
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', violetColor] // light to dark
const yellowColor = '#dbb40c'
const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
const magentaColor = '#6f2565'
@@ -23,7 +16,6 @@ export default {
greyDark: '#555',
grey: '#888',
greyLight: '#CCC',
greyVeryLight: '#F4F4F4',
orange: '#F38337',
purple: '#3A2671',
purpleLight: '#938EB2',
@@ -31,37 +23,37 @@ export default {
turquoise: '#CFECEA',
turquoiseLight: '#E9F2ED',
iconColors: {
bleeding: {
'bleeding': {
color: redColor,
shades: shadesOfRed,
},
mucus: {
'mucus': {
color: violetColor,
shades: shadesOfViolet,
},
cervix: {
'cervix': {
color: yellowColor,
shades: shadesOfYellow,
},
sex: {
'sex': {
color: magentaColor,
shades: shadesOfMagenta,
},
desire: {
'desire': {
color: pinkColor,
shades: shadesOfPink,
},
pain: {
'pain': {
color: lightGreenColor,
shades: [lightGreenColor],
},
mood: {
'mood': {
color: orangeColor,
shades: [orangeColor],
},
note: {
'note': {
color: mintColor,
shades: [mintColor],
},
},
}
}
+13 -5
View File
@@ -1,19 +1,27 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react-native'
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()),
}))
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (str, options) => {
return str + (options ? JSON.stringify(options) : '')
},
}),
}))
describe('AcceptLicense', () => {
test('should accept license when clicking ok button', async () => {
test('On clicking OK button, the license is accepted', async () => {
const mockedSetLicense = jest.fn()
render(<AcceptLicense setLicense={mockedSetLicense} />)
const okButton = screen.getByText('OK')
const okButton = screen.getByText('ok', { exact: false })
fireEvent(okButton, 'click')
@@ -21,9 +29,9 @@ describe('AcceptLicense', () => {
expect(mockedSetLicense).toHaveBeenCalled()
})
test('should render cancel button', async () => {
test('There is a Cancel button', async () => {
render(<AcceptLicense setLicense={jest.fn()} />)
screen.getByText('Cancel')
screen.getByText('cancel', { exact: false })
})
})
+11 -3
View File
@@ -1,16 +1,24 @@
import React from 'react'
import { render, screen } from '@testing-library/react-native'
import License from '../components/settings/License'
import { render, screen } from './test-utils'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (str, options) => {
return str + (options ? JSON.stringify(options) : '')
},
}),
}))
describe('License screen', () => {
test('should display license text with correct year', async () => {
test('It should have a correct year', async () => {
render(<License />)
const year = new Date().getFullYear().toString()
screen.getByText(year, { exact: false })
})
test('should match the snapshot', async () => {
test('It should match the snapshot', async () => {
const licenseScreen = render(<License />)
expect(licenseScreen).toMatchSnapshot()
+13 -15
View File
@@ -1,9 +1,9 @@
// 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
style={
{
Object {
"backgroundColor": "#E9F2ED",
"flex": 1,
}
@@ -11,8 +11,8 @@ exports[`License screen should match the snapshot 1`] = `
>
<RCTScrollView
contentContainerStyle={
[
{
Array [
Object {
"backgroundColor": "#E9F2ED",
"flexGrow": 1,
},
@@ -23,13 +23,13 @@ exports[`License screen should match the snapshot 1`] = `
<View>
<Text
style={
[
{
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
{
Object {
"alignSelf": "center",
"color": "#3A2671",
"fontFamily": "Jost-Bold",
@@ -41,11 +41,11 @@ exports[`License screen should match the snapshot 1`] = `
]
}
>
drip. an open-source cycle tracking app
title
</Text>
<View
style={
{
Object {
"marginBottom": 34.285714285714285,
"marginHorizontal": 34.285714285714285,
}
@@ -53,8 +53,8 @@ exports[`License screen should match the snapshot 1`] = `
>
<Text
style={
[
{
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
@@ -63,14 +63,12 @@ exports[`License screen should match the snapshot 1`] = `
]
}
>
Copyright (C) 2023 Heart of Code e.V.
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{"currentYear":2022}
</Text>
<Text
onPress={[Function]}
style={
{
Object {
"color": "#3A2671",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
@@ -3,7 +3,7 @@
exports[`Footnote component when children are present, renders them 1`] = `
<View
style={
{
Object {
"alignContent": "flex-start",
"flexDirection": "row",
"marginBottom": 8.571428571428571,
@@ -13,13 +13,13 @@ exports[`Footnote component when children are present, renders them 1`] = `
>
<Text
style={
[
{
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
{
Object {
"color": "#F38337",
},
]
@@ -29,18 +29,18 @@ exports[`Footnote component when children are present, renders them 1`] = `
</Text>
<Text
linkStyle={
{
Object {
"color": "white",
}
}
style={
[
{
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
{
Object {
"color": "#555",
"paddingLeft": 21.428571428571427,
},
-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 }
+6
View File
@@ -50,8 +50,14 @@ module.exports = () => {
}
const pkgJSON = JSON.parse(fs.readFileSync('./package.json'))
const pkgLockJSON = JSON.parse(fs.readFileSync('./package-lock.json'))
pkgJSON.version = nextVersion
pkgLockJSON.version = nextVersion
fs.writeFileSync('./package.json', JSON.stringify(pkgJSON, null, 2))
fs.writeFileSync(
'./package-lock.json',
JSON.stringify(pkgLockJSON, null, 2)
)
await ReactNativeVersion.version(
{
+2990 -2344
View File
File diff suppressed because it is too large Load Diff