Merge branch 'master' into link-nfp-rules

This commit is contained in:
Julia Friesel
2019-02-02 10:26:18 +01:00
54 changed files with 981 additions and 269 deletions
+6 -6
View File
@@ -1,4 +1,4 @@
# Contributing to drip
# Contributing to drip aka CONDRIBUTING
So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_tone4: :wave\_tone5:
@@ -8,7 +8,7 @@ So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
[How can I contribute?](#how-can-i-contribute)
[How can I condribute?](#how-can-i-condribute)
[Thank you](#thank-you)
@@ -23,9 +23,9 @@ We have prepared something for **you**: check out our [README](https://gitlab.co
Let us know if you want to suggest improvements for the README and open a merge request (which is just like Github's pull request)
## How can I contribute?
## How can I condribute?
### Your First Code Contribution
### Your First Code Condribution
We are fans of labels, at least for our issues. You can find a list of `newbie` issues [here](https://gitlab.com/bloodyhealth/drip/issues?label_name%5B%5D=Newbie).
If you decide to work on an issue, please click on `Create branch` based on that issue. You can find this as a dropdown option right under `Create merge request`.
@@ -40,7 +40,7 @@ You found a bug :bug:? Please feel free to investigate and save logs for more sp
Before creating a new issue, please review the [list of existing issues](https://gitlab.com/bloodyhealth/drip/issues) to make sure the bug is unknown. You are invited to describe the bug in all its details e.g. in what context/settings has it occurred?
To send us a new issue you can also use our gitlab email: incoming+bloodyhealth/drip@incoming.gitlab.com. It will automagically add a new issue to the list with a description text taken from the body of your email.
To send us a new issue you can also use our [gitlab email](mailto:incoming+bloodyhealth/drip@incoming.gitlab.com). It will automagically add a new issue to the list with a description text taken from the body of your email.
### Suggesting Enhancements
@@ -54,5 +54,5 @@ To send us a new issue you can also use our [gitlab email](mailto:incoming+blood
![](https://media.giphy.com/media/kPA88elN9kYco/giphy.gif)
Thank you for contributing to open source, thank you for contributing to drip!
Thank you for condributing to open source, thank you for condributing to drip!
Much love from Bloody Health :heart\_exclamation:
+52 -20
View File
@@ -1,74 +1,106 @@
# Bloody Health Cycle Tracker
# drip, the open-source cycle tracking app
A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle and/or for fertility awareness!
Find more information on [our website](https://bloodyhealth.gitlab.io/).
The app is build in React Native and currently developed for Android.
Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/drip/blob/master/CONTRIBUTING.md).
## Development setup
1. Install [Android Studio](https://developer.android.com/studio/) - you'll need it to install some dependencies.
1. Make sure you are running Node 8 (newer versions wont work). It's easiest to switch Node versions using `nvm`, heres how to do it:
```
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
nvm install v8
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
$ nvm install v8
```
1. Clone this repository:
```
git clone git@gitlab.com:bloodyhealth/drip.git
cd drip
$ git clone git@gitlab.com:bloodyhealth/drip.git
$ cd drip
```
and run
```
npm install
$ npm install
```
1. Open Android Studio and click on "Open an existing Android Studio project". Navigate to the drip repository you cloned and double click the android folder. It detects, downloads and cofigures requirements that might be missing, like the NDK and CMake to build the native code part of the project. Also see the [nodejs-mobile repository](https://github.com/janeasystems/nodejs-mobile) for the necessary prerequisites for your system.
1. Either start a [virtual device in Android Studio](https://developer.android.com/studio/run/emulator) or [set your physical device like your Android phone up](https://developer.android.com/training/basics/firstapp/running-app) to run the app.
1. Open a terminal and run `npm run android`
1. Open a terminal and run
```
$ npm run android
```
1. In another tab, run `npm run log` to see logging output
1. To see logging output, run the following command in another tab:
```
$ npm run log
```
1. Run `adb shell input keyevent 82` and select enable hot reloading (see https://facebook.github.io/react-native/docs/debugging.html)
1. Run the following command and select enable hot reloading (see https://facebook.github.io/react-native/docs/debugging.html):
```
$ adb shell input keyevent 82
```
1. We recommend installing an [ESLint plugin in your editor](https://eslint.org/docs/user-guide/integrations#editors). There's an `.eslintrc` file in this project which will be used by the plugin to check your code for style errors and potential bugs.
## Troubleshooting
### [MacOS] Java problems
### Troubleshooting
#### [MacOS] Java problems
Make sure that you have Java 1.8 by running `java -version`.
If you don't have Java installed, or your Java version is different, the app may not work. You can try just using Android Studio's Java by prepending it to your `$PATH` in your shell profile:
```
export PATH="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin:${PATH}"
```
```
$ export PATH="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin:${PATH}"
```
Now, `which java` should output `/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java`, and the correct Java version should be used.
### [MacOS] Ninja
#### [MacOS] Ninja
If `npm` says `CMake was unable to find a build program corresponding to "Ninja".`:
```
brew install ninja
$ brew install ninja
```
### [MacOS] adb not on the path
If you get error messages about `adb` not being found on your path:
```
ln -s ~/Library/Android/sdk/platform-tools/adb /usr/local/bin/adb
$ ln -s ~/Library/Android/sdk/platform-tools/adb /usr/local/bin/adb
```
## Windows problems
### [Windows 10] react native problems
Unfortunately, the react native version we use doesn't work on Windows 10 it seems, find [more info here](https://github.com/facebook/react-native/issues/20015).
## Tests
You can run the tests with `npm test`.
You can run the tests with:
```
$ npm test
```
## Debugging
In order to see logging output from the app, run `npm run log` in a separate terminal.
In order to see logging output from the app, run `npm run log` in a separate terminal. You can output specific code you want to see, with:
`console.log(theVariableIWantToSeeHere)`
or just a random string to check if this piece of code is actually running:
`console.log("HELLO")`.
## NFP rules
More information about how the app calculates fertility status and bleeding predictions in the [wiki on Gitlab](https://gitlab.com/bloodyhealth/drip/wikis/home)
## Adding a new tracking icon
1. We use [fontello](http://fontello.com/) to create icon fonts for us. You need to upload the complete set of tracking icons (bleeding, mucus, ...) including the new icon you wish to add, all in SVG.
2. Download webfont from fontello
3. Copy both the content of `config.json` and `font.tff` into `assets/fonts`, replacing it with the current content of `config-drip-icon-font.json` and `drip-icon-font.tff`.
4. Now run the following command in your console:
```
$ 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+40 -26
View File
@@ -7,21 +7,7 @@
"ascent": 850,
"glyphs": [
{
"uid": "1997bdc6b7d535061226f74d34c92481",
"css": "drip-icon-sex",
"code": 59398,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M559.7 683.7C536.5 702.7 513.1 721.5 489.6 740.1 497.1 739.1 504.7 738.1 512.3 737.1 444.8 690.1 380.4 638.6 320.2 582.6 245.1 512.6 171.9 432.9 130.6 337.6 96.7 259.5 97.9 160.9 173.4 107.5 210.3 81.4 255.6 72.8 300 77.5 354.6 83.1 404.3 113.5 442.5 151.6 459.6 168.7 486.1 142.2 469 125.1 391.2 47.5 272.2 10.5 170.8 65 128 88.1 96.3 127.5 80.8 173.3 65.6 218 69.2 267.6 82 312.3 110.3 411.7 181.6 496.8 252.6 569.1 317.7 635.5 390.1 694.9 465.3 749.5 474.6 756.3 483.9 762.9 493.3 769.5 500.8 774.7 509.8 771.5 516.1 766.6 539.6 748 563 729.3 586.2 710.2 594.1 703.7 592.9 690.4 586.2 683.7 578.4 676 567.6 677.3 559.7 683.7L559.7 683.7ZM475.8 246.5C522 168.5 601.4 102.7 689.7 79.2 727.7 69.1 769.1 68.9 804.7 86.9 846.9 108.3 874.8 153 886.7 197.7 901 251.6 883.3 308.5 859 356.7 828.6 416.7 785.1 469.6 739.4 518.5 723 536.1 749.4 562.7 766 545 815.7 491.8 862.6 434 895 368.4 922.6 312.5 937.9 249.5 922.8 187.7 910.7 138.1 879.1 90.5 836.5 62.1 800.5 38.1 755.2 31.3 712.9 36.8 602 51.2 499.4 133.1 443.4 227.5 431 248.4 463.4 267.2 475.8 246.5L475.8 246.5ZM617.7 965.1C647.4 964 672.2 945.2 679.8 916.2 682.6 905.3 681.5 892.8 681.5 881.7 681.5 844.8 681.5 807.8 681.5 770.9 681.5 721.3 681.5 671.8 681.5 622.2 681.5 598 644 598 644 622.2 644 667.7 644 713.2 644 758.8 644 793.2 644 827.6 644 862 644 884.7 651.4 926.4 617.7 927.6 593.6 928.5 593.5 966 617.7 965.1L617.7 965.1ZM553.9 699.7C553.9 741.1 553.9 782.5 553.9 823.9 553.9 848.1 553.9 872.3 553.9 896.5 553.9 933.9 579.3 963.7 617.7 965.1 641.9 966 641.8 928.5 617.7 927.6 593.2 926.7 591.4 905.3 591.4 886.5 591.4 860.7 591.4 834.9 591.4 809.1 591.4 772.6 591.4 736.1 591.4 699.7 591.4 675.5 553.9 675.5 553.9 699.7L553.9 699.7ZM644 627.4C644 657.7 644 688 644 718.3 644 742.5 654 762.3 673.9 776.2 699 793.9 735 786.6 755.1 765.1 769.2 750 771.6 731 771.6 711.4 771.6 692.4 771.6 673.3 771.6 654.3 771.6 628.5 771.6 602.8 771.6 577 771.6 562.7 771.6 548.4 771.6 534 771.6 509.9 734.1 509.9 734.1 534 734.1 592 734.1 650 734.1 707.9 734.1 722.8 734.9 736.6 720.2 745.5 702.1 756.4 681.5 739.9 681.5 720.9 681.5 690 681.5 659.1 681.5 628.2 681.5 627.9 681.5 627.7 681.5 627.4 681.5 603.2 644 603.2 644 627.4L644 627.4Z",
"width": 1000
},
"search": [
"drip-icon-sex"
]
},
{
"uid": "5a7d6d7d4e51bd96e25119b5103ef11b",
"uid": "4f484b20e814e8731840cd67d0f8ba3d",
"css": "drip-icon-bleeding",
"code": 59392,
"src": "custom_icons",
@@ -35,7 +21,7 @@
]
},
{
"uid": "e3b976b3410fb55084ca71693bd0bb49",
"uid": "75d9ba1baffd8f79881fe026da7fb340",
"css": "drip-icon-cervix",
"code": 59393,
"src": "custom_icons",
@@ -49,11 +35,25 @@
]
},
{
"uid": "37fdd360714e62dd2e7788676c5259f2",
"css": "drip-icon-desire",
"uid": "2c24d7b60d46a65942c1b0f1e14b39a2",
"css": "drip-icon-mood",
"code": 59394,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M913 500C913 611 868 717 789 796 709 874 600 915 490 913 379 911 273 862 198 781 121 700 83 590 87 479 92 369 144 265 226 191 309 116 421 80 531 88 635 95 732 144 802 218 874 294 913 396 913 500 913 524 951 524 951 500 950 386 907 276 829 192 752 109 644 59 531 50 417 42 303 80 214 152 127 222 70 326 54 436 38 550 66 667 133 761 198 853 296 917 406 941 519 965 639 944 737 883 832 825 903 731 934 624 945 584 950 542 951 500 951 476 913 476 913 500M302 421C302 438 288 452 271 452 254 452 240 438 240 421 240 404 254 390 271 390 288 390 302 404 302 421M760 421C760 438 746 452 729 452 712 452 698 438 698 421 698 404 712 390 729 390 746 390 760 404 760 421",
"width": 1000
},
"search": [
"drip-icon-mood"
]
},
{
"uid": "1cb199b75d815374a7c95351251c3268",
"css": "drip-icon-desire",
"code": 59395,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M508 446.6C531 408.2 569.1 376.1 612.1 363.1 629.6 357.9 649.7 356.1 666.7 363.4 686.7 372.2 700.1 390.9 707.4 411 722.9 453.9 697.1 499.2 673 533.3 641.7 577.5 600.5 615.1 559.5 650.2 540.6 666.4 521.1 682 501.5 697.5 509.1 696.5 516.7 695.5 524.2 694.6 492.1 672.1 461.4 647.7 432.3 621.4 393.8 586.7 357.2 547.9 332.7 501.8 312.2 463.4 305.1 415.4 335.6 379.9 350.4 362.6 371.7 352.4 394.4 351.6 422.7 350.6 447.8 365.6 467.3 384.9 484.5 401.8 511.1 375.3 493.8 358.3 451.7 316.9 391 299.8 337.5 330.1 290.4 356.9 271.3 411.9 279.6 463.4 287.7 513.6 321.2 558.5 353.9 595.8 387.4 633.9 426.2 667.4 466.3 698.3 479.1 708.1 492.1 717.7 505.3 726.9 512.7 732.1 521.7 729 528 724 567.3 692.9 606 661.1 641.6 625.9 679.8 588.3 716.8 545.6 737.2 495.4 756.7 447.7 751.6 399.1 719.7 358.1 689.8 319.5 637.9 314.6 594.5 329.5 545.4 346.2 502.1 383.4 475.7 427.7 463.2 448.5 495.7 467.3 508 446.6L508 446.6ZM53.7 537.2C54.8 535.6 56 534 57.2 532.4 58.6 530.5 59.9 528.7 61.3 526.9 62.2 525.7 63.1 524.6 63.9 523.4 66.1 520.7 61.2 527 65.1 521.9 77.8 505.7 91.2 489.9 104.9 474.5 147.7 426.8 195.9 382 246.9 345.2 309.5 300.1 378.8 264.8 454.7 249.7 538 233.2 624.2 248.5 699.2 288.9 803.3 344.9 882.4 439.9 946.3 537.2 947.3 529.6 948.3 522.1 949.2 514.5 872 592 788.2 666.6 688.9 714.6 606.2 754.7 514.4 767.2 424.5 746.8 279.9 713.9 156.3 613.7 50.8 514.5 33.2 497.9 6.6 524.4 24.2 541 120.7 631.7 228 717.3 353.2 764.4 443.9 798.5 542.3 804 635.3 775.7 768.7 735.2 879.3 637.8 975.8 541 981.5 535.3 983.3 525.3 978.7 518.3 909.5 412.9 823.7 311.4 710.2 252.3 630.2 210.6 540.6 196.8 451.8 212.3 307.8 237.4 182.5 334.6 85.9 439.8 63 464.7 40.7 490.6 21.3 518.3 7.4 538.1 39.9 556.8 53.7 537.2Z",
"width": 1000
@@ -63,9 +63,9 @@
]
},
{
"uid": "5dcc5c051418d85ae94bfaac3c8175c7",
"uid": "813a69db3b2730571df3cf0eb0bb3528",
"css": "drip-icon-mucus",
"code": 59395,
"code": 59396,
"src": "custom_icons",
"selected": true,
"svg": {
@@ -77,9 +77,9 @@
]
},
{
"uid": "d8fad6833f4dee2490000651bccc7d4a",
"uid": "fdfbc3e281bcd53763b75cb37c73bbd7",
"css": "drip-icon-note",
"code": 59396,
"code": 59397,
"src": "custom_icons",
"selected": true,
"svg": {
@@ -91,9 +91,9 @@
]
},
{
"uid": "9263adab051a978c30a160dc73875a3c",
"uid": "6e43bf3b2a6702e1ff7654a1839261d6",
"css": "drip-icon-pain",
"code": 59397,
"code": 59398,
"src": "custom_icons",
"selected": true,
"svg": {
@@ -105,11 +105,25 @@
]
},
{
"uid": "1313779ea10951cb173798dab27f8fdb",
"css": "drip-icon-temperature",
"uid": "d215f0e211e5036effaaae07df935e8c",
"css": "drip-icon-sex",
"code": 59399,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M559.7 683.7C536.5 702.7 513.1 721.5 489.6 740.1 497.1 739.1 504.7 738.1 512.3 737.1 444.8 690.1 380.4 638.6 320.2 582.6 245.1 512.6 171.9 432.9 130.6 337.6 96.7 259.5 97.9 160.9 173.4 107.5 210.3 81.4 255.6 72.8 300 77.5 354.6 83.1 404.3 113.5 442.5 151.6 459.6 168.7 486.1 142.2 469 125.1 391.2 47.5 272.2 10.5 170.8 65 128 88.1 96.3 127.5 80.8 173.3 65.6 218 69.2 267.6 82 312.3 110.3 411.7 181.6 496.8 252.6 569.1 317.7 635.5 390.1 694.9 465.3 749.5 474.6 756.3 483.9 762.9 493.3 769.5 500.8 774.7 509.8 771.5 516.1 766.6 539.6 748 563 729.3 586.2 710.2 594.1 703.7 592.9 690.4 586.2 683.7 578.4 676 567.6 677.3 559.7 683.7L559.7 683.7ZM475.8 246.5C522 168.5 601.4 102.7 689.7 79.2 727.7 69.1 769.1 68.9 804.7 86.9 846.9 108.3 874.8 153 886.7 197.7 901 251.6 883.3 308.5 859 356.7 828.6 416.7 785.1 469.6 739.4 518.5 723 536.1 749.4 562.7 766 545 815.7 491.8 862.6 434 895 368.4 922.6 312.5 937.9 249.5 922.8 187.7 910.7 138.1 879.1 90.5 836.5 62.1 800.5 38.1 755.2 31.3 712.9 36.8 602 51.2 499.4 133.1 443.4 227.5 431 248.4 463.4 267.2 475.8 246.5L475.8 246.5ZM617.7 965.1C647.4 964 672.2 945.2 679.8 916.2 682.6 905.3 681.5 892.8 681.5 881.7 681.5 844.8 681.5 807.8 681.5 770.9 681.5 721.3 681.5 671.8 681.5 622.2 681.5 598 644 598 644 622.2 644 667.7 644 713.2 644 758.8 644 793.2 644 827.6 644 862 644 884.7 651.4 926.4 617.7 927.6 593.6 928.5 593.5 966 617.7 965.1L617.7 965.1ZM553.9 699.7C553.9 741.1 553.9 782.5 553.9 823.9 553.9 848.1 553.9 872.3 553.9 896.5 553.9 933.9 579.3 963.7 617.7 965.1 641.9 966 641.8 928.5 617.7 927.6 593.2 926.7 591.4 905.3 591.4 886.5 591.4 860.7 591.4 834.9 591.4 809.1 591.4 772.6 591.4 736.1 591.4 699.7 591.4 675.5 553.9 675.5 553.9 699.7L553.9 699.7ZM644 627.4C644 657.7 644 688 644 718.3 644 742.5 654 762.3 673.9 776.2 699 793.9 735 786.6 755.1 765.1 769.2 750 771.6 731 771.6 711.4 771.6 692.4 771.6 673.3 771.6 654.3 771.6 628.5 771.6 602.8 771.6 577 771.6 562.7 771.6 548.4 771.6 534 771.6 509.9 734.1 509.9 734.1 534 734.1 592 734.1 650 734.1 707.9 734.1 722.8 734.9 736.6 720.2 745.5 702.1 756.4 681.5 739.9 681.5 720.9 681.5 690 681.5 659.1 681.5 628.2 681.5 627.9 681.5 627.7 681.5 627.4 681.5 603.2 644 603.2 644 627.4L644 627.4Z",
"width": 1000
},
"search": [
"drip-icon-sex"
]
},
{
"uid": "7c566174d70d495b28a413d7a60f6df4",
"css": "drip-icon-temperature",
"code": 59400,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M477.7 400.3C503.8 400.3 529.8 400.3 555.8 400.3 580 400.3 580 362.8 555.8 362.8 529.8 362.8 503.8 362.8 477.7 362.8 453.6 362.8 453.6 400.3 477.7 400.3L477.7 400.3ZM330.5 530.7C356.5 530.7 382.6 530.7 408.6 530.7 432.8 530.7 432.8 493.2 408.6 493.2 382.6 493.2 356.5 493.2 330.5 493.2 306.3 493.2 306.3 530.7 330.5 530.7L330.5 530.7ZM330.5 400.3C356.5 400.3 382.6 400.3 408.6 400.3 432.8 400.3 432.8 362.8 408.6 362.8 382.6 362.8 356.5 362.8 330.5 362.8 306.3 362.8 306.3 400.3 330.5 400.3L330.5 400.3ZM330.5 269.9C356.5 269.9 382.6 269.9 408.6 269.9 432.8 269.9 432.8 232.4 408.6 232.4 382.6 232.4 356.5 232.4 330.5 232.4 306.3 232.4 306.3 269.9 330.5 269.9L330.5 269.9ZM574.8 76.7C573.4 44.2 549.4 20.1 516.9 18.8 484.4 17.4 460.2 46.3 458.9 76.7 458.5 87.2 458.8 97.7 458.8 108.2 458.9 173.1 458.9 238.1 458.9 303.1 459 421.8 459.1 540.5 459.1 659.2 459.1 659.3 459.1 659.3 459.1 659.3 463.7 653.2 468.3 647.2 472.9 641.2 414.5 656.6 368.9 700.7 350.4 758 332.1 814.9 347.7 877.4 385.7 922.4 423.3 966.9 484.1 987.5 541.2 979.2 597.3 971.1 646.7 934.3 671.3 883.2 717.7 786.7 662.7 670.5 560.6 641.8 565.2 647.8 569.8 653.8 574.4 659.9 574.5 537.5 574.6 415.1 574.7 292.7 574.8 243.8 574.8 194.9 574.8 146 574.9 121.8 537.4 121.8 537.3 146 537.2 268.4 537.1 390.8 537 513.2 537 562.1 536.9 611 536.9 659.9 536.9 668.2 542.6 675.7 550.6 678 603.9 692.9 644.3 738.8 650.1 794.3 655.8 848.9 627.5 900.7 580 927.4 533.3 953.7 472.2 947.5 430.7 914 387.4 878.8 370.8 821.1 386.6 768 399.9 723 438.1 689.2 482.9 677.3 490.9 675.2 496.6 667.5 496.6 659.3 496.6 541.3 496.5 423.4 496.4 305.5 496.4 245.4 496.4 185.4 496.3 125.3 496.3 110.9 496.3 96.6 496.3 82.2 496.3 77.5 496.5 73.3 497.9 68.7 504.7 46.7 536.5 56.5 537.3 76.7 538.3 100.8 575.9 100.9 574.8 76.7L574.8 76.7Z",
"width": 1000
Binary file not shown.
Binary file not shown.
-8
View File
@@ -13,14 +13,6 @@ export default function AppText(props) {
)
}
export function AppTextLight(props) {
return (
<Text style={[styles.appTextLight, props.style]}>
{props.children}
</Text>
)
}
export function ActionHint(props) {
if(props.isVisible) {
return (
+79 -37
View File
@@ -11,6 +11,7 @@ import SettingsMenu from './settings/settings-menu'
import settingsViews from './settings'
import Stats from './stats'
import {headerTitles, menuTitles} from '../i18n/en/labels'
import InfoSymptom from './cycle-day/symptoms/info-symptom'
import setupNotifications from '../lib/notifications'
// design wants everyhting lowercased, but we don't
@@ -19,20 +20,17 @@ const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
acc[curr] = headerTitles[curr].toLowerCase()
return acc
}, {})
const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => {
acc[curr] = menuTitles[curr].toLowerCase()
return acc
}, {})
const isSymptomView = name => Object.keys(symptomViews).includes(name)
const isSettingsView = name => Object.keys(settingsViews).includes(name)
const isMenuItem = name => Object.keys(menuTitles).includes(name)
const HOME_PAGE = 'Home'
const INFO_SYMPTOM_PAGE = 'InfoSymptom'
const CYCLE_DAY_PAGE = 'CycleDay'
const SETTINGS_MENU_PAGE = 'SettingsMenu'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
currentPage: 'Home'
currentPage: HOME_PAGE
}
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonPress)
setupNotifications(this.navigate)
@@ -43,63 +41,107 @@ export default class App extends Component {
}
navigate = (pageName, props) => {
const { currentPage } = this.state
// for the back button to work properly, we want to
// remember two origins: which menu item we came from
// and from where we navigated to the symptom view (day
// view or home page)
if (isMenuItem(this.state.currentPage)) {
this.menuOrigin = this.state.currentPage
if (this.isMenuItem()) {
this.menuOrigin = currentPage
}
this.originForSymptomView = this.state.currentPage
this.setState({currentPage: pageName, currentProps: props})
if (!this.isSymptomView() && !this.isInfoSymptomView()) {
this.originForSymptomView = currentPage
}
this.setState({ currentPage: pageName, currentProps: props })
}
handleBackButtonPress = () => {
if (this.state.currentPage === 'Home') return false
if (isSymptomView(this.state.currentPage)) {
const { currentPage, currentProps } = this.state
if (currentPage === HOME_PAGE) return false
if (this.isSymptomView()) {
this.navigate(
this.originForSymptomView, { date: this.state.currentProps.date }
this.originForSymptomView, { date: currentProps.date }
)
} else if (isSettingsView(this.state.currentPage)) {
this.navigate('SettingsMenu')
} else if(this.state.currentPage === 'CycleDay') {
} else if (this.isSettingsView()) {
this.navigate(SETTINGS_MENU_PAGE)
} else if (currentPage === CYCLE_DAY_PAGE) {
this.navigate(this.menuOrigin)
} else if (this.isInfoSymptomView()) {
const { date, cycleDay, symptomView } = currentProps
this.navigate(
symptomView, { date, cycleDay })
} else {
this.navigate('Home')
this.navigate(HOME_PAGE)
}
return true
}
isMenuItem() {
return Object.keys(menuTitles).includes(this.state.currentPage)
}
isSymptomView() {
return Object.keys(symptomViews).includes(this.state.currentPage)
}
isInfoSymptomView() {
return this.state.currentPage === INFO_SYMPTOM_PAGE
}
isSettingsView() {
return Object.keys(settingsViews).includes(this.state.currentPage)
}
isDefaultView() {
const { currentPage } = this.state
return currentPage !== CYCLE_DAY_PAGE &&
!this.isSymptomView() &&
!this.isInfoSymptomView()
}
render() {
const page = {
Home, Calendar, CycleDay, Chart, SettingsMenu, ...settingsViews, Stats, ...symptomViews
}[this.state.currentPage]
const { currentPage, currentProps } = this.state
const pages = {
Home,
Calendar,
CycleDay,
Chart,
InfoSymptom,
SettingsMenu,
...settingsViews,
Stats,
...symptomViews
}
const page = pages[currentPage]
const title = headerTitlesLowerCase[currentPage]
const isSymptomView = this.isSymptomView()
return (
<View style={{flex: 1}}>
{this.state.currentPage != 'CycleDay' && !isSymptomView(this.state.currentPage) &&
{this.isDefaultView() &&
<Header title={title} />
}
{this.isInfoSymptomView() &&
<Header title={title} goBack={this.handleBackButtonPress} />
}
{isSymptomView &&
<Header
title={headerTitlesLowerCase[this.state.currentPage]}
/>}
{isSymptomView(this.state.currentPage) &&
<Header
title={headerTitlesLowerCase[this.state.currentPage]}
title={title}
isSymptomView={true}
goBack={this.handleBackButtonPress}
date={this.state.currentProps.date}
date={currentProps.date}
goToSymptomInfo={() => this.navigate(INFO_SYMPTOM_PAGE, {
symptomView: currentPage,
...currentProps
})}
/>}
{React.createElement(page, {
navigate: this.navigate,
...this.state.currentProps
...currentProps
})}
{!isSymptomView(this.state.currentPage) &&
<Menu
navigate={this.navigate}
titles={menuTitlesLowerCase}
currentPage={this.state.currentPage}
/>
{!isSymptomView &&
<Menu navigate={this.navigate} currentPage={currentPage} />
}
</View>
)
+2
View File
@@ -23,6 +23,7 @@ const symptomIcons = {
desire: <DripIcon size={16} name='drip-icon-desire' color={styles.iconShades.desire[2]}/>,
sex: <DripIcon size={16} name='drip-icon-sex' color={styles.iconShades.sex[2]}/>,
pain: <DripIcon size={16} name='drip-icon-pain' color={styles.iconShades.pain[0]}/>,
mood: <DripIcon size={16} name='drip-icon-mood' color={styles.iconShades.mood[0]}/>,
note: <DripIcon size={16} name='drip-icon-note' color={styles.iconShades.note[0]}/>
}
@@ -62,6 +63,7 @@ export default class CycleChart extends Component {
'sex',
'desire',
'pain',
'mood',
'note'
].filter((symptomName) => {
return this.cycleDaysSortedByDate.some(cycleDay => {
+16
View File
@@ -44,6 +44,10 @@ export default class DayColumn extends Component {
// is any pain documented?
acc.pain = cycleDay.pain &&
Object.values(cycleDay.pain).some(x => x === true)
} else if (symptom === 'mood') {
// is mood documented?
acc.mood = cycleDay.mood &&
Object.values(cycleDay.mood).some(x => x === true)
}
acc[`${symptom}Exclude`] = cycleDay[symptom] && cycleDay[symptom].exclude
return acc
@@ -214,6 +218,18 @@ export default class DayColumn extends Component {
/>
</SymptomIconView>
),
mood: (
<SymptomIconView
value={this.data.mood}
symptomHeight={symptomHeight}
key='mood'
>
<View
{...styles.symptomIcon}
backgroundColor={styles.iconShades.mood}
/>
</SymptomIconView>
),
note: (
<SymptomIconView
value={this.data.note}
+2 -1
View File
@@ -77,7 +77,8 @@ const styles = {
'#9e346c',
],
'pain': ['#bccd67'],
'note': ['#6CA299']
'mood': ['#bc6642'],
'note': ['#6ca299']
},
yAxis: {
width: 27,
+37 -9
View File
@@ -14,7 +14,7 @@ import * as labels from '../../i18n/en/cycle-day'
import AppText from '../app-text'
import DripIcon from '../../assets/drip-icons'
const bleedingLabels = labels.bleeding
const bleedingLabels = labels.bleeding.labels
const feelingLabels = labels.mucus.feeling.categories
const textureLabels = labels.mucus.texture.categories
const openingLabels = labels.cervix.opening.categories
@@ -24,6 +24,7 @@ const intensityLabels = labels.intensity
const sexLabels = labels.sex.categories
const contraceptiveLabels = labels.contraceptives.categories
const painLabels = labels.pain.categories
const moodLabels = labels.mood.categories
export default class CycleDayOverView extends Component {
constructor(props) {
@@ -56,7 +57,7 @@ export default class CycleDayOverView extends Component {
const l = {
bleeding: bleeding => {
if (isNumber(bleeding.value)) {
let bleedingLabel = `${bleedingLabels[bleeding.value]}`
let bleedingLabel = bleedingLabels[bleeding.value]
if (bleeding.exclude) bleedingLabel = "( " + bleedingLabel + " )"
return bleedingLabel
}
@@ -143,6 +144,25 @@ export default class CycleDayOverView extends Component {
painLabel = painLabel.join(', ')
return painLabel
}
},
mood: mood => {
let moodLabel = []
if (mood && Object.values(mood).some(val => val)){
Object.keys(mood).forEach(key => {
if(mood[key] && key !== 'other' && key !== 'note') {
moodLabel.push(moodLabels[key])
}
if(key === 'other' && mood.other) {
let label = moodLabels[key]
if(mood.note) {
label = `${label} (${mood.note})`
}
moodLabel.push(label)
}
})
moodLabel = moodLabel.join(', ')
return moodLabel
}
}
}
@@ -226,6 +246,14 @@ export default class CycleDayOverView extends Component {
iconName='drip-icon-pain'
>
</SymptomBox>
<SymptomBox
title='Mood'
onPress={() => this.navigate('MoodEditView')}
data={this.getLabel('mood')}
disabled={dateInFuture}
iconName='drip-icon-mood'
>
</SymptomBox>
<SymptomBox
title='Note'
onPress={() => this.navigate('NoteEditView')}
@@ -236,9 +264,9 @@ export default class CycleDayOverView extends Component {
{/* this is just to make the last row adhere to the grid
(and) because there are no pseudo properties in RN */}
<FillerBoxes />
</View >
</ScrollView >
</View >
</View>
</ScrollView>
</View>
)
}
}
@@ -248,9 +276,9 @@ export default class CycleDayOverView extends Component {
class SymptomBox extends Component {
render() {
const d = this.props.data
const boxActive = d ? styles.symptomBoxActive : {}
const textActive = d ? styles.symptomTextActive : {}
const hasData = this.props.data
const boxActive = hasData ? styles.symptomBoxActive : {}
const textActive = hasData ? styles.symptomTextActive : {}
const disabledStyle = this.props.disabled ? styles.symptomInFuture : {}
return (
@@ -259,7 +287,7 @@ class SymptomBox extends Component {
disabled={this.props.disabled}
>
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
<DripIcon name={this.props.iconName} size={50} color={d ? 'white' : 'black'}/>
<DripIcon name={this.props.iconName} size={50} color={hasData ? 'white' : 'black'}/>
<AppText style={[textActive, disabledStyle]}>
{this.props.title.toLowerCase()}
</AppText>
+3 -1
View File
@@ -6,6 +6,7 @@ import NoteEditView from './note'
import DesireEditView from './desire'
import SexEditView from './sex'
import PainEditView from './pain'
import MoodEditView from './mood'
export default {
BleedingEditView,
@@ -15,5 +16,6 @@ export default {
NoteEditView,
DesireEditView,
SexEditView,
PainEditView
PainEditView,
MoodEditView
}
@@ -0,0 +1,43 @@
import React, { Component } from 'react'
import {
View,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import AppText from '../../app-text'
import * as labels from '../../../i18n/en/symptom-info.js'
export default class InfoSymptom extends Component {
render() {
const symptomView = this.props.symptomView
const symptomMapping = {
BleedingEditView: 'bleeding',
CervixEditView: 'cervix',
DesireEditView: 'desire',
MucusEditView: 'mucus',
NoteEditView: 'note',
PainEditView: 'pain',
SexEditView: 'sex',
TemperatureEditView: 'temperature'
}
const currentSymptom = symptomMapping[symptomView]
const currentSymptomText = labels.symptomInfo[currentSymptom]
const currentSymptomTitle = labels.symptomTitle[currentSymptom]
return (
<ScrollView>
<View style={[styles.textWrappingView]}>
<AppText style={styles.title}>
{currentSymptomTitle}
</AppText>
<AppText style={styles.paragraph}>
{currentSymptomText}
{labels.symptomTitle.currentSymptomTitle}
</AppText>
<AppText style={styles.paragraph}>
{labels.symptomInfo.currentSymptomText}
</AppText>
</View>
</ScrollView>
)
}
}
+78
View File
@@ -0,0 +1,78 @@
import React, { Component } from 'react'
import {
ScrollView,
TextInput,
View
} from 'react-native'
import { saveSymptom } from '../../../db'
import { mood as labels } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section'
import styles from '../../../styles'
export default class Mood extends Component {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
if (cycleDay && cycleDay.mood) {
this.state = Object.assign({}, cycleDay.mood)
} else {
this.state = {}
}
if (this.state.note) {
this.state.other = true
}
}
toggleState = (key) => {
const curr = this.state[key]
this.setState({[key]: !curr})
if (key === 'other' && !curr) {
this.setState({focusTextArea: true})
}
}
render() {
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
explainer={labels.explainer}
>
<SelectBoxGroup
labels={labels.categories}
onSelect={this.toggleState}
optionsState={this.state}
/>
{ this.state.other &&
<TextInput
autoFocus={this.state.focusTextArea}
multiline={true}
placeholder="Enter"
value={this.state.note}
onChangeText={(val) => {
this.setState({note: val})
}}
/>
}
</SymptomSection>
</ScrollView>
<ActionButtonFooter
symptom='mood'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('mood', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
)
}
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import {
View,
Text} from 'react-native'
import styles from '../../styles'
export default function DefaultHeader(props) {
return (
<View style={styles.header}>
<View style={styles.accentCircle} />
<Text style={styles.headerText}>
{props.title}
</Text>
</View >
)
}
+16 -19
View File
@@ -1,27 +1,24 @@
import React from 'react'
import {
View,
Text,
Dimensions
} from 'react-native'
import styles from '../../styles'
import { Dimensions } from 'react-native'
import CycleDayHeader from './cycle-day'
import DefaultHeader from './default'
import InfoSymptomHeader from './info-symptom'
import SymptomViewHeader from './symptom-view'
export default function Header(p) {
const middle = Dimensions.get('window').width / 2
const props = Object.assign({}, p, {middle})
return (
props.isCycleDayOverView ?
<CycleDayHeader {...props} />
: props.isSymptomView ?
<SymptomViewHeader {...props}/>
:
<View style={styles.header}>
<View style={styles.accentCircle} />
<Text style={styles.headerText}>
{props.title}
</Text>
</View >
)
if (props.isCycleDayOverView) {
return (<CycleDayHeader {...props} />)
}
else if (props.isSymptomView) {
return (<SymptomViewHeader {...props} />)
}
else if (props.title === 'info') {
return (<InfoSymptomHeader {...props} />)
}
else {
return (<DefaultHeader {...props} />)
}
}
+32
View File
@@ -0,0 +1,32 @@
import React from 'react'
import {
Text,
TouchableOpacity,
View
} from 'react-native'
import styles, { iconStyles } from '../../styles'
import NavigationArrow from './navigation-arrow'
import Icon from 'react-native-vector-icons/Entypo'
export default function InfoSymptomHeader(props) {
return (
<View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}>
<View
style={styles.accentCircle}
left={props.middle - styles.accentCircle.width / 2}
/>
<NavigationArrow direction='left' {...props}/>
<View>
<Text style={styles.headerText}>
{props.title}
</Text>
</View>
<TouchableOpacity style={styles.hiddenIcon}>
<Icon
name={'chevron-thin-right'}
{...iconStyles.hiddenIcon}
/>
</TouchableOpacity>
</View>
)
}
+10 -2
View File
@@ -1,7 +1,9 @@
import React from 'react'
import {
View,
Text} from 'react-native'
Text,
TouchableOpacity
} from 'react-native'
import styles, { iconStyles } from '../../styles'
import FeatherIcon from 'react-native-vector-icons/Feather'
import NavigationArrow from './navigation-arrow'
@@ -26,11 +28,17 @@ export default function SymptomViewHeader(props) {
{formatDate(props.date)}
</Text>
</View >
<TouchableOpacity
onPress={() => props.goToSymptomInfo()}
style={styles.infoButton}
>
<FeatherIcon
name='info'
name="info"
style={styles.symptomInfoIcon}
{...iconStyles.symptomHeaderIcons}
/>
</TouchableOpacity>
</View>
)
}
@@ -2,5 +2,8 @@ import {links} from '../../i18n/en/settings'
export default function(url) {
const link = Object.values(links).find(link => link.url === url)
if (url === 'mailto:bloodyhealth@mailbox.org') {
console.log(links.email.url === url)
}
return link ? link.text : url
}
+15 -13
View File
@@ -1,16 +1,20 @@
import React, { Component } from 'react'
import { ScrollView, View, TouchableOpacity, TouchableHighlight, Dimensions } from 'react-native'
import { ScrollView, View, TouchableHighlight, TouchableOpacity, Dimensions } from 'react-native'
import { LocalDate, ChronoUnit } from 'js-joda'
import Icon from 'react-native-vector-icons/Entypo'
import Hyperlink from 'react-native-hyperlink'
import { secondaryColor, cycleDayColor, periodColor } from '../styles'
import { home as labels, bleedingPrediction as predictLabels, shared, links } from '../i18n/en/labels'
import {
home as labels,
bleedingPrediction as predictLabels,
shared,
} from '../i18n/en/labels'
import links from '../i18n/en/links'
import cycleModule from '../lib/cycle'
import { getCycleDaysSortedByDate } from '../db'
import { getFertilityStatusForDay } from '../lib/sympto-adapter'
import replace from './helpers/replace-url-with-text'
import styles from '../styles'
import AppText, { AppTextLight } from './app-text'
import AppText from './app-text'
import DripHomeIcon from '../assets/drip-home-icons'
import Button from './button'
@@ -36,9 +40,9 @@ const ShowMoreToggler = ({ isShowingMore, onToggle }) => {
const IconText = ({ children, wrapperStyles }) => {
return (
<View style={[styles.homeIconTextWrapper, wrapperStyles]}>
<AppTextLight style={styles.iconText}>
<AppText style={styles.iconText}>
{ children }
</AppTextLight>
</AppText>
</View>
)
}
@@ -76,8 +80,6 @@ export default class Home extends Component {
bleedingPredictionRange: getBleedingPredictionRange(prediction),
...fertilityStatus
}
this.cycleDays = getCycleDaysSortedByDate()
}
passTodayTo(componentName) {
@@ -96,7 +98,7 @@ export default class Home extends Component {
labels.cycleDayKnown(cycleDayNumber) :
labels.cycleDayNotEnoughInfo
const { statusText } = this.state;
const { statusText } = this.state
return (
<View flex={1}>
@@ -156,12 +158,12 @@ export default class Home extends Component {
}
{ isShowingMore &&
<View>
<AppText styles={styles.paragraph}>
{ statusText && <AppText>{ `${status}.` }</AppText> }
<Hyperlink linkStyle={styles.link} linkText={replace}>
<AppText>${links.moreToNfp.url}`}</AppText>
</Hyperlink>
<AppText styles={styles.paragraph}>
{ statusText }
</AppText>
<AppText>{links.moreAboutNfp.url}</AppText>
</Hyperlink>
</View>
}
</HomeElement>
+1 -1
View File
@@ -13,7 +13,7 @@ const labels = settingsLabels.license
export default function License({setLicense}) {
return (
<ScrollView style={styles.licensePage}>
<Hyperlink linkStyle={styles.link} linkText={replace}>
<Hyperlink linkStyle={styles.link} linkText={replace} linkDefault>
<AppText style={styles.settingsSegmentTitle}>{labels.title}</AppText>
<AppText>{labels.text}</AppText>
</Hyperlink>
+61 -23
View File
@@ -1,46 +1,84 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
Text,
TouchableOpacity
} from 'react-native'
import settingsViews from './settings'
import { menuTitles } from '../i18n/en/labels'
import styles, { iconStyles, secondaryColor } from '../styles'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
export default class Menu extends Component {
makeMenuItem = ({ title, icon, onPress}, i) => {
const styleActive = (this.props.currentPage.toLowerCase() === title) ?
{color: secondaryColor} : {}
const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => {
acc[curr] = menuTitles[curr].toLowerCase()
return acc
}, {})
const menuItems = [
{
labelKey: 'Home',
icon: 'home',
component: 'Home',
},
{
labelKey: 'Calendar',
icon: 'calendar-range',
component: 'Calendar',
},
{
labelKey: 'Chart',
icon: 'chart-line',
component: 'Chart',
},
{
labelKey: 'Stats',
icon: 'chart-pie',
component: 'Stats',
},
{
labelKey: 'Settings',
icon: 'settings',
component: 'SettingsMenu',
children: Object.keys(settingsViews),
}
]
const MenuItem = ({ icon, labelKey, active, onPress }) => {
const styleActive = active ? { color: secondaryColor } : null
return (
<TouchableOpacity
onPress={onPress}
style={styles.menuItem}
key={i.toString()}
onPress={onPress}
>
<Icon name={icon} {...iconStyles.menuIcon} {...styleActive} />
<Text style={[styles.menuText, styleActive]}>
{title}
{menuTitlesLowerCase[labelKey]}
</Text>
</TouchableOpacity>
)
}
}
goTo(componentName) {
this.props.navigate(componentName)
}
render() {
const t = this.props.titles
const Menu = ({ currentPage, navigate }) => {
return (
<View style={styles.menu}>
{[
{ title: t.Home, icon: 'home', onPress: () => this.goTo('Home') },
{ title: t.Calendar, icon: 'calendar-range', onPress: () => this.goTo('Calendar') },
{ title: t.Chart, icon: 'chart-line', onPress: () => this.goTo('Chart') },
{ title: t.Stats, icon: 'chart-pie', onPress: () => this.goTo('Stats') },
{ title: t.Settings, icon: 'settings', onPress: () => this.goTo('SettingsMenu') },
].map(this.makeMenuItem)}
{ menuItems.map(({ icon, labelKey, component, children }) => {
const isActive = (component === currentPage) ||
(children && children.indexOf(currentPage) !== -1)
return (
<MenuItem
key={labelKey}
labelKey={labelKey}
icon={icon}
active={isActive}
onPress={() => navigate(component)}
/>
)}
)}
</View >
)
}
}
export default Menu
+2 -2
View File
@@ -12,7 +12,7 @@ export default class AboutSection extends Component {
return (
<ScrollView>
<SettingsSegment title={labels.aboutSection.title}>
<Hyperlink linkStyle={styles.link} linkText={replace}>
<Hyperlink linkStyle={styles.link} linkText={replace} linkDefault>
<AppText>{labels.aboutSection.text}</AppText>
</Hyperlink>
</SettingsSegment>
@@ -23,7 +23,7 @@ export default class AboutSection extends Component {
<AppText>{labels.credits.note}</AppText>
</SettingsSegment>
<SettingsSegment title={labels.website.title}>
<Hyperlink linkStyle={styles.link}>
<Hyperlink linkStyle={styles.link} linkDefault>
<AppText>{links.website.url}</AppText>
</Hyperlink>
</SettingsSegment>
+1 -1
View File
@@ -11,7 +11,7 @@ export default class License extends Component {
return (
<ScrollView>
<View style={styles.settingsSegment}>
<Hyperlink linkStyle={styles.link} linkText={replace}>
<Hyperlink linkStyle={styles.link} linkText={replace} linkDefault>
<AppText style={styles.settingsSegmentTitle}>{`${labels.license.title} `}</AppText>
<AppText>{`${labels.license.text} `}</AppText>
</Hyperlink>
+1 -1
View File
@@ -33,7 +33,7 @@ export default class Settings extends Component {
<Icon name="info-with-circle" style={iconStyles.infoInHeading}/>
<AppText style={styles.settingsSegmentTitle}>{`${labels.preOvu.title} `}</AppText>
</View>
<Hyperlink linkStyle={styles.link} linkText={replaceUrlWithText}>
<Hyperlink linkStyle={styles.link} linkText={replaceUrlWithText} linkDefault>
<AppText>{labels.preOvu.note}</AppText>
</Hyperlink>
</SettingsSegment>
@@ -64,7 +64,6 @@ export default class ConfirmWithPassword extends Component {
}
render() {
const { password } = this.state
const labels = settings.passwordSettings
@@ -75,13 +74,28 @@ export default class ConfirmWithPassword extends Component {
value={password}
onChangeText={this.handlePasswordInput}
/>
<View style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between'
}}>
<SettingsButton
onPress={this.props.onCancel}
secondary
>
{shared.cancel}
</SettingsButton>
<SettingsButton
onPress={this.initPasswordCheck}
disabled={!password}
style={{
flex: 1,
}}
>
{shared.confirmToProceed}
</SettingsButton>
</View>
</View>
)
}
@@ -5,16 +5,22 @@ import { TouchableOpacity } from 'react-native'
import AppText from '../../app-text'
import styles from '../../../styles'
const SettingsButton = ({ children, ...props }) => {
const SettingsButton = ({ children, style, secondary, ...props }) => {
return (
<TouchableOpacity
style={[
styles.settingsButton,
props.disabled ? styles.settingsButtonDisabled : null
secondary ? null : styles.settingsButtonAccent,
props.disabled ? styles.settingsButtonDisabled : null,
style
]}
{ ...props }
>
<AppText style={styles.settingsButtonText}>
<AppText style={
secondary ?
styles.settingsButtonSecondaryText :
styles.settingsButtonText
}>
{children}
</AppText>
</TouchableOpacity>
+188
View File
@@ -0,0 +1,188 @@
import cycleModule from '../../lib/cycle'
const TemperatureSchema = {
name: 'Temperature',
properties: {
value: 'double',
exclude: 'bool',
time: {
type: 'string',
optional: true
},
note: {
type: 'string',
optional: true
}
}
}
const BleedingSchema = {
name: 'Bleeding',
properties: {
value: 'int',
exclude: 'bool'
}
}
const MucusSchema = {
name: 'Mucus',
properties: {
feeling: 'int',
texture: 'int',
value: 'int',
exclude: 'bool'
}
}
const CervixSchema = {
name: 'Cervix',
properties: {
opening: 'int',
firmness: 'int',
position: {type: 'int', optional: true },
exclude: 'bool'
}
}
const NoteSchema = {
name: 'Note',
properties: {
value: 'string'
}
}
const DesireSchema = {
name: 'Desire',
properties: {
value: 'int'
}
}
const SexSchema = {
name: 'Sex',
properties: {
solo: { type: 'bool', optional: true },
partner: { type: 'bool', optional: true },
condom: { type: 'bool', optional: true },
pill: { type: 'bool', optional: true },
iud: { type: 'bool', optional: true },
patch: { type: 'bool', optional: true },
ring: { type: 'bool', optional: true },
implant: { type: 'bool', optional: true },
diaphragm: { type: 'bool', optional: true },
none: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const PainSchema = {
name: 'Pain',
properties: {
cramps: { type: 'bool', optional: true },
ovulationPain: { type: 'bool', optional: true },
headache: { type: 'bool', optional: true },
backache: { type: 'bool', optional: true },
nausea: { type: 'bool', optional: true },
tenderBreasts: { type: 'bool', optional: true },
migraine: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const MoodSchema = {
name: 'Mood',
properties: {
happy: { type: 'bool', optional: true },
sad: { type: 'bool', optional: true },
stressed: { type: 'bool', optional: true },
balanced: { type: 'bool', optional: true },
fine: { type: 'bool', optional: true },
anxious: { type: 'bool', optional: true },
energetic: { type: 'bool', optional: true },
fatigue: { type: 'bool', optional: true },
angry: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const CycleDaySchema = {
name: 'CycleDay',
primaryKey: 'date',
properties: {
date: 'string',
temperature: {
type: 'Temperature',
optional: true
},
isCycleStart: 'bool',
bleeding: {
type: 'Bleeding',
optional: true
},
mucus: {
type: 'Mucus',
optional: true
},
cervix: {
type: 'Cervix',
optional: true
},
note: {
type: 'Note',
optional: true
},
desire: {
type: 'Desire',
optional: true
},
sex: {
type: 'Sex',
optional: true
},
pain: {
type: 'Pain',
optional: true
},
mood: {
type: 'Mood',
optional: true
}
}
}
export default {
schema: [
CycleDaySchema,
TemperatureSchema,
BleedingSchema,
MucusSchema,
CervixSchema,
NoteSchema,
DesireSchema,
SexSchema,
PainSchema,
MoodSchema
],
schemaVersion: 3,
migration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion >= 3) return
const oldBleedingDays = oldRealm.objects('CycleDay')
.filtered('bleeding != null')
.sorted('date', true)
const { isMensesStart } = cycleModule({
bleedingDaysSortedByDate: oldBleedingDays
})
const newBleedingDays = newRealm.objects('CycleDay')
.filtered('bleeding != null')
.sorted('date', true)
oldBleedingDays.forEach((day, i) => {
newBleedingDays[i].isCycleStart = isMensesStart(day)
})
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
import schema0 from './0.js'
import schema1 from './1.js'
import schema2 from './2.js'
import schema3 from './3.js'
export default [schema0, schema1, schema2]
export default [schema0, schema1, schema2, schema3]
+16
View File
@@ -86,6 +86,22 @@ export const pain = {
explainer: 'How did your body feel today?'
}
export const mood = {
categories: {
happy: 'Happy',
sad: 'Sad',
stressed: 'Stressed',
balanced: 'Balanced',
fine: 'Fine',
anxious: 'Anxious',
energetic: 'Energetic',
fatigue: 'Fatigue',
angry: 'Angry',
other: 'Other'
},
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.',
+3 -1
View File
@@ -39,7 +39,9 @@ export const headerTitles = {
NoteEditView: 'Note',
DesireEditView: 'Desire',
SexEditView: 'Sex',
PainEditView: 'Pain'
PainEditView: 'Pain',
MoodEditView: 'Mood',
InfoSymptom: 'Info'
}
export const menuTitles = {
+21
View File
@@ -0,0 +1,21 @@
export default {
gitlab: {
url: 'https://gitlab.com/bloodyhealth/drip',
text: 'GitLab'
},
email: {
url: 'mailto:bloodyhealth@mailbox.org',
text: 'email'
},
wiki: {
url: 'https://gitlab.com/bloodyhealth/drip/wikis/home',
text: 'wiki'
},
website: {
url: 'https://bloodyhealth.gitlab.io/'
},
moreAboutNfp: {
url: 'https://gitlab.com/bloodyhealth/drip/wikis/nfp/intro',
text: 'More'
},
}
+4 -24
View File
@@ -1,25 +1,4 @@
export const links = {
gitlab: {
url: 'https://gitlab.com/bloodyhealth/drip',
text: 'GitLab'
},
email: {
url: 'mailto:bl00dyhealth@mailbox.org',
text: 'email'
},
wiki: {
url: 'https://gitlab.com/bloodyhealth/drip/wikis/home',
text: 'wiki'
},
website: {
url: 'https://bloodyhealth.gitlab.io/'
},
moreToNfp: {
url: 'https://gitlab.com/bloodyhealth/drip/wikis/nfp/intro',
text: 'More'
},
}
import links from './links'
export default {
menuTitles: {
@@ -51,7 +30,8 @@ export default {
deleteOption: 'Import and delete existing',
errors: {
couldNotOpenFile: 'Could not open file',
postFix: 'No data was imported or changed'
postFix: 'No data was imported or changed',
futureEdit: 'Future dates may only contain a note, no other symptoms'
},
success: {
message: 'Data successfully imported'
@@ -128,7 +108,7 @@ export default {
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: https://www.gnu.org/licenses/gpl-3.0.html
You can contact us by email at bloodyhealth@mailbox.com.`
You can contact us by bloodyhealth@mailbox.org.`
},
version: {
title: 'Version'
+45
View File
@@ -0,0 +1,45 @@
export const symptomInfo = {
bleeding: `Tracking menstrual bleeding allows you to know the beginning and the end of a menstrual cycle.
After a while of tracking it you will get an overview of how long your cycles last on average, whether the length of your last cycles vary a lot and how many days of menstrual bleeding you usually experience.
The app allows you to track different intensity of bleeding: spotting, light, medium and heavy. Every bleeding value that is not excluded is taken into account for fertility calculation and prediction for the start of next cycles.
Excluding bleeding values is for tracking bleeding when it's not marking the start of a new cycle or the continuation of menstrual bleeding the day(s) before,
e.g. bleeding caused by miscarriage or ovulation.`,
cervix: `The cervix is located inside the body at the end of the vaginal canal, between the vagina and the uterus.
Tracking how open and how firm the cervix feels like can help determine in which phase of a menstrual cycle you are.
When you track your cervix as a secondary symptom in addition to tracking your basal body temperature, the app helps you identify in which phase of your cycle you are.`,
desire: `Sexual desire can be influenced by the menstrual cycle and its hormonal changes. The app allows you to track the intensity of your sexual desire for your mere information, it is not taken into account for any calculation.`,
mucus: `Cervical mucus can help determine in which phase of the menstrual cycle you are.
When you track your cervical mucus as a secondary symptom in addition to tracking your basal body temperature, the app helps you identify in which phase of your cycle you are.
The values you enter for both feeling and texture of your cervical mucus are combined by drip into one of five NFP-conforming values, from least to most fertile: t, ∅, f, S, and S+. Please note that drip does not yet support "parenthesis values": According to NFP rules, you can qualify a mucus value by putting parentheses around it, to indicate that it doesn't fully meet the descriptors of one of the five categories, and instead is in between. This functionality will be supported in the future.
`,
note: `Note allows you to track any extra information you want to save here.`,
pain: `Keep track of different kinds of pain you experience. They may be influenced by or have an impact on your menstrual cycles.`,
sex: `Did you know that having an orgasm can help release cramps?`,
temperature: `One of the body signs you need to track for knowing your fertility status is your body basal temperature. The body temperature changes over the course of a menstrual cycle, it rises after ovulation.
What is body basal temperature?
It's your temperature after laying still for at least 6 hours. For many this is when they are waking up in the morning after sleeping at least 6 hours and before getting up.
Which thermometer to use?
The thermometer must indicate 2 decimal places.
How to measure?
You can either measure rectally, vaginally or orally. If you chose rectal or vaginal measurement you need to measure for at least 3 minutes long. If you chose oral measurement you want to measure for at least 5 minutes long. Pick one way and stick to it.`
}
export const symptomTitle = {
bleeding: "Tracking menstrual bleeding",
cervix: "Tracking your cervix",
desire: "Tracking sexual desire",
mucus: "Tracking cervixal mucus",
note: "Notes",
pain: "Tracking pain",
sex: "Tracking sex and contraceptives",
temperature: "Tracking body basal temperature"
}
+54 -6
View File
@@ -69,8 +69,20 @@
F165D5E4692041AD900573C8 /* Prompt-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CB2405AD4098483C85C1A261 /* Prompt-Thin.ttf */; };
BF3587E45FCA48DEA13183A1 /* fontello.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BB0CB875BB2749F4A46AA5F1 /* fontello.ttf */; };
BBD61F152BE74DD7AED99DFB /* drip-icon-font.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 05154E9AE0EA4BE19F3D9E0B /* drip-icon-font.ttf */; };
603395317D2E430CB186563E /* Dosis-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E877E85FF5D24E5BA0E2FAF6 /* Dosis-Bold.ttf */; };
FB7D6EB165EA43ECBA23EC7A /* Dosis-Book.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B2B00C6F14084717B1C21702 /* Dosis-Book.ttf */; };
E4809ED51AE54B3EBFDEFDFA /* Dosis-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 753D3CC26048498884261EC6 /* Dosis-ExtraBold.ttf */; };
DC49A82960AF42C3B9168DB3 /* Dosis-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D5A481D3A46E4831A3863C68 /* Dosis-ExtraLight.ttf */; };
64083B0F5264453FAC04251B /* Dosis-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 97B6A2FB33264796A4D917E0 /* Dosis-Light.ttf */; };
9CA5B31E59524C9490DAFD48 /* Dosis-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2A1E40B398D54045B358F0DB /* Dosis-Medium.ttf */; };
082F2BD2BEA046FE8EE58763 /* Dosis-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2A26A6F601D64F3A8D4A02B0 /* Dosis-SemiBold.ttf */; };
DAA390B1EE7442D88A768596 /* drip-home-icon-font.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 94973D7B7BBA4B0FBE713A0E /* drip-home-icon-font.ttf */; };
BA7CE1E95B7843D7B0CF85FF /* drip-home-icons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 887F1D52A4684A5280CB79AA /* drip-home-icons.ttf */; };
26DAA39DDC6B436E8342239B /* Dosis-medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 096E5227936940FEBA7321FE /* Dosis-medium.ttf */; };
B9A5B9946C4C456C823B7641 /* Prompt-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */; };
5D921C348AC14944835A4D82 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */; };
2B66457E5A344222AB41C4FF /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D05637D8F19344B098982AE5 /* OpenSans-Regular.ttf */; };
71D0BCE4666A4AB8A0874B5A /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -414,8 +426,20 @@
CB2405AD4098483C85C1A261 /* Prompt-Thin.ttf */ = {isa = PBXFileReference; name = "Prompt-Thin.ttf"; path = "../assets/fonts/Prompt-Thin.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
BB0CB875BB2749F4A46AA5F1 /* fontello.ttf */ = {isa = PBXFileReference; name = "fontello.ttf"; path = "../assets/fonts/fontello.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
05154E9AE0EA4BE19F3D9E0B /* drip-icon-font.ttf */ = {isa = PBXFileReference; name = "drip-icon-font.ttf"; path = "../assets/fonts/drip-icon-font.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
E877E85FF5D24E5BA0E2FAF6 /* Dosis-Bold.ttf */ = {isa = PBXFileReference; name = "Dosis-Bold.ttf"; path = "../assets/fonts/Dosis-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
B2B00C6F14084717B1C21702 /* Dosis-Book.ttf */ = {isa = PBXFileReference; name = "Dosis-Book.ttf"; path = "../assets/fonts/Dosis-Book.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
753D3CC26048498884261EC6 /* Dosis-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Dosis-ExtraBold.ttf"; path = "../assets/fonts/Dosis-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
D5A481D3A46E4831A3863C68 /* Dosis-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Dosis-ExtraLight.ttf"; path = "../assets/fonts/Dosis-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
97B6A2FB33264796A4D917E0 /* Dosis-Light.ttf */ = {isa = PBXFileReference; name = "Dosis-Light.ttf"; path = "../assets/fonts/Dosis-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
2A1E40B398D54045B358F0DB /* Dosis-Medium.ttf */ = {isa = PBXFileReference; name = "Dosis-Medium.ttf"; path = "../assets/fonts/Dosis-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
2A26A6F601D64F3A8D4A02B0 /* Dosis-SemiBold.ttf */ = {isa = PBXFileReference; name = "Dosis-SemiBold.ttf"; path = "../assets/fonts/Dosis-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
94973D7B7BBA4B0FBE713A0E /* drip-home-icon-font.ttf */ = {isa = PBXFileReference; name = "drip-home-icon-font.ttf"; path = "../assets/fonts/drip-home-icon-font.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
887F1D52A4684A5280CB79AA /* drip-home-icons.ttf */ = {isa = PBXFileReference; name = "drip-home-icons.ttf"; path = "../assets/fonts/drip-home-icons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
096E5227936940FEBA7321FE /* Dosis-medium.ttf */ = {isa = PBXFileReference; name = "Dosis-medium.ttf"; path = "../assets/fonts/Dosis-medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Prompt-ExtraLight.ttf"; path = "../assets/fonts/Prompt-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
D05637D8F19344B098982AE5 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; name = "OpenSans-Regular.ttf"; path = "../assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; name = "OpenSans-SemiBold.ttf"; path = "../assets/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -724,8 +748,20 @@
CB2405AD4098483C85C1A261 /* Prompt-Thin.ttf */,
BB0CB875BB2749F4A46AA5F1 /* fontello.ttf */,
05154E9AE0EA4BE19F3D9E0B /* drip-icon-font.ttf */,
E877E85FF5D24E5BA0E2FAF6 /* Dosis-Bold.ttf */,
B2B00C6F14084717B1C21702 /* Dosis-Book.ttf */,
753D3CC26048498884261EC6 /* Dosis-ExtraBold.ttf */,
D5A481D3A46E4831A3863C68 /* Dosis-ExtraLight.ttf */,
97B6A2FB33264796A4D917E0 /* Dosis-Light.ttf */,
2A1E40B398D54045B358F0DB /* Dosis-Medium.ttf */,
2A26A6F601D64F3A8D4A02B0 /* Dosis-SemiBold.ttf */,
94973D7B7BBA4B0FBE713A0E /* drip-home-icon-font.ttf */,
887F1D52A4684A5280CB79AA /* drip-home-icons.ttf */,
096E5227936940FEBA7321FE /* Dosis-medium.ttf */,
5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */,
673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */,
D05637D8F19344B098982AE5 /* OpenSans-Regular.ttf */,
644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */,
);
name = Resources;
sourceTree = "<group>";
@@ -761,9 +797,9 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
2B572382D4504B8FB4B9D251 /* Embed Frameworks */,
0909337B049F4ECB933412B2 /* Build NodeJS Mobile Native Modules */,
B6AAD324BDBA4E92AB5627B3 /* Sign NodeJS Mobile Native Modules */,
3DE3CFA1BEC54E9D9B5B9D47 /* Remove NodeJS Mobile Framework Simulator Strips */,
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */,
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */,
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */,
);
buildRules = (
);
@@ -1202,8 +1238,20 @@
F165D5E4692041AD900573C8 /* Prompt-Thin.ttf in Resources */,
BF3587E45FCA48DEA13183A1 /* fontello.ttf in Resources */,
BBD61F152BE74DD7AED99DFB /* drip-icon-font.ttf in Resources */,
603395317D2E430CB186563E /* Dosis-Bold.ttf in Resources */,
FB7D6EB165EA43ECBA23EC7A /* Dosis-Book.ttf in Resources */,
E4809ED51AE54B3EBFDEFDFA /* Dosis-ExtraBold.ttf in Resources */,
DC49A82960AF42C3B9168DB3 /* Dosis-ExtraLight.ttf in Resources */,
64083B0F5264453FAC04251B /* Dosis-Light.ttf in Resources */,
9CA5B31E59524C9490DAFD48 /* Dosis-Medium.ttf in Resources */,
082F2BD2BEA046FE8EE58763 /* Dosis-SemiBold.ttf in Resources */,
DAA390B1EE7442D88A768596 /* drip-home-icon-font.ttf in Resources */,
BA7CE1E95B7843D7B0CF85FF /* drip-home-icons.ttf in Resources */,
26DAA39DDC6B436E8342239B /* Dosis-medium.ttf in Resources */,
B9A5B9946C4C456C823B7641 /* Prompt-ExtraLight.ttf in Resources */,
5D921C348AC14944835A4D82 /* OpenSans-Light.ttf in Resources */,
2B66457E5A344222AB41C4FF /* OpenSans-Regular.ttf in Resources */,
71D0BCE4666A4AB8A0874B5A /* OpenSans-SemiBold.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1253,7 +1301,7 @@
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
0909337B049F4ECB933412B2 /* Build NodeJS Mobile Native Modules */ = {
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1321,7 +1369,7 @@ fi
popd
";
};
B6AAD324BDBA4E92AB5627B3 /* Sign NodeJS Mobile Native Modules */ = {
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1381,7 +1429,7 @@ find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -del
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
";
};
3DE3CFA1BEC54E9D9B5B9D47 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
+12
View File
@@ -72,8 +72,20 @@
<string>Prompt-Thin.ttf</string>
<string>fontello.ttf</string>
<string>drip-icon-font.ttf</string>
<string>Dosis-Bold.ttf</string>
<string>Dosis-Book.ttf</string>
<string>Dosis-ExtraBold.ttf</string>
<string>Dosis-ExtraLight.ttf</string>
<string>Dosis-Light.ttf</string>
<string>Dosis-Medium.ttf</string>
<string>Dosis-SemiBold.ttf</string>
<string>drip-home-icon-font.ttf</string>
<string>drip-home-icons.ttf</string>
<string>Dosis-medium.ttf</string>
<string>Prompt-ExtraLight.ttf</string>
<string>OpenSans-Light.ttf</string>
<string>OpenSans-Regular.ttf</string>
<string>OpenSans-SemiBold.ttf</string>
</array>
</dict>
</plist>
+14
View File
@@ -7,6 +7,8 @@ import {
updateCycleStartsForAllCycleDays
} from '../../db'
import getColumnNamesForCsv from './get-csv-column-names'
import { LocalDate } from 'js-joda'
import labels from '../../i18n/en/settings'
export default async function importCsv(csv, deleteFirst) {
const parseFuncs = {
@@ -48,6 +50,7 @@ export default async function importCsv(csv, deleteFirst) {
//remove symptoms where all fields are null
putNullForEmptySymptoms(cycleDays)
throwIfFutureData(cycleDays)
if (deleteFirst) {
tryToImportWithDelete(cycleDays)
@@ -88,3 +91,14 @@ function getDbType(modelProperties, path) {
const modelName = modelProperties[path[0]].objectType
return getDbType(schema[modelName], path.slice(1))
}
function throwIfFutureData(cycleDays) {
const today = LocalDate.now().toString()
for (const i in cycleDays) {
const day = cycleDays[i]
// notes are allowed for future dates but everything else isn't
if (day.date > today && Object.keys(day).some(symptom => symptom != 'date' && symptom != 'note')) {
throw new Error(labels.import.errors.futureEdit)
}
}
}
+39 -20
View File
@@ -13,8 +13,10 @@ export const shadesOfRed = [
export const cycleDayColor = '#29287f'
export const periodColor = '#802249'
const fontRegular = 'Prompt-Light'
const fontLight = 'Prompt-Thin'
const headerFont = 'Prompt-ExtraLight'
const textFont = 'OpenSans-Light'
const textFontBold = 'OpenSans-SemiBold'
const regularSize = 16
const hintSize = 14
@@ -27,18 +29,15 @@ const colorInActive = '#666666'
export default StyleSheet.create({
appText: {
color: 'black',
fontFamily: fontRegular,
fontSize: regularSize
},
appTextLight: {
color: 'black',
fontFamily: fontLight,
fontSize: regularSize
fontFamily: textFont,
fontSize: regularSize,
letterSpacing: 0.5
},
actionHint: {
color: secondaryColor,
fontFamily: fontRegular,
fontFamily: textFont,
fontSize: hintSize,
fontWeight: 'bold',
margin: defaultIndentation
},
paragraph: {
@@ -46,6 +45,7 @@ export default StyleSheet.create({
},
emphasis: {
fontWeight: 'bold',
fontFamily: textFontBold
},
link: {
color: cycleDayColor,
@@ -69,20 +69,20 @@ export default StyleSheet.create({
},
dateHeader: {
fontSize: 20,
fontFamily: fontLight,
fontFamily: headerFont,
color: fontOnPrimaryColor,
textAlign: 'center',
},
headerText: {
fontSize: 30,
fontFamily: fontLight,
fontFamily: headerFont,
color: fontOnPrimaryColor,
textAlign: 'center',
paddingBottom: 4
},
accentCircle: {
borderColor: secondaryColor,
borderWidth: 0.5,
borderWidth: 1,
width: 40,
height: 40,
borderRadius: 100,
@@ -133,7 +133,7 @@ export default StyleSheet.create({
},
homeCircle: {
borderRadius: 100,
borderWidth: 0.7,
borderWidth: 2.3,
width: 80,
height: 80,
alignItems: 'center',
@@ -155,7 +155,7 @@ export default StyleSheet.create({
fontSize: 15,
color: fontOnPrimaryColor,
textAlign: 'center',
fontFamily: fontLight
fontFamily: headerFont
},
symptomViewHeading: {
fontSize: 20,
@@ -229,6 +229,9 @@ export default StyleSheet.create({
navigationArrow: {
padding: 20
},
hiddenIcon: {
padding: 20
},
menu: {
backgroundColor: primaryColor,
alignItems: 'center',
@@ -243,10 +246,11 @@ export default StyleSheet.create({
},
menuText: {
color: fontOnPrimaryColor,
fontFamily: fontLight
fontFamily: headerFont
},
menuTextInActive: {
color: colorInActive
color: colorInActive,
fontFamily: headerFont
},
temperatureTextInput: {
fontSize: 20,
@@ -272,18 +276,22 @@ export default StyleSheet.create({
marginTop: defaultTopMargin,
marginHorizontal: defaultIndentation,
padding: 7,
fontFamily: textFont
},
settingsSegmentLast: {
marginBottom: defaultTopMargin,
},
settingsSegmentTitle: {
fontWeight: 'bold'
fontWeight: 'bold',
fontFamily: textFontBold
},
settingsButton: {
backgroundColor: secondaryColor,
padding: 10,
alignItems: 'center',
margin: 10,
margin: 10
},
settingsButtonAccent: {
backgroundColor: secondaryColor
},
settingsButtonDisabled: {
backgroundColor: colorInActive
@@ -291,6 +299,10 @@ export default StyleSheet.create({
settingsButtonText: {
color: fontOnPrimaryColor
},
settingsButtonSecondaryText: {
color: secondaryColor
},
statsRow: {
flexDirection: 'row',
width: '100%'
@@ -402,6 +414,9 @@ export default StyleSheet.create({
marginTop: 20,
color: 'grey'
},
infoButton: {
paddingVertical: 20
},
licensePage: {
paddingVertical: 20,
paddingHorizontal: 10
@@ -442,5 +457,9 @@ export const iconStyles = {
infoInHeading: {
marginRight: 5,
color: 'black'
},
hiddenIcon: {
size: 20,
display: 'none'
}
}