Compare commits

..

3 Commits

Author SHA1 Message Date
MariaZ 9b9db80949 Center temp label in yAxis 2022-09-30 21:53:53 +02:00
MariaZ 6cfbcd0408 Show label if height allows it, decrease # of ticks based on temp range 2022-09-30 21:51:27 +02:00
MariaZ 93608ca8ef Set Tick text minHeight 2022-09-30 21:20:01 +02:00
47 changed files with 3391 additions and 2927 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
@@ -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>
+13 -8
View File
@@ -3,6 +3,7 @@ import { LocalDate } from '@js-joda/core'
import { scaleObservable, unitObservable } from '../../local-storage'
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
import { getCycleDay, getAmountOfCycleDays } from '../../db'
import { Sizes } from '../../styles'
//YAxis helpers
@@ -50,15 +51,19 @@ export function getTickList(columnHeight) {
const label = tick.toFixed(1)
let shouldShowLabel
// when temp range <= 2, units === 0.1 we show temp values with step 0.2
// when temp range > 2, units === 0.5 we show temp values with step 0.5
// when units === 0.1 and tick height is big enough, we show temp values with step 0.2
// when units === 0.5 and tick height is not big enough, we show temp values with step 1
// otherwise we show temp values with step 0.5
if (unit === 0.1) {
// show label with step 0.2
shouldShowLabel = !((label * 10) % 2)
} else {
// show label with step 0.5
shouldShowLabel = !((label * 10) % 5)
switch (true) {
case unit === 0.1 && tickHeight > Sizes.base + 2:
shouldShowLabel = !((label * 10) % 2)
break
case unit === 0.5 && tickHeight <= Sizes.base + 2:
shouldShowLabel = !((label * 10) % 10)
break
default:
shouldShowLabel = !((label * 10) % 5)
}
// don't show label, if first or last tick
+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',
},
{
@@ -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.delete'),
onPress: () => startImport(true),
},
{
text: t('dialog.replace'),
onPress: () => startImport(false),
},
])
}
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 Segment from '../../common/segment'
import { openImportDialog, getFileContent, importData } from './import-dialog'
import openShareDialogAndExport from './export-dialog'
import DeleteData from './delete-data'
import labels from '../../../i18n/en/settings'
import ImportData from './ImportData'
import { ACTION_DELETE, ACTION_EXPORT, ACTION_IMPORT } from '../../../config'
const DataManagement = () => {
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 = () => {
setIsDeletingData(false)
setCurrentAction(ACTION_EXPORT)
openShareDialogAndExport()
}
const startImport = () => {
setCurrentAction(ACTION_IMPORT)
openImportDialog(startImportFlow)
}
if (isLoading) return <AppLoadingView />
const isDeletingData = currentAction === ACTION_DELETE
return (
<AppPage>
<Segment title={labels.export.button}>
@@ -31,15 +48,17 @@ const DataManagement = () => {
{labels.export.button}
</Button>
</Segment>
<ImportData
resetIsDeletingData={() => setIsDeletingData(false)}
setIsLoading={setIsLoading}
/>
<Segment title={labels.import.button}>
<AppText>{labels.import.segmentExplainer}</AppText>
<Button isCTA onPress={startImport}>
{labels.import.button}
</Button>
</Segment>
<Segment title={labels.deleteSegment.title} last>
<AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData
isDeletingData={isDeletingData}
onStartDeletion={() => setIsDeletingData(true)}
onStartDeletion={() => setCurrentAction(ACTION_DELETE)}
/>
</Segment>
</AppPage>
+1 -1
View File
@@ -1,6 +1,6 @@
import Reminders from './reminders/reminders'
import NfpSettings from './nfp-settings'
import DataManagement from './data-management/DataManagement'
import DataManagement from './data-management'
import Password from './password'
import About from './About'
import License from './License'
+8 -5
View File
@@ -1,6 +1,10 @@
import { PixelRatio, StatusBar } from 'react-native'
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 = [
'bleeding',
'temperature',
@@ -15,13 +19,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,7 +40,7 @@ export const HIT_SLOP = {
top: verticalScale(20),
bottom: verticalScale(20),
left: scale(20),
right: scale(20),
right: scale(20)
}
export const STATUSBAR_HEIGHT = StatusBar.currentHeight
+9 -40
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": {
@@ -88,29 +80,6 @@
}
},
"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",
+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',
+36
View File
@@ -1,6 +1,24 @@
import links from './links'
export default {
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: '',
},
},
export: {
errors: {
noData: 'There is no data to export',
@@ -13,6 +31,24 @@ export default {
segmentExplainer:
'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: {
title: 'Delete app data',
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
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',
})
}
}
+10 -4
View File
@@ -12,10 +12,16 @@ export const unitObservable = Observable()
unitObservable.set(TEMP_SCALE_UNITS)
scaleObservable((scale) => {
const scaleRange = scale.max - scale.min
if (scaleRange <= 1.5) {
unitObservable.set(0.1)
} else {
unitObservable.set(0.5)
switch (true) {
case scaleRange <= 1:
unitObservable.set(0.1)
break
case scaleRange > 1 && scaleRange <= 2:
unitObservable.set(0.2)
break
default:
unitObservable.set(0.5)
}
})
+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