Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efd8ed0e08 | |||
| 35224ba722 | |||
| 2ca033d6d1 | |||
| 09edaec721 | |||
| 3cedcf601e | |||
| e4d97b362f | |||
| 7bbb6eaeab | |||
| 09cb31035c | |||
| 536017914c | |||
| b2457f4751 | |||
| fd10a78a40 | |||
| d8cb679d77 | |||
| 839acfa22d | |||
| a0482291cf | |||
| 37f718d1b0 | |||
| 9b0abd5367 | |||
| c8521cf5ed | |||
| f7c6f4bfd0 | |||
| 15a0b3d270 | |||
| 4e37f1b7de | |||
| 56e90b69e6 | |||
| b481bd8352 | |||
| e33c13e5e0 | |||
| 693c766da8 | |||
| fc4bc625ce | |||
| 0e7d84874c | |||
| a93a338e6e | |||
| 003f825ef4 | |||
| fc520c4a20 | |||
| eaf01e98d5 | |||
| f5894c028e | |||
| a29c0e2eec | |||
| 18466ebcee | |||
| b65b5f3561 | |||
| 3e8f15e04e | |||
| b17c86ffd6 | |||
| 24df5cea31 | |||
| 610383a103 | |||
| ad47b4bee0 | |||
| ffe8fab822 | |||
| 661abc8aee | |||
| 46a02560e8 | |||
| 9ec52b78cf | |||
| 8a65c081a8 | |||
| 72823ef95c | |||
| 27776f93cc | |||
| ef4095d61c | |||
| 4fc11d2f7e | |||
| 37152b3fec | |||
| 405e27bdbe | |||
| 1c73509473 | |||
| 5855ea0a34 | |||
| 27bb25e6da | |||
| 8150b791ed | |||
| 3c0ea9b208 | |||
| d0d691c6af | |||
| 29c3b9b108 | |||
| c037c630d3 | |||
| 31888c3331 | |||
| faad51f4f1 | |||
| e395730d98 | |||
| 092b557396 | |||
| 4ebec41a0a | |||
| e69ca93382 | |||
| aa440742ca | |||
| b9ec4c7108 | |||
| d66e5f36e5 | |||
| 69c546f684 | |||
| dc304afe75 | |||
| 63bc8a1daf | |||
| 7fa130f9e1 | |||
| 5f55500831 | |||
| d23b5fe420 | |||
| 311cee0e17 | |||
| 76137fc000 | |||
| 09614319e3 | |||
| ebbfd8aba5 | |||
| 1662bb29f0 | |||
| a59dfa8336 | |||
| d7f1eb81fb | |||
| 82803bbb83 | |||
| 8a8b3217fa | |||
| e11acbab78 |
@@ -2,6 +2,47 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## v1.2403.19 Android & iOS
|
||||
|
||||
### Changes
|
||||
|
||||
- Disables temperature slider if temperature tracking off
|
||||
- Disables secondary symptom if fertility and or cervix/cervical mucus are off
|
||||
- Disables temperature reminder if temperature tracking off
|
||||
- Disabled period reminder if period predictions off
|
||||
- Return from sympto adapter if fertility off
|
||||
- Restructure settings menu
|
||||
- Unify wording to "sympto-thermal method"
|
||||
- Format decimal to x.0 instead of x.00 used for standard deviation and average cycle in stats
|
||||
- Use SelectTabGroup for secondary symptom customization
|
||||
|
||||
- Android changes after updating React Native to 0.68.3
|
||||
- Update Android Gradle plugin from 7.0.3 to 7.0.4
|
||||
- Update NDK to "24.0.8215888" only for M1 users which added support for aarch64
|
||||
- Update metadata phone screenshots for Fdroid store listing
|
||||
- Updated dependencies:
|
||||
- @react-native-community/datetimepicker from 6.3.1 to 7.2.0
|
||||
- @react-native-async-storage/async-storage from ^1.17.9 to ^1.18.2
|
||||
- metro-react-native-babel-preset from ^0.66.2 to ^0.67.0
|
||||
|
||||
### Adds
|
||||
|
||||
- Customization settings can turn on & off:
|
||||
|
||||
- Tracking categories
|
||||
- Period predictions
|
||||
- Fertility phases calculation
|
||||
|
||||
- Home displays text elements depending on customization settings
|
||||
- Chart displays tracking category elements depending on customization settings
|
||||
- CycleDay displays tracking category elements and exclude switch depending on customization settings
|
||||
- Reminder can be disabled depending on customization settings
|
||||
- Adds disabled and more styling to AppSwitch
|
||||
- Adds TrackingCategorySwitch
|
||||
- Adds disabled, more styling and alert to SelectTabGroup
|
||||
- Adds more marginTop to License page
|
||||
- Adds info text to Password menu item in Settings
|
||||
|
||||
## v1.2401.17 iOS
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -33,9 +33,10 @@ We are an open source project and we highly appreciate contributions. At the sam
|
||||
- 🔮 open source
|
||||
- 🩸 feminist and gender inclusive
|
||||
- 🔒 secure: data entered stays with that person/on their device
|
||||
- 🔬 science based: we implemented the symptothermal method
|
||||
- 🔬 science-based: we implemented the sympto-thermal method
|
||||
|
||||
This means that we will never implement anything that contradicts these core values. Some examples: We will never build a cloud integration, we will never make an ovulation prediction.
|
||||
|
||||
- If you would like to make a sustainable contribution to the project, we would be happy to join the game.
|
||||
|
||||
### Reporting Bugs or Making Suggestions
|
||||
@@ -48,7 +49,6 @@ If you found a bug or have suggestions, please :one: first review the [list of e
|
||||
- If you want to open a merge request, yeah :tada: exciting! We are using a template for merge requests to make sure we explain what we have done and why.
|
||||
- Keep in mind that people who will review your merge request are more motivated to do so when the merge request is well explained and ideally not too big.
|
||||
|
||||
|
||||
### Thank you
|
||||
|
||||

|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
# How to release a new app version for Android
|
||||
# How to release
|
||||
|
||||
_Note: You need the release-key for Android to bundle a signed release that can be uploaded and published via the Google Play Store. A similar process for Apple requires a certificate to upload and publish the app to the App Store. More documentation on 'How to release a new app version for iOS' coming soon._
|
||||
drip is developed in React Native for iOS and Android and is released on 4 different platforms:
|
||||
|
||||
# Table of Contents
|
||||
1. [Google Play Store](https://play.google.com/store/apps/details?id=com.drip)
|
||||
2. [Apple App Store](https://apps.apple.com/us/app/drip/id1584564949)
|
||||
3. [F-Droid](https://f-droid.org/packages/com.drip/)
|
||||
4. [drip Website](https://dripapp.org)
|
||||
|
||||
1. [version updating](#Version-updating)
|
||||
2. [android building](#Building-in-Android)
|
||||
- [APK](#APK)
|
||||
- [AAB](#AAB)
|
||||
3. [release sharing](#Share-the-release)
|
||||
In an ideal world the app version is the same across platforms. In reality this has never been the case.
|
||||
|
||||
Releasing a new version is very exciting and brings happy changes like fixing a bug, improving a feature, updating dependencies or adding a new functionality to the app. It is more than just pressing the button "publish new version".
|
||||
|
||||
_Note_: You need the release-key for Android to bundle a signed release that can be uploaded and published via the Google Play Store. A similar process for Apple requires a certificate to upload and publish the app to the App Store.
|
||||
|
||||
### Release steps
|
||||
|
||||
1. [Version updating](#version-updating)
|
||||
2. [Android builds](#android-builds)
|
||||
3. [iOS builds](#ios-builds)
|
||||
4. [User testing](#user-testing)
|
||||
5. [Changelog](#changelog)
|
||||
6. [Release notes](#release-notes)
|
||||
7. [Release tag](#release-tag)
|
||||
8. [Phone screenshots](#phone-screenshots)
|
||||
9. [Publishing](#publishing)
|
||||
10. [Communication](#communication)
|
||||
11. [Self care](#self-care)
|
||||
|
||||
## Version updating
|
||||
|
||||
@@ -25,7 +42,16 @@ yarn release
|
||||
|
||||
The versionName and versionCode [are defined here](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/android/app/build.gradle#L137-138) and in [package.json](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/package.json#L3).
|
||||
|
||||
## Building in Android
|
||||
**Note for iOS**
|
||||
|
||||
Update the version number for iOS in `ios/drip/Info.plist` under:
|
||||
|
||||
```
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2403.19</string>
|
||||
```
|
||||
|
||||
### Android builds
|
||||
|
||||
APK versus AAB
|
||||
|
||||
@@ -33,7 +59,7 @@ APK versus AAB
|
||||
|
||||
(https://developer.android.com/build/building-cmdline)
|
||||
|
||||
### APK
|
||||
#### APK
|
||||
|
||||
To build a release apk file, run the following command:
|
||||
|
||||
@@ -55,7 +81,7 @@ _which is a shortcut for:_ `zipalign -v -p 4 ./android/app/build/outputs/apk/rel
|
||||
|
||||
It adds a file name `app-release_signed.apk` in the same folder in `./android/app/build/outputs/apk/release/`
|
||||
|
||||
### AAB
|
||||
#### AAB
|
||||
|
||||
To build a release aab file, run:
|
||||
|
||||
@@ -75,29 +101,74 @@ yarn sign-android-aab-release
|
||||
|
||||
_which is a shortcut for:_ `jarsigner -keystore ./android/app/drip-release-key.keystore ./android/app/build/outputs/bundle/release/app-release.aab drip-release-key`
|
||||
|
||||
## Share the release
|
||||
### iOS builds
|
||||
|
||||
### Gitlab repository
|
||||
To build an .ipa archive file for an upload to the AppStore you need to go to xCode and select Build -> "Any iOS Device" and under "Product" -> "Archive".
|
||||
|
||||
Once the archiving process has completed you can chose to do the following:
|
||||
|
||||
"Distribute the app"
|
||||
|
||||
- TestFlight & App Store for when you want to upload it for external testing and/or production release
|
||||
- TestFlight Internal Only for when you want to upload it for internal testing
|
||||
|
||||
## User testing
|
||||
|
||||
To enable external testing you need to remember that Google Play and Apple App Store might take up to 1 day for their review process. "External testing" for iOS allows testing drip on Testflight anonymously via a public link. "Open testing" for Android allows testing drip on Google Play as beta tester below the normal production listing.
|
||||
|
||||
For a quick and easy way to share an apk to testers who are willing to sideload drip onto their Android phones, do this: Upload a signed apk to the Gitlab repository of the drip website under `/release` https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/tree/main/release and maybe adapt the name of the apk with a more specific name than "app-release.apk". Now you can simply share a direct link to download your newly bundled apk, e.g. [a download link for v1.2311.14](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/blob/main/release/v1.2311.14.apk).
|
||||
|
||||
## Changelog
|
||||
|
||||
The [changelog](https://gitlab.com/bloodyhealth/drip/-/blob/main/CHANGELOG.md) should reflect the technical / code changes between a previous and the new version. Please update the changelog file with any relevant additions, fixes and changes in the following format:
|
||||
|
||||
**v1.yymm.d**
|
||||
|
||||
**Changes**
|
||||
|
||||
Changing the color of funky button
|
||||
Updating a library from 1.2.3 to 2.3.4
|
||||
|
||||
**Adds**
|
||||
|
||||
New feature for calendar
|
||||
|
||||
**Fixed**
|
||||
|
||||
Small bug in chart
|
||||
|
||||
## Release notes
|
||||
|
||||
These notes are for the users and curious ones who may want to start using drip. They should be based on the changelog but written in a friendly and easy to understand way. The focus is on the user perspective and the impact of the changes for the user. Behind the scenes and in depth code changes are less relevant.
|
||||
|
||||
Google Play limits these notes to 500 characters, whereas Apple's App Store limits these notes to 4.000 characters. In Fdroid there are no release notes.
|
||||
|
||||
## Release tag
|
||||
|
||||
[Tags](https://gitlab.com/bloodyhealth/drip/-/tags) can mark a specific point in the coding/commmit history and helps us identify the version status of a released app. They are named "iOS-v1.2401.17" or "Release-v1.yymm.d".
|
||||
|
||||
## Phone screenshots
|
||||
|
||||
If there are visual changes in the app you may want to update the screenshots for the Google Play Store listing, which allows up to 8 and for Apple's App Store, which allows up to 10 screenshots. Keep in mind that both Google Play and Apple have specific resolution requirements. You'll find Google's in Grow -> Store presence -> Main Store Listing -> Phone screenshots and Apple's on the main App Store Connect site. Here is a link for [Apple's screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications).
|
||||
|
||||
Please also update [phone screenshots for the website](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/tree/main/assets) and set links on [/index](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/blob/f3da9776b1943ffa32458e74ef86eeca98c1891c/index.html#L47) and [/media](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/blob/c7f999bb7ad736345321537cbffa3f4c24eeee6d/media.html#L33) that can then also be attached to a social media post.
|
||||
|
||||
## Publishing
|
||||
|
||||
### Google Play Console
|
||||
|
||||
Upload a signed aab to the [Google Play Console for developers](https://play.google.com/console/) and add it to the "App bundle explorer". This requires a higher versionCode and a different version name compared to previously uploaded aab or apk files.
|
||||
You can decide if you want the new app version to get released for testing (internal, closed or open) or for production. Keep in mind that any track other than "internal testing" triggers an external review by Google and might take a few hours.
|
||||
|
||||
#### Phone screenshots
|
||||
### Apple App Store Connect
|
||||
|
||||
If there are visual changes in the app you may want to update the screenshots for the Google Play Store listing. Keep in mind that Google Play has specific resolution requirements. You'll find them in Grow -> Store presence -> Main Store Listing -> Phone screenshots.
|
||||
Upload a new version and submit it for review, before it can be published.
|
||||
|
||||
### drip website
|
||||
|
||||
After a new version has been published on Google Play (or F-Droid) the apk version that is downloadable directly from the [drip website](https://dripapp.org) needs to get updated as well. Therefore you upload a signed apk to the [repository](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/) and adapt the name and link on /index.html.
|
||||
Last time I checked it was [here](f3da9776b1943ffa32458e74ef86eeca98c1891c/index.html#L114).
|
||||
After a new version has been published on Google Play (or F-Droid) the apk version that is downloadable directly from the [drip website](https://dripapp.org) needs to get updated as well. Therefore you upload a signed apk to the [repository](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/) as [we did in this commit](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/commit/f8c0f90c1ae9f23bf8e1bc311790b85443149a4d), and adapt the name and link on /index.html [as we did in this commit](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/commit2f8850ff5fa78615a4f335b625ea4a67d4acf03a) and [this commit](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/commit/f3da9776b1943ffa32458e74ef86eeca98c1891c). Last time I checked it was [here](f3da9776b1943ffa32458e74ef86eeca98c1891c/index.html#L114).
|
||||
|
||||
#### Phone screenshots
|
||||
|
||||
Please also update [phone screenshots here](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/tree/main/assets) and set links on [/index](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/blob/f3da9776b1943ffa32458e74ef86eeca98c1891c/index.html#L47) and [/media](https://gitlab.com/bloodyhealth/bloodyhealth.gitlab.io/-/blob/c7f999bb7ad736345321537cbffa3f4c24eeee6d/media.html#L33) that can then also be attached to a social media post.
|
||||
## Communication
|
||||
|
||||
You probably want to share the app update by posting on one or more of these platforms:
|
||||
|
||||
@@ -106,3 +177,7 @@ You probably want to share the app update by posting on one or more of these pla
|
||||
- [Ko-fi](https://ko-fi.com/dripapp)
|
||||
- [Linkedin](https://www.linkedin.com/company/34899684/)
|
||||
- Different tech, privacy, feminist oriented slacks
|
||||
|
||||
## Self care
|
||||
|
||||
Congratulations. Take a break, eat some chocolate, go see a live show of your favorite band, masturbate <3!
|
||||
|
||||
@@ -134,8 +134,8 @@ android {
|
||||
applicationId "com.drip"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 27
|
||||
versionName "1.2402.15"
|
||||
versionCode 33
|
||||
versionName "1.2403.19"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
determinePredictionText,
|
||||
formatWithOrdinalSuffix,
|
||||
} from './helpers/home'
|
||||
import { periodPredictionObservable } from '../local-storage'
|
||||
import {
|
||||
fertilityTrackingObservable,
|
||||
periodPredictionObservable,
|
||||
} from '../local-storage'
|
||||
|
||||
import { Colors, Fonts, Sizes, Spacing } from '../styles'
|
||||
import { LocalDate } from '@js-joda/core'
|
||||
@@ -28,11 +31,12 @@ const Home = ({ navigate, setDate }) => {
|
||||
navigate('CycleDay')
|
||||
}
|
||||
|
||||
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
|
||||
const todayDateString = LocalDate.now().toString()
|
||||
const { getCycleDayNumber, getPredictedMenses } = cycleModule()
|
||||
const cycleDayNumber = getCycleDayNumber(todayDateString)
|
||||
const { status, phase, statusText } =
|
||||
getFertilityStatusForDay(todayDateString)
|
||||
isFertilityTrackingEnabled && getFertilityStatusForDay(todayDateString)
|
||||
const isPeriodPredictionEnabled = periodPredictionObservable.value
|
||||
const prediction = determinePredictionText(getPredictedMenses(), t)
|
||||
|
||||
@@ -47,6 +51,7 @@ const Home = ({ navigate, setDate }) => {
|
||||
>
|
||||
<AppText style={styles.title}>{moment().format('MMM Do YYYY')}</AppText>
|
||||
|
||||
{/* display if at least 1 bleeding day has been entered */}
|
||||
{cycleDayNumber && (
|
||||
<View style={styles.line}>
|
||||
<AppText style={styles.whiteSubtitle}>{cycleDayText}</AppText>
|
||||
@@ -55,7 +60,9 @@ const Home = ({ navigate, setDate }) => {
|
||||
</AppText>
|
||||
</View>
|
||||
)}
|
||||
{phase && (
|
||||
|
||||
{/* display if fertility tracking enabled and if phase 1, 2 or 3 has been identified */}
|
||||
{isFertilityTrackingEnabled && phase && (
|
||||
<View style={styles.line}>
|
||||
<AppText style={styles.whiteSubtitle}>
|
||||
{formatWithOrdinalSuffix(phase)}
|
||||
@@ -67,11 +74,14 @@ const Home = ({ navigate, setDate }) => {
|
||||
<Asterisk />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{isPeriodPredictionEnabled && (
|
||||
<View style={styles.line}>
|
||||
<AppText style={styles.turquoiseText}>{prediction}</AppText>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!isFertilityTrackingEnabled && <View style={styles.largePadding}></View>}
|
||||
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
|
||||
{t('labels.home.addDataForToday')}
|
||||
</Button>
|
||||
@@ -110,6 +120,9 @@ const styles = StyleSheet.create({
|
||||
color: 'white',
|
||||
fontSize: Sizes.subtitle,
|
||||
},
|
||||
largePadding: {
|
||||
padding: Spacing.large,
|
||||
},
|
||||
})
|
||||
|
||||
Home.propTypes = {
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
painTrackingCategoryObservable,
|
||||
sexTrackingCategoryObservable,
|
||||
temperatureTrackingCategoryObservable,
|
||||
mucusTrackingCategoryObservable,
|
||||
cervixTrackingCategoryObservable,
|
||||
} from '../../local-storage'
|
||||
import { makeColumnInfo } from '../helpers/chart'
|
||||
|
||||
@@ -72,6 +74,10 @@ const CycleChart = ({ navigate, setDate }) => {
|
||||
const symptomRowEnabledSymptoms = symptomRowSymptoms.filter((symptom) => {
|
||||
if (symptom === 'sex') {
|
||||
return sexTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'mucus') {
|
||||
return mucusTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'cervix') {
|
||||
return cervixTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'desire') {
|
||||
return desireTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'pain') {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Switch, View } from 'react-native'
|
||||
import { Platform, StyleSheet, Switch, View } from 'react-native'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import AppText from './app-text'
|
||||
|
||||
import { Colors, Containers } from '../../styles'
|
||||
import { Colors, Containers, Spacing } from '../../styles'
|
||||
|
||||
const AppSwitch = ({ onToggle, text, value, disabled }) => {
|
||||
const trackColor = { true: Colors.turquoiseDark }
|
||||
@@ -34,9 +34,14 @@ AppSwitch.propTypes = {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...Containers.rowContainer,
|
||||
marginTop: Spacing.tiny,
|
||||
},
|
||||
switch: {
|
||||
flex: 1,
|
||||
transform:
|
||||
Platform.OS === 'ios'
|
||||
? [{ scaleX: 0.8 }, { scaleY: 0.8 }]
|
||||
: [{ scaleX: 1 }, { scaleY: 1 }],
|
||||
},
|
||||
textContainer: {
|
||||
flex: 4,
|
||||
|
||||
@@ -6,13 +6,14 @@ import AppText from './app-text'
|
||||
|
||||
import { Colors, Containers, Spacing, Typography } from '../../styles'
|
||||
|
||||
const Segment = ({ children, last, title }) => {
|
||||
const Segment = ({ children, last, title, subheader }) => {
|
||||
const containerStyle = last ? styles.containerLast : styles.container
|
||||
const commonStyle = Object.assign({}, containerStyle)
|
||||
|
||||
return (
|
||||
<View style={commonStyle}>
|
||||
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||
{subheader && <AppText style={styles.subheader}>{subheader}</AppText>}
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
@@ -23,6 +24,7 @@ Segment.propTypes = {
|
||||
last: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
title: PropTypes.string,
|
||||
subheader: PropTypes.string,
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -39,6 +41,11 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
...Typography.subtitle,
|
||||
},
|
||||
subheader: {
|
||||
...Typography.subtitle,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: Spacing.zero,
|
||||
},
|
||||
})
|
||||
|
||||
export default Segment
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react'
|
||||
import { Platform, StyleSheet, Switch, View } from 'react-native'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import AppText from './app-text'
|
||||
|
||||
import DripIcon from '../../assets/drip-icons'
|
||||
import { Colors, Containers, Sizes, Spacing } from '../../styles'
|
||||
|
||||
const TrackingCategorySwitch = ({ onToggle, symptom, text, value }) => {
|
||||
const trackColor = { true: Colors.turquoiseDark }
|
||||
const iconColor = value ? Colors.iconColors[symptom].color : Colors.grey
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.iconContainer}>
|
||||
<DripIcon
|
||||
color={iconColor}
|
||||
name={`drip-icon-${symptom}`}
|
||||
size={Sizes.title}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.textContainer}>
|
||||
<AppText>{text}</AppText>
|
||||
</View>
|
||||
<Switch
|
||||
onValueChange={onToggle}
|
||||
style={styles.appSwitch}
|
||||
value={value}
|
||||
trackColor={trackColor}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
TrackingCategorySwitch.propTypes = {
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
symptom: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
value: PropTypes.bool,
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...Containers.rowContainer,
|
||||
marginVertical: Spacing.tiny,
|
||||
},
|
||||
iconContainer: {
|
||||
marginRight: Spacing.tiny,
|
||||
flex: 1,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 5,
|
||||
},
|
||||
appSwitch: {
|
||||
flex: 2,
|
||||
transform:
|
||||
Platform.OS === 'ios'
|
||||
? [{ scaleX: 0.8 }, { scaleY: 0.8 }]
|
||||
: [{ scaleX: 1 }, { scaleY: 1 }],
|
||||
},
|
||||
})
|
||||
export default TrackingCategorySwitch
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
painTrackingCategoryObservable,
|
||||
sexTrackingCategoryObservable,
|
||||
temperatureTrackingCategoryObservable,
|
||||
mucusTrackingCategoryObservable,
|
||||
cervixTrackingCategoryObservable,
|
||||
} from '../../local-storage'
|
||||
import { Spacing } from '../../styles'
|
||||
import { SYMPTOMS } from '../../config'
|
||||
@@ -40,6 +42,10 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
|
||||
return temperatureTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'sex') {
|
||||
return sexTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'mucus') {
|
||||
return mucusTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'cervix') {
|
||||
return cervixTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'desire') {
|
||||
return desireTrackingCategoryObservable.value ? symptom : null
|
||||
} else if (symptom === 'pain') {
|
||||
|
||||
@@ -1,22 +1,61 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||
import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||
|
||||
import AppText from '../common/app-text'
|
||||
|
||||
import { Colors, Containers } from '../../styles'
|
||||
import labels from '../../i18n/en/settings'
|
||||
|
||||
export default function SelectTabGroup({
|
||||
activeButton,
|
||||
buttons,
|
||||
onSelect,
|
||||
disabled,
|
||||
}) {
|
||||
// TODO https://gitlab.com/bloodyhealth/drip/-/issues/707
|
||||
const oneTimeTransformIntoNumber =
|
||||
typeof activeButton === 'boolean' && Number(activeButton)
|
||||
const isSecondarySymptomSwitch =
|
||||
buttons[0]['label'] === labels.secondarySymptom.mucus
|
||||
|
||||
// Disable is only used for secondarySymptom in customization, if more come up maybe consider more tidy solution
|
||||
const showDisabledAlert = (label) => {
|
||||
if (
|
||||
label === labels.secondarySymptom.cervix ||
|
||||
label === labels.secondarySymptom.mucus
|
||||
) {
|
||||
Alert.alert(
|
||||
labels.secondarySymptom.disabled.title,
|
||||
labels.secondarySymptom.disabled.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{buttons.map(({ label, value }, i) => {
|
||||
const isActive = value === activeButton
|
||||
const boxStyle = [styles.box, isActive && styles.boxActive]
|
||||
const textStyle = [styles.text, isActive && styles.textActive]
|
||||
const isActive =
|
||||
value === activeButton || value === oneTimeTransformIntoNumber
|
||||
const boxStyle = [
|
||||
styles.box,
|
||||
isActive && styles.boxActive,
|
||||
isSecondarySymptomSwitch && styles.purpleBox,
|
||||
isSecondarySymptomSwitch && isActive && styles.activePurpleBox,
|
||||
disabled && styles.disabledBox,
|
||||
]
|
||||
const textStyle = [
|
||||
styles.text,
|
||||
isSecondarySymptomSwitch && styles.purpleText,
|
||||
isActive && styles.textActive,
|
||||
disabled && styles.greyText,
|
||||
]
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => onSelect(value)}
|
||||
onPress={() =>
|
||||
!disabled ? onSelect(value) : showDisabledAlert(label)
|
||||
}
|
||||
key={i}
|
||||
style={boxStyle}
|
||||
>
|
||||
@@ -32,6 +71,7 @@ SelectTabGroup.propTypes = {
|
||||
activeButton: PropTypes.number,
|
||||
buttons: PropTypes.array.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -50,4 +90,20 @@ const styles = StyleSheet.create({
|
||||
textActive: {
|
||||
color: 'white',
|
||||
},
|
||||
purpleBox: {
|
||||
borderColor: Colors.purple,
|
||||
},
|
||||
activePurpleBox: {
|
||||
backgroundColor: Colors.purple,
|
||||
},
|
||||
purpleText: {
|
||||
color: Colors.purple,
|
||||
},
|
||||
greyText: {
|
||||
color: Colors.grey,
|
||||
},
|
||||
disabledBox: {
|
||||
borderColor: Colors.grey,
|
||||
backgroundColor: Colors.turquoiseLight,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ const SymptomBox = ({
|
||||
editedSymptom,
|
||||
setEditedSymptom,
|
||||
}) => {
|
||||
const { t } = useTranslation(null, { keyPrefix: 'cycleDay.symptomBox' })
|
||||
const { t } = useTranslation(null, { keyPrefix: 'symptoms' })
|
||||
const isSymptomEdited = editedSymptom === symptom
|
||||
const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
|
||||
const isExcluded = symptomData !== null ? symptomData.exclude : false
|
||||
|
||||
@@ -15,6 +15,7 @@ import Temperature from './temperature'
|
||||
import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day'
|
||||
import { showToast } from '../helpers/general'
|
||||
|
||||
import { fertilityTrackingObservable } from '../../local-storage'
|
||||
import { shared as sharedLabels } from '../../i18n/en/labels'
|
||||
import info from '../../i18n/en/symptom-info'
|
||||
import { Colors, Containers, Sizes, Spacing } from '../../styles'
|
||||
@@ -25,6 +26,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
|
||||
const [shouldShowInfo, setShouldShowInfo] = useState(false)
|
||||
const getParsedData = () => JSON.parse(JSON.stringify(data))
|
||||
const onPressLearnMore = () => setShouldShowInfo(!shouldShowInfo)
|
||||
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
|
||||
|
||||
const onEditNote = (note) => {
|
||||
const parsedData = getParsedData()
|
||||
@@ -167,7 +169,10 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
|
||||
</Segment>
|
||||
)
|
||||
})}
|
||||
{shouldShow(symptomConfig.excludeText) && (
|
||||
{/* show exclude AppSwitch for bleeding, mucus, cervix, temperature */}
|
||||
{/* but if fertility is off only for bleeding */}
|
||||
{shouldShow(symptomConfig.excludeText) &&
|
||||
(symptom === 'bleeding' || isFertilityTrackingEnabled) && (
|
||||
<Segment style={styles.segmentBorder}>
|
||||
<AppSwitch
|
||||
onToggle={onExcludeToggle}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { LocalDate } from '@js-joda/core'
|
||||
|
||||
import { scaleObservable, unitObservable } from '../../local-storage'
|
||||
import {
|
||||
fertilityTrackingObservable,
|
||||
scaleObservable,
|
||||
unitObservable,
|
||||
} from '../../local-storage'
|
||||
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
||||
import { getCycleDay, getAmountOfCycleDays } from '../../db'
|
||||
|
||||
@@ -270,7 +274,8 @@ export function nfpLines() {
|
||||
if (dateString < cycle.startDate) updateCurrentCycle(dateString)
|
||||
if (cycle.noMoreCycles) return ret
|
||||
|
||||
const tempShift = cycle.status.temperatureShift
|
||||
const tempShift =
|
||||
fertilityTrackingObservable.value && cycle.status.temperatureShift
|
||||
|
||||
if (tempShift) {
|
||||
if (tempShift.firstHighMeasurementDay.date === dateString) {
|
||||
|
||||
@@ -1,37 +1,49 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Alert, Pressable } from 'react-native'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Alert, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import AppIcon from '../../common/app-icon'
|
||||
import AppPage from '../../common/app-page'
|
||||
import AppSwitch from '../../common/app-switch'
|
||||
import AppText from '../../common/app-text'
|
||||
import { Colors, Spacing, Typography } from '../../../styles'
|
||||
import TemperatureSlider from './temperature-slider'
|
||||
import Segment from '../../common/segment'
|
||||
import TrackingCategorySwitch from '../../common/tracking-category-switch'
|
||||
import SelectTabGroup from '../../cycle-day/select-tab-group'
|
||||
|
||||
import {
|
||||
desireTrackingCategoryObservable,
|
||||
fertilityTrackingObservable,
|
||||
moodTrackingCategoryObservable,
|
||||
noteTrackingCategoryObservable,
|
||||
painTrackingCategoryObservable,
|
||||
sexTrackingCategoryObservable,
|
||||
temperatureTrackingCategoryObservable,
|
||||
mucusTrackingCategoryObservable,
|
||||
cervixTrackingCategoryObservable,
|
||||
periodPredictionObservable,
|
||||
useCervixAsSecondarySymptomObservable,
|
||||
saveDesireTrackingCategory,
|
||||
saveFertilityTrackingEnabled,
|
||||
saveMoodTrackingCategory,
|
||||
saveNoteTrackingCategory,
|
||||
savePainTrackingCategory,
|
||||
saveMucusTrackingCategory,
|
||||
saveCervixTrackingCategory,
|
||||
savePeriodPrediction,
|
||||
saveSexTrackingCategory,
|
||||
saveTemperatureTrackingCategory,
|
||||
saveUseCervix,
|
||||
periodPredictionObservable,
|
||||
useCervixObservable,
|
||||
saveUseCervixAsSecondarySymptom,
|
||||
} from '../../../local-storage'
|
||||
import labels from '../../../i18n/en/settings'
|
||||
import { SYMPTOMS } from '../../../config'
|
||||
|
||||
const Settings = () => {
|
||||
const [shouldUseCervix, setShouldUseCervix] = useState(
|
||||
useCervixObservable.value
|
||||
)
|
||||
const { t } = useTranslation(null, { keyPrefix: 'symptoms' })
|
||||
|
||||
const [useCervixAsSecondarySymptom, setUseCervixAsSecondarySymptom] =
|
||||
useState(useCervixAsSecondarySymptomObservable.value)
|
||||
|
||||
const [isPeriodPredictionEnabled, setPeriodPrediction] = useState(
|
||||
periodPredictionObservable.value
|
||||
@@ -40,6 +52,14 @@ const Settings = () => {
|
||||
const [isTemperatureTrackingCategoryEnabled, setTemperatureTrackingCategory] =
|
||||
useState(temperatureTrackingCategoryObservable.value)
|
||||
|
||||
const [isMucusTrackingCategoryEnabled, setMucusTrackingCategory] = useState(
|
||||
mucusTrackingCategoryObservable.value
|
||||
)
|
||||
|
||||
const [isCervixTrackingCategoryEnabled, setCervixTrackingCategory] = useState(
|
||||
cervixTrackingCategoryObservable.value
|
||||
)
|
||||
|
||||
const [isSexTrackingCategoryEnabled, setSexTrackingCategory] = useState(
|
||||
sexTrackingCategoryObservable.value
|
||||
)
|
||||
@@ -60,19 +80,33 @@ const Settings = () => {
|
||||
noteTrackingCategoryObservable.value
|
||||
)
|
||||
|
||||
const [isEnabled, setIsEnabled] = useState(false)
|
||||
const toggleSwitch = () => setIsEnabled((previousState) => !previousState)
|
||||
const [isFertilityTrackingEnabled, setFertilityTrackingEnabled] = useState(
|
||||
fertilityTrackingObservable.value
|
||||
)
|
||||
|
||||
const fertilityTrackingToggle = (value) => {
|
||||
setFertilityTrackingEnabled(value)
|
||||
saveFertilityTrackingEnabled(value)
|
||||
}
|
||||
|
||||
const temperatureTrackingCategoryToggle = (value) => {
|
||||
setTemperatureTrackingCategory(value)
|
||||
saveTemperatureTrackingCategory(value)
|
||||
if (!value) {
|
||||
setFertilityTrackingEnabled(false)
|
||||
saveFertilityTrackingEnabled(false)
|
||||
}
|
||||
}
|
||||
const mucusTrackingCategoryToggle = (value) => {
|
||||
manageSecondarySymptom(cervixTrackingCategoryObservable.value, value)
|
||||
}
|
||||
const cervixTrackingCategoryToggle = (value) => {
|
||||
manageSecondarySymptom(value, mucusTrackingCategoryObservable.value)
|
||||
}
|
||||
|
||||
const sexTrackingCategoryToggle = (value) => {
|
||||
setSexTrackingCategory(value)
|
||||
saveSexTrackingCategory(value)
|
||||
}
|
||||
|
||||
const desireTrackingCategoryToggle = (value) => {
|
||||
setDesireTrackingCategory(value)
|
||||
saveDesireTrackingCategory(value)
|
||||
@@ -89,111 +123,229 @@ const Settings = () => {
|
||||
setNoteTrackingCategory(value)
|
||||
saveNoteTrackingCategory(value)
|
||||
}
|
||||
|
||||
const onPeriodPredictionToggle = (value) => {
|
||||
setPeriodPrediction(value)
|
||||
savePeriodPrediction(value)
|
||||
}
|
||||
|
||||
const fertilityTrackingText = isFertilityTrackingEnabled
|
||||
? labels.fertilityTracking.on
|
||||
: labels.fertilityTracking.off
|
||||
|
||||
const periodPredictionText = isPeriodPredictionEnabled
|
||||
? labels.periodPrediction.on
|
||||
: labels.periodPrediction.off
|
||||
|
||||
const onCervixToggle = (value) => {
|
||||
setShouldUseCervix(value)
|
||||
saveUseCervix(value)
|
||||
const secondarySymptomButtons = [
|
||||
{
|
||||
label: labels.secondarySymptom.mucus,
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: labels.secondarySymptom.cervix,
|
||||
value: 1,
|
||||
},
|
||||
]
|
||||
|
||||
const onSelectTab = (value) => {
|
||||
if (isMucusTrackingCategoryEnabled && isCervixTrackingCategoryEnabled) {
|
||||
setUseCervixAsSecondarySymptom(value)
|
||||
saveUseCervixAsSecondarySymptom(value)
|
||||
} else {
|
||||
secondarySymptomDisabledPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
const cervixText = shouldUseCervix
|
||||
? labels.useCervix.cervixModeOn
|
||||
: labels.useCervix.cervixModeOff
|
||||
// is needed so secondary symptom is set correct on load
|
||||
useEffect(() => {
|
||||
manageSecondarySymptom(
|
||||
cervixTrackingCategoryObservable.value,
|
||||
mucusTrackingCategoryObservable.value
|
||||
)
|
||||
}, [])
|
||||
|
||||
const manageSecondarySymptom = (cervix, mucus) => {
|
||||
if (!cervix && mucus) {
|
||||
setUseCervixAsSecondarySymptom(0)
|
||||
saveUseCervixAsSecondarySymptom(0)
|
||||
} else if (cervix && !mucus) {
|
||||
setUseCervixAsSecondarySymptom(1)
|
||||
saveUseCervixAsSecondarySymptom(1)
|
||||
} else if (!cervix && !mucus) {
|
||||
setFertilityTrackingEnabled(false)
|
||||
saveFertilityTrackingEnabled(false)
|
||||
}
|
||||
setMucusTrackingCategory(mucus)
|
||||
saveMucusTrackingCategory(mucus)
|
||||
setCervixTrackingCategory(cervix)
|
||||
saveCervixTrackingCategory(cervix)
|
||||
}
|
||||
|
||||
const secondarySymptomDisabledPrompt = () => {
|
||||
if (!isFertilityTrackingEnabled) {
|
||||
Alert.alert(
|
||||
labels.secondarySymptom.disabled.title,
|
||||
labels.secondarySymptom.disabled.message
|
||||
)
|
||||
} else if (
|
||||
!isMucusTrackingCategoryEnabled == isCervixTrackingCategoryEnabled
|
||||
) {
|
||||
Alert.alert(
|
||||
labels.secondarySymptom.disabled.title,
|
||||
labels.secondarySymptom.disabled.noSecondaryEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const manageFertilityFeature =
|
||||
isTemperatureTrackingCategoryEnabled &&
|
||||
(isMucusTrackingCategoryEnabled || isCervixTrackingCategoryEnabled)
|
||||
|
||||
const cervixText = useCervixAsSecondarySymptom
|
||||
? labels.secondarySymptom.cervixModeOn
|
||||
: labels.secondarySymptom.cervixModeOff
|
||||
|
||||
const sliderDisabledPrompt = () => {
|
||||
if (!isTemperatureTrackingCategoryEnabled) {
|
||||
Alert.alert(labels.disabled.title, labels.disabled.message)
|
||||
Alert.alert(labels.tempScale.disabled, labels.tempScale.disabledMessage)
|
||||
}
|
||||
}
|
||||
|
||||
const fertilityDisabledPrompt = () => {
|
||||
if (!manageFertilityFeature) {
|
||||
Alert.alert(
|
||||
labels.fertilityTracking.disabledTitle,
|
||||
labels.fertilityTracking.disabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AppPage title={'Customization'}>
|
||||
<Segment title={'Tracking categories'}>
|
||||
<AppSwitch
|
||||
<AppPage title={labels.customization.title}>
|
||||
<Segment title={labels.customization.trackingCategories}>
|
||||
<TrackingCategorySwitch
|
||||
onToggle={temperatureTrackingCategoryToggle}
|
||||
text={SYMPTOMS[1]}
|
||||
text={t(SYMPTOMS[1])}
|
||||
value={isTemperatureTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[1]}
|
||||
/>
|
||||
<AppSwitch
|
||||
<TrackingCategorySwitch
|
||||
onToggle={(enabled) => {
|
||||
mucusTrackingCategoryToggle(enabled)
|
||||
}}
|
||||
text={t(SYMPTOMS[2])}
|
||||
value={isMucusTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[2]}
|
||||
/>
|
||||
<TrackingCategorySwitch
|
||||
onToggle={(enabled) => {
|
||||
cervixTrackingCategoryToggle(enabled)
|
||||
}}
|
||||
text={t(SYMPTOMS[3])}
|
||||
value={isCervixTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[3]}
|
||||
/>
|
||||
<TrackingCategorySwitch
|
||||
onToggle={sexTrackingCategoryToggle}
|
||||
text={SYMPTOMS[4]}
|
||||
text={t(SYMPTOMS[4])}
|
||||
value={isSexTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[4]}
|
||||
/>
|
||||
<AppSwitch
|
||||
<TrackingCategorySwitch
|
||||
onToggle={desireTrackingCategoryToggle}
|
||||
text={SYMPTOMS[5]}
|
||||
text={t(SYMPTOMS[5])}
|
||||
value={isDesireTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[5]}
|
||||
/>
|
||||
<AppSwitch
|
||||
<TrackingCategorySwitch
|
||||
onToggle={painTrackingCategoryToggle}
|
||||
text={SYMPTOMS[6]}
|
||||
text={t(SYMPTOMS[6])}
|
||||
value={isPainTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[6]}
|
||||
/>
|
||||
<AppSwitch
|
||||
<TrackingCategorySwitch
|
||||
onToggle={moodTrackingCategoryToggle}
|
||||
text={SYMPTOMS[7]}
|
||||
text={t(SYMPTOMS[7])}
|
||||
value={isMoodTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[7]}
|
||||
/>
|
||||
<AppSwitch
|
||||
<TrackingCategorySwitch
|
||||
onToggle={noteTrackingCategoryToggle}
|
||||
text={SYMPTOMS[8]}
|
||||
text={t(SYMPTOMS[8])}
|
||||
value={isNoteTrackingCategoryEnabled}
|
||||
symptom={SYMPTOMS[8]}
|
||||
/>
|
||||
</Segment>
|
||||
|
||||
<Segment title={'Fertility feature'}>
|
||||
<Pressable onPress={fertilityDisabledPrompt}>
|
||||
<Segment title={labels.fertilityTracking.title}>
|
||||
<AppText>{labels.fertilityTracking.message}</AppText>
|
||||
<AppSwitch
|
||||
onToggle={toggleSwitch}
|
||||
text={'If turned on ...'}
|
||||
value={isEnabled}
|
||||
onToggle={fertilityTrackingToggle}
|
||||
text={fertilityTrackingText}
|
||||
value={isFertilityTrackingEnabled}
|
||||
disabled={!manageFertilityFeature}
|
||||
/>
|
||||
</Segment>
|
||||
|
||||
<Pressable onPress={sliderDisabledPrompt}>
|
||||
<Segment title={labels.tempScale.segmentTitle}>
|
||||
{isTemperatureTrackingCategoryEnabled && (
|
||||
<>
|
||||
<AppText>{labels.tempScale.segmentExplainer}</AppText>
|
||||
<TemperatureSlider />
|
||||
</>
|
||||
)}
|
||||
{!isTemperatureTrackingCategoryEnabled && (
|
||||
<AppText>{labels.disabled.message}</AppText>
|
||||
)}
|
||||
</Segment>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={sliderDisabledPrompt}>
|
||||
<Segment title={labels.useCervix.title}>
|
||||
{isTemperatureTrackingCategoryEnabled && (
|
||||
<AppSwitch
|
||||
onToggle={onCervixToggle}
|
||||
text={cervixText}
|
||||
value={shouldUseCervix}
|
||||
/>
|
||||
)}
|
||||
{!isTemperatureTrackingCategoryEnabled && (
|
||||
<AppText>{labels.disabled.message}</AppText>
|
||||
)}
|
||||
</Segment>
|
||||
</Pressable>
|
||||
|
||||
<Segment title={labels.periodPrediction.title} last>
|
||||
<Segment title={labels.periodPrediction.title}>
|
||||
<AppSwitch
|
||||
onToggle={onPeriodPredictionToggle}
|
||||
text={periodPredictionText}
|
||||
value={isPeriodPredictionEnabled}
|
||||
/>
|
||||
</Segment>
|
||||
|
||||
<Segment
|
||||
subheader={labels.customization.subheaderSymptoThermalMethod}
|
||||
last
|
||||
></Segment>
|
||||
|
||||
<Pressable onPress={sliderDisabledPrompt}>
|
||||
<Segment title={labels.tempScale.segmentTitle}>
|
||||
<AppText>{labels.tempScale.segmentExplainer}</AppText>
|
||||
<TemperatureSlider disabled={!isTemperatureTrackingCategoryEnabled} />
|
||||
</Segment>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={secondarySymptomDisabledPrompt}>
|
||||
<Segment title={labels.secondarySymptom.title}>
|
||||
<AppText>{cervixText}</AppText>
|
||||
<SelectTabGroup
|
||||
activeButton={useCervixAsSecondarySymptom}
|
||||
buttons={secondarySymptomButtons}
|
||||
onSelect={(value) => onSelectTab(value)}
|
||||
disabled={!isFertilityTrackingEnabled}
|
||||
/>
|
||||
</Segment>
|
||||
</Pressable>
|
||||
<Segment last>
|
||||
<View style={styles.line}>
|
||||
<AppIcon
|
||||
color={Colors.purple}
|
||||
name="info-with-circle"
|
||||
style={styles.icon}
|
||||
/>
|
||||
<AppText style={styles.title}>{labels.preOvu.title}</AppText>
|
||||
</View>
|
||||
<AppText>{labels.preOvu.note}</AppText>
|
||||
</Segment>
|
||||
</AppPage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
icon: {
|
||||
marginRight: Spacing.base,
|
||||
},
|
||||
line: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
...Typography.subtitle,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import PropTypes from 'prop-types'
|
||||
import Slider from '@ptomasroos/react-native-multi-slider'
|
||||
|
||||
import alertError from '../common/alert-error'
|
||||
@@ -10,7 +11,7 @@ import { Colors, Sizes } from '../../../styles'
|
||||
import labels from '../../../i18n/en/settings'
|
||||
import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config'
|
||||
|
||||
const TemperatureSlider = () => {
|
||||
const TemperatureSlider = ({ disabled }) => {
|
||||
const savedValue = scaleObservable.value
|
||||
const [minTemperature, setMinTemperature] = useState(savedValue.min)
|
||||
const [maxTemperature, setMaxTemperature] = useState(savedValue.max)
|
||||
@@ -25,6 +26,14 @@ const TemperatureSlider = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const sliderAccentBackground = disabled
|
||||
? styles.disabledSliderAccentBackground
|
||||
: styles.sliderAccentBackground
|
||||
|
||||
const sliderBackground = disabled
|
||||
? styles.disabledSliderBackground
|
||||
: styles.sliderBackground
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Slider
|
||||
@@ -35,11 +44,13 @@ const TemperatureSlider = () => {
|
||||
max={TEMP_MAX}
|
||||
min={TEMP_MIN}
|
||||
onValuesChange={onTemperatureSliderChange}
|
||||
selectedStyle={styles.sliderAccentBackground}
|
||||
step={TEMP_SLIDER_STEP}
|
||||
trackStyle={styles.slider}
|
||||
unselectedStyle={styles.sliderBackground}
|
||||
values={[minTemperature, maxTemperature]}
|
||||
enabledOne={!disabled}
|
||||
enabledTwo={!disabled}
|
||||
selectedStyle={sliderAccentBackground}
|
||||
unselectedStyle={sliderBackground}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
@@ -47,6 +58,10 @@ const TemperatureSlider = () => {
|
||||
|
||||
export default TemperatureSlider
|
||||
|
||||
TemperatureSlider.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
@@ -54,6 +69,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
marker: {
|
||||
backgroundColor: Colors.turquoiseDark,
|
||||
|
||||
borderRadius: 50,
|
||||
elevation: 4,
|
||||
height: Sizes.subtitle,
|
||||
@@ -66,7 +82,13 @@ const styles = StyleSheet.create({
|
||||
sliderAccentBackground: {
|
||||
backgroundColor: Colors.turquoiseDark,
|
||||
},
|
||||
disabledSliderAccentBackground: {
|
||||
backgroundColor: Colors.grey,
|
||||
},
|
||||
sliderBackground: {
|
||||
backgroundColor: Colors.turquoise,
|
||||
},
|
||||
disabledSliderBackground: {
|
||||
backgroundColor: Colors.greyLight,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,7 +11,6 @@ const menuItems = [
|
||||
{ label: 'reminders', componentName: 'Reminders' },
|
||||
{ label: 'dataManagement', componentName: 'DataManagement' },
|
||||
{ label: 'password', componentName: 'Password' },
|
||||
{ label: 'info', componentName: 'Info' },
|
||||
]
|
||||
|
||||
const SettingsMenu = ({ navigate }) => {
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
"chart": {
|
||||
"tutorial": "You can swipe the chart to view more dates."
|
||||
},
|
||||
"cycleDay": {
|
||||
"symptomBox": {
|
||||
"symptoms": {
|
||||
"bleeding": "bleeding",
|
||||
"temperature": "temperature",
|
||||
"mucus": "cervical mucus",
|
||||
@@ -18,7 +17,6 @@
|
||||
"sex": "sex",
|
||||
"pain": "pain",
|
||||
"mood": "mood"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"bleedingPrediction": {
|
||||
@@ -114,19 +112,19 @@
|
||||
"menuItem": {
|
||||
"dataManagement": {
|
||||
"name": "Data",
|
||||
"text": "import, export or delete your data"
|
||||
"text": "Import, export or delete your data"
|
||||
},
|
||||
"customization": {
|
||||
"name": "Customization",
|
||||
"text": "define how you want to use drip"
|
||||
"text": "Define how you want to use drip"
|
||||
},
|
||||
"password": {
|
||||
"name": "Password",
|
||||
"text": "set or edit your password"
|
||||
"text": "Set, edit or delete your password"
|
||||
},
|
||||
"reminders": {
|
||||
"name": "Reminders",
|
||||
"text": "turn on/off reminders"
|
||||
"text": "Turn on/off reminders"
|
||||
},
|
||||
"info": {
|
||||
"name": "Info",
|
||||
|
||||
@@ -4,70 +4,71 @@ export const intensity = ['low', 'medium', 'high']
|
||||
export const bleeding = {
|
||||
labels: ['spotting', 'light', 'medium', 'heavy'],
|
||||
heaviness: {
|
||||
header: "Heaviness",
|
||||
explainer: "How heavy is the bleeding?",
|
||||
header: 'Heaviness',
|
||||
explainer: 'How heavy is the bleeding?',
|
||||
},
|
||||
exclude: {
|
||||
header: "Exclude",
|
||||
explainer: "You can exclude this value if it's not menstrual bleeding"
|
||||
}
|
||||
header: 'Exclude',
|
||||
explainer: "You can exclude this value if it's not menstrual bleeding",
|
||||
},
|
||||
}
|
||||
|
||||
export const cervix = {
|
||||
subcategories: {
|
||||
opening: 'opening',
|
||||
firmness: 'firmness',
|
||||
position: 'position'
|
||||
position: 'position',
|
||||
},
|
||||
opening: {
|
||||
categories: ['closed', 'medium', 'open'],
|
||||
explainer: 'Is your cervix open or closed?'
|
||||
explainer: 'Is your cervix open or closed?',
|
||||
},
|
||||
firmness: {
|
||||
categories: ['hard', 'soft'],
|
||||
explainer: "When it's hard, it might feel like the tip of your nose"
|
||||
explainer: "When it's hard, it might feel like the tip of your nose",
|
||||
},
|
||||
position: {
|
||||
categories: ['low', 'medium', 'high'],
|
||||
explainer: 'How high up in the vagina is the cervix?'
|
||||
explainer: 'How high up in the vagina is the cervix?',
|
||||
},
|
||||
excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection.",
|
||||
actionHint: 'Choose values for at least "Opening" and "Firmness" to save.'
|
||||
excludeExplainer:
|
||||
"You can exclude this value if you don't want to use it for fertility detection.",
|
||||
}
|
||||
|
||||
export const mucus = {
|
||||
subcategories: {
|
||||
feeling: 'feeling',
|
||||
texture: 'texture'
|
||||
texture: 'texture',
|
||||
},
|
||||
feeling: {
|
||||
categories: ['dry', 'nothing', 'wet', 'slippery'],
|
||||
explainer: 'What does your vaginal entrance feel like?'
|
||||
explainer: 'What does your vaginal entrance feel like?',
|
||||
},
|
||||
texture: {
|
||||
categories: ['nothing', 'creamy', 'egg white'],
|
||||
explainer: "Looking at and touching your cervical mucus, which describes it best?"
|
||||
explainer:
|
||||
'Looking at and touching your cervical mucus, which describes it best?',
|
||||
},
|
||||
excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection",
|
||||
actionHint: 'Choose values for both "Feeling" and "Texture" to save.'
|
||||
excludeExplainer:
|
||||
"You can exclude this value if you don't want to use it for fertility detection",
|
||||
}
|
||||
|
||||
export const desire = {
|
||||
header: 'Intensity',
|
||||
explainer: 'How would you rate your sexual desire?'
|
||||
explainer: 'How would you rate your sexual desire?',
|
||||
}
|
||||
|
||||
export const sex = {
|
||||
categories:{
|
||||
categories: {
|
||||
solo: 'solo',
|
||||
partner: 'partner',
|
||||
},
|
||||
header: "Activity",
|
||||
header: 'Activity',
|
||||
explainer: 'Were you sexually active today?',
|
||||
}
|
||||
|
||||
export const contraceptives = {
|
||||
categories:{
|
||||
categories: {
|
||||
condom: 'condom',
|
||||
pill: 'pill',
|
||||
iud: 'iud',
|
||||
@@ -78,8 +79,8 @@ export const contraceptives = {
|
||||
none: 'none',
|
||||
other: 'other',
|
||||
},
|
||||
header: "Contraceptives",
|
||||
explainer: 'Did you use contraceptives?'
|
||||
header: 'Contraceptives',
|
||||
explainer: 'Did you use contraceptives?',
|
||||
}
|
||||
|
||||
export const pain = {
|
||||
@@ -91,9 +92,9 @@ export const pain = {
|
||||
nausea: 'nausea',
|
||||
tenderBreasts: 'tender breasts',
|
||||
migraine: 'migraine',
|
||||
other: 'other'
|
||||
other: 'other',
|
||||
},
|
||||
explainer: 'How did your body feel today?'
|
||||
explainer: 'How did your body feel today?',
|
||||
}
|
||||
|
||||
export const mood = {
|
||||
@@ -107,34 +108,39 @@ export const mood = {
|
||||
energetic: 'energetic',
|
||||
fatigue: 'fatigue',
|
||||
angry: 'angry',
|
||||
other: 'other'
|
||||
other: 'other',
|
||||
},
|
||||
explainer: 'How did you feel today?'
|
||||
explainer: 'How did you feel today?',
|
||||
}
|
||||
|
||||
export const temperature = {
|
||||
outOfRangeWarning: 'This temperature value is out of the current range for the temperature chart. You can change the range in the settings.',
|
||||
outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
|
||||
outOfRangeWarning:
|
||||
'This temperature value is out of the current range for the temperature chart. You can change the range in the settings.',
|
||||
outOfAbsoluteRangeWarning:
|
||||
'This temperature value is too high or low to be shown on the temperature chart.',
|
||||
temperature: {
|
||||
header: "Temperature",
|
||||
explainer: 'Take your temperature right after waking up, before getting out of bed'
|
||||
header: 'Temperature',
|
||||
explainer:
|
||||
'Take your temperature right after waking up, before getting out of bed',
|
||||
},
|
||||
time: "Time",
|
||||
time: 'Time',
|
||||
note: {
|
||||
header: "Note",
|
||||
explainer: 'Is there anything that could have influenced this value, such as bad sleep or alcohol consumption?'
|
||||
header: 'Note',
|
||||
explainer:
|
||||
'Is there anything that could have influenced this value, such as bad sleep or alcohol consumption?',
|
||||
},
|
||||
exclude: {
|
||||
header: "Exclude",
|
||||
explainer: "You can exclude this value if you don't want to use it for fertility detection"
|
||||
}
|
||||
header: 'Exclude',
|
||||
explainer:
|
||||
"You can exclude this value if you don't want to use it for fertility detection",
|
||||
},
|
||||
}
|
||||
|
||||
export const noteExplainer = "Anything you want to add for the day?"
|
||||
export const noteExplainer = 'Anything you want to add for the day?'
|
||||
|
||||
export const general = {
|
||||
cycleDayNumber: "Cycle day ",
|
||||
today: "Today"
|
||||
cycleDayNumber: 'Cycle day ',
|
||||
today: 'Today',
|
||||
}
|
||||
|
||||
export const sharedDialogs = {
|
||||
@@ -144,5 +150,5 @@ export const sharedDialogs = {
|
||||
reallyDeleteData: 'Yes, I am sure',
|
||||
save: 'Save',
|
||||
delete: 'Delete',
|
||||
disabledInfo: 'There is some data missing'
|
||||
disabledInfo: 'There is some data missing',
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import links from './links'
|
||||
|
||||
export default {
|
||||
customization: {
|
||||
title: 'Customization',
|
||||
trackingCategories: 'Tracking categories',
|
||||
subheaderSymptoThermalMethod: 'Sympto-thermal method settings',
|
||||
},
|
||||
export: {
|
||||
errors: {
|
||||
noData: 'There is no data to export',
|
||||
@@ -32,17 +37,16 @@ export default {
|
||||
tempScale: {
|
||||
segmentTitle: 'Temperature scale',
|
||||
segmentExplainer:
|
||||
'Change the minimum and maximum value for the temperature chart',
|
||||
'Change the minimum and maximum value for the temperature chart.',
|
||||
min: 'Min',
|
||||
max: 'Max',
|
||||
loadError: 'Could not load saved temperature scale settings',
|
||||
saveError: 'Could not save temperature scale settings',
|
||||
disabled: 'Disabled',
|
||||
disabledMessage:
|
||||
'To use the temperature scale please first enable temperature tracking above.',
|
||||
},
|
||||
disabled: {
|
||||
title: 'This feature is turned off',
|
||||
message:
|
||||
'Please first enable the temperature tracking category in the customization settings.',
|
||||
},
|
||||
|
||||
tempReminder: {
|
||||
title: 'Temperature reminder',
|
||||
noTimeSet: 'Set a time for a daily reminder to take your temperature',
|
||||
@@ -66,17 +70,36 @@ export default {
|
||||
'To use the period reminder please first enable period predictions in the customization settings.',
|
||||
},
|
||||
},
|
||||
useCervix: {
|
||||
fertilityTracking: {
|
||||
title: 'Fertility phases calculation',
|
||||
disabledTitle: 'Disabled',
|
||||
disabled:
|
||||
'To use fertility phases calculation please enable both temperature tracking and either cervical mucus or cervix tracking above.',
|
||||
message:
|
||||
'If you enter menstrual bleeding, temperature and cervical mucus or cervix data according to the sympto-thermal method, drip will calculate cycle phases with the provided data.',
|
||||
on: 'If you switch this off, drip will not show fertility related information.',
|
||||
off: 'If you switch this on, drip will show fertility related information.',
|
||||
},
|
||||
secondarySymptom: {
|
||||
title: 'Secondary symptom',
|
||||
cervixModeOn:
|
||||
'Cervix values are being used for symptothermal fertility detection. You can switch here to use cervical mucus values for symptothermal fertility detection',
|
||||
'Cervix values are being used for fertility detection according to the sympto-thermal method.',
|
||||
cervixModeOff:
|
||||
'By default, cervical mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection',
|
||||
'Cervical mucus values are being used for fertility detection according to the sympto-thermal method.',
|
||||
disabled: {
|
||||
title: 'Disabled',
|
||||
message:
|
||||
'To set a secondary symptom please first enable the cervical mucus or cervix tracking category as well as temperature and fertility phases calculation above.',
|
||||
noSecondaryEnabled:
|
||||
'To switch the secondary symptom both cervical mucus and cervix need to be enabled above.',
|
||||
},
|
||||
mucus: 'cervical mucus',
|
||||
cervix: 'cervix',
|
||||
},
|
||||
periodPrediction: {
|
||||
title: 'Period predictions',
|
||||
on: 'drip predicts your 3 next menstrual bleedings based on the statistics of your previously tracked cycles, min 3 complete cycles.',
|
||||
off: 'There are no predictions for menstrual cycles displayed. If turned on the calendar and the home screen will display period predictions.',
|
||||
on: 'drip predicts your 3 next menstrual bleedings based on statistics if you previously tracked at least 3 complete cycles.',
|
||||
off: 'There are no predictions for menstrual cycles displayed. If turned on, the calendar and the home screen will display period predictions.',
|
||||
},
|
||||
passwordSettings: {
|
||||
title: 'App password',
|
||||
@@ -119,6 +142,6 @@ Making any changes to your password setting will keep your data as it was before
|
||||
},
|
||||
preOvu: {
|
||||
title: 'Infertile days at cycle start',
|
||||
note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past symptothermal info is available.`,
|
||||
note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past sympto-thermal info is available.`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
After tracking at least 3 menstrual cycles, drip. will give you an overview of
|
||||
· how long your cycles last on average (in "stats"),
|
||||
· whether the length of your cycles varied significantly (in "stats" and in bleeding predictions)
|
||||
· and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar").
|
||||
· and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar") if this functionality is enabled in the customization settings.
|
||||
|
||||
The app allows you to track different intensities of bleeding. On the chart and on the calendar, bleeding values are colored in different shades of red. The darker, the more intense your bleeding. Every bleeding value that is not excluded is taken into account for fertility calculation and period predictions.
|
||||
|
||||
@@ -74,7 +74,7 @@ ${generalInfo.curiousNfp}`,
|
||||
title: 'Tracking cervical mucus',
|
||||
text: `Cervical mucus can help determine in which phase of the menstrual cycle you are.
|
||||
|
||||
By default the secondary symptom the app uses for the sympto-thermal method is cervical mucus.
|
||||
By default the secondary symptom the app uses for the sympto-thermal method is cervical mucus. You can change this in the customization settings.
|
||||
|
||||
· How to identify fertile cervical mucus?
|
||||
Tracking the feeling and the texture of your cervical mucus on a daily basis helps you identify changes of the quality of the cervical mucus. The values you enter for both feeling and texture of your cervical mucus are combined by drip. into one of five values following the sympto-thermal method.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2401.17</string>
|
||||
<string>1.2403.19</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function getSensiplanMucus(feeling, texture) {
|
||||
export default function getNfpMucus(feeling, texture) {
|
||||
if (typeof feeling != 'number' || typeof texture != 'number') return null
|
||||
|
||||
const feelingMapping = {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import getFertilityStatus from 'sympto'
|
||||
import cycleModule from './cycle'
|
||||
import { useCervixObservable } from '../local-storage'
|
||||
import { fertilityTrackingObservable, useCervixAsSecondarySymptomObservable } from '../local-storage'
|
||||
import { fertilityStatus as labels } from '../i18n/en/labels'
|
||||
|
||||
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
|
||||
|
||||
export function getFertilityStatusForDay(dateString) {
|
||||
if (!isFertilityTrackingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const status = getCycleStatusForDay(dateString)
|
||||
if (!status) return {
|
||||
status: labels.fertile,
|
||||
@@ -34,6 +40,10 @@ export function getFertilityStatusForDay(dateString) {
|
||||
}
|
||||
|
||||
export function getCycleStatusForDay(dateString, opts = {}) {
|
||||
if (!isFertilityTrackingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
getCycleForDay,
|
||||
getCyclesBefore,
|
||||
@@ -57,7 +67,7 @@ export function getCycleStatusForDay(dateString, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
cycleInfo.secondarySymptom = useCervixObservable.value ? 'cervix' : 'mucus'
|
||||
cycleInfo.secondarySymptom = useCervixAsSecondarySymptomObservable.value ? 'cervix' : 'mucus'
|
||||
|
||||
return getFertilityStatus(cycleInfo)
|
||||
}
|
||||
|
||||
@@ -59,12 +59,19 @@ export async function savePeriodPrediction(bool) {
|
||||
}
|
||||
}
|
||||
|
||||
export const useCervixObservable = Observable()
|
||||
setObvWithInitValue('useCervix', useCervixObservable, false)
|
||||
export const useCervixAsSecondarySymptomObservable = Observable()
|
||||
setObvWithInitValue(
|
||||
'useCervixAsSecondarySymptom',
|
||||
useCervixAsSecondarySymptomObservable,
|
||||
0
|
||||
)
|
||||
|
||||
export async function saveUseCervix(bool) {
|
||||
await AsyncStorage.setItem('useCervix', JSON.stringify(bool))
|
||||
useCervixObservable.set(bool)
|
||||
export async function saveUseCervixAsSecondarySymptom(value) {
|
||||
await AsyncStorage.setItem(
|
||||
'useCervixAsSecondarySymptom',
|
||||
JSON.stringify(value)
|
||||
)
|
||||
useCervixAsSecondarySymptomObservable.set(value)
|
||||
}
|
||||
|
||||
export const hasEncryptionObservable = Observable()
|
||||
@@ -100,13 +107,30 @@ export async function saveTemperatureTrackingCategory(bool) {
|
||||
temperatureTrackingCategoryObservable.set(bool)
|
||||
|
||||
if (!temperatureTrackingCategoryObservable.value) {
|
||||
const result = await AsyncStorage.getItem('tempReminder')
|
||||
if (JSON.parse(result).enabled) {
|
||||
// if temperature tracking is turned off, the temperature reminder gets disabled
|
||||
const tempReminderResult = await AsyncStorage.getItem('tempReminder')
|
||||
if (tempReminderResult && JSON.parse(tempReminderResult).enabled) {
|
||||
tempReminderObservable.set(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mucusTrackingCategoryObservable = Observable()
|
||||
setObvWithInitValue('mucus', mucusTrackingCategoryObservable, true)
|
||||
|
||||
export async function saveMucusTrackingCategory(bool) {
|
||||
await AsyncStorage.setItem('mucus', JSON.stringify(bool))
|
||||
mucusTrackingCategoryObservable.set(bool)
|
||||
}
|
||||
|
||||
export const cervixTrackingCategoryObservable = Observable()
|
||||
setObvWithInitValue('cervix', cervixTrackingCategoryObservable, true)
|
||||
|
||||
export async function saveCervixTrackingCategory(bool) {
|
||||
await AsyncStorage.setItem('cervix', JSON.stringify(bool))
|
||||
cervixTrackingCategoryObservable.set(bool)
|
||||
}
|
||||
|
||||
export const sexTrackingCategoryObservable = Observable()
|
||||
setObvWithInitValue('sex', sexTrackingCategoryObservable, true)
|
||||
|
||||
@@ -147,6 +171,14 @@ export async function saveNoteTrackingCategory(bool) {
|
||||
noteTrackingCategoryObservable.set(bool)
|
||||
}
|
||||
|
||||
export const fertilityTrackingObservable = Observable()
|
||||
setObvWithInitValue('fertilityTracking', fertilityTrackingObservable, true)
|
||||
|
||||
export async function saveFertilityTrackingEnabled(bool) {
|
||||
await AsyncStorage.setItem('fertilityTracking', JSON.stringify(bool))
|
||||
fertilityTrackingObservable.set(bool)
|
||||
}
|
||||
|
||||
async function setObvWithInitValue(key, obv, defaultValue) {
|
||||
const result = await AsyncStorage.getItem(key)
|
||||
let value
|
||||
|
||||
|
After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 172 KiB |
@@ -1 +1 @@
|
||||
drip. menstrual cycle and fertility tracking
|
||||
drip. period & fertility tracker
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "drip.",
|
||||
"version": "1.2402.15",
|
||||
"version": "1.2403.19",
|
||||
"contributors": [
|
||||
"Julia Friesel <julia.friesel@gmail.com>",
|
||||
"Marie Kochsiek",
|
||||
|
||||
@@ -18,6 +18,7 @@ const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
|
||||
const lightGreenColor = '#bccd67'
|
||||
const orangeColor = '#bc6642'
|
||||
const mintColor = '#6ca299'
|
||||
const turquoiseDark = '#69CBC1'
|
||||
|
||||
export default {
|
||||
greyDark: '#555',
|
||||
@@ -27,7 +28,7 @@ export default {
|
||||
orange: '#F38337',
|
||||
purple: '#3A2671',
|
||||
purpleLight: '#938EB2',
|
||||
turquoiseDark: '#69CBC1',
|
||||
turquoiseDark: turquoiseDark,
|
||||
turquoise: '#CFECEA',
|
||||
turquoiseLight: '#E9F2ED',
|
||||
iconColors: {
|
||||
@@ -35,6 +36,9 @@ export default {
|
||||
color: redColor,
|
||||
shades: shadesOfRed,
|
||||
},
|
||||
temperature: {
|
||||
color: turquoiseDark,
|
||||
},
|
||||
mucus: {
|
||||
color: violetColor,
|
||||
shades: shadesOfViolet,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { scale } from 'react-native-size-matters'
|
||||
|
||||
export default {
|
||||
zero: '0%',
|
||||
tiny: scale(4),
|
||||
small: scale(10),
|
||||
base: scale(16),
|
||||
large: scale(20),
|
||||
symptomTileWidth: '48%',
|
||||
textWidth: '70%'
|
||||
textWidth: '70%',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import getNfpMucus from '../lib/nfp-mucus'
|
||||
|
||||
describe('getNfpMucus', () => {
|
||||
test('returns null if there is no value for feeling or texture', () => {
|
||||
expect(getNfpMucus()).toBeNull()
|
||||
expect(getNfpMucus(undefined, 3)).toBeNull()
|
||||
expect(getNfpMucus(2, undefined)).toBeNull()
|
||||
})
|
||||
|
||||
describe('results in t for:', () => {
|
||||
test('dry feeling and no texture', function () {
|
||||
const nfpValue = getNfpMucus(0, 0)
|
||||
expect(nfpValue).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in Ø for:', () => {
|
||||
test('no feeling and no texture', function () {
|
||||
const nfpValue = getNfpMucus(1, 0)
|
||||
expect(nfpValue).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in f for:', () => {
|
||||
test('wet feeling and no texture', function () {
|
||||
const nfpValue = getNfpMucus(2, 0)
|
||||
expect(nfpValue).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in S for:', () => {
|
||||
test('dry feeling and creamy texture', function () {
|
||||
const nfpValue = getNfpMucus(0, 1)
|
||||
expect(nfpValue).toEqual(3)
|
||||
})
|
||||
|
||||
test('no feeling and creamy texture', function () {
|
||||
const nfpValue = getNfpMucus(1, 1)
|
||||
expect(nfpValue).toEqual(3)
|
||||
})
|
||||
|
||||
test('wet feeling and creamy texture', function () {
|
||||
const nfpValue = getNfpMucus(2, 1)
|
||||
expect(nfpValue).toEqual(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in +S for:', () => {
|
||||
test('dry feeling and egg white texture', function () {
|
||||
const nfpValue = getNfpMucus(0, 2)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('no feeling and egg white texture', function () {
|
||||
const nfpValue = getNfpMucus(1, 2)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('wet feeling and egg white texture', function () {
|
||||
const nfpValue = getNfpMucus(2, 2)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and egg white texture', function () {
|
||||
const nfpValue = getNfpMucus(3, 2)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and creamy texture', function () {
|
||||
const nfpValue = getNfpMucus(3, 1)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and no texture', function () {
|
||||
const nfpValue = getNfpMucus(3, 0)
|
||||
expect(nfpValue).toEqual(4)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,79 +0,0 @@
|
||||
import getSensiplanMucus from '../lib/nfp-mucus'
|
||||
|
||||
describe('getSensiplanMucus', () => {
|
||||
test('returns null if there is no value for feeling or texture', () => {
|
||||
expect(getSensiplanMucus()).toBeNull()
|
||||
expect(getSensiplanMucus(undefined, 3)).toBeNull()
|
||||
expect(getSensiplanMucus(2, undefined)).toBeNull()
|
||||
})
|
||||
|
||||
describe('results in t for:', () => {
|
||||
test('dry feeling and no texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(0, 0)
|
||||
expect(sensiplanValue).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in Ø for:', () => {
|
||||
test('no feeling and no texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(1, 0)
|
||||
expect(sensiplanValue).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in f for:', () => {
|
||||
test('wet feeling and no texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(2, 0)
|
||||
expect(sensiplanValue).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in S for:', () => {
|
||||
test('dry feeling and creamy texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(0, 1)
|
||||
expect(sensiplanValue).toEqual(3)
|
||||
})
|
||||
|
||||
test('no feeling and creamy texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(1, 1)
|
||||
expect(sensiplanValue).toEqual(3)
|
||||
})
|
||||
|
||||
test('wet feeling and creamy texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(2, 1)
|
||||
expect(sensiplanValue).toEqual(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('results in +S for:', () => {
|
||||
test('dry feeling and egg white texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(0, 2)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('no feeling and egg white texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(1, 2)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('wet feeling and egg white texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(2, 2)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and egg white texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(3, 2)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and creamy texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(3, 1)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
|
||||
test('slippery feeling and no texture', function () {
|
||||
const sensiplanValue = getSensiplanMucus(3, 0)
|
||||
expect(sensiplanValue).toEqual(4)
|
||||
})
|
||||
})
|
||||
})
|
||||