Compare commits

...

132 Commits

Author SHA1 Message Date
Julia Friesel e8b0d60fdd release: 0.1905.29-beta 2019-05-29 13:30:46 +02:00
bl00dymarie 5e32f4d7dc Merge branch 'Bugfix-password-crash' into 'master'
Makes sure style is an array for it to spread

See merge request bloodyhealth/drip!230
2019-05-29 10:58:07 +00:00
emelko 8008c8e2cc Makes sure style is an array for it to spread 2019-05-29 12:25:00 +02:00
Julia Friesel 2cc5ffb50f Run gradle clean before assembleRelease 2019-05-29 08:16:09 +02:00
emelko e7949377a2 release: 0.1905.28-beta 2019-05-28 16:06:56 +02:00
bl00dymarie 592f2b3e16 Merge branch '386-bug-fertility-text-is-missing-on-homescreen' into 'master'
Make sure fertility text is displayed on home screen

Closes #386

See merge request bloodyhealth/drip!228
2019-05-28 08:17:12 +00:00
Julia Friesel 7506e911f7 Merge branch '377-put-donation-button-in-app' into 'master'
Resolve "put donation button in app"

Closes #377

See merge request bloodyhealth/drip!229
2019-05-28 08:08:50 +00:00
emelko 09bbfe8a7d Displays all the text for Home Elements;
Shortens margin btw Home Elements;
Adds missing "visit" to text
2019-05-28 09:38:34 +02:00
Julia Friesel bb1bd949c3 Add donation section to about 2019-05-28 09:03:43 +02:00
bl00dymarie 406b71250e Merge branch '384-bug-header-title-in-settings-moves-up' into 'master'
Gets rid of hidden icon in back button header

Closes #384

See merge request bloodyhealth/drip!227
2019-05-28 06:39:59 +00:00
bl00dymarie 6228cd8953 Merge branch 'Space-between-info-icon-and-text' into 'master'
Make uniform info icon and leave some space

See merge request bloodyhealth/drip!223
2019-05-28 06:38:14 +00:00
bl00dymarie 2623fedec9 Merge branch '382-bug-cervical-mucus-title-broken-on-symptom-view' into 'master'
Changes fontSize of titles in SymptomBoxes;

Closes #382

See merge request bloodyhealth/drip!226
2019-05-28 06:37:44 +00:00
bl00dymarie bfdad895ce Merge branch '375-change-description-of-mucus-and-cervix-on-cycle-day-overview' into 'master'
Adds more descriptive mucus and cervix labels for cycle day overview

Closes #375

See merge request bloodyhealth/drip!221
2019-05-28 06:36:35 +00:00
emelko dce8138e12 Gets rid of hidden icon in back button header 2019-05-27 23:42:37 +02:00
emelko 7864ebfc41 Adds subcategories of cevix and mucus as labels 2019-05-27 22:42:23 +02:00
emelko 442bcc5cee Adds subcategory names to the selected options 2019-05-27 17:49:39 +02:00
emelko e8aac6d41e Changes fontSize of titles in SymptomBoxes;makes sure it stays in 1 line 2019-05-27 16:51:38 +02:00
Julia Friesel 2bbe737101 Fix delete button 2019-05-26 19:27:55 +02:00
Julia Friesel 8856b3f1bc Merge branch '381-real-autosave' into 'master'
Resolve "real autosave"

Closes #381

See merge request bloodyhealth/drip!225
2019-05-26 12:23:16 +00:00
emelko aadc36f45c Get rid of extra styling for non functional info icon 2019-05-26 13:04:20 +02:00
Julia Friesel 6c56b6f83c Clean up 2019-05-26 12:47:22 +02:00
Julia Friesel 018ec3bcda Add autosave for temperate 2019-05-26 09:04:51 +02:00
Julia Friesel bc358bd9ed Auto save whenever symptom view updates 2019-05-26 07:39:15 +02:00
Julia Friesel f8eef66810 Merge branch '359-delete-data-bug-fix' into 'master'
Resolve "Bug: Settings > Manage your data > Delete app data"

Closes #359

See merge request bloodyhealth/drip!224
2019-05-26 04:08:54 +00:00
bl00dymarie ac2bad1de0 Merge branch '376-consider-rename-mucus-into-cervical-mucus' into 'master'
Specifying mucus as cervical mucus

Closes #376

See merge request bloodyhealth/drip!222
2019-05-25 21:38:47 +00:00
mashazyu 6cbfcb9d64 Fixes delete data bug 2019-05-25 17:45:02 +02:00
emelko 39df9c2ec0 Make uniform info icon and leave some space 2019-05-25 12:37:14 +02:00
emelko 138f1d28b3 Align droplet text on homescreen 2019-05-25 12:08:42 +02:00
bl00dymarie e2abc4dbeb Merge branch 'marie-move-info-button-out-of-header-to-body' into 'master'
Move info button out of header to body

See merge request bloodyhealth/drip!217
2019-05-25 09:35:31 +00:00
Julia Friesel 0cfc85933c Make info modal only as big as it needs to be 2019-05-25 09:17:39 +02:00
Julia Friesel c6fd5e6db2 Make sure info icon is always well pressable 2019-05-25 07:09:36 +02:00
emelko 8e57febf9e Specifying mucus as cervical mucus 2019-05-24 22:50:40 +02:00
bl00dymarie ed38a5b450 Merge branch '362-rearrange-home-screen-elements' into 'master'
Resolve "Rearrange home screen elements"

Closes #362

See merge request bloodyhealth/drip!220
2019-05-24 10:28:33 +00:00
Julia Friesel 16b3199892 Make sure drop text is always positioned correctly 2019-05-24 11:23:12 +02:00
emelko d13a776970 Position icon text for droplet 2019-05-23 22:07:46 +02:00
emelko 418b9c0f5d Styling for homescreen elements to breathe 2019-05-23 17:23:36 +02:00
Julia Friesel c35f333daa Merge branch 'import-alert-buttons-order' into 'master'
Changes order of buttons in the import alert

Closes #372

See merge request bloodyhealth/drip!219
2019-05-22 15:40:40 +00:00
Julia Friesel 668acb4afe simple way to rearrange home screen 2019-05-22 17:30:53 +02:00
emelko ac0690ec9f Set show more on homescreen to default and get rid of more/less switch 2019-05-22 17:27:31 +02:00
Julia Friesel cef2e850d7 Don't render delete icon, instead of just setting it invisible 2019-05-21 17:32:51 +02:00
Julia Friesel 4b469f2f49 Make isDeleteIconActive more readable 2019-05-21 17:08:26 +02:00
Julia Friesel b0b2e8ceb5 Merge branch '365-show-incomplete-symptom-in-day-view' into 'master'
Resolve "show incomplete symptom in day view"

Closes #365

See merge request bloodyhealth/drip!218
2019-05-21 14:22:04 +00:00
Julia Friesel 82a9bf0a0f Update README.md 2019-05-21 14:20:45 +00:00
Sofiya Tepikin 417083b0c1 Changes order of buttons in the import alert 2019-05-20 17:16:05 +02:00
Julia Friesel 25ed0d168e Remove formatting improvements that clutter up the diff 2019-05-20 15:17:27 +02:00
Julia Friesel d7b599d03b Fix cervix value display on overview 2019-05-20 06:44:34 +02:00
Julia Friesel 3f52cae04b Fix mucus value display on overview 2019-05-20 06:32:58 +02:00
Julia Friesel 59c3636139 Don't show delete icon just because symptom info is open 2019-05-20 06:08:46 +02:00
Julia Friesel 472d793627 Clean up markup 2019-05-19 19:00:05 +02:00
Julia Friesel 43b98ed9a5 Merge branch '364-delete-or-clear-button-that-is-visible-as-soon-as-any-value-is-selected-saved' into 'master'
Resolve "delete or clear button that is visible as soon as any value is selected/saved"

Closes #364

See merge request bloodyhealth/drip!216
2019-05-19 16:52:02 +00:00
Julia Friesel 69d2517dd2 Use own modal instead of alert for symptom info 2019-05-19 18:44:12 +02:00
Julia Friesel 0f6567e66b Merge branch '345-move-info-button-out-of-header-to-body' into marie-move-info-button-out-of-header-to-body 2019-05-19 17:53:14 +02:00
emelko c81f7d6291 Gets rid of trailing spaces 2019-05-19 13:09:21 +02:00
emelko e9c18add5e Gets rid of old info symptom screen 2019-05-19 12:57:07 +02:00
emelko 4071c30b8b [WIP] Adds info button to body as alert for:
* mood, pain, temperature
2019-05-19 12:56:52 +02:00
emelko ff1afcb8dc Adds info button to the body as alert 2019-05-19 12:56:08 +02:00
Julia Friesel d2c0891a68 Try out moving it to body 2019-05-18 06:40:36 +02:00
Julia Friesel a557733d30 For temperature, only show delete button when certain fields active 2019-05-17 16:47:31 +02:00
Julia Friesel 0f7ab6c803 Let symptom views overwrite isDeleteIconActive method 2019-05-17 16:09:49 +02:00
Julia Friesel b7088c44f2 Show or hide delete button based on entered data 2019-05-17 12:07:54 +02:00
Julia Friesel 00294ff6f6 Remove unused style 2019-05-17 11:54:50 +02:00
Julia Friesel 1d61675ef7 Ask before deleting entry 2019-05-17 11:01:50 +02:00
Julia Friesel 6a8d22f9f0 Change icon to trash can 2019-05-17 10:56:36 +02:00
emelko 6811b5a692 Replace info icon in header with delete 2019-05-16 16:10:17 +02:00
Julia Friesel 9ff37e2874 Merge branch '371-bug-back-navigation-doesn-t-wait-for-temperature-range-warning' into 'master'
Await alert result before navigating back

Closes #371 and #373

See merge request bloodyhealth/drip!215
2019-05-14 17:29:15 +00:00
Julia Friesel f022fb6b8a Await alert result before navigating back 2019-05-14 13:37:35 +02:00
Julia Friesel 37564621e0 Merge branch '363-alert-for-cervix-and-mucus-when-trying-to-navigate-away-and-one-value-is-missing' into 'master'
Basic auto save

Closes #363

See merge request bloodyhealth/drip!214
2019-05-13 18:56:40 +00:00
Julia Friesel 5057e1a38e Address MR change requests 2019-05-13 20:41:57 +02:00
Julia Friesel e781919434 Reset inadvertently changed file 2019-05-13 20:34:13 +02:00
Julia Friesel 3715e0c4d2 Filter incomplete mucus values in sympto adapter 2019-05-13 20:30:25 +02:00
Julia Friesel 3ff996e946 Don't crash on missing temperature value 2019-05-13 19:07:46 +02:00
Julia Friesel 6011bd9208 Fix linter problems 2019-05-13 12:55:28 +02:00
Julia Friesel d1e16abe34 Make header back arrow function for auto save 2019-05-13 12:55:28 +02:00
Julia Friesel bc13f5c1e6 Remove action button footer from symptom views 2019-05-13 12:55:28 +02:00
Julia Friesel ecf3ebf16d When nothing entered, delete entry 2019-05-13 12:55:28 +02:00
Julia Friesel 08fd3afc34 Add symptom view component with back button listener 2019-05-13 12:55:28 +02:00
Julia Friesel 2528c03315 Remove save button from footer 2019-05-13 12:55:28 +02:00
Julia Friesel d322e557a3 Remove unused line 2019-05-13 12:55:28 +02:00
Julia Friesel 9fd17d769e Make saving incomplete value possible 2019-05-13 12:55:28 +02:00
Julia Friesel ca68186351 Filter out incomplete cervix value days in sympto adapter 2019-05-13 12:49:37 +02:00
Julia Friesel 8d6f5d637b Update sympto 2019-05-13 12:49:37 +02:00
Julia Friesel c6790fe271 Add migration making mucus and cervix values optional 2019-05-13 12:49:37 +02:00
Julia Friesel 6402370eaf Don't compute nfp mucus value when data missing 2019-05-13 12:49:37 +02:00
Julia Friesel f1ca709f25 Add test for missing mucus vaues 2019-05-13 12:49:37 +02:00
tina 51160b033b Merge branch 'remove_default_permissions' into 'master'
excludes internet and system alert window from default permission

Closes #337

See merge request bloodyhealth/drip!212
2019-05-11 10:35:06 +00:00
tina c4884f8f8f excludes internet and system alert window from default permission 2019-05-10 13:28:59 +02:00
tina 647567abd2 Merge branch '250-check-vertical-line-width-in-chart' into 'master'
Fix line width in chart

Closes #250

See merge request bloodyhealth/drip!205
2019-05-10 11:19:09 +00:00
bl00dymarie f123dbf730 Merge branch '342-bleeding-prediction-on-homescreen' into 'master'
Resolve "Bleeding prediction on homescreen"

Closes #342

See merge request bloodyhealth/drip!202
2019-05-10 11:15:57 +00:00
emelko bc0d36ed54 Adds comment for bleeding prediction ranges 2019-05-10 13:11:36 +02:00
tina 745f874ccf Merge branch 'action_button_as_buttons' into 'master'
makes the action button footer more like buttons

Closes #318

See merge request bloodyhealth/drip!208
2019-05-10 11:02:33 +00:00
emelko ea36d4ec7a Change if statement with conditional operator 2019-05-10 12:59:11 +02:00
Julia Friesel bb5623a621 Merge branch '355-make-update-version-script' into 'master'
Resolve "make release script"

Closes #355

See merge request bloodyhealth/drip!209
2019-05-10 10:38:06 +00:00
tina cf6c601c47 changes action buttons color to teal, rounded corners for buttons in settings 2019-05-10 12:11:09 +02:00
Julia Friesel 8efe906e87 Filter release commits from changelog 2019-05-06 12:21:28 +02:00
Julia Friesel 3924c04e56 Add update-changelog script 2019-05-06 12:21:21 +02:00
Julia Friesel a9401d4a0f Remove square brackets from CHANGELOG. They are parsed as links 2019-05-06 12:21:09 +02:00
Julia Friesel 53ec882e53 Add commit-release and npm scripts 2019-05-06 12:21:09 +02:00
Julia Friesel 7387db4a69 Add update version script from manyverse 2019-05-06 12:21:01 +02:00
Julia Friesel 9ef84f9b31 Update RN to 58 2019-05-06 10:39:48 +02:00
Julia Friesel f9c928d45c Update eslint 2019-05-06 09:59:22 +02:00
Julia Friesel 70860860d5 Merge branch '238-loading-screen-for-data-import' into 'master'
Add loading screen for data import

Closes #238

See merge request bloodyhealth/drip!206
2019-05-06 06:44:54 +00:00
Julia Friesel 6a98b28427 Remove superfluous try/catch 2019-05-06 08:42:43 +02:00
Julia Friesel 9ee7819462 Rename methods 2019-05-06 08:37:26 +02:00
Julia Friesel 5a320e148c Merge branch '348-password-screen' into 'master'
Removes logo and adds header on the main login screen

Closes #348

See merge request bloodyhealth/drip!211
2019-05-03 17:51:47 +00:00
Julia Friesel 767aa23841 Merge branch 'app-page-rendering-update' into 'master'
Improves readability of app page rendering

See merge request bloodyhealth/drip!210
2019-05-03 17:49:00 +00:00
mashazyu 227aa69677 Add loading screen to data import 2019-05-02 18:53:44 +02:00
mashazyu 722c372f2d Fix line width in chart 2019-05-02 14:03:30 +02:00
tina 92e02c48dc Merge branch '347-maxlength-in-temp-input' into 'master'
adds maxLength to temperature input field

Closes #347

See merge request bloodyhealth/drip!207
2019-05-01 16:46:18 +00:00
Sofiya Tepikin 3449746ead Improves readability of app page rendering 2019-05-01 11:26:50 +02:00
Julia Friesel ee52f75927 Update README.md 2019-05-01 05:30:10 +00:00
tina 9c8cc8de2e makes the action button footer more like buttons 2019-05-01 00:04:38 +02:00
tina ea279338e0 adds maxLength to temperature input field 2019-04-30 23:24:44 +02:00
mashazyu f15dc75908 Removes logo and adds header on the main login screen 2019-04-30 20:23:09 +02:00
Julia Friesel c00684ebbb Adjust version name 2019-04-29 13:47:58 +02:00
Julia Friesel d0f4ae5109 Don't build for x86 2019-04-29 12:00:57 +02:00
Julia Friesel 187fbef207 Upgrade nodejs-mobile-rn to latest 2019-04-29 10:28:47 +02:00
Julia Friesel 4c655b13c3 Merge branch 'master' of gitlab.com:bloodyhealth/drip 2019-04-29 08:48:10 +02:00
Julia Friesel 2276b37741 Remove unneeded maven repo and upgrade gradle to 4.10 2019-04-29 08:47:58 +02:00
bl00dymarie 7c5d0b9ae2 Merge branch '344-lowercase-for-symptom-values' into 'master'
Lowercase values for sex, pain and mood

Closes #344

See merge request bloodyhealth/drip!204
2019-04-25 10:14:11 +00:00
emelko b8b2d3d2bf Lowercase values for sex, pain and mood 2019-04-25 11:48:03 +02:00
emelko 3ec4d2218e Adds getTime function for bleedingPredictions reuse; minor style formatting 2019-04-25 11:41:41 +02:00
emelko dc5b34546a Renames function to say what it 'does' 2019-04-25 11:01:33 +02:00
emelko 6577038f22 Rename to predictedBleeding 2019-04-25 10:58:04 +02:00
emelko 93abaf99be Nicer formatting for past bleeding prediction 2019-04-24 12:53:04 +02:00
emelko dd24e6058f Fixes prediction range in drop on homescreen 2019-04-23 20:44:59 +02:00
bl00dymarie ded3a79bbc Merge branch 'temperature-screen-improvement' into 'master'
temperature screen styling update

See merge request bloodyhealth/drip!196
2019-04-23 17:19:49 +00:00
bl00dymarie ab0970e33f Merge branch 'Remove-excessive-permissions' into 'master'
Removes permissions not required for debug or production

See merge request bloodyhealth/drip!201
2019-04-23 17:09:27 +00:00
bl00dymarie 49d5d77a82 Merge branch 'new-tag' into 'master'
0.0.3

See merge request bloodyhealth/drip!200
2019-04-23 06:55:52 +00:00
emelko 0cf8820e94 Removes permissions not required for debug or production 2019-04-22 16:43:13 +02:00
emelko 1ef3585cdc 0.0.3 2019-04-17 16:35:33 +02:00
bl00dymarie 25ff6ca01f Update CHANGELOG.md 2019-04-17 14:33:02 +00:00
mashazyu c22bff5948 temperature screen styling update 2019-04-14 22:43:57 +02:00
68 changed files with 4528 additions and 4484 deletions
+37 -6
View File
@@ -1,24 +1,55 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.1905.29-beta
- Auto save functionality for all symptoms
- Add donation section to about
- Styling fixes
- Clearer labels on cycle day overview
- Rename mucus to cervical mucus
- Set show more on homescreen to default and get rid of more/less switch
- Add loading screen to data import
- Fix line width in chart
- Removes logo and adds header on the main login screen
- Nicer formatting for past bleeding prediction
- Fixes prediction range in drop on homescreen
- Removes permissions not required for debug or production
- Temperature screen styling update
## v0.0.3 - 2019-04-17
## [0.0.2] - 2019-04-16
## Second updated beta release version
### Changes
- Removes Google services from notification library and use fork of react-native-push-notification: <https://github.com/github:jfr3000/react-native-push-notification>
### Fixed
- Button functionality in settings for password
## v0.0.2 - 2019-04-09
## Second updated beta release version
### Changes
- First day of the week in calendar is now Monday instead of Sunday
- Minor styling consistency
### Fixed
- Typos
- Bleeding value is visible in shortcut from Homescreen
- Delete button for sex, pain and mood
- Dates on chart
## [0.0.1] - 2019-02-15
## v0.0.1 - 2019-02-15
## First beta release version
### Added (list of core functionality)
- you can track your menstrual bleeding
- you can track symptoms related to natural family planning (nfp), i.e. basal temperature and mucus or cervix
- you can use nfp symptoms for fertility awareness (drip implements the sympto-thermal method)
@@ -28,4 +59,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- you can see basic stats about your cycle lengths
- you can encrypt your data and protect it with a password
- you can import and export your data in a nice format (csv)
- you can set reminders (daily reminder for taking your temperature or once per cycle for your next period
- you can set reminders (daily reminder for taking your temperature or once per cycle for your next period
+4 -8
View File
@@ -3,7 +3,7 @@
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.
The app is built in React Native and currently developed for Android.
Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/drip/blob/master/CONTRIBUTING.md).
@@ -11,11 +11,11 @@ Here --> you will find our [contributing guide](https://gitlab.com/bloodyhealth/
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:
1. Make sure you are running Node 10 (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
$ nvm install v10
```
1. Clone this repository:
@@ -75,10 +75,6 @@ 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
```
### [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:
```
@@ -96,7 +92,7 @@ More information about how the app calculates fertility status and bleeding pred
## 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.
1. We use [fontello](http://fontello.com/) to create icon fonts for us. You need to upload the complete set of tracking icons (bleeding, cervical 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:
+13 -13
View File
@@ -102,10 +102,10 @@ android {
applicationId "com.drip"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
versionCode 3
versionName "0.1905.29-beta"
ndk {
abiFilters "armeabi-v7a", "x86"
abiFilters "armeabi-v7a"
}
}
signingConfigs {
@@ -123,7 +123,7 @@ android {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86"
include "armeabi-v7a", "arm64-v8a"
}
}
buildTypes {
@@ -138,7 +138,7 @@ android {
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2]
def versionCodes = ["armeabi-v7a":1, "arm64-v8a":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
@@ -149,14 +149,14 @@ android {
}
dependencies {
compile project(':nodejs-mobile-react-native')
compile project(':react-native-restart')
compile project(':react-native-push-notification')
compile project(':react-native-vector-icons')
compile project(':react-native-fs')
compile project(':react-native-document-picker')
compile project(':react-native-share')
compile project(':realm')
implementation project(':realm')
implementation project(':react-native-vector-icons')
implementation project(':react-native-share')
implementation project(':react-native-restart')
implementation project(':react-native-push-notification')
implementation project(':react-native-fs')
implementation project(':react-native-document-picker')
implementation project(':nodejs-mobile-react-native')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
compile "com.facebook.react:react-native:+" // From node_modules
+3
View File
@@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission tools:node="remove" android:name="android.permission.READ_PHONE_STATE" />
<uses-permission tools:node="remove" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission tools:node="remove" android:name="android.permission.READ_EXTERNAL_STORAGE" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.drip"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission tools:node="remove" android:name="android.permission.INTERNET" />
</manifest>
+4 -9
View File
@@ -29,19 +29,14 @@ allprojects {
}
ext {
buildToolsVersion = "26.0.3"
buildToolsVersion = "27.0.3"
minSdkVersion = 16
compileSdkVersion = 26
targetSdkVersion = 26
supportLibVersion = "26.1.0"
compileSdkVersion = 27
targetSdkVersion = 27
supportLibVersion = "27.1.1"
}
subprojects {project ->
buildscript {
repositories {
maven { url = 'https://dl.bintray.com/android/android-tools/' }
}
}
// https://stackoverflow.com/questions/52613089/getting-verifyreleaseresources-error-after-upgrading-react-native
afterEvaluate {
if (project.hasProperty("android")) {
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
import React from 'react'
import { View } from 'react-native'
import AppText from './app-text'
import { shared } from '../i18n/en/labels'
const AppLoadingView = () => {
return (
<View flex={1}>
<View style={{flex:1, justifyContent: 'center'}}>
<AppText style={{alignSelf: 'center'}}>{shared.loading}</AppText>
</View>
</View>
)
}
export default AppLoadingView
+26
View File
@@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { TextInput } from 'react-native'
import styles from '../styles'
export default function AppTextInput({ style, ...props }) {
if (!Array.isArray(style)) style = [style]
return (
<TextInput
style={[styles.textInputField, ...style]}
autoFocus={props.autoFocus}
onChangeText={props.onChangeText}
value={props.value}
placeholder={props.placeholder}
{...props}
/>
)
}
AppTextInput.propTypes = {
secureTextEntry: PropTypes.bool
}
AppTextInput.defaultProps = {
style: []
}
-13
View File
@@ -18,19 +18,6 @@ export default function AppText(props) {
)
}
export function ActionHint(props) {
if(props.isVisible) {
return (
<AppText
style={[styles.actionHint, props.style]}>
{props.children}
</AppText>
)
} else {
return null
}
}
export function SymptomSectionHeader(props) {
return (
<AppText style={styles.symptomViewHeading}>
+9 -29
View File
@@ -11,7 +11,6 @@ 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
@@ -22,7 +21,6 @@ const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
}, {})
const HOME_PAGE = 'Home'
const INFO_SYMPTOM_PAGE = 'InfoSymptom'
const CYCLE_DAY_PAGE = 'CycleDay'
const SETTINGS_MENU_PAGE = 'SettingsMenu'
@@ -49,7 +47,7 @@ export default class App extends Component {
if (this.isMenuItem()) {
this.menuOrigin = currentPage
}
if (!this.isSymptomView() && !this.isInfoSymptomView()) {
if (!this.isSymptomView()) {
this.originForSymptomView = currentPage
}
this.setState({ currentPage: pageName, currentProps: props })
@@ -66,10 +64,6 @@ export default class App extends Component {
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_PAGE)
}
@@ -84,10 +78,6 @@ export default class App extends Component {
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)
}
@@ -104,42 +94,32 @@ export default class App extends Component {
Calendar,
CycleDay,
Chart,
InfoSymptom,
SettingsMenu,
...settingsViews,
Stats,
...symptomViews
}
const page = pages[currentPage]
const Page = pages[currentPage]
const title = headerTitlesLowerCase[currentPage]
return (
<View style={{flex: 1}}>
{this.isDefaultView() &&
<Header title={title} />
}
{(this.isInfoSymptomView() || this.isSettingsView()) &&
{(this.isSettingsView()) &&
<Header
title={title}
showBackButton={true}
goBack={this.handleBackButtonPress}
/>
}
{this.isSymptomView() &&
<Header
title={title}
isSymptomView={true}
goBack={this.handleBackButtonPress}
date={currentProps.date}
goToSymptomInfo={() => this.navigate(INFO_SYMPTOM_PAGE, {
symptomView: currentPage,
...currentProps
})}
/>}
{React.createElement(page, {
navigate: this.navigate,
...currentProps
})}
<Page
navigate={this.navigate}
{...currentProps}
handleBackButtonPress={this.handleBackButtonPress}
/>
{!this.isSymptomView() &&
<Menu navigate={this.navigate} currentPage={currentPage} />
+2 -5
View File
@@ -10,6 +10,7 @@ import { cycleDayColor } from '../../styles'
import { scaleObservable } from '../../local-storage'
import config from '../../config'
import AppText from '../app-text'
import AppLoadingView from '../app-loading'
import { shared as labels } from '../../i18n/en/labels'
import DripIcon from '../../assets/drip-icons'
import DripHomeIcon from '../../assets/drip-home-icons'
@@ -133,11 +134,7 @@ export default class CycleChart extends Component {
onLayout={this.onLayout}
style={{ flexDirection: 'row', flex: 1 }}
>
{!this.state.chartLoaded &&
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
<AppText>{labels.loading}</AppText>
</View>
}
{!this.state.chartLoaded && <AppLoadingView />}
{this.state.chartHeight && this.state.chartLoaded &&
<View>
+6 -5
View File
@@ -7,7 +7,8 @@ export const dotRadius = 5
const lineWidth = 1.5
const colorLtl = '#feb47b'
const gridColor = '#d3d3d3'
const gridLineWidth = 0.5
const gridLineWidthVertical = 0.6
const gridLineWidthHorizontal = 0.3
const numberLabelFontSize = 13
const styles = {
@@ -44,7 +45,7 @@ const styles = {
},
stroke: {
color: gridColor,
width: gridLineWidth,
width: gridLineWidthVertical,
}
},
symptomIcon: {
@@ -110,10 +111,10 @@ const styles = {
},
horizontalGrid: {
position:'absolute',
borderColor: gridColor,
borderWidth: gridLineWidth,
width: '100%',
borderStyle: 'solid',
borderBottomColor: gridColor,
borderBottomWidth: gridLineWidthHorizontal,
width: '100%',
left: config.columnWidth
},
nfpLine: {
+32 -36
View File
@@ -11,15 +11,11 @@ import { getCycleDay } from '../../db'
import cycleModule from '../../lib/cycle'
import styles from '../../styles'
import * as labels from '../../i18n/en/cycle-day'
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
import AppText from '../app-text'
import DripIcon from '../../assets/drip-icons'
const bleedingLabels = labels.bleeding.labels
const feelingLabels = labels.mucus.feeling.categories
const textureLabels = labels.mucus.texture.categories
const openingLabels = labels.cervix.opening.categories
const firmnessLabels = labels.cervix.firmness.categories
const positionLabels = labels.cervix.position.categories
const intensityLabels = labels.intensity
const sexLabels = labels.sex.categories
const contraceptiveLabels = labels.contraceptives.categories
@@ -72,28 +68,25 @@ export default class CycleDayOverView extends Component {
}
},
mucus: mucus => {
const categories = ['feeling', 'texture', 'value']
if (categories.every(c => isNumber(mucus[c]))) {
let mucusLabel = [feelingLabels[mucus.feeling], textureLabels[mucus.texture]].join(', ')
mucusLabel += `\n${labels.mucusNFP[mucus.value]}`
if (mucus.exclude) mucusLabel = `(${mucusLabel})`
return mucusLabel
}
const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
let label = filledCategories.map(category => {
return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
}).join(', ')
if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
if (mucus.exclude) label = `(${label})`
return label
},
cervix: cervix => {
let cervixLabel = []
if (isNumber(cervix.opening) && isNumber(cervix.firmness)) {
cervixLabel.push(
openingLabels[cervix.opening],
firmnessLabels[cervix.firmness]
)
if (isNumber(cervix.position)) {
cervixLabel.push(positionLabels[cervix.position])
}
cervixLabel = cervixLabel.join(', ')
if (cervix.exclude) cervixLabel = `(${cervixLabel})`
return cervixLabel
}
const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
let label = filledCategories.map(category => {
return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
}).join(', ')
if (cervix.exclude) label = `(${label})`
return label
},
note: note => {
return note.value
@@ -188,7 +181,7 @@ export default class CycleDayOverView extends Component {
<ScrollView>
<View style={styles.symptomBoxesView}>
<SymptomBox
title='Bleeding'
title={symptomTitles.bleeding}
onPress={() => this.navigate('BleedingEditView')}
data={this.getLabel('bleeding')}
disabled={dateInFuture}
@@ -196,7 +189,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Temperature'
title={symptomTitles.temperature}
onPress={() => this.navigate('TemperatureEditView')}
data={this.getLabel('temperature')}
disabled={dateInFuture}
@@ -204,7 +197,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Mucus'
title={symptomTitles.mucus}
onPress={() => this.navigate('MucusEditView')}
data={this.getLabel('mucus')}
disabled={dateInFuture}
@@ -212,7 +205,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Cervix'
title={symptomTitles.cervix}
onPress={() => this.navigate('CervixEditView')}
data={this.getLabel('cervix')}
disabled={dateInFuture}
@@ -220,7 +213,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Desire'
title={symptomTitles.desire}
onPress={() => this.navigate('DesireEditView')}
data={this.getLabel('desire')}
disabled={dateInFuture}
@@ -228,7 +221,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Sex'
title={symptomTitles.sex}
onPress={() => this.navigate('SexEditView')}
data={this.getLabel('sex')}
disabled={dateInFuture}
@@ -236,7 +229,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Pain'
title={symptomTitles.pain}
onPress={() => this.navigate('PainEditView')}
data={this.getLabel('pain')}
disabled={dateInFuture}
@@ -244,7 +237,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Mood'
title={symptomTitles.mood}
onPress={() => this.navigate('MoodEditView')}
data={this.getLabel('mood')}
disabled={dateInFuture}
@@ -252,7 +245,7 @@ export default class CycleDayOverView extends Component {
>
</SymptomBox>
<SymptomBox
title='Note'
title={symptomTitles.note}
onPress={() => this.navigate('NoteEditView')}
data={this.getLabel('note')}
iconName='drip-icon-note'
@@ -285,7 +278,10 @@ class SymptomBox extends Component {
>
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
<DripIcon name={this.props.iconName} size={50} color={hasData ? 'white' : 'black'}/>
<AppText style={[textActive, disabledStyle]}>
<AppText
style={[textActive, disabledStyle, {fontSize: 15}]}
numberOfLines={1}
>
{this.props.title.toLowerCase()}
</AppText>
</View>
@@ -319,4 +315,4 @@ class FillerBoxes extends Component {
function isNumber(val) {
return typeof val === 'number'
}
}
@@ -1,94 +0,0 @@
import React, { Component } from 'react'
import {
View, TouchableOpacity, Text, Alert, ToastAndroid
} from 'react-native'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import { saveSymptom } from '../../../db'
import styles, {iconStyles} from '../../../styles'
import {sharedDialogs as labels} from '../../../i18n/en/cycle-day'
export default class ActionButtonFooter extends Component {
render() {
const {
symptom,
currentSymptomValue,
date,
saveAction,
saveDisabled,
navigate,
autoShowDayView = true}
= this.props
const navigateToOverView = () => navigate('CycleDay', {date})
const buttons = [
{
title: labels.delete,
action: () => {
Alert.alert(
labels.areYouSureTitle,
labels.areYouSureToDelete,
[{
text: labels.cancel,
style: 'cancel'
}, {
text: labels.reallyDeleteData,
onPress: () => {
saveSymptom(symptom, date)
navigateToOverView()
}
}]
)
},
disabledCondition: (!currentSymptomValue ||
(Object.keys(currentSymptomValue).length === 0 && currentSymptomValue.constructor === Object) ||
(Object.values(currentSymptomValue).every(x => !x) && currentSymptomValue.constructor === Object)
),
icon: 'delete-outline'
}, {
title: labels.save,
action: () => {
if(saveDisabled) {
ToastAndroid.show(labels.disabledInfo, ToastAndroid.LONG)
} else {
saveAction()
if (autoShowDayView) navigateToOverView()
}
},
disabledCondition: saveDisabled,
icon: 'content-save-outline'
}
]
return (
<View style={styles.menu}>
{buttons.map(({ title, action, disabledCondition, icon }, i) => {
const textStyle = [styles.menuText]
if (disabledCondition) {
textStyle.push(styles.menuTextInActive)
}
const iconStyle = disabledCondition ?
Object.assign(
{},
iconStyles.menuIcon,
iconStyles.menuIconInactive
)
:
iconStyles.menuIcon
return (
<TouchableOpacity
onPress={action}
style={styles.menuItem}
key={i.toString()}
>
<Icon name={icon} {...iconStyle} />
<Text style={textStyle}>
{title.toLowerCase()}
</Text>
</TouchableOpacity>
)
})}
</View>
)
}
}
+41 -46
View File
@@ -1,29 +1,39 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
Switch,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { bleeding } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Bleeding extends Component {
export default class Bleeding extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.bleeding = cycleDay && cycleDay.bleeding
this.makeActionButtons = props.makeActionButtons
this.state = {
currentValue: this.bleeding && this.bleeding.value,
exclude: this.bleeding ? this.bleeding.exclude : false
}
}
render() {
symptomName = 'bleeding'
autoSave = () => {
if (typeof this.state.currentValue != 'number') {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
value: this.state.currentValue,
exclude: this.state.exclude
})
}
renderContent() {
const bleedingRadioProps = [
{ label: bleeding.labels[0], value: 0 },
{ label: bleeding.labels[1], value: 1 },
@@ -31,45 +41,30 @@ export default class Bleeding extends Component {
{ label: bleeding.labels[3], value: 3 },
]
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
header={bleeding.heaviness.header}
explainer={bleeding.heaviness.explainer}
>
<SelectTabGroup
buttons={bleedingRadioProps}
active={this.state.currentValue}
onSelect={val => this.setState({ currentValue: val })}
/>
</SymptomSection>
<SymptomSection
header={bleeding.exclude.header}
explainer={bleeding.exclude.explainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
<ActionButtonFooter
symptom='bleeding'
date={this.props.date}
currentSymptomValue={this.bleeding}
saveAction={() => {
saveSymptom('bleeding', this.props.date, {
value: this.state.currentValue,
exclude: this.state.exclude
})
}}
saveDisabled={typeof this.state.currentValue != 'number'}
navigate={this.props.navigate}
/>
</View>
<ScrollView style={styles.page}>
<SymptomSection
header={bleeding.heaviness.header}
explainer={bleeding.heaviness.explainer}
>
<SelectTabGroup
buttons={bleedingRadioProps}
active={this.state.currentValue}
onSelect={val => this.setState({ currentValue: val })}
/>
</SymptomSection>
<SymptomSection
header={bleeding.exclude.header}
explainer={bleeding.exclude.explainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
)
}
}
+67 -71
View File
@@ -1,27 +1,40 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
Switch,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { cervix as labels } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section'
import { ActionHint } from '../../app-text'
import SymptomView from './symptom-view'
export default class Cervix extends Component {
export default class Cervix extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.cervix = cycleDay && cycleDay.cervix
this.makeActionButtons = props.makeActionButtons
this.state = this.cervix ? this.cervix : {}
}
render() {
symptomName = 'cervix'
autoSave = () => {
const nothingEntered = ['opening', 'firmness', 'position'].every(val => typeof this.state[val] != 'number')
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
opening: this.state.opening,
firmness: this.state.firmness,
position: this.state.position,
exclude: Boolean(this.state.exclude)
})
}
renderContent() {
const cervixOpeningRadioProps = [
{ label: labels.opening.categories[0], value: 0 },
{ label: labels.opening.categories[1], value: 1 },
@@ -36,70 +49,53 @@ export default class Cervix extends Component {
{ label: labels.position.categories[1], value: 1 },
{ label: labels.position.categories[2], value: 2 }
]
const mandatoryNotCompletedYet = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
// TODO saving this info for notice when leaving incomplete data
// const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
header="Opening"
explainer={labels.opening.explainer}
>
<SelectTabGroup
buttons={cervixOpeningRadioProps}
active={this.state.opening}
onSelect={val => this.setState({ opening: val })}
/>
</SymptomSection>
<SymptomSection
header="Firmness"
explainer={labels.firmness.explainer}
>
<SelectTabGroup
buttons={cervixFirmnessRadioProps}
active={this.state.firmness}
onSelect={val => this.setState({ firmness: val })}
/>
</SymptomSection>
<SymptomSection
header="Position"
explainer={labels.position.explainer}
>
<SelectTabGroup
buttons={cervixPositionRadioProps}
active={this.state.position}
onSelect={val => this.setState({ position: val })}
/>
</SymptomSection>
<SymptomSection
header="Exclude"
explainer="You can exclude this value if you don't want to use it for fertility detection"
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
<ActionButtonFooter
symptom='cervix'
date={this.props.date}
currentSymptomValue={this.cervix}
saveAction={() => {
saveSymptom('cervix', this.props.date, {
opening: this.state.opening,
firmness: this.state.firmness,
position: this.state.position,
exclude: Boolean(this.state.exclude)
})
}}
saveDisabled={mandatoryNotCompletedYet}
navigate={this.props.navigate}
/>
</View>
<ScrollView style={styles.page}>
<SymptomSection
header="Opening"
explainer={labels.opening.explainer}
>
<SelectTabGroup
buttons={cervixOpeningRadioProps}
active={this.state.opening}
onSelect={val => this.setState({ opening: val })}
/>
</SymptomSection>
<SymptomSection
header="Firmness"
explainer={labels.firmness.explainer}
>
<SelectTabGroup
buttons={cervixFirmnessRadioProps}
active={this.state.firmness}
onSelect={val => this.setState({ firmness: val })}
/>
</SymptomSection>
<SymptomSection
header="Position"
explainer={labels.position.explainer}
>
<SelectTabGroup
buttons={cervixPositionRadioProps}
active={this.state.position}
onSelect={val => this.setState({ position: val })}
/>
</SymptomSection>
<SymptomSection
header="Exclude"
explainer="You can exclude this value if you don't want to use it for fertility detection"
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
)
}
}
+26 -31
View File
@@ -1,56 +1,51 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { intensity, desire } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Desire extends Component {
export default class Desire extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.desire = cycleDay && cycleDay.desire
this.makeActionButtons = props.makeActionButtons
const desireValue = this.desire && this.desire.value
this.state = { currentValue: desireValue }
}
render() {
symptomName = 'desire'
autoSave = () => {
if (typeof this.state.currentValue != 'number') {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({ value: this.state.currentValue })
}
renderContent() {
const desireRadioProps = [
{ label: intensity[0], value: 0 },
{ label: intensity[1], value: 1 },
{ label: intensity[2], value: 2 }
]
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
header={desire.header}
explainer={desire.explainer}
>
<SelectTabGroup
buttons={desireRadioProps}
active={this.state.currentValue}
onSelect={val => this.setState({ currentValue: val })}
/>
</SymptomSection>
</ScrollView>
<ActionButtonFooter
symptom='desire'
date={this.props.date}
currentSymptomValue={this.desire}
saveAction={() => {
saveSymptom('desire', this.props.date, { value: this.state.currentValue })
}}
saveDisabled={typeof this.state.currentValue != 'number'}
navigate={this.props.navigate}
/>
</View>
<ScrollView style={styles.page}>
<SymptomSection
header={desire.header}
explainer={desire.explainer}
>
<SelectTabGroup
buttons={desireRadioProps}
active={this.state.currentValue}
onSelect={val => this.setState({ currentValue: val })}
/>
</SymptomSection>
</ScrollView>
)
}
}
+18 -31
View File
@@ -1,35 +1,22 @@
import React, { Component } from 'react'
import { ScrollView } from 'react-native'
import React from 'react'
import { ScrollView, View, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/SimpleLineIcons'
import AppText from '../../app-text'
import labels from '../../../i18n/en/symptom-info.js'
import FramedSegment from '../../framed-segment'
import styles from '../../../styles/index'
import styles, {iconStyles} from '../../../styles/index'
export default class InfoSymptom extends Component {
render() {
const symptomView = this.props.symptomView
const symptomMapping = {
BleedingEditView: 'bleeding',
CervixEditView: 'cervix',
DesireEditView: 'desire',
MoodEditView: 'mood',
MucusEditView: 'mucus',
NoteEditView: 'note',
PainEditView: 'pain',
SexEditView: 'sex',
TemperatureEditView: 'temperature'
}
const currentSymptom = symptomMapping[symptomView]
return (
<ScrollView>
<FramedSegment
style={styles.framedSegmentLast}
title={labels[currentSymptom].title}
>
<AppText>{labels[currentSymptom].text}</AppText>
</FramedSegment>
</ScrollView>
)
}
export default function InfoSymptom(props) {
return (
<View style={styles.infoPopUpWrapper}>
<View style={styles.dimmed}></View>
<View style={styles.infoPopUp}>
<TouchableOpacity onPress={props.close} style={styles.infoSymptomClose}>
<Icon name='close' {...iconStyles.infoPopUpClose}/>
</TouchableOpacity>
<ScrollView style={styles.infoSymptomText}>
<AppText>{labels[props.symptom].text}</AppText>
</ScrollView>
</View>
</View>
)
}
+33 -37
View File
@@ -1,17 +1,14 @@
import React, { Component } from 'react'
import React from 'react'
import {
ScrollView,
TextInput,
View
} from 'react-native'
import { saveSymptom } from '../../../db'
TextInput} from 'react-native'
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'
import SymptomView from './symptom-view'
export default class Mood extends Component {
export default class Mood extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
@@ -25,6 +22,21 @@ export default class Mood extends Component {
}
}
symptomName = "mood"
autoSave = () => {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => {
const curr = this.state[key]
this.setState({[key]: !curr})
@@ -33,19 +45,18 @@ export default class Mood extends Component {
}
}
render() {
renderContent() {
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 &&
<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}
@@ -55,24 +66,9 @@ export default class Mood extends Component {
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>
}
</SymptomSection>
</ScrollView>
)
}
}
+59 -63
View File
@@ -1,28 +1,43 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
Switch,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { mucus as labels } from '../../../i18n/en/cycle-day'
import computeNfpValue from '../../../lib/nfp-mucus'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section'
import { ActionHint } from '../../app-text'
import SymptomView from './symptom-view'
export default class Mucus extends Component {
export default class Mucus extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.mucus = cycleDay && cycleDay.mucus
this.makeActionButtons = props.makeActionButtons
this.state = this.mucus ? this.mucus : {}
}
render() {
symptomName = 'mucus'
autoSave = () => {
const nothingEntered = ['feeling', 'texture'].every(val => typeof this.state[val] != 'number')
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const feeling = this.state.feeling
const texture = this.state.texture
this.saveSymptomEntry({
feeling,
texture,
value: computeNfpValue(feeling, texture),
exclude: Boolean(this.state.exclude)
})
}
renderContent() {
const mucusFeeling = [
{ label: labels.feeling.categories[0], value: 0 },
{ label: labels.feeling.categories[1], value: 1 },
@@ -34,62 +49,43 @@ export default class Mucus extends Component {
{ label: labels.texture.categories[1], value: 1 },
{ label: labels.texture.categories[2], value: 2 }
]
const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
// TODO leaving this info for notice when leaving incomplete data
// const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
header='Feeling'
explainer={labels.feeling.explainer}
>
<SelectTabGroup
buttons={mucusFeeling}
onSelect={val => this.setState({ feeling: val })}
active={this.state.feeling}
/>
</SymptomSection>
<SymptomSection
header='Texture'
explainer={labels.texture.explainer}
>
<SelectTabGroup
buttons={mucusTexture}
onSelect={val => this.setState({ texture: val })}
active={this.state.texture}
/>
</SymptomSection>
<SymptomSection
header="Exclude"
explainer={labels.excludeExplainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
<ActionButtonFooter
symptom='mucus'
date={this.props.date}
currentSymptomValue={this.mucus}
saveAction={() => {
const feeling = this.state.feeling
const texture = this.state.texture
saveSymptom('mucus', this.props.date, {
feeling,
texture,
value: computeNfpValue(feeling, texture),
exclude: Boolean(this.state.exclude)
})
}}
saveDisabled={mandatoryNotCompletedYet}
navigate={this.props.navigate}
/>
</View>
<ScrollView style={styles.page}>
<SymptomSection
header='Feeling'
explainer={labels.feeling.explainer}
>
<SelectTabGroup
buttons={mucusFeeling}
onSelect={val => this.setState({ feeling: val })}
active={this.state.feeling}
/>
</SymptomSection>
<SymptomSection
header='Texture'
explainer={labels.texture.explainer}
>
<SelectTabGroup
buttons={mucusTexture}
onSelect={val => this.setState({ texture: val })}
active={this.state.texture}
/>
</SymptomSection>
<SymptomSection
header="Exclude"
explainer={labels.excludeExplainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
)
}
}
+31 -36
View File
@@ -1,60 +1,55 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
ScrollView,
TextInput,
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import ActionButtonFooter from './action-button-footer'
import SymptomSection from './symptom-section'
import { noteExplainer } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels'
import SymptomView from './symptom-view'
export default class Note extends Component {
export default class Note extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.note = cycleDay && cycleDay.note
this.makeActionButtons = props.makeActionButtons
this.state = {
currentValue: this.note && this.note.value || ''
}
}
render() {
symptomName = 'note'
autoSave = () => {
if (!this.state.currentValue) {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
value: this.state.currentValue
})
}
renderContent() {
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
explainer={noteExplainer}
>
<TextInput
autoFocus={!this.state.currentValue}
multiline={true}
placeholder={sharedLabels.enter}
onChangeText={(val) => {
this.setState({ currentValue: val })
}}
value={this.state.currentValue}
/>
</SymptomSection>
</ScrollView>
<ActionButtonFooter
symptom='note'
date={this.props.date}
currentSymptomValue={this.note}
saveAction={() => {
saveSymptom('note', this.props.date, {
value: this.state.currentValue
})
}}
saveDisabled={!this.state.currentValue}
navigate={this.props.navigate}
/>
</View>
<ScrollView style={styles.page}>
<SymptomSection
explainer={noteExplainer}
>
<TextInput
autoFocus={!this.state.currentValue}
multiline={true}
placeholder={sharedLabels.enter}
onChangeText={(val) => {
this.setState({ currentValue: val })
}}
value={this.state.currentValue}
/>
</SymptomSection>
</ScrollView>
)
}
}
+33 -36
View File
@@ -1,18 +1,16 @@
import React, { Component } from 'react'
import React from 'react'
import {
ScrollView,
TextInput,
View
} from 'react-native'
import { saveSymptom } from '../../../db'
import { pain as labels } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section'
import styles from '../../../styles'
import SymptomView from './symptom-view'
export default class Pain extends Component {
export default class Pain extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
@@ -26,6 +24,22 @@ export default class Pain extends Component {
}
}
symptomName = 'pain'
autoSave = () => {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => {
const curr = this.state[key]
this.setState({[key]: !curr})
@@ -34,19 +48,18 @@ export default class Pain extends Component {
}
}
render() {
renderContent() {
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 &&
<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}
@@ -56,24 +69,8 @@ export default class Pain extends Component {
this.setState({note: val})
}}
/>
}
</SymptomSection>
</ScrollView>
<ActionButtonFooter
symptom='pain'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('pain', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
)
}
</SymptomSection>
</ScrollView>)
}
}
+44 -46
View File
@@ -1,18 +1,16 @@
import React, { Component } from 'react'
import React from 'react'
import {
TextInput,
View,
ScrollView
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Sex extends Component {
export default class Sex extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
@@ -26,6 +24,22 @@ export default class Sex extends Component {
if (this.state.note) this.state.other = true
}
symptomName = "sex"
autoSave = () => {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => {
const curr = this.state[key]
this.setState({[key]: !curr})
@@ -34,32 +48,31 @@ export default class Sex extends Component {
}
}
render() {
renderContent() {
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<SymptomSection
header={sexLabels.header}
explainer={sexLabels.explainer}
>
<SelectBoxGroup
labels={sexLabels.categories}
onSelect={this.toggleState}
optionsState={this.state}
/>
</SymptomSection>
<SymptomSection
header={contraceptivesLabels.header}
explainer={contraceptivesLabels.explainer}
>
<SelectBoxGroup
labels={contraceptivesLabels.categories}
onSelect={this.toggleState}
optionsState={this.state}
/>
</SymptomSection>
<ScrollView style={styles.page}>
<SymptomSection
header={sexLabels.header}
explainer={sexLabels.explainer}
>
<SelectBoxGroup
labels={sexLabels.categories}
onSelect={this.toggleState}
optionsState={this.state}
/>
</SymptomSection>
<SymptomSection
header={contraceptivesLabels.header}
explainer={contraceptivesLabels.explainer}
>
<SelectBoxGroup
labels={contraceptivesLabels.categories}
onSelect={this.toggleState}
optionsState={this.state}
/>
</SymptomSection>
{this.state.other &&
{this.state.other &&
<TextInput
autoFocus={this.state.focusTextArea}
multiline={true}
@@ -69,23 +82,8 @@ export default class Sex extends Component {
this.setState({ note: val })
}}
/>
}
</ScrollView>
<ActionButtonFooter
symptom='sex'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('sex', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
}
</ScrollView>
)
}
}
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { View } from 'react-native'
import AppText, { SymptomSectionHeader } from '../../app-text'
import styles from '../../../styles'
export default class SymptomSection extends Component {
render() {
@@ -13,16 +14,20 @@ export default class SymptomSection extends Component {
}
}
return (
<View style={placeHeadingInline}>
<SymptomSectionHeader flex={1}>{p.header}</SymptomSectionHeader>
<View style={[placeHeadingInline, styles.symptomSection]}>
{ p.header &&
<SymptomSectionHeader flex={1}>{p.header}</SymptomSectionHeader>
}
<View
flexDirection={p.inline ? 'row' : null}
flex={1}
alignItems={p.inline ? 'center' : null}
>
<View flex={1}>
<AppText>{p.explainer}</AppText>
</View>
{ p.explainer && (
<View flex={1}>
<AppText>{p.explainer}</AppText>
</View>
)}
{p.children}
</View>
</View>
@@ -0,0 +1,97 @@
import React, { Component } from 'react'
import {
View, Alert, TouchableOpacity
} from 'react-native'
import { saveSymptom } from '../../../db'
import InfoPopUp from './info-symptom'
import Header from '../../header/symptom-view'
import { headerTitles } from '../../../i18n/en/labels'
import { sharedDialogs } from '../../../i18n/en/cycle-day'
import Icon from 'react-native-vector-icons/Entypo'
import styles, { iconStyles } from '../../../styles'
export default class SymptomView extends Component {
constructor(props) {
super()
this.date = props.date
this.navigate = props.navigate
this.state = {
showInfo: false
}
}
componentDidUpdate() {
this.autoSave()
}
saveSymptomEntry(entry) {
saveSymptom(this.symptomName, this.date, entry)
}
deleteSymptomEntry() {
saveSymptom(this.symptomName, this.date)
}
isDeleteIconActive() {
const symptomValueHasBeenFilledOut = key => {
// the state tracks whether the symptom info should be shown,
// we ignore that property
if (key === 'showInfo') return
// is there any meaningful value in the current state?
return this.state[key] || this.state[key] === 0
}
const symptomValues = Object.keys(this.state)
return symptomValues.some(symptomValueHasBeenFilledOut)
}
render() {
return (
<View style={{flex: 1}}>
<Header
title={headerTitles[this.symptomName].toLowerCase()}
date={this.date}
goBack={this.props.handleBackButtonPress}
deleteIconActive={this.isDeleteIconActive()}
deleteEntry={() => {
Alert.alert(
sharedDialogs.areYouSureTitle,
sharedDialogs.areYouSureToDelete,
[{
text: sharedDialogs.cancel,
style: 'cancel'
}, {
text: sharedDialogs.reallyDeleteData,
onPress: () => {
this.deleteSymptomEntry()
this.props.handleBackButtonPress()
}
}]
)
}}
/>
<View flex={1}>
{ this.renderContent() }
<TouchableOpacity
onPress={() => {
this.setState({showInfo: true})
}}
style={styles.infoButtonSymptomView}
>
<Icon
name="info-with-circle"
style={iconStyles.info}
/>
</TouchableOpacity>
{ this.state.showInfo &&
<InfoPopUp
symptom={this.symptomName}
close={() => this.setState({showInfo: false})}
/>
}
</View>
</View>
)
}
}
+130 -148
View File
@@ -1,33 +1,32 @@
import React, { Component } from 'react'
import React from 'react'
import {
View,
TextInput,
Switch,
Keyboard,
Alert,
ScrollView
} from 'react-native'
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
import padWithZeros from '../../helpers/pad-time-with-zeros'
import { getPreviousTemperature, saveSymptom } from '../../../db'
import { getPreviousTemperature } from '../../../db'
import styles from '../../../styles'
import { LocalTime, ChronoUnit } from 'js-joda'
import { temperature as labels } from '../../../i18n/en/cycle-day'
import { scaleObservable } from '../../../local-storage'
import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import config from '../../../config'
import AppTextInput from '../../app-text-input'
import AppText from '../../app-text'
import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
const minutes = ChronoUnit.MINUTES
export default class Temp extends Component {
export default class Temp extends SymptomView {
constructor(props) {
super(props)
const cycleDay = props.cycleDay
this.temperature = cycleDay && cycleDay.temperature
this.makeActionButtons = props.makeActionButtons
const temp = this.temperature
@@ -35,7 +34,6 @@ export default class Temp extends Component {
exclude: temp ? temp.exclude : false,
time: temp ? temp.time : LocalTime.now().truncatedTo(minutes).toString(),
isTimePickerVisible: false,
outOfRange: null,
note: temp ? temp.note : null
}
@@ -44,170 +42,154 @@ export default class Temp extends Component {
if (temp.value === Math.floor(temp.value)) {
this.state.temperature = `${this.state.temperature}.0`
}
this.state.outOfRangeWarning = makeOutOfRangeWarningMessage(this.state.temperature)
} else {
const prevTemp = getPreviousTemperature(this.props.date)
const prevTemp = getPreviousTemperature(props.date)
if (prevTemp) {
this.state.temperature = prevTemp.toString()
this.state.suggestedTemperature = prevTemp.toString()
this.state.isSuggestion = true
}
}
}
saveTemperature = () => {
symptomName = 'temperature'
isDeleteIconActive() {
return ['temperature', 'note', 'exclude'].some(key => {
// the time is always and the suggested temp sometimes prefilled, so they're not relevant for setting
// the delete button active.
return this.state[key] || this.state[key] === 0
})
}
autoSave = () => {
if (typeof this.state.temperature != 'string' || this.state.temperature === '') {
this.deleteSymptomEntry()
return
}
const dataToSave = {
value: Number(this.state.temperature),
exclude: this.state.exclude,
time: this.state.time,
note: this.state.note
}
saveSymptom('temperature', this.props.date, dataToSave)
this.props.navigate('CycleDay', {date: this.props.date})
}
checkRangeAndSave = () => {
const value = Number(this.state.temperature)
const absolute = {
min: config.temperatureScale.min,
max: config.temperatureScale.max
}
const scale = scaleObservable.value
let warningMsg
if (value < absolute.min || value > absolute.max) {
warningMsg = labels.outOfAbsoluteRangeWarning
} else if (value < scale.min || value > scale.max) {
warningMsg = labels.outOfRangeWarning
}
if (warningMsg) {
Alert.alert(
sharedLabels.warning,
warningMsg,
[
{ text: sharedLabels.cancel },
{ text: sharedLabels.save, onPress: this.saveTemperature}
]
)
} else {
this.saveTemperature()
}
this.saveSymptomEntry(dataToSave)
}
render() {
setTemperature = (temperature) => {
if (isNaN(Number(temperature))) return
this.setState({
temperature, isSuggestion: false,
outOfRangeWarning: makeOutOfRangeWarningMessage(temperature)
})
}
setNote = (note) => {
this.setState({ note })
}
showTimePicker = () => {
Keyboard.dismiss()
this.setState({ isTimePickerVisible: true })
}
renderContent() {
const inputStyle = [styles.temperatureTextInput]
if (this.state.isSuggestion) {
inputStyle.push(styles.temperatureTextInputSuggestion)
}
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.page}>
<View>
<SymptomSection
header={labels.temperature.header}
explainer={labels.temperature.explainer}
inline={true}
>
<TempInput
value={this.state.temperature}
setState={(val) => this.setState(val)}
isSuggestion={this.state.isSuggestion}
/>
</SymptomSection>
<SymptomSection
header={labels.time}
inline={true}
>
<TextInput
style={styles.temperatureTextInput}
onFocus={() => {
Keyboard.dismiss()
this.setState({ isTimePickerVisible: true })
}}
value={this.state.time}
/>
<DateTimePicker
mode="time"
isVisible={this.state.isTimePickerVisible}
onConfirm={jsDate => {
this.setState({
time: padWithZeros(jsDate),
isTimePickerVisible: false
})
}}
onCancel={() => this.setState({ isTimePickerVisible: false })}
/>
</SymptomSection>
<SymptomSection
header={labels.note.header}
explainer={labels.note.explainer}
>
<TextInput
multiline={true}
autoFocus={this.state.focusTextArea}
placeholder={sharedLabels.enter}
value={this.state.note}
onChangeText={(val) => {
this.setState({ note: val })
}}
/>
</SymptomSection>
<SymptomSection
header={labels.exclude.header}
explainer={labels.exclude.explainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
<ScrollView style={styles.page}>
<SymptomSection
header={labels.temperature.header}
explainer={labels.temperature.explainer}
>
<View style={styles.framedSegmentInlineChildren}>
<AppTextInput
style={[inputStyle]}
autoFocus={true}
value={this.state.temperature || this.state.suggestedTemperature}
onChangeText={this.setTemperature}
keyboardType='numeric'
maxLength={5}
/>
<AppText style={{ marginLeft: 5 }}>°C</AppText>
</View>
</ScrollView>
<ActionButtonFooter
symptom='temperature'
date={this.props.date}
currentSymptomValue={this.temperature}
saveAction={() => this.checkRangeAndSave()}
saveDisabled={
this.state.temperature === '' ||
isNaN(Number(this.state.temperature)) ||
isInvalidTime(this.state.time)
{this.state.outOfRangeWarning &&
<AppText style={styles.hint}>
{this.state.outOfRangeWarning}
</AppText>
}
navigate={this.props.navigate}
autoShowDayView={false}
/>
</View>
</SymptomSection>
<SymptomSection
header={labels.time}
>
<View style={styles.framedSegmentInlineChildren}>
<AppTextInput
style={[styles.temperatureTextInput]}
onFocus={this.showTimePicker}
value={this.state.time}
/>
<DateTimePicker
mode="time"
isVisible={this.state.isTimePickerVisible}
onConfirm={jsDate => {
this.setState({
time: padWithZeros(jsDate),
isTimePickerVisible: false
})
}}
onCancel={() => this.setState({ isTimePickerVisible: false })}
/>
</View>
</SymptomSection>
<SymptomSection
header={labels.note.header}
explainer={labels.note.explainer}
>
<AppTextInput
multiline={true}
autoFocus={this.state.focusTextArea}
placeholder={sharedLabels.enter}
value={this.state.note}
onChangeText={this.setNote}
/>
</SymptomSection>
<SymptomSection
header={labels.exclude.header}
explainer={labels.exclude.explainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
)
}
}
class TempInput extends Component {
render() {
const style = [styles.temperatureTextInput]
if (this.props.isSuggestion) {
style.push(styles.temperatureTextInputSuggestion)
}
return (
<TextInput
style={style}
onChangeText={(val) => {
if (isNaN(Number(val))) return
this.props.setState({ temperature: val, isSuggestion: false })
}}
keyboardType='numeric'
value={this.props.value}
onBlur={this.checkRange}
autoFocus={true}
/>
)
}
}
function makeOutOfRangeWarningMessage(temperature) {
if (temperature === '') return
const value = Number(temperature)
const { min, max } = config.temperatureScale
const range = { min, max }
const scale = scaleObservable.value
let warningMsg
function isInvalidTime(timeString) {
try {
LocalTime.parse(timeString)
} catch (err) {
return true
if (value < range.min || value > range.max) {
warningMsg = labels.outOfAbsoluteRangeWarning
} else if (value < scale.min || value > scale.max) {
warningMsg = labels.outOfRangeWarning
} else {
warningMsg = null
}
return false
return warningMsg
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { View } from 'react-native'
import AppText from './app-text'
import styles from '../styles'
const FramedSegment = ({ children, ...props }) => {
const FramedSegment = ({children, ...props}) => {
const style = [styles.framedSegment, props.style]
if (props.last) style.push(styles.framedSegmentLast)
return (
+1 -9
View File
@@ -1,12 +1,10 @@
import React from 'react'
import {
Text,
TouchableOpacity,
View
} from 'react-native'
import styles, { iconStyles } from '../../styles'
import styles from '../../styles'
import NavigationArrow from './navigation-arrow'
import Icon from 'react-native-vector-icons/Entypo'
export default function BackButtonHeader(props) {
return (
@@ -21,12 +19,6 @@ export default function BackButtonHeader(props) {
{props.title}
</Text>
</View>
<TouchableOpacity style={styles.hiddenIcon}>
<Icon
name={'chevron-thin-right'}
{...iconStyles.hiddenIcon}
/>
</TouchableOpacity>
</View>
)
}
+1 -6
View File
@@ -3,7 +3,6 @@ import { Dimensions } from 'react-native'
import CycleDayHeader from './cycle-day'
import DefaultHeader from './default'
import BackButtonHeader from './back-button'
import SymptomViewHeader from './symptom-view'
export default function Header(p) {
const middle = Dimensions.get('window').width / 2
@@ -11,11 +10,7 @@ export default function Header(p) {
if (props.isCycleDayOverView) {
return (<CycleDayHeader {...props} />)
}
else if (props.isSymptomView) {
return (<SymptomViewHeader {...props} />)
}
else if (props.showBackButton) {
} else if (props.showBackButton) {
return (<BackButtonHeader {...props} />)
}
else {
+5 -1
View File
@@ -8,6 +8,10 @@ export default function NavigationArrow(props) {
left: 'chevron-thin-left',
right: 'chevron-thin-right'
}[props.direction]
const iconPosition = {
left: 'navigationArrowLeft',
right: 'navigationArrowRight'
}[props.direction]
let pressHandler
if (props.goBack) {
pressHandler = () => props.goBack()
@@ -19,7 +23,7 @@ export default function NavigationArrow(props) {
}
return (
<TouchableOpacity
style={styles.navigationArrow}
style={[styles.navigationArrow, styles[iconPosition]]}
onPress={pressHandler}
>
<Icon
+18 -13
View File
@@ -2,19 +2,21 @@ import React from 'react'
import {
View,
Text,
TouchableOpacity
TouchableOpacity,
Dimensions
} from 'react-native'
import styles, { iconStyles } from '../../styles'
import FeatherIcon from 'react-native-vector-icons/Feather'
import Icon from 'react-native-vector-icons/AntDesign'
import NavigationArrow from './navigation-arrow'
import formatDate from '../helpers/format-date'
export default function SymptomViewHeader(props) {
const middle = Dimensions.get('window').width / 2
return (
<View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}>
<View
style={styles.accentCircle}
left={props.middle - styles.accentCircle.width / 2}
left={middle - styles.accentCircle.width / 2}
/>
<NavigationArrow
direction='left'
@@ -28,16 +30,19 @@ export default function SymptomViewHeader(props) {
{formatDate(props.date)}
</Text>
</View >
<TouchableOpacity
onPress={() => props.goToSymptomInfo()}
style={styles.infoButton}
>
<FeatherIcon
name="info"
style={styles.symptomInfoIcon}
{...iconStyles.symptomHeaderIcons}
/>
</TouchableOpacity>
{ props.deleteIconActive &&
<TouchableOpacity
onPress={props.deleteEntry}
style={[
styles.headerDeleteButton,
]}
>
<Icon
name="delete"
{...iconStyles.symptomHeaderIcons}
/>
</TouchableOpacity>
}
</View>
)
+4
View File
@@ -7,3 +7,7 @@ export default function (date) {
const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY')
return formattedDate.toLowerCase()
}
export function formatDateForShortText (date) {
return moment(date.toString()).format('dddd, MMMM Do')
}
+54 -81
View File
@@ -1,14 +1,12 @@
import { ChronoUnit, LocalDate } from 'js-joda'
import React, { Component } from 'react'
import { Dimensions, ScrollView, TouchableHighlight, View } from 'react-native'
import Icon from 'react-native-vector-icons/Entypo'
import { ScrollView, View } from 'react-native'
import DripHomeIcon from '../assets/drip-home-icons'
import { getCycleDay } from '../db'
import {
bleedingPrediction as predictLabels,
home as labels,
shared,
home as labels
} from '../i18n/en/labels'
import links from '../i18n/en/links'
import cycleModule from '../lib/cycle'
@@ -16,25 +14,7 @@ import { getFertilityStatusForDay } from '../lib/sympto-adapter'
import styles, { cycleDayColor, periodColor, secondaryColor } from '../styles'
import AppText from './app-text'
import Button from './button'
const ShowMoreToggler = ({ isShowingMore, onToggle }) => {
const {height, width} = Dimensions.get('window')
const leftPosition = isShowingMore ? 10 : width - 40
const style = isShowingMore ? styles.showLess : styles.showMore
const topPosition = height / 2 - styles.header.height - 30
return (
<TouchableHighlight
onPress={onToggle}
style={[style, { top: topPosition, left: leftPosition}]}
>
<View style={{alignItems: 'center'}}>
<AppText>{isShowingMore ? shared.less : shared.more}</AppText>
<Icon name='chevron-thin-down' />
</View>
</TouchableHighlight>
)
}
import { formatDateForShortText } from './helpers/format-date'
const IconText = ({ children, wrapperStyles }) => {
return (
@@ -50,15 +30,22 @@ const HomeElement = ({ children, onPress, buttonColor, buttonLabel }) => {
return (
<View
onPress={ onPress }
style={ styles.homeIconElement }
style={ styles.homeElement }
>
{ children }
<Button
style={styles.homeButton}
onPress={ onPress }
backgroundColor={ buttonColor }>
{ buttonLabel }
</Button>
<View style={styles.homeIconAndText}>
{children[0]}
{children[1]}
</View>
<View style={{paddingLeft: 15}}>
{children.slice(2)}
<Button
style={styles.homeButton}
onPress={ onPress }
backgroundColor={ buttonColor }>
{ buttonLabel }
</Button>
</View>
</View>
)
}
@@ -74,7 +61,6 @@ export default class Home extends Component {
const fertilityStatus = getFertilityStatusForDay(this.todayDateString)
this.state = {
isShowingMore: false,
cycleDayNumber: this.getCycleDayNumber(this.todayDateString),
predictionText: determinePredictionText(prediction),
bleedingPredictionRange: getBleedingPredictionRange(prediction),
@@ -90,12 +76,8 @@ export default class Home extends Component {
})
}
toggleShowingMore = () => {
this.setState({ isShowingMore: !this.state.isShowingMore })
}
render() {
const { isShowingMore, cycleDayNumber, phase, status } = this.state
const { cycleDayNumber, phase, status } = this.state
const { navigate } = this.props
const cycleDayMoreText = cycleDayNumber ?
labels.cycleDayKnown(cycleDayNumber) :
@@ -116,13 +98,11 @@ export default class Home extends Component {
<View>
<DripHomeIcon name="circle" size={80} color={cycleDayColor}/>
</View>
<IconText wrapperStyles={styles.wrapperCycle}>
<IconText wrapperStyles={styles.wrapperIcon}>
{cycleDayNumber || labels.unknown}
</IconText>
{ isShowingMore &&
<AppText style={styles.paragraph}>{cycleDayMoreText}</AppText>
}
<AppText style={styles.homeDescriptionText}>{cycleDayMoreText}</AppText>
</HomeElement>
<HomeElement
@@ -130,19 +110,15 @@ export default class Home extends Component {
buttonColor={ periodColor }
buttonLabel={ labels.trackPeriod }
>
<View>
<DripHomeIcon name="drop" size={105} color={periodColor} />
</View>
<DripHomeIcon name="drop" size={100} color={periodColor} />
<IconText wrapperStyles={styles.wrapperDrop}>
<IconText wrapperStyles={{top: '45%', ...styles.wrapperIcon}}>
{this.state.bleedingPredictionRange}
</IconText>
{ isShowingMore &&
<AppText style={styles.paragraph}>
{this.state.predictionText}
</AppText>
}
<AppText style={styles.homeDescriptionText}>
{this.state.predictionText}
</AppText>
</HomeElement>
<HomeElement
@@ -152,51 +128,50 @@ export default class Home extends Component {
>
<View style={styles.homeCircle}/>
<IconText wrapperStyles={styles.wrapperCircle}>
<IconText wrapperStyles={styles.wrapperIcon}>
{ phase ? phase.toString() : labels.unknown }
</IconText>
{ phase &&
<AppText>{`${labels.phase(phase)} (${status})`}</AppText>
}
{ isShowingMore &&
<View>
<AppText styles={styles.paragraph}>
{ `${statusText} ${links.wiki.url}.` }
</AppText>
</View>
<AppText style={styles.homeDescriptionText}>
{`${labels.phase(phase)} (${status})`}
</AppText>
}
<AppText style={styles.homeDescriptionText}>
{ `${statusText} Visit ${links.wiki.url}.` }
</AppText>
</HomeElement>
</View>
</ScrollView>
<ShowMoreToggler
isShowingMore={isShowingMore}
onToggle={this.toggleShowingMore}
/>
</View>
)
}
}
function getTimes(prediction) {
const todayDate = LocalDate.now()
const predictedBleedingStart = LocalDate.parse(prediction[0][0])
/* the range of predicted bleeding days can be either 3 or 5 */
const predictedBleedingEnd = LocalDate.parse(prediction[0][ prediction[0].length - 1 ])
const daysToEnd = todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)
return { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd }
}
function determinePredictionText(bleedingPrediction) {
if (!bleedingPrediction.length) return predictLabels.noPrediction
const todayDate = LocalDate.now()
const bleedingStart = LocalDate.parse(bleedingPrediction[0][0])
const bleedingEnd = LocalDate.parse(
bleedingPrediction[0][ bleedingPrediction[0].length - 1 ]
)
if (todayDate.isBefore(bleedingStart)) {
const { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd } = getTimes(bleedingPrediction)
if (todayDate.isBefore(predictedBleedingStart)) {
return predictLabels.predictionInFuture(
todayDate.until(bleedingStart, ChronoUnit.DAYS),
todayDate.until(bleedingEnd, ChronoUnit.DAYS)
todayDate.until(predictedBleedingStart, ChronoUnit.DAYS),
todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)
)
}
if (todayDate.isAfter(bleedingEnd)) {
if (todayDate.isAfter(predictedBleedingEnd)) {
return predictLabels.predictionInPast(
bleedingStart.toString(), bleedingEnd.toString()
formatDateForShortText(predictedBleedingStart),
formatDateForShortText(predictedBleedingEnd)
)
}
const daysToEnd = todayDate.until(bleedingEnd, ChronoUnit.DAYS)
if (daysToEnd === 0) {
return predictLabels.predictionStartedNoDaysLeft
} else if (daysToEnd === 1) {
@@ -208,14 +183,12 @@ function determinePredictionText(bleedingPrediction) {
function getBleedingPredictionRange(prediction) {
if (!prediction.length) return labels.unknown
const todayDate = LocalDate.now()
const bleedingStart = LocalDate.parse(prediction[0][0])
const bleedingEnd = LocalDate.parse(prediction[0][ prediction[0].length - 1 ])
if (todayDate.isBefore(bleedingStart)) {
return `${todayDate.until(bleedingStart, ChronoUnit.DAYS)}-${todayDate.until(bleedingEnd, ChronoUnit.DAYS)}`
const { todayDate, predictedBleedingStart, predictedBleedingEnd, daysToEnd } = getTimes(prediction)
if (todayDate.isBefore(predictedBleedingStart)) {
return `${todayDate.until(predictedBleedingStart, ChronoUnit.DAYS)}-${todayDate.until(predictedBleedingEnd, ChronoUnit.DAYS)}`
}
if (todayDate.isAfter(bleedingEnd)) {
if (todayDate.isAfter(predictedBleedingEnd)) {
return labels.unknown
}
return '0'
return (daysToEnd === 0 ? '0' : `0 - ${daysToEnd}`)
}
+5 -6
View File
@@ -1,10 +1,11 @@
import React, { Component } from 'react'
import { View, TextInput, TouchableOpacity, Alert, Image } from 'react-native'
import { View, TextInput, TouchableOpacity, Alert } from 'react-native'
import nodejs from 'nodejs-mobile-react-native'
import { saveEncryptionFlag } from '../local-storage'
import AppText from './app-text'
import Header from './header'
import styles from '../styles'
import { passwordPrompt as labels, shared } from '../i18n/en/labels'
import { passwordPrompt as labels, shared, menuTitles } from '../i18n/en/labels'
import { requestHash, deleteDbAndOpenNew, openDb } from '../db'
export default class PasswordPrompt extends Component {
@@ -87,12 +88,10 @@ export default class PasswordPrompt extends Component {
render() {
return (
<View flex={1}>
<Header title={menuTitles.PasswordPrompt.toLowerCase()} />
{this.state.showPasswordPrompt &&
<View style={styles.passwordPromptPage}>
<Image
source={require('../assets/drip_small.png')}
style={styles.passwordPromptImage}
/>
<TextInput
onChangeText={val => this.setState({ password: val })}
style={styles.passwordPromptField}
+3
View File
@@ -18,6 +18,9 @@ export default class AboutSection extends Component {
<FramedSegment title={labels.credits.title}>
<AppText>{labels.credits.note}</AppText>
</FramedSegment>
<FramedSegment title={labels.donate.title}>
<AppText>{labels.donate.note}</AppText>
</FramedSegment>
<FramedSegment title={labels.website.title}>
<AppText>{links.website.url}</AppText>
</FramedSegment>
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import RNFS from 'react-native-fs'
import { Alert, ToastAndroid } from 'react-native'
import PropTypes from 'prop-types'
import { clearDb, isDbEmpty } from '../../../db'
import { hasEncryptionObservable } from '../../../local-storage'
@@ -24,6 +25,7 @@ export default class DeleteData extends Component {
}
onAlertConfirmation = () => {
this.props.onStartDeletion()
if (this.state.isPasswordSet) {
this.setState({ isConfirmingWithPassword: true })
} else {
@@ -78,8 +80,9 @@ export default class DeleteData extends Component {
render() {
const { isConfirmingWithPassword } = this.state
const { isDeletingData } = this.props
if (isConfirmingWithPassword) {
if (isConfirmingWithPassword && isDeletingData) {
return (
<ConfirmWithPassword
onSuccess={this.deleteAppData}
@@ -94,4 +97,9 @@ export default class DeleteData extends Component {
</SettingsButton>
)
}
}
DeleteData.propTypes = {
isDeletingData: PropTypes.bool,
onStartDeletion: PropTypes.func.isRequired
}
@@ -6,23 +6,23 @@ import { shared as sharedLabels } from '../../../i18n/en/labels'
import labels from '../../../i18n/en/settings'
import alertError from '../shared/alert-error'
export default function openImportDialogAndImport() {
export function openImportDialog(onImportData) {
Alert.alert(
labels.import.title,
labels.import.message,
[{
text: labels.import.replaceOption,
onPress: () => getFileContentAndImport({ deleteExisting: false })
text: sharedLabels.cancel, style: 'cancel', onPress: () => { }
}, {
text: labels.import.deleteOption,
onPress: () => getFileContentAndImport({ deleteExisting: true })
onPress: () => onImportData(true)
}, {
text: sharedLabels.cancel, style: 'cancel', onPress: () => { }
text: labels.import.replaceOption,
onPress: () => onImportData(false)
}]
)
}
async function getFileContentAndImport({ deleteExisting }) {
export async function getFileContent() {
let fileInfo
try {
fileInfo = await new Promise((resolve, reject) => {
@@ -45,8 +45,13 @@ async function getFileContentAndImport({ deleteExisting }) {
return importError(labels.import.errors.couldNotOpenFile)
}
return fileContent
}
export async function importData(shouldDeleteExistingData, fileContent) {
try {
await importCsv(fileContent, deleteExisting)
await importCsv(fileContent, shouldDeleteExistingData)
Alert.alert(sharedLabels.successTitle, labels.import.success.message)
} catch(err) {
importError(err.message)
+83 -29
View File
@@ -1,37 +1,91 @@
import React from 'react'
import { ScrollView } from 'react-native'
import React, { Component } from 'react'
import { ScrollView, View } from 'react-native'
import AppText from '../../app-text'
import FramedSegment from '../../framed-segment'
import AppLoadingView from '../../app-loading'
import SettingsButton from '../shared/settings-button'
import openImportDialogAndImport from './import-dialog'
import { openImportDialog, getFileContent, importData } from './import-dialog'
import openShareDialogAndExport from './export-dialog'
import DeleteData from './delete-data'
import labels from '../../../i18n/en/settings'
const DataManagement = () => {
return (
<ScrollView>
<FramedSegment title={labels.export.button}>
<AppText>{labels.export.segmentExplainer}</AppText>
<SettingsButton onPress={openShareDialogAndExport}>
{labels.export.button}
</SettingsButton>
</FramedSegment>
<FramedSegment title={labels.import.button}>
<AppText>{labels.import.segmentExplainer}</AppText>
<SettingsButton onPress={openImportDialogAndImport}>
{labels.import.button}
</SettingsButton>
</FramedSegment>
<FramedSegment
title={labels.deleteSegment.title}
last
>
<AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData />
</FramedSegment>
</ScrollView>
)
}
export default class DataManagement extends Component {
export default DataManagement
constructor(props) {
super(props)
this.state = {
isLoading: false,
currentAction: null
}
}
startLoading = () => {
this.setState({ isLoading: true })
}
endLoading = () => {
this.setState({ isLoading: false })
}
startImportFlow = async (shouldDeleteExistingData) => {
this.startLoading()
const fileContent = await getFileContent()
if (fileContent) {
await importData(shouldDeleteExistingData, fileContent)
}
this.endLoading()
}
startExport = () => {
this.setCurrentAction('export')
openShareDialogAndExport()
}
startImport = () => {
this.setCurrentAction('import')
openImportDialog(this.startImportFlow)
}
setCurrentAction = (action) => {
this.setState({ currentAction: action })
}
render() {
const { currentAction } = this.state
return (
<View flex={1}>
{this.state.isLoading && <AppLoadingView />}
{!this.state.isLoading &&
<ScrollView>
<View>
<FramedSegment title={labels.export.button}>
<AppText>{labels.export.segmentExplainer}</AppText>
<SettingsButton onPress={this.startExport}>
{labels.export.button}
</SettingsButton>
</FramedSegment>
<FramedSegment title={labels.import.button}>
<AppText>{labels.import.segmentExplainer}</AppText>
<SettingsButton
onPress= {this.startImport}
>
{labels.import.button}
</SettingsButton>
</FramedSegment>
<FramedSegment
title={labels.deleteSegment.title}
last
>
<AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData
isDeletingData = { currentAction === 'delete' }
onStartDeletion = {() => this.setCurrentAction('delete')}
/>
</FramedSegment>
</View>
</ScrollView>
}
</View>
)
}
}
+3 -3
View File
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import {
ScrollView, View
} from 'react-native'
import styles, { iconStyles } from '../../../styles'
import styles from '../../../styles'
import labels from '../../../i18n/en/settings'
import AppText from '../../app-text'
import FramedSegment from '../../framed-segment'
@@ -28,8 +28,8 @@ export default class Settings extends Component {
</FramedSegment>
<FramedSegment style={styles.framedSegmentLast} >
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<Icon name="info-with-circle" style={iconStyles.infoInHeading}/>
<AppText style={styles.framedSegmentTitle}>{`${labels.preOvu.title} `}</AppText>
<Icon name="info-with-circle"/>
<AppText style={styles.framedSegmentTitle}>{` ${labels.preOvu.title} `}</AppText>
</View>
<AppText>{labels.preOvu.note}</AppText>
</FramedSegment>
+15 -12
View File
@@ -1,19 +1,22 @@
import React from 'react'
import { TextInput } from 'react-native'
import styles, {secondaryColor} from '../../../styles'
import PropTypes from 'prop-types'
import AppTextInput from '../../app-text-input'
import styles from '../../../styles'
export default function PasswordField(props) {
return (
<TextInput
style={styles.passwordField}
autoFocus={props.autoFocus === false ? false : true}
secureTextEntry={true}
onChangeText={props.onChangeText}
value={props.value}
placeholder={props.placeholder}
borderWidth={1}
borderColor={secondaryColor}
borderStyle={'solid'}
<AppTextInput
style={ styles.passwordField }
secureTextEntry
{...props}
/>
)
}
PasswordField.propTypes = {
placeholder: PropTypes.string,
value: PropTypes.string,
onChangeText: PropTypes.func,
autoFocus: PropTypes.bool
}
+171
View File
@@ -0,0 +1,171 @@
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: { type: 'int', optional: true },
texture: { type: 'int', optional: true },
value: { type: 'int', optional: true },
exclude: 'bool'
}
}
const CervixSchema = {
name: 'Cervix',
properties: {
opening: { type: 'int', optional: true },
firmness: { type: 'int', optional: true },
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: 4,
migration: (oldRealm) => {
if (oldRealm.schemaVersion >= 4) return
}
}
+2 -1
View File
@@ -2,5 +2,6 @@ import schema0 from './0.js'
import schema1 from './1.js'
import schema2 from './2.js'
import schema3 from './3.js'
import schema4 from './4.js'
export default [schema0, schema1, schema2, schema3]
export default [schema0, schema1, schema2, schema3, schema4]
+39 -30
View File
@@ -14,6 +14,11 @@ export const bleeding = {
}
export const cervix = {
subcategories: {
opening: 'opening',
firmness: 'firmness',
position: 'position'
},
opening: {
categories: ['closed', 'medium', 'open'],
explainer: 'Is your cervix open or closed?'
@@ -30,6 +35,10 @@ export const cervix = {
}
export const mucus = {
subcategories: {
feeling: 'feeling',
texture: 'texture'
},
feeling: {
categories: ['dry', 'nothing', 'wet', 'slippery'],
explainer: 'What does your vaginal entrance feel like?'
@@ -49,8 +58,8 @@ export const desire = {
export const sex = {
categories:{
solo: 'Solo',
partner: 'Partner',
solo: 'solo',
partner: 'partner',
},
header: "Activity",
explainer: 'Were you sexually active today?',
@@ -58,15 +67,15 @@ export const sex = {
export const contraceptives = {
categories:{
condom: 'Condom',
pill: 'Pill',
iud: 'IUD',
patch: 'Patch',
ring: 'Ring',
implant: 'Implant',
diaphragm: 'Diaphragm',
none: 'None',
other: 'Other',
condom: 'condom',
pill: 'pill',
iud: 'iud',
patch: 'patch',
ring: 'ring',
implant: 'implant',
diaphragm: 'diaphragm',
none: 'none',
other: 'other',
},
header: "Contraceptives",
explainer: 'Did you use contraceptives?'
@@ -74,30 +83,30 @@ export const contraceptives = {
export const pain = {
categories: {
cramps: 'Cramps',
ovulationPain: 'Ovulation pain',
headache: 'Headache',
backache: 'Backache',
nausea: 'Nausea',
tenderBreasts: 'Tender breasts',
migraine: 'Migraine',
other: 'Other'
cramps: 'cramps',
ovulationPain: 'ovulation pain',
headache: 'headache',
backache: 'backache',
nausea: 'nausea',
tenderBreasts: 'tender breasts',
migraine: 'migraine',
other: 'other'
},
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'
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?'
}
@@ -107,7 +116,7 @@ export const temperature = {
outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
saveAnyway: 'Save anyway',
temperature: {
header: "Temperature (°C)",
header: "Temperature",
explainer: 'Take your temperature right after waking up, before getting out of bed'
},
time: "Time",
+13 -14
View File
@@ -15,8 +15,6 @@ export const shared = {
date: 'Date',
cycleDayWithLinebreak: 'Cycle\nday',
loading: 'Loading ...',
more: 'more',
less: 'less',
enter: 'Enter'
}
@@ -32,15 +30,15 @@ export const headerTitles = {
Password: settingsTitles.password,
About: settingsTitles.about,
License: settingsTitles.license,
BleedingEditView: 'Bleeding',
TemperatureEditView: 'Temperature',
MucusEditView: 'Mucus',
CervixEditView: 'Cervix',
NoteEditView: 'Note',
DesireEditView: 'Desire',
SexEditView: 'Sex',
PainEditView: 'Pain',
MoodEditView: 'Mood',
bleeding: 'Bleeding',
temperature: 'Temperature',
mucus: 'Cervical Mucus',
cervix: 'Cervix',
note: 'Note',
desire: 'Desire',
sex: 'Sex',
pain: 'Pain',
mood: 'Mood',
InfoSymptom: 'Info'
}
@@ -50,6 +48,7 @@ export const menuTitles = {
Chart: 'Chart',
Stats: 'Stats',
Settings: 'Settings',
PasswordPrompt: 'Drip'
}
export const stats = {
@@ -110,12 +109,12 @@ export const fertilityStatus = {
infertile: 'infertile',
fertileUntilEvening: 'Fertile phase ends in the evening',
unknown: 'We cannot show any cycle information because no period data has been added.',
preOvuText: "With NFP rules, you may assume 5 days of infertility at the beginning of your cycle, provided you don't observe any fertile mucus or cervix values.",
periOvuText: "We have not been able to detect both a temperature shift and mucus or cervix shift. Please find more information on NFP rules here:",
preOvuText: "With NFP rules, you may assume 5 days of infertility at the beginning of your cycle, provided you don't observe any fertile cervical mucus or cervix values.",
periOvuText: "We have not been able to detect both a temperature shift and cervical mucus or cervix shift. Please find more information on NFP rules here:",
postOvuText: tempRule => {
return (
'We have detected a temperature shift (' + ['regular', '1st exception', '2nd exception'][tempRule] +
' temperature rule), as well as a mucus shift according to NFP rules. You may assume infertility, but always remember to ' +
' temperature rule), as well as a cervical mucus shift according to NFP rules. You may assume infertility, but always remember to ' +
'double-check for yourself. Make sure the data makes sense to you.'
)
}
+4
View File
@@ -14,4 +14,8 @@ export default {
website: {
url: 'https://bloodyhealth.gitlab.io/'
},
donate: {
url: 'https://ko-fi.com/dripapp',
text: 'here'
}
}
+6 -2
View File
@@ -74,8 +74,8 @@ export default {
},
useCervix: {
title: 'Secondary symptom',
cervixModeOn: 'Cervix values are being used for symptothermal fertility detection. You can switch here to use mucus values for symptothermal fertility detection',
cervixModeOff: 'By default, mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection'
cervixModeOn: 'Cervix values are being used for symptothermal fertility detection. You can switch here to use cervical mucus values for symptothermal fertility detection',
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'
},
passwordSettings: {
title: 'App password',
@@ -123,5 +123,9 @@ You can contact us by bloodyhealth@mailbox.org.`
credits: {
title: 'Credits',
note: 'Thanks and lots of <3 to all of our contributors as well as Susanne Umscheid for the wonderful design, and Paula Härtel for the symptom icons.'
},
donate: {
title: 'Buy us a coffee!',
note: `The Bloody Health team is always grateful for donations, big or small, that help us maintain this app and develop new features. You can donate ${links.donate.url}. Thank you! You're awesome.`
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
import links from './links'
export const generalInfo = {
chartNfp: `On the chart, you can track fertility signs. When both a valid temperature shift and a mucus or cervix shift have been detected, an orange line will be displayed on the chart. This indicates the end of the peri-ovulatory and the beginning of the post-ovulatory phase.`,
chartNfp: `On the chart, you can track fertility signs. When both a valid temperature shift and a cervical mucus or cervix shift have been detected, an orange line will be displayed on the chart. This indicates the end of the peri-ovulatory and the beginning of the post-ovulatory phase.`,
curiousNfp: `If you are curious to learn more about the sympto-thermal method that is used for fertility tracking within the app, you can visit ${links.wiki.url}.`,
cycleRelation: `It may be influenced by or have an impact on your menstrual cycles and its hormonal changes.`,
excludeExplainer: `You can exclude these values, so they won't be taken into account for any fertility calculation.`,
@@ -85,9 +85,9 @@ From lowest to best quality:
· S = (no OR wet feeling + creamy texture),
· S+ = (any feeling + egg white texture) OR (slippery feeling + any texture).
On the chart, mucus is colored in blue: the darker the shade of blue the better the quality of your mucus.
On the chart, cervical mucus is colored in blue: the darker the shade of blue the better the quality of your cervical mucus.
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.
Please note that drip does not yet support "parenthesis values": According to NFP rules, you can qualify a cervical 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.
${generalInfo.chartNfp}
+155 -36
View File
@@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -72,6 +71,8 @@
E09F3B05A4F84E9883101CC7 /* libRNNodeJsMobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */; };
E43EF009AC8C4698AB322190 /* NodeMobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C225FC4966694B9FBD32E946 /* NodeMobile.framework */; };
E4584E55EEC24302A3E84A23 /* nodejs-project in Resources */ = {isa = PBXBuildFile; fileRef = 6466AE2461BE4FA88B8372F0 /* nodejs-project */; };
77500FAD5ADD402AAD2D9972 /* AntDesign.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3102FB76D69C42938E0E126D /* AntDesign.ttf */; };
AA800A96BB73482AA90E29B8 /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -515,6 +516,8 @@
F5039D0A572B4BBCB7995891 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
F59A471BDE4144A1A41D4B52 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = "<group>"; };
F992F2D99E614DD79FAD6565 /* libRNNodeJsMobile.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNNodeJsMobile.a; sourceTree = "<group>"; };
3102FB76D69C42938E0E126D /* AntDesign.ttf */ = {isa = PBXFileReference; name = "AntDesign.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; name = "OpenSans-LightItalic.ttf"; path = "../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -606,6 +609,8 @@
5E7B0A75F8004C6699B70F86 /* Prompt-ExtraLight.ttf */,
673C016DDDD74C2F89050279 /* OpenSans-Light.ttf */,
644690BCCEBF41789960B9A2 /* OpenSans-SemiBold.ttf */,
3102FB76D69C42938E0E126D /* AntDesign.ttf */,
885BDE0B3896402F99D0C860 /* OpenSans-LightItalic.ttf */,
);
name = Resources;
sourceTree = "<group>";
@@ -949,9 +954,9 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
2B572382D4504B8FB4B9D251 /* Embed Frameworks */,
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */,
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */,
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */,
D6AE48E8124449EE863F342D /* Build NodeJS Mobile Native Modules */,
7D43D3729FAE42C0859CDC36 /* Sign NodeJS Mobile Native Modules */,
54483AF1DA474A71931D6D6A /* Remove NodeJS Mobile Framework Simulator Strips */,
);
buildRules = (
);
@@ -1510,6 +1515,8 @@
B9A5B9946C4C456C823B7641 /* Prompt-ExtraLight.ttf in Resources */,
5D921C348AC14944835A4D82 /* OpenSans-Light.ttf in Resources */,
71D0BCE4666A4AB8A0874B5A /* OpenSans-SemiBold.ttf in Resources */,
77500FAD5ADD402AAD2D9972 /* AntDesign.ttf in Resources */,
AA800A96BB73482AA90E29B8 /* OpenSans-LightItalic.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1559,47 +1566,159 @@
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
6292723A374D49FDB7CF4114 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
D6AE48E8124449EE863F342D /* Build NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Remove NodeJS Mobile Framework Simulator Strips";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nFRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"\nFRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"\nif [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then\n if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then\n lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"\n rm \"$FRAMEWORK_BINARY_PATH\"\n mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"\n echo \"Removed simulator strip from NodeMobile.framework\"\n fi\nfi\n";
};
BF4F9DB28A984C43A497C8E6 /* Sign NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Sign NodeJS Mobile Native Modules";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\n# Create Info.plist for each framework built and loader override.\nPATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\nNODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"\nnode \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR\n# Embed every resulting .framework in the application and delete them afterwards.\nembed_framework()\n{\n FRAMEWORK_NAME=\"$(basename \"$1\")\"\n cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n \n /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"\n}\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done\n\n#Delete gyp temporary .deps dependency folders from the project structure.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete\n\n#Delete frameworks from their build paths\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n";
};
FF1D4199225D4DA692E5AEB6 /* Build NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Build NodeJS Mobile Native Modules";
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\nset -e\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, look for it in the project's\n#nodejs-assets/BUILD_NATIVE_MODULES.txt file.\nNODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"\nPREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"\n if [ -f \"$PREFERENCE_FILE_PATH\" ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"\n fi\nfi\nif [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then\n# If build native modules preference is not set, try to find .gyp files\n#to turn it on.\n gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))\n if [ ${#gypfiles[@]} -gt 0 ]; then\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=1\n else\n NODEJS_MOBILE_BUILD_NATIVE_MODULES=0\n fi\nfi\nif [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi\n# Delete object files that may already come from within the npm package.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete\n# Delete bundle contents that may be there from previous builds.\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete\nfind \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete\n# Apply patches to the modules package.json\nif [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then\n PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"\n NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"\n node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR\nfi\n# Get the nodejs-mobile-gyp location\nif [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"\nelse\n NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"\nfi\nNODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js\n# Rebuild modules with right environment\nNODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"\npushd $CODESIGNING_FOLDER_PATH/nodejs-project/\nif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]\nthen\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source\nelse\n GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source\nfi\npopd\n";
shellScript = "
set -e
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
# If build native modules preference is not set, look for it in the project's
#nodejs-assets/BUILD_NATIVE_MODULES.txt file.
NODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"
PREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"
if [ -f \"$PREFERENCE_FILE_PATH\" ]; then
NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"
fi
fi
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
# If build native modules preference is not set, try to find .gyp files
#to turn it on.
gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))
if [ ${#gypfiles[@]} -gt 0 ]; then
NODEJS_MOBILE_BUILD_NATIVE_MODULES=1
else
NODEJS_MOBILE_BUILD_NATIVE_MODULES=0
fi
fi
if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
# Delete object files that may already come from within the npm package.
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type f -delete
# Delete bundle contents that may be there from previous builds.
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.node/*\" -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.node\" -type d -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
# Apply patches to the modules package.json
if [ -d \"$CODESIGNING_FOLDER_PATH\"/nodejs-project/node_modules/ ]; then
PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"
NODEJS_PROJECT_MODULES_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/node_modules/ && pwd )\"
node \"$PATCH_SCRIPT_DIR\"/patch-package.js $NODEJS_PROJECT_MODULES_DIR
fi
# Get the nodejs-mobile-gyp location
if [ -d \"$PROJECT_DIR/../node_modules/nodejs-mobile-gyp/\" ]; then
NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-gyp/ && pwd )\"
else
NODEJS_MOBILE_GYP_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/node_modules/nodejs-mobile-gyp/ && pwd )\"
fi
NODEJS_MOBILE_GYP_BIN_FILE=\"$NODEJS_MOBILE_GYP_DIR\"/bin/node-gyp.js
# Rebuild modules with right environment
NODEJS_HEADERS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/ios/libnode/ && pwd )\"
pushd $CODESIGNING_FOLDER_PATH/nodejs-project/
if [ \"$PLATFORM_NAME\" == \"iphoneos\" ]
then
GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"arm64\" npm --verbose rebuild --build-from-source
else
GYP_DEFINES=\"OS=ios\" npm_config_nodedir=\"$NODEJS_HEADERS_DIR\" npm_config_node_gyp=\"$NODEJS_MOBILE_GYP_BIN_FILE\" npm_config_platform=\"ios\" npm_config_format=\"make-ios\" npm_config_node_engine=\"chakracore\" npm_config_arch=\"x64\" npm --verbose rebuild --build-from-source
fi
popd
";
};
7D43D3729FAE42C0859CDC36 /* Sign NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Sign NodeJS Mobile Native Modules";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
set -e
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
# If build native modules preference is not set, look for it in the project's
#nodejs-assets/BUILD_NATIVE_MODULES.txt file.
NODEJS_ASSETS_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../nodejs-assets/ && pwd )\"
PREFERENCE_FILE_PATH=\"$NODEJS_ASSETS_DIR/BUILD_NATIVE_MODULES.txt\"
if [ -f \"$PREFERENCE_FILE_PATH\" ]; then
NODEJS_MOBILE_BUILD_NATIVE_MODULES=\"$(cat $PREFERENCE_FILE_PATH | xargs)\"
fi
fi
if [ -z \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then
# If build native modules preference is not set, try to find .gyp files
#to turn it on.
gypfiles=($(find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -type f -name \"*.gyp\"))
if [ ${#gypfiles[@]} -gt 0 ]; then
NODEJS_MOBILE_BUILD_NATIVE_MODULES=1
else
NODEJS_MOBILE_BUILD_NATIVE_MODULES=0
fi
fi
if [ \"1\" != \"$NODEJS_MOBILE_BUILD_NATIVE_MODULES\" ]; then exit 0; fi
# Delete object files
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.o\" -type f -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.a\" -type f -delete
# Create Info.plist for each framework built and loader override.
PATCH_SCRIPT_DIR=\"$( cd \"$PROJECT_DIR\" && cd ../node_modules/nodejs-mobile-react-native/scripts/ && pwd )\"
NODEJS_PROJECT_DIR=\"$( cd \"$CODESIGNING_FOLDER_PATH\" && cd nodejs-project/ && pwd )\"
node \"$PATCH_SCRIPT_DIR\"/ios-create-plists-and-dlopen-override.js $NODEJS_PROJECT_DIR
# Embed every resulting .framework in the application and delete them afterwards.
embed_framework()
{
FRAMEWORK_NAME=\"$(basename \"$1\")\"
cp -r \"$1\" \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/\"
/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY --preserve-metadata=identifier,entitlements,flags --timestamp=none \"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/$FRAMEWORK_NAME\"
}
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d | while read frmwrk_path; do embed_framework \"$frmwrk_path\"; done
#Delete gyp temporary .deps dependency folders from the project structure.
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/.deps/*\" -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \".deps\" -type d -delete
#Delete frameworks from their build paths
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -delete
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
";
};
54483AF1DA474A71931D6D6A /* Remove NodeJS Mobile Framework Simulator Strips */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Remove NodeJS Mobile Framework Simulator Strips";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "
set -e
FRAMEWORK_BINARY_PATH=\"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/NodeMobile.framework/NodeMobile\"
FRAMEWORK_STRIPPED_PATH=\"$FRAMEWORK_BINARY_PATH-strip\"
if [ \"$PLATFORM_NAME\" != \"iphonesimulator\" ]; then
if $(lipo \"$FRAMEWORK_BINARY_PATH\" -verify_arch \"x86_64\") ; then
lipo -output \"$FRAMEWORK_STRIPPED_PATH\" -remove \"x86_64\" \"$FRAMEWORK_BINARY_PATH\"
rm \"$FRAMEWORK_BINARY_PATH\"
mv \"$FRAMEWORK_STRIPPED_PATH\" \"$FRAMEWORK_BINARY_PATH\"
echo \"Removed simulator strip from NodeMobile.framework\"
fi
fi
";
};
/* End PBXShellScriptBuildPhase section */
+2
View File
@@ -86,6 +86,8 @@
<string>OpenSans-Light.ttf</string>
<string>OpenSans-Regular.ttf</string>
<string>OpenSans-SemiBold.ttf</string>
<string>AntDesign.ttf</string>
<string>OpenSans-LightItalic.ttf</string>
</array>
</dict>
</plist>
+3
View File
@@ -1,4 +1,7 @@
export default function (feeling, texture) {
if (typeof feeling != 'number' || typeof texture != 'number') return null
const feelingMapping = {
0: 0,
1: 1,
+2 -2
View File
@@ -58,9 +58,9 @@ export default function setupNotifications(navigate) {
function setupPeriodReminder() {
const bleedingPrediction = cycleModule().getPredictedMenses()
if (bleedingPrediction.length > 0) {
const bleedingStart = Moment(bleedingPrediction[0][0], "YYYY-MM-DD")
const predictedBleedingStart = Moment(bleedingPrediction[0][0], "YYYY-MM-DD")
// 3 days before and at 6 am
const reminderDate = bleedingStart
const reminderDate = predictedBleedingStart
.subtract(3, 'days')
.hours(6)
.minutes(0)
+13 -1
View File
@@ -108,7 +108,15 @@ function formatCycleForSympto(cycle) {
if (day[symptomName] && day[symptomName].exclude) {
delete day[symptomName]
}
});
})
// remove days with incomplete cervix values
if (hasIncompleteCervixValue(day)) {
delete day.cervix
}
// remove days with incomplete mucus value (because nfp-mucus returns null when that's the case)
if (day.mucus && day.mucus.value === null) {
delete day.mucus
}
// change format
['bleeding', 'temperature', 'mucus'].forEach(symptomName => {
if (day[symptomName]) day[symptomName] = day[symptomName].value
@@ -119,4 +127,8 @@ function formatCycleForSympto(cycle) {
// we get earliest last, but sympto wants earliest first
formatted.reverse()
return formatted
}
function hasIncompleteCervixValue(day) {
return day.cervix && (typeof day.cervix.opening != 'number' || typeof day.cervix.firmness != 'number')
}
+2932 -3379
View File
File diff suppressed because it is too large Load Diff
+22 -12
View File
@@ -1,6 +1,6 @@
{
"name": "drip",
"version": "0.0.2",
"version": "0.1905.29-beta",
"contributors": [
"Julia Friesel <julia.friesel@gmail.com>",
"Marie Kochsiek",
@@ -14,7 +14,12 @@
"test": "mocha --recursive --require @babel/register test && npm run lint",
"test-watch": "mocha --recursive --require @babel/register --watch test",
"lint": "eslint components lib test",
"devtool": "adb shell input keyevent 82"
"devtool": "adb shell input keyevent 82",
"update-version": "./tools/update-version.js",
"build-android-release": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd ..",
"commit-release": "./tools/commit-release",
"update-changelog": "./tools/update-changelog.js",
"release": "npm run update-version && npm run build-android-release && npm run commit-release"
},
"dependencies": {
"@ptomasroos/react-native-multi-slider": "^1.0.0",
@@ -25,12 +30,12 @@
"js-base64": "^2.4.8",
"js-joda": "^1.8.2",
"moment": "^2.22.2",
"nodejs-mobile-react-native": "^0.3.0",
"nodejs-mobile-react-native": "^0.4.1",
"object-path": "^0.11.4",
"obv": "0.0.1",
"prop-types": "^15.6.2",
"react": "^16.6.3",
"react-native": "^0.57.8",
"react": "16.6.1",
"react-native": "0.58.0-rc.0",
"react-native-calendars": "^1.19.3",
"react-native-document-picker": "^2.1.0",
"react-native-fs": "^2.13.3",
@@ -39,20 +44,25 @@
"react-native-push-notification": "github:jfr3000/react-native-push-notification",
"react-native-restart": "0.0.7",
"react-native-share": "^1.1.3",
"react-native-vector-icons": "^5.0.0",
"react-native-vector-icons": "^6.4.2",
"realm": "^2.22.0",
"sympto": "^1.0.0"
"sympto": "^1.0.4"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
"@babel/register": "^7.0.0",
"@babel/core": "^7.2.2",
"@babel/register": "^7.0.0",
"babel-eslint": "^10.0.1",
"basic-changelog": "gitlab:bloodyhealth/basic-changelog",
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"eslint": "^4.19.1",
"eslint": "^5.16.0",
"eslint-plugin-react": "^7.8.2",
"jase": "^1.2.0",
"left-pad": "^1.3.0",
"metro-react-native-babel-preset": "^0.51.1",
"mocha": "^5.2.0"
"mocha": "^5.2.0",
"react-native-version": "^3.1.0",
"readline": "^1.3.0"
},
"description": "A menstrual cycle tracking app that's open-source and leaves your data on your phone. Use it to track your menstrual cycle or for fertility awareness!",
"main": "index.js",
@@ -79,4 +89,4 @@
"assets/fonts"
]
}
}
}
+86 -66
View File
@@ -17,6 +17,7 @@ const headerFont = 'Prompt-ExtraLight'
const textFont = 'OpenSans-Light'
const textFontBold = 'OpenSans-SemiBold'
const textFontItalic = 'OpenSans-LightItalic'
const regularSize = 16
const hintSize = 14
@@ -43,12 +44,9 @@ export default StyleSheet.create({
fontSize: regularSize,
letterSpacing: 0.5
},
actionHint: {
color: secondaryColor,
fontFamily: textFont,
hint: {
fontFamily: textFontItalic,
fontSize: hintSize,
fontWeight: 'bold',
margin: defaultIndentation
},
paragraph: {
marginBottom: defaultBottomMargin
@@ -104,9 +102,6 @@ export default StyleSheet.create({
marginLeft: 10,
marginTop: 6
},
homeView: {
marginHorizontal: 50,
},
button: {
paddingVertical: 10,
paddingHorizontal: 20,
@@ -120,29 +115,26 @@ export default StyleSheet.create({
homeButtonText: {
color: fontOnPrimaryColor
},
homeIconElement: {
alignItems: 'center',
marginTop: 15
homeView: {
margin: 40,
},
homeDescriptionText: {
width: 200,
marginBottom: defaultBottomMargin,
},
homeElement: {
marginBottom: 30,
flexDirection: 'row',
},
homeIconTextWrapper: {
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10,
},
wrapperCycle: {
homeIconAndText: {
justifyContent: 'center'
},
wrapperIcon: {
width: 80,
height: 77,
position: 'absolute'
},
wrapperDrop: {
width: 81,
height: 85,
marginTop: 20,
position: 'absolute'
},
wrapperCircle: {
width: 80,
height: 80,
position: 'absolute'
},
homeCircle: {
@@ -157,14 +149,6 @@ export default StyleSheet.create({
iconText: {
fontSize: 25
},
showMore: {
transform: [{rotate: '90deg'}],
position: 'absolute',
},
showLess: {
transform: [{rotate: '270deg'}],
position: 'absolute'
},
cycleDayNumber: {
fontSize: 15,
color: fontOnPrimaryColor,
@@ -173,12 +157,10 @@ export default StyleSheet.create({
},
symptomViewHeading: {
fontWeight: 'bold',
fontFamily: textFontBold,
marginTop: 10
fontFamily: textFontBold
},
symptomInfoIcon: {
marginRight: 20,
marginLeft: 20
symptomSection: {
marginBottom: 10
},
symptomBoxImage: {
width: 50,
@@ -236,16 +218,12 @@ export default StyleSheet.create({
justifyContent: 'center',
height: 80
},
headerCycleDay: {
flexDirection: 'row',
justifyContent: 'space-between',
},
navigationArrow: {
padding: 20
},
hiddenIcon: {
padding: 20
padding: 20,
position: 'absolute'
},
navigationArrowLeft: { left: 0 },
navigationArrowRight: { right: 0 },
menu: {
backgroundColor: primaryColor,
alignItems: 'center',
@@ -269,16 +247,12 @@ export default StyleSheet.create({
temperatureTextInput: {
fontSize: 20,
color: 'black',
textAlign: 'center'
textAlign: 'center',
width: '30%'
},
temperatureTextInputSuggestion: {
color: '#939393'
},
actionButtonRow: {
flexDirection: 'row',
justifyContent: 'space-evenly',
marginTop: 50
},
symptomEditButton: {
width: 130
},
@@ -299,10 +273,40 @@ export default StyleSheet.create({
fontWeight: 'bold',
fontFamily: textFontBold
},
framedSegmentInlineChildren: {
flexDirection: 'row',
alignItems: 'center'
},
infoPopUpWrapper: {
position: 'absolute',
width: '100%',
height: '100%'
},
infoPopUp: {
backgroundColor: 'white',
padding: 15,
marginHorizontal: 20,
marginTop: 20,
maxHeight: '92%'
},
dimmed: {
position: 'absolute',
backgroundColor: 'black',
opacity: 0.5,
width: '100%',
height: '100%'
},
infoSymptomClose: {
alignItems: 'flex-end'
},
infoSymptomText: {
marginTop: 10
},
settingsButton: {
padding: 10,
alignItems: 'center',
margin: 10
margin: 10,
borderRadius: 5,
},
settingsButtonAccent: {
backgroundColor: secondaryColor
@@ -374,7 +378,8 @@ export default StyleSheet.create({
borderLeftWidth: null
},
page: {
marginHorizontal: 10
marginHorizontal: 10,
marginTop: 20,
},
calendarToday: {
fontWeight: 'bold',
@@ -383,13 +388,21 @@ export default StyleSheet.create({
marginTop: 1
},
passwordField: {
padding: 10,
marginTop: 10,
marginHorizontal: 10,
backgroundColor: 'white'
marginTop: 10
},
textInputField: {
padding: 10,
marginVertical: 10,
backgroundColor: 'white',
borderColor: secondaryColor,
borderStyle: 'solid',
borderWidth: 1,
},
passwordPromptPage: {
padding: 30,
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
passwordPromptField: {
@@ -418,8 +431,16 @@ export default StyleSheet.create({
marginTop: 20,
color: 'grey'
},
infoButton: {
paddingVertical: 20
headerDeleteButton: {
paddingHorizontal: 20,
paddingVertical: 20,
position: 'absolute',
right: 0
},
infoButtonSymptomView: {
position: 'absolute',
padding: 15,
right: 0
},
licensePage: {
paddingVertical: 20,
@@ -451,6 +472,10 @@ export const iconStyles = {
symptomBoxActive: {
color: fontOnPrimaryColor
},
info: {
color: secondaryColor,
fontSize: 25
},
menuIcon: {
size: 20,
color: fontOnPrimaryColor
@@ -458,12 +483,7 @@ export const iconStyles = {
menuIconInactive: {
color: colorInActive,
},
infoInHeading: {
marginRight: 5,
color: 'black'
},
hiddenIcon: {
size: 20,
display: 'none'
infoPopUpClose: {
size: 25
}
}
+5
View File
@@ -7,6 +7,11 @@ chai.use(dirtyChai)
import getSensiplanMucus from '../lib/nfp-mucus'
describe('getSensiplanMucus', () => {
it('returns null if there is no value for feeling or texture', () => {
expect(getSensiplanMucus()).to.be.null()
expect(getSensiplanMucus(undefined, 3)).to.be.null()
expect(getSensiplanMucus(2, undefined)).to.be.null()
})
describe('results in t for:', () => {
it('dry feeling and no texture', function () {
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
set -eEu -o pipefail
shopt -s extdebug
IFS=$'\n\t'
trap 'onFailure $?' ERR
function onFailure() {
echo "Unhandled script error $1 at ${BASH_SOURCE[0]}:${BASH_LINENO[0]}" >&2
exit 1
}
git add -A
git commit -m "release: $(cat package.json | $(npm bin)/jase version)"
git tag v$(cat package.json | $(npm bin)/jase version)
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env node
const updateChangelog = require('basic-changelog')
const opts = {
filterCommitsStartingWith: ['release:']
}
updateChangelog('./CHANGELOG.md', opts, err => {
if (err) {
console.error('Something went wrong trying to update the changelog:')
console.error(err)
return
}
console.log('Changelog successfully updated')
})
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env node
// from https://gitlab.com/staltz/manyverse/blob/master/tools/update-version.js
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const ReactNativeVersion = require('react-native-version')
const readline = require('readline')
const leftPad = require('left-pad')
const path = require('path')
const fs = require('fs')
const currentVersion = JSON.parse(fs.readFileSync('./package.json')).version
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function createTodaysVersion(attempt) {
const today = new Date()
const yy = today.getFullYear() - 2000 // So it's two digits
const mm = leftPad(today.getMonth() + 1, 2, '0')
const d = today.getDate()
if (attempt === 0) {
return `0.${yy}${mm}.${d}-beta`
} else {
const letter = String.fromCharCode(97 + attempt) // 0=a, 1=b, 2=c, ...
return `0.${yy}${mm}.${d}-beta.${letter}`
}
}
let nextVersion
for (let i = 0 /* letter a */; i <= 25 /* letter z */; i++) {
nextVersion = createTodaysVersion(i)
if (nextVersion !== currentVersion) break
}
if (nextVersion === currentVersion) {
console.error('I dont know what else to generate beyong ' + nextVersion)
process.exit(1)
}
rl.question('Next version will be `' + nextVersion + '`, okay? y/n ', yn => {
if (yn !== 'y' && yn !== 'Y') {
console.log('Release cancelled.\n')
process.exit(1)
return
}
const pkgJSON = JSON.parse(fs.readFileSync('./package.json'))
const pkgLockJSON = JSON.parse(fs.readFileSync('./package-lock.json'))
pkgJSON.version = nextVersion
pkgLockJSON.version = nextVersion
fs.writeFileSync('./package.json', JSON.stringify(pkgJSON, null, 2))
fs.writeFileSync('./package-lock.json', JSON.stringify(pkgLockJSON, null, 2))
ReactNativeVersion.version(
{
neverAmend: true,
target: 'android',
},
path.resolve(__dirname, '../'),
).catch(err => {
console.error(err)
process.exit(1)
})
rl.close()
})