Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 448e0a8476 | |||
| b1d35afc87 | |||
| 50a16ee0dd | |||
| d67c912e1d | |||
| 5ca129fd47 | |||
| 2d4aa722c9 | |||
| 3d4a3ea7bf | |||
| df732e5acc | |||
| 19942d3afc | |||
| 8065dad6a0 | |||
| 5325f071bd | |||
| a261154889 | |||
| 176b60be89 | |||
| 3e918226d2 | |||
| d2b7577fba | |||
| dd18611498 | |||
| aa0e5b6836 | |||
| 659eb8da31 | |||
| 03905be5f1 | |||
| 6f36428fd2 | |||
| fcde07f2f5 | |||
| 9dee0bd443 | |||
| f1f18e6f93 | |||
| 3efc4efcad | |||
| 8721a87bb7 | |||
| f829b371e6 | |||
| 8e73a64397 | |||
| 199aa198d1 | |||
| 7616d79687 | |||
| 83b80e5623 | |||
| 9aba800119 | |||
| e5d2a7e4cf | |||
| 5856dca47b | |||
| e61f8c9483 | |||
| 5fc2320afe | |||
| 9a87bf760c | |||
| a51e485545 | |||
| 0d821c7b39 | |||
| 97d9e8bd25 | |||
| 3b576e8173 | |||
| 68ef06b93e | |||
| a35d757f84 | |||
| 2fb20c0031 | |||
| 17266d46dd | |||
| 7aff04946e | |||
| 45bc9f6c51 | |||
| 97ff3deac3 | |||
| 08d18c1241 | |||
| af5c24c466 | |||
| 6a7323269a | |||
| 4e6653c54c | |||
| ab1acf21ba | |||
| d25af176d3 | |||
| d375645316 | |||
| 80cc9bc73d | |||
| 8f6ae97ef9 | |||
| 8715f53140 | |||
| b00b97d411 | |||
| 748b5bcbd1 | |||
| 9e907dad0b | |||
| 08e01346f3 | |||
| ada60a745b | |||
| 59aef1126d | |||
| 8145619f28 | |||
| c00e3f2bef | |||
| ead6db12a9 | |||
| 60cde25f76 | |||
| 9c9d06f192 | |||
| d47ae0de0e | |||
| 44b916354d | |||
| 42bf02a0c7 | |||
| 72bcd34272 | |||
| 89ff4b2cdc | |||
| 8f34cbc260 | |||
| 295e751217 | |||
| 45da01ba07 | |||
| c270c7a55f | |||
| f9eb06f197 | |||
| 9d3c332453 | |||
| e5418c32e2 | |||
| fbfc4d6621 | |||
| 77908afd60 | |||
| 750ceeaaf4 | |||
| 5af29f9628 | |||
| cf63fbb37c | |||
| 1f83621e7d | |||
| 3778c1454f | |||
| 59ee05eb1f | |||
| 7916bae848 | |||
| b429954ad2 | |||
| 4f4dc85f8c | |||
| 6de05f0d70 | |||
| 99a2bf256d | |||
| 14c2d17a20 | |||
| b91547032d | |||
| 0451b078ad | |||
| 5a555f5965 | |||
| 885da5c293 | |||
| ef16cfd041 | |||
| 550b1e6314 | |||
| 0c3bfdfced | |||
| c367a1e233 | |||
| 3d2d659b54 | |||
| cbe9f3947d | |||
| 1f5d869b17 | |||
| 9d6c0aa65c | |||
| fe5f734cff | |||
| 111475b2e3 | |||
| c2e7bf8761 | |||
| 579c4af869 | |||
| 7afaa1e156 | |||
| 61ca71fd1f | |||
| bba25aeaac | |||
| 45cff710c8 | |||
| 42bc91d9c2 | |||
| 6c89e642b4 | |||
| 9d7121f5d6 | |||
| eff15e67ee | |||
| 1575aa29cd | |||
| 08deb87df4 | |||
| f2706f509a | |||
| a68ec248b0 | |||
| 842e8a2a24 | |||
| c090e14835 | |||
| 29d76d19b1 | |||
| b6c0fd0dce | |||
| 1077f878ae | |||
| cec8dbaf38 | |||
| b9d85042ef | |||
| f0caffacdd | |||
| bf4446c742 | |||
| 4d6f0db30a | |||
| ce92b0af33 | |||
| e101ecb8d1 | |||
| e609d51528 | |||
| e80d8880b0 | |||
| 3de92b9f77 | |||
| 652bc1ffe2 | |||
| 1a46e1bb2e | |||
| fb519c4380 | |||
| 78d4077fb4 | |||
| 6fb1c7cce9 | |||
| 4f30db69f3 | |||
| 3f8f6dbbe6 | |||
| 8c1c72ccc9 | |||
| 1fc7bc17b9 | |||
| d3e795a51f | |||
| 4f736d5fe2 | |||
| afb53a6f82 | |||
| b798f12266 | |||
| 9d05b27d32 | |||
| 2f7aba0137 | |||
| 722981d224 | |||
| ab45d2f1ca | |||
| b974ba1d7e | |||
| 9cf542f4b7 | |||
| 06337a7356 | |||
| 3ac28e527d |
@@ -4,10 +4,7 @@
|
|||||||
"mocha": true,
|
"mocha": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:react/recommended"
|
|
||||||
],
|
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
@@ -16,31 +13,18 @@
|
|||||||
},
|
},
|
||||||
"ecmaVersion": 2018
|
"ecmaVersion": 2018
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["react"],
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": require('./package.json').dependencies.react,
|
"version": require("./package.json").dependencies.react
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": ["error", 2],
|
||||||
"error",
|
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||||
2
|
|
||||||
],
|
|
||||||
"no-console": [
|
|
||||||
"error",
|
|
||||||
{ allow: ["warn", "error"] }
|
|
||||||
],
|
|
||||||
"space-before-function-paren": 0,
|
"space-before-function-paren": 0,
|
||||||
"semi": [
|
"semi": ["warn", "never"],
|
||||||
"warn",
|
"space-infix-ops": ["warn"],
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"space-infix-ops": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"no-trailing-spaces": "error",
|
"no-trailing-spaces": "error",
|
||||||
@@ -53,6 +37,6 @@
|
|||||||
"ignoreTemplateLiterals": true
|
"ignoreTemplateLiterals": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"no-multi-spaces": 2,
|
"no-multi-spaces": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#
|
#
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
build/
|
build/
|
||||||
@@ -65,6 +68,7 @@ android/app/.project
|
|||||||
|
|
||||||
ios/Podfile.lock
|
ios/Podfile.lock
|
||||||
android/app/src/main/res/drawable-*
|
android/app/src/main/res/drawable-*
|
||||||
|
android/app/src/main/assets/*
|
||||||
|
|
||||||
# nodejs-mobile creates these with every npm install
|
# nodejs-mobile creates these with every npm install
|
||||||
nodejs-assets/nodejs-project/sample-*
|
nodejs-assets/nodejs-project/sample-*
|
||||||
|
|||||||
@@ -2,55 +2,315 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## v0.20-05-2.beta
|
## v1.2102.28
|
||||||
|
|
||||||
### Adds
|
|
||||||
* Feature allowing app chart not to show temperature part, when temperature is not tracked and corresponding refactoring
|
|
||||||
* Detox support for e2e testing and addition of the e2e tests
|
|
||||||
* Introduction of Redux global state (date and navigation are stored locally now)
|
|
||||||
* Introduction of clear.sh script to the project automising clearing project caches and packages reinstallation
|
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
* Update of chart shades for bleeding
|
- Temperature range is now between 35 - 39°C and its default values are now set to 35.5 - 37.5°C
|
||||||
* Eslint rule cleanup and addition of new rules (checks for PropTypes definition for React components, multi spaces)
|
|
||||||
* sympto library upgrade to version 2.2.0
|
|
||||||
* Preparation for support of drip on iOS devices
|
|
||||||
* Updates representation of the incomplete mucus and cervix values on chart
|
|
||||||
* React Native update to 0.59.10
|
|
||||||
* Refactoring of header, cycle day overview, temperature edit view pages
|
|
||||||
* Setting minimum SDK version to 23 to allow using drip on earlier versions of Android
|
|
||||||
|
|
||||||
### Fixes
|
### Fixed
|
||||||
* Fixes adding notes to the future dates
|
- Blocks invalid input of temperature value
|
||||||
* Fixes app exiting with error when hitting back button on device
|
- Error message for incorrect password on login screen
|
||||||
* Fixes Sex symptom showing on y axis of chart even though the contraception method was deleted
|
- Phase text on home screen for last fertile day
|
||||||
* Fix of the clear.sh file name in package.json
|
- Styling improvements
|
||||||
* Fix of navigation from chart to the cycle day overview
|
|
||||||
* Bug fix for maximum value of mucus not showing on chart
|
## v1.2101.9
|
||||||
* Fixes delete button bug on symptom edit page
|
|
||||||
* Fix of home screen centering
|
### Adds
|
||||||
|
|
||||||
|
- Introduces complete redesign of all sections of the app
|
||||||
|
- Adds new font
|
||||||
|
- Adds Lisa as condriputor
|
||||||
|
- Adds updated text about credits.
|
||||||
|
- Adds missing notification icon
|
||||||
|
- Adds padding between keyboard and text input
|
||||||
|
- Adds limit line length on text of symptom box
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Updates createVersion tag for production releases
|
||||||
|
- Better wording for prediction text
|
||||||
|
- Changes the icon
|
||||||
|
- Changes font color of marked calendar days
|
||||||
|
- Updates styling of Stats page
|
||||||
|
- Updates settings menu styling
|
||||||
|
- Increases hitSlop of menu icon and navigation arrows
|
||||||
|
- Sets calendar pastScrollRange to 10 years
|
||||||
|
- Introduces RN Alert component styling update
|
||||||
|
- Introduces PasswordPrompt component redesign
|
||||||
|
- Updates button activity definition when entering new password
|
||||||
|
- Forbids landscape orientation for app
|
||||||
|
- Updates README.md
|
||||||
|
- Updates sdk 28 -> 29 and migrate to androidx
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed drip typo
|
||||||
|
- Fixed the date label on chart from breaking
|
||||||
|
- Fixed chart dots and lines
|
||||||
|
- Fixed error on highes/lowest scale values
|
||||||
|
- Fixed extra horizontal grid line on chart
|
||||||
|
- Fixed error occurring when navigating back from settings section
|
||||||
|
- Fixed redirect to TemperatureEditView from reminder
|
||||||
|
- Fixed ordinal number suffix on chart date labels
|
||||||
|
- Fixed bug when .8 and .3 labels are not shown in chart
|
||||||
|
- Fixed react-native-vector-icon
|
||||||
|
- Fixed AppLoadingView component centering
|
||||||
|
|
||||||
|
## v0.2007-12.beta
|
||||||
|
|
||||||
|
### Adds
|
||||||
|
|
||||||
|
- Allows chart not to show temperature part, when temperature is not tracked and corresponding refactoring
|
||||||
|
- Detox support for e2e testing and addition of the e2e tests
|
||||||
|
- Introduces Redux global state (date and navigation are stored locally now)
|
||||||
|
- Introduces clear.sh script to the project automising clearing project caches and packages reinstallation
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Updates of chart shades for bleeding
|
||||||
|
- Eslint rule cleanup and addition of new rules (checks for PropTypes definition for React components, multi spaces)
|
||||||
|
- sympto library upgrade to version 2.2.0
|
||||||
|
- Preparation for support of drip on iOS devices
|
||||||
|
- Updates representation of the incomplete mucus and cervix values on chart
|
||||||
|
- React Native update to 0.59.10
|
||||||
|
- Refactors of header, cycle day overview, temperature edit view pages
|
||||||
|
- Setting minimum SDK version to 23 to allow using drip on earlier versions of Android
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed adding notes to the future dates
|
||||||
|
- Fixed app exiting with error when hitting back button on device
|
||||||
|
- Fixed Sex symptom showing on y axis of chart even though the contraception method was deleted
|
||||||
|
- Fixed of the clear.sh file name in package.json
|
||||||
|
- Fixed of navigation from chart to the cycle day overview
|
||||||
|
- Bug fix for maximum value of mucus not showing on chart
|
||||||
|
- Fixed delete button bug on symptom edit page
|
||||||
|
- Fixed of home screen centering
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
* Update of node.js to fix security issue
|
|
||||||
|
- Updates of node.js to fix security issue
|
||||||
|
|
||||||
|
## v0.2005.3-beta
|
||||||
|
|
||||||
|
- Adds arm64-v8a and x86_64 for supporting 64-bit architecture
|
||||||
|
- Adds Mariya & Sofiya as contributors <3
|
||||||
|
- Fixed the error on app exiting on via the device back button
|
||||||
|
- Updates README.md
|
||||||
|
- Allows to enter note in the future
|
||||||
|
- Chart navigation bug fix.
|
||||||
|
- Changes clear to lowercase to make it case sensitive and executable
|
||||||
|
- fix 306 by setting other-note empty as contraception method 'other' is deactivated
|
||||||
|
- Don't show temperature chart part of chart when temp not tracked
|
||||||
|
- Bring in different shades for desire dots on chart
|
||||||
|
- Splits the rest of the tests without modifying them
|
||||||
|
- Moves out the test for getCyclesBefore method of cycle module
|
||||||
|
- Moves out the test for getPReviousCycle method of cycle module
|
||||||
|
- Lint rule react prop types addition
|
||||||
|
- Adds test and fixes getCycleByStartDate method of cycle module
|
||||||
|
- Moves out the tests for getCycleDayNumber and organises them
|
||||||
|
- Adds propTypes definition
|
||||||
|
- Gets rid of a top level prop passed down through a tree of components
|
||||||
|
- Cleanups symptom view
|
||||||
|
- Removes unnecessary prop and defines the missing propTypes
|
||||||
|
- Adds propTypes definition
|
||||||
|
- Gets rid of the redundant state on Home
|
||||||
|
- Moves out home helpers from the component
|
||||||
|
- Moves out HomeElement component
|
||||||
|
- Moves out IconText component
|
||||||
|
- Resets the date in store for today date when navigating home
|
||||||
|
- Sets initial value of date in the store
|
||||||
|
- Removes redundant state and corrects the cycle day prop
|
||||||
|
- Use new published sympto
|
||||||
|
- Fixed missing navigation state on exiting the app
|
||||||
|
- Adds e2e test device config for Nexus 5
|
||||||
|
- De-duplicate line
|
||||||
|
- Fixed navigation logic
|
||||||
|
- Adds go back functionality
|
||||||
|
- Adds navigation tree to define the hierarchy
|
||||||
|
- Moves navigation to the state
|
||||||
|
- Removes the lowercasing to the header title component
|
||||||
|
- Remove now superfluous check for bleeding symptom
|
||||||
|
- Adds remaining tests for maybeSetNewCycleStart
|
||||||
|
- Adds test for deleted bleeding value
|
||||||
|
- Extract maybeSetNewCycleStart into own module
|
||||||
|
- Set new cycle start when bleeding value excluded
|
||||||
|
- Changes the name of the main component
|
||||||
|
- Makes drip work on iOS
|
||||||
|
- Adds a handy script to clear builds/cache/etc
|
||||||
|
- Fixed bug - not showing maximum value of mucus in chart
|
||||||
|
- Moves calculations functions to helpers file
|
||||||
|
- Moves YAxis & HorizontalGrid components in a common conditional expression
|
||||||
|
- Moves auxiliary functions from day-column.js component file to helpers file
|
||||||
|
- Moves Surface element to TemperatureColumn component
|
||||||
|
- Introduces CycleDayLabel component
|
||||||
|
- Introduces TemperatureColumn component
|
||||||
|
- Introduces ChartLine component
|
||||||
|
- Formatting fix
|
||||||
|
- Introduces SymptomCell component
|
||||||
|
- Introduces HorizontalGrid component
|
||||||
|
- Moves out chart (data modelling) helpers to a separate file
|
||||||
|
- Introduces Tick & TickList components
|
||||||
|
- Introduces ChartLegend component
|
||||||
|
- Introduces SymptomIcon component
|
||||||
|
- Rafactors symptom color definition
|
||||||
|
- Introduces YAxis component
|
||||||
|
- Use updated sympto
|
||||||
|
- Fixed typo, and removes a redundant line
|
||||||
|
- Naming update: isFertile>isClosedAndHard, getSymptomColorIndex>symptomColorMethods; update of symptom index retrieval for the sake of readibility
|
||||||
|
- Naming update, change switch to object, fertility logic review
|
||||||
|
- make graph display for incomplete mucus and cervix values
|
||||||
|
- Fixed some warnings on build
|
||||||
|
- Updates the RN version to 0.59.10
|
||||||
|
- Re-add missing build script
|
||||||
|
- Updates the RN version to 0.59
|
||||||
|
- Moves metadata directory to root of project. So fdroid can find it.
|
||||||
|
- Cleans the console.log
|
||||||
|
- Adds test for data deletion
|
||||||
|
- Refactors the header
|
||||||
|
- Replaces the inheritance with composition pattern in the Symptom view
|
||||||
|
- Adds e2e symptom data input tests and necessary testIDs to the existing components
|
||||||
|
- Splits the temperature view to simplify it
|
||||||
|
- Updates README.md
|
||||||
|
- Fixed the cycle day data is not being passed to the symptom view
|
||||||
|
- Fixed the date not being set on changing cycle day, and adds a test for this case
|
||||||
|
- Starts using redux store for storing the date
|
||||||
|
- Redux initial setup
|
||||||
|
- Implements review feedback
|
||||||
|
- Splits <CycleDayOverView /> to smaller components, to simplify it
|
||||||
|
- Adds e2e test setup to README
|
||||||
|
- Adding more tests
|
||||||
|
- Adds initial tests
|
||||||
|
- Introduces detox
|
||||||
|
- Moves app store metadata for here from fdroiddata repo
|
||||||
|
- Set minSdk to Marshmallow (earlier versions don't work)
|
||||||
|
- Only show timestamp when it has a value
|
||||||
|
- Refactors App wrapper component
|
||||||
|
- Fixed reopenning after back button
|
||||||
|
- Make home screen centered
|
||||||
|
- Adds release wizard
|
||||||
|
- Updates nodejs-mobile to fix security issue
|
||||||
|
|
||||||
## v0.1905.29-beta
|
## v0.1905.29-beta
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
* Auto save functionality for all symptoms
|
|
||||||
* Add donation section to about
|
- Auto save functionality for all symptoms
|
||||||
* Clearer labels on cycle day overview
|
- Adds donation section to about
|
||||||
* Rename mucus to cervical mucus
|
- Clearer labels on cycle day overview
|
||||||
* Set show more on homescreen to default and get rid of more/less switch
|
- Rename mucus to cervical mucus
|
||||||
* Add loading screen to data import
|
- Set show more on homescreen to default and get rid of more/less switch
|
||||||
* Removes logo and adds header on the main login screen
|
- Adds loading screen to data import
|
||||||
* Nicer formatting for past bleeding prediction
|
- Removes logo and adds header on the main login screen
|
||||||
* Removes permissions not required for debug or production
|
- Nicer formatting for past bleeding prediction
|
||||||
* Temperature screen styling update
|
- Removes permissions not required for debug or production
|
||||||
|
- Temperature screen styling update
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Styling
|
|
||||||
* Line width in chart
|
- Styling
|
||||||
* Prediction range in drop on homescreen
|
- Line width in chart
|
||||||
|
- Prediction range in drop on homescreen
|
||||||
|
|
||||||
|
## v0.1905.28-beta
|
||||||
|
|
||||||
|
- Displays all the text for Home Elements; Shortens margin btw Home Elements; Adds missing "visit" to text
|
||||||
|
- Adds donation section to about
|
||||||
|
- Gets rid of hidden icon in back button header
|
||||||
|
- Adds subcategories of cevix and mucus as labels
|
||||||
|
- Adds subcategory names to the selected options
|
||||||
|
- Changes fontSize of titles in SymptomBoxes;makes sure it stays in 1 line
|
||||||
|
- Fixed delete button
|
||||||
|
- Get rid of extra styling for non functional info icon
|
||||||
|
- Clean up
|
||||||
|
- Adds autosave for temperate
|
||||||
|
- Auto save whenever symptom view updates
|
||||||
|
- Fixed delete data bug
|
||||||
|
- Make uniform info icon and leave some space
|
||||||
|
- Align droplet text on homescreen
|
||||||
|
- Make info modal only as big as it needs to be
|
||||||
|
- Make sure info icon is always well pressable
|
||||||
|
- Specifying mucus as cervical mucus
|
||||||
|
- Make sure drop text is always positioned correctly
|
||||||
|
- Position icon text for droplet
|
||||||
|
- Styling for homescreen elements to breathe
|
||||||
|
- simple way to rearrange home screen
|
||||||
|
- Set show more on homescreen to default and get rid of more/less switch
|
||||||
|
- Don't render delete icon, instead of just setting it invisible
|
||||||
|
- Make isDeleteIconActive more readable
|
||||||
|
- Updates README.md
|
||||||
|
- Changes order of buttons in the import alert
|
||||||
|
- Remove formatting improvements that clutter up the diff
|
||||||
|
- Fixed cervix value display on overview
|
||||||
|
- Fixed mucus value display on overview
|
||||||
|
- Don't show delete icon just because symptom info is open
|
||||||
|
- Clean up markup
|
||||||
|
- Use own modal instead of alert for symptom info
|
||||||
|
- Gets rid of trailing spaces
|
||||||
|
- Gets rid of old info symptom screen
|
||||||
|
- \[WIP] Adds info button to body as alert for: \* mood, pain, temperature
|
||||||
|
- Adds info button to the body as alert
|
||||||
|
- Try out moving it to body
|
||||||
|
- For temperature, only show delete button when certain fields active
|
||||||
|
- Let symptom views overwrite isDeleteIconActive method
|
||||||
|
- Show or hide delete button based on entered data
|
||||||
|
- Remove unused style
|
||||||
|
- Ask before deleting entry
|
||||||
|
- Changes icon to trash can
|
||||||
|
- Replace info icon in header with delete
|
||||||
|
- Await alert result before navigating back
|
||||||
|
- Address MR change requests
|
||||||
|
- Reset inadvertently changed file
|
||||||
|
- Filter incomplete mucus values in sympto adapter
|
||||||
|
- Don't crash on missing temperature value
|
||||||
|
- Make header back arrow function for auto save
|
||||||
|
- Remove action button footer from symptom views
|
||||||
|
- When nothing entered, delete entry
|
||||||
|
- Adds symptom view component with back button listener
|
||||||
|
- Remove save button from footer
|
||||||
|
- Remove unused line
|
||||||
|
- Make saving incomplete value possible
|
||||||
|
- Filter out incomplete cervix value days in sympto adapter
|
||||||
|
- Updates sympto
|
||||||
|
- Adds migration making mucus and cervix values optional
|
||||||
|
- Don't compute nfp mucus value when data missing
|
||||||
|
- Adds test for missing mucus vaues
|
||||||
|
- excludes internet and system alert window from default permission
|
||||||
|
- Adds comment for bleeding prediction ranges
|
||||||
|
- Changes if statement with conditional operator
|
||||||
|
- changes action buttons color to teal, rounded corners for buttons in settings
|
||||||
|
- Fixed line width in chart
|
||||||
|
- makes the action button footer more like buttons
|
||||||
|
- Adds getTime function for bleedingPredictions reuse; minor style formatting
|
||||||
|
- Renames function to say what it 'does'
|
||||||
|
- Rename to predictedBleeding
|
||||||
|
- Nicer formatting for past bleeding prediction
|
||||||
|
- Fixed prediction range in drop on homescreen
|
||||||
|
|
||||||
|
## v0.1905.10-beta
|
||||||
|
|
||||||
|
- Filter release commits from changelog
|
||||||
|
- Adds update-changelog script
|
||||||
|
- Remove square brackets from CHANGELOG. They are parsed as links
|
||||||
|
- Adds commit-release and npm scripts
|
||||||
|
- Adds update version script from manyverse
|
||||||
|
- Updates RN to 58
|
||||||
|
- Remove superfluous try/catch
|
||||||
|
- Rename methods
|
||||||
|
- Adds loading screen to data import
|
||||||
|
- Improves readability of app page rendering
|
||||||
|
- Updates README.md
|
||||||
|
- adds maxLength to temperature input field
|
||||||
|
- Removes logo and adds header on the main login screen
|
||||||
|
- Adjust version name
|
||||||
|
- Don't build for x86
|
||||||
|
- Upgrade nodejs-mobile-rn to latest
|
||||||
|
- Remove unneeded maven repo and upgrade gradle to 4.10
|
||||||
|
- Lowercase values for sex, pain and mood
|
||||||
|
- Removes permissions not required for debug or production
|
||||||
|
- Adds proptypes to DeletePassword, ChangePassword and ConfirmWithPassword components
|
||||||
|
- Delete password button bug fix
|
||||||
|
- temperature screen styling update
|
||||||
|
|
||||||
## v0.0.3 - 2019-04-17
|
## v0.0.3 - 2019-04-17
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Contributing to drip aka CONDRIBUTING
|
# Contributing to drip aka CONDRIPUTING
|
||||||
So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_tone4: :wave\_tone5:
|
So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_tone4: :wave\_tone5:
|
||||||
|
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_
|
|||||||
|
|
||||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||||
|
|
||||||
[How can I condribute?](#how-can-i-condribute)
|
[How can I condripute?](#how-can-i-condripute)
|
||||||
|
|
||||||
[Thank you](#thank-you)
|
[Thank you](#thank-you)
|
||||||
|
|
||||||
@@ -23,9 +23,9 @@ We have prepared something for **you**: check out our [README](https://gitlab.co
|
|||||||
|
|
||||||
Let us know if you want to suggest improvements for the README and open a merge request (which is just like Github's pull request)
|
Let us know if you want to suggest improvements for the README and open a merge request (which is just like Github's pull request)
|
||||||
|
|
||||||
## How can I condribute?
|
## How can I condripute?
|
||||||
|
|
||||||
### Your First Code Condribution
|
### Your First Code Condripution
|
||||||
|
|
||||||
We are fans of labels, at least for our issues. You can find a list of `newbie` issues [here](https://gitlab.com/bloodyhealth/drip/issues?label_name%5B%5D=Newbie).
|
We are fans of labels, at least for our issues. You can find a list of `newbie` issues [here](https://gitlab.com/bloodyhealth/drip/issues?label_name%5B%5D=Newbie).
|
||||||
If you decide to work on an issue, please click on `Create branch` based on that issue. You can find this as a dropdown option right under `Create merge request`.
|
If you decide to work on an issue, please click on `Create branch` based on that issue. You can find this as a dropdown option right under `Create merge request`.
|
||||||
@@ -54,5 +54,5 @@ To send us a new issue you can also use our [gitlab email](mailto:incoming+blood
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Thank you for condributing to open source, thank you for condributing to drip!
|
Thank you for condriputing to open source, thank you for condriputing to drip!
|
||||||
Much love from Bloody Health :heart\_exclamation:
|
Much love from Bloody Health :heart\_exclamation:
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
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!
|
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/).
|
Find more information on [our website](https://bloodyhealth.gitlab.io/).
|
||||||
|
|
||||||
|
[<img src="https://bloodyhealth.gitlab.io/assets/get.png"
|
||||||
|
alt="Get it here"
|
||||||
|
height="55">](https://bloodyhealth.gitlab.io/release/5.apk)
|
||||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
alt="Get it on F-Droid"
|
alt="Get it on F-Droid"
|
||||||
height="80">](https://f-droid.org/packages/com.drip/)
|
height="80">](https://f-droid.org/packages/com.drip/)
|
||||||
@@ -12,7 +15,9 @@ Find more information on [our website](https://bloodyhealth.gitlab.io/).
|
|||||||
|
|
||||||
The app is built 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).
|
▶ [How to contribute to the project](https://gitlab.com/bloodyhealth/drip/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
▶ [How to release a new version](https://gitlab.com/bloodyhealth/drip/blob/master/RELEASE.md)
|
||||||
|
|
||||||
## Development setup
|
## Development setup
|
||||||
|
|
||||||
@@ -22,7 +27,7 @@ Install [Android Studio](https://developer.android.com/studio/) - you'll need it
|
|||||||
|
|
||||||
#### 2. Node version
|
#### 2. Node version
|
||||||
|
|
||||||
Make sure you are running Node 10 (newer versions won’t work). It's easiest to switch Node versions using `nvm`, here’s how to do it:
|
Make sure you are running Node 10 (newer versions won't work). It's easiest to switch Node versions using `nvm`, here's how to do it:
|
||||||
|
|
||||||
|
|
||||||
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
|
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
|
||||||
@@ -106,10 +111,9 @@ You can run the tests with:
|
|||||||
1. Check what testing device is specified in [package.json](https://gitlab.com/bloodyhealth/drip/blob/master/package.json) under:
|
1. Check what testing device is specified in [package.json](https://gitlab.com/bloodyhealth/drip/blob/master/package.json) under:
|
||||||
```
|
```
|
||||||
{"detox":
|
{"detox":
|
||||||
{"configurations":
|
{"configurations":
|
||||||
{"name": "NEXUS_DEVICE_OR_WHATEVER_SPECIFIED_DEVICE"}
|
{"name": "NEXUS_DEVICE_OR_WHATEVER_SPECIFIED_DEVICE"}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
2. Check if the current device is already installed on your machine. Go to `cd ~/Android/sdk/emulator/` or wherever you have Android installed on your machine. Here you can run `./emulator -list-avds` and compare the devices with the one you found in step 1.
|
2. Check if the current device is already installed on your machine. Go to `cd ~/Android/sdk/emulator/` or wherever you have Android installed on your machine. Here you can run `./emulator -list-avds` and compare the devices with the one you found in step 1.
|
||||||
@@ -137,12 +141,12 @@ or just a random string to check if this piece of code is actually running:
|
|||||||
`console.log("HELLO")`.
|
`console.log("HELLO")`.
|
||||||
|
|
||||||
## NFP rules
|
## NFP rules
|
||||||
More information about how the app calculates fertility status and bleeding predictions in the [wiki on Gitlab](https://gitlab.com/bloodyhealth/drip/wikis/home)
|
More information about how the app calculates fertility status and bleeding predictions in the [wiki on Gitlab](https://gitlab.com/bloodyhealth/drip/wikis/home).
|
||||||
|
|
||||||
## Adding a new tracking icon
|
## 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, cervical 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
|
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`.
|
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:
|
4. Now run the following command in your console:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# How to release a new version
|
||||||
|
|
||||||
|
Note: You need the release-key to bundle a release that can be uploaded to Google Play Store.
|
||||||
|
|
||||||
|
Run the release wizard that takes you through all the steps necessary to prepare a new release:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run release
|
||||||
|
```
|
||||||
|
This will trigger the following:
|
||||||
|
* update version number
|
||||||
|
* create a new tag for the release
|
||||||
|
* update the changelog
|
||||||
|
* make a release commit
|
||||||
|
|
||||||
|
To then bundle a release run the following command on your branch:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd android && ./gradlew bundleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
This command creates an `app.aab` file in the folder `/android/app/build/outputs/bundle/release`.
|
||||||
|
|
||||||
|
[More on Android App Bundle](https://blog.swmansion.com/make-your-react-native-app-3x-smaller-44c993eda2c9)
|
||||||
|
|
||||||
|
You need to manually push the created tag to Gitlab:
|
||||||
|
|
||||||
|
```
|
||||||
|
git push origin <tagname>
|
||||||
|
```
|
||||||
|
Also don't forget to push your branch to Gitlab and review and merge it if ready!
|
||||||
|
|
||||||
|
Yay, done (have a scoop of ice cream, I suggest coconut 🍦)!
|
||||||
@@ -107,8 +107,8 @@ android {
|
|||||||
applicationId "com.drip"
|
applicationId "com.drip"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 3
|
versionCode 8
|
||||||
versionName "0.1905.29-beta"
|
versionName "1.2102.28"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,8 @@ dependencies {
|
|||||||
implementation project(':react-native-document-picker')
|
implementation project(':react-native-document-picker')
|
||||||
implementation project(':nodejs-mobile-react-native')
|
implementation project(':nodejs-mobile-react-native')
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
implementation 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||||
androidTestImplementation('com.wix:detox:+') { transitive = true }
|
androidTestImplementation('com.wix:detox:+') { transitive = true }
|
||||||
androidTestImplementation 'junit:junit:4.12'
|
androidTestImplementation 'junit:junit:4.12'
|
||||||
|
|||||||
@@ -5,4 +5,4 @@
|
|||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|
||||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
|
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -23,13 +23,15 @@
|
|||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:screenOrientation="sensorPortrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="com.drip.provider"
|
android:authorities="com.drip.provider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<font
|
||||||
|
android:font="@font/jost400"
|
||||||
|
android:fontStyle="normal"
|
||||||
|
android:fontWeight="400" />
|
||||||
|
</font-family>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 430 B After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 542 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -9,4 +9,9 @@
|
|||||||
|
|
||||||
<!-- a secondary color for controls like checkboxes and text fields -->
|
<!-- a secondary color for controls like checkboxes and text fields -->
|
||||||
<color name="colorAccent">#4FAFA7</color>
|
<color name="colorAccent">#4FAFA7</color>
|
||||||
|
|
||||||
|
<!-- custom colors -->
|
||||||
|
<color name="grey">#A5A5A5</color>
|
||||||
|
<color name="orange">#F38337</color>
|
||||||
|
<color name="purple">#3A2671</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#000D19</color>
|
<color name="ic_launcher_background">#3A2671</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,7 +3,27 @@
|
|||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ButtonBarStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
<item name="android:textColor">@color/grey</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="PositiveButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:textColor">@color/orange</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="TitleStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:gravity">left</item>
|
||||||
|
<item name="android:textColor">@color/purple</item>
|
||||||
|
<item name="android:textSize">22sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -36,20 +36,20 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
buildToolsVersion = "28.0.3"
|
buildToolsVersion = "29.0.3"
|
||||||
minSdkVersion = 23
|
minSdkVersion = 23
|
||||||
compileSdkVersion = 28
|
compileSdkVersion = 29
|
||||||
targetSdkVersion = 28
|
targetSdkVersion = 29
|
||||||
supportLibVersion = "28.0.0"
|
supportLibVersion = "29.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
afterEvaluate {project ->
|
afterEvaluate {project ->
|
||||||
if (project.hasProperty("android")) {
|
if (project.hasProperty("android")) {
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '29.0.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,6 @@
|
|||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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 = {
|
|
||||||
autoFocus: PropTypes.bool,
|
|
||||||
onChangeText: PropTypes.func,
|
|
||||||
placeholder: PropTypes.string,
|
|
||||||
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
value: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
AppTextInput.defaultProps = {
|
|
||||||
style: []
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Text } from 'react-native'
|
|
||||||
import styles from "../styles"
|
|
||||||
import Link from './link'
|
|
||||||
|
|
||||||
export default function AppText({ children, onPress, numberOfLines, style}) {
|
|
||||||
// we parse for links in case the text contains any
|
|
||||||
return (
|
|
||||||
<Link>
|
|
||||||
<Text style={[styles.appText, style]}
|
|
||||||
onPress={onPress}
|
|
||||||
numberOfLines={numberOfLines}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AppText.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
numberOfLines: PropTypes.number,
|
|
||||||
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import { openDb } from '../db'
|
|||||||
import App from './app'
|
import App from './app'
|
||||||
import PasswordPrompt from './password-prompt'
|
import PasswordPrompt from './password-prompt'
|
||||||
import License from './license'
|
import License from './license'
|
||||||
import AppLoadingView from './app-loading'
|
import AppLoadingView from './common/app-loading'
|
||||||
|
|
||||||
import store from "../store"
|
import store from "../store"
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View, BackHandler } from 'react-native'
|
import { BackHandler, StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
@@ -10,7 +10,7 @@ import { getNavigation, navigate, goBack } from '../slices/navigation'
|
|||||||
import Header from './header'
|
import Header from './header'
|
||||||
import Menu from './menu'
|
import Menu from './menu'
|
||||||
import { viewsList } from './views'
|
import { viewsList } from './views'
|
||||||
import { isSymptomView, isSettingsView } from './pages'
|
import { isSettingsView } from './pages'
|
||||||
|
|
||||||
import { headerTitles } from '../i18n/en/labels'
|
import { headerTitles } from '../i18n/en/labels'
|
||||||
import setupNotifications from '../lib/notifications'
|
import setupNotifications from '../lib/notifications'
|
||||||
@@ -64,9 +64,8 @@ class App extends Component {
|
|||||||
const Page = viewsList[currentPage]
|
const Page = viewsList[currentPage]
|
||||||
const title = headerTitles[currentPage]
|
const title = headerTitles[currentPage]
|
||||||
|
|
||||||
const isSymptomEditView = isSymptomView(currentPage)
|
|
||||||
const isSettingsSubView = isSettingsView(currentPage)
|
const isSettingsSubView = isSettingsView(currentPage)
|
||||||
const isCycleDayView = currentPage === 'CycleDay'
|
const isTemperatureEditView = currentPage === 'TemperatureEditView'
|
||||||
|
|
||||||
const headerProps = {
|
const headerProps = {
|
||||||
title,
|
title,
|
||||||
@@ -76,24 +75,25 @@ class App extends Component {
|
|||||||
const pageProps = {
|
const pageProps = {
|
||||||
cycleDay: date && getCycleDay(date),
|
cycleDay: date && getCycleDay(date),
|
||||||
date,
|
date,
|
||||||
|
isTemperatureEditView,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={styles.container}>
|
||||||
{
|
<Header { ...headerProps } />
|
||||||
!isSymptomEditView &&
|
|
||||||
!isCycleDayView &&
|
|
||||||
<Header { ...headerProps } />
|
|
||||||
}
|
|
||||||
|
|
||||||
<Page { ...pageProps } />
|
<Page { ...pageProps } />
|
||||||
|
<Menu />
|
||||||
{ !isSymptomEditView && <Menu /> }
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return({
|
return({
|
||||||
date: getDate(state),
|
date: getDate(state),
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { TouchableOpacity } from 'react-native'
|
|
||||||
import AppText from './app-text'
|
|
||||||
import styles from '../styles'
|
|
||||||
|
|
||||||
export default function Button({
|
|
||||||
backgroundColor,
|
|
||||||
children,
|
|
||||||
onPress,
|
|
||||||
style,
|
|
||||||
testID
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPress}
|
|
||||||
style={[styles.button, style, { backgroundColor }]}
|
|
||||||
testID={testID}
|
|
||||||
>
|
|
||||||
<AppText style={styles.homeButtonText}>{children}</AppText>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button.propTypes = {
|
|
||||||
backgroundColor: PropTypes.string,
|
|
||||||
children: PropTypes.node,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
style: PropTypes.object,
|
|
||||||
testID: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { CalendarList } from 'react-native-calendars'
|
import { CalendarList } from 'react-native-calendars'
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
import { setDate } from '../slices/date'
|
import { setDate } from '../slices/date'
|
||||||
import { navigate } from '../slices/navigation'
|
import { navigate } from '../slices/navigation'
|
||||||
|
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
import { getBleedingDaysSortedByDate } from '../db'
|
import { getBleedingDaysSortedByDate } from '../db'
|
||||||
import cycleModule from '../lib/cycle'
|
import cycleModule from '../lib/cycle'
|
||||||
import { shadesOfRed, calendarTheme } from '../styles/index'
|
|
||||||
import styles from '../styles/index'
|
|
||||||
import nothingChanged from '../db/db-unchanged'
|
import nothingChanged from '../db/db-unchanged'
|
||||||
|
import {
|
||||||
|
calendarTheme,
|
||||||
|
predictionToCalFormat,
|
||||||
|
toCalFormat,
|
||||||
|
todayToCalFormat,
|
||||||
|
} from './helpers/calendar'
|
||||||
|
|
||||||
class CalendarView extends Component {
|
class CalendarView extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setDate: PropTypes.func.isRequired,
|
setDate: PropTypes.func.isRequired,
|
||||||
navigate: PropTypes.func.isRequired
|
navigate: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -26,7 +30,7 @@ class CalendarView extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
||||||
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
||||||
todayInCalFormat: todayToCalFormat()
|
todayInCalFormat: todayToCalFormat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bleedingDays.addListener(this.setStateWithCalFormattedDays)
|
this.bleedingDays.addListener(this.setStateWithCalFormattedDays)
|
||||||
@@ -38,7 +42,7 @@ class CalendarView extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
||||||
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
||||||
todayInCalFormat: todayToCalFormat()
|
todayInCalFormat: todayToCalFormat(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,93 +56,44 @@ class CalendarView extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
todayInCalFormat,
|
||||||
|
bleedingDaysInCalFormat,
|
||||||
|
predictedBleedingDaysInCalFormat,
|
||||||
|
} = this.state
|
||||||
|
const markedDates = Object.assign(
|
||||||
|
{},
|
||||||
|
todayInCalFormat,
|
||||||
|
bleedingDaysInCalFormat,
|
||||||
|
predictedBleedingDaysInCalFormat
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarList
|
<View style={styles.container}>
|
||||||
onDayPress={this.passDateToDayView.bind(this)}
|
<CalendarList
|
||||||
markedDates={
|
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
||||||
Object.assign(
|
firstDay={1}
|
||||||
{},
|
onDayPress={this.passDateToDayView.bind(this)}
|
||||||
this.state.todayInCalFormat,
|
markedDates={markedDates}
|
||||||
this.state.bleedingDaysInCalFormat,
|
markingType='custom'
|
||||||
this.state.predictedBleedingDaysInCalFormat
|
theme={calendarTheme}
|
||||||
)
|
// Max amount of months allowed to scroll to the past.
|
||||||
}
|
pastScrollRange={120}
|
||||||
markingType={'custom'}
|
/>
|
||||||
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
</View>
|
||||||
firstDay={1}
|
|
||||||
theme={calendarTheme}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return({
|
return {
|
||||||
setDate: (date) => dispatch(setDate(date)),
|
setDate: (date) => dispatch(setDate(date)),
|
||||||
navigate: (page) => dispatch(navigate(page)),
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(CalendarView)
|
|
||||||
|
|
||||||
|
|
||||||
function toCalFormat(bleedingDaysSortedByDate) {
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
return bleedingDaysSortedByDate.reduce((acc, day) => {
|
|
||||||
acc[day.date] = {
|
|
||||||
customStyles: {
|
|
||||||
container: {
|
|
||||||
backgroundColor: shadesOfRed[day.bleeding.value],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (day.date === todayDateString) {
|
|
||||||
acc[day.date].customStyles.text = styles.calendarToday
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function predictionToCalFormat(predictedDays) {
|
|
||||||
if (!predictedDays.length) return {}
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
const middleIndex = (predictedDays[0].length - 1) / 2
|
|
||||||
return predictedDays.reduce((acc, setOfDays) => {
|
|
||||||
setOfDays.reduce((accSet, day, i) => {
|
|
||||||
accSet[day] = {
|
|
||||||
customStyles: {
|
|
||||||
container: {
|
|
||||||
borderColor: (i === middleIndex) ? shadesOfRed[3] : shadesOfRed[2],
|
|
||||||
borderWidth: 3,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
marginTop: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (day === todayDateString) {
|
|
||||||
accSet[day].customStyles.text = Object.assign(
|
|
||||||
{},
|
|
||||||
styles.calendarToday,
|
|
||||||
{marginTop: -2}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return accSet
|
|
||||||
}, acc)
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function todayToCalFormat() {
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
const todayFormated = {}
|
|
||||||
todayFormated[todayDateString] = {
|
|
||||||
customStyles: {
|
|
||||||
text: styles.calendarToday
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return todayFormated
|
}
|
||||||
}
|
|
||||||
|
export default connect(null, mapDispatchToProps)(CalendarView)
|
||||||
|
|||||||
@@ -1,32 +1,38 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
import DripHomeIcon from '../../assets/drip-home-icons'
|
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
import { cycleDayColor } from '../../styles'
|
|
||||||
|
|
||||||
|
import { Typography } from '../../styles'
|
||||||
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
import { shared as labels } from '../../i18n/en/labels'
|
import { shared as labels } from '../../i18n/en/labels'
|
||||||
|
|
||||||
const ChartLegend = ({ xAxisHeight }) => {
|
const ChartLegend = ({ height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, styles.chartLegend, {height: xAxisHeight}]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<DripHomeIcon
|
<AppText style={styles.textBold}>#</AppText>
|
||||||
name="circle"
|
<AppText style={styles.text}>{labels.date}</AppText>
|
||||||
size={styles.yAxis.width - 7}
|
|
||||||
color={cycleDayColor}
|
|
||||||
/>
|
|
||||||
<AppText style={styles.yAxisLabels.dateLabel}>
|
|
||||||
{labels.date.toLowerCase()}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartLegend.propTypes = {
|
ChartLegend.propTypes = {
|
||||||
xAxisHeight: PropTypes.number.isRequired
|
height: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label,
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default ChartLegend
|
export default ChartLegend
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ import PropTypes from 'prop-types'
|
|||||||
|
|
||||||
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
|
import { CHART_STROKE_WIDTH, CHART_GRID_LINE_HORIZONTAL_WIDTH } from '../../config'
|
||||||
|
|
||||||
const ChartLine = ({ path, isNfpLine = false }) => {
|
const ChartLine = ({ path, isNfpLine }) => {
|
||||||
const strokeStyle =
|
const color = isNfpLine ? Colors.orange : Colors.grey
|
||||||
isNfpLine ? styles.nfpLine.stroke : styles.column.stroke.color
|
const width = isNfpLine
|
||||||
const strokeWidth =
|
? CHART_STROKE_WIDTH : CHART_GRID_LINE_HORIZONTAL_WIDTH * 2.5
|
||||||
isNfpLine ? styles.nfpLine.strokeWidth : styles.column.stroke.width
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shape
|
<Shape d={path} stroke={color} strokeWidth={width} />
|
||||||
stroke={strokeStyle}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
d={path}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,4 +21,8 @@ ChartLine.propTypes = {
|
|||||||
isNfpLine: PropTypes.bool,
|
isNfpLine: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChartLine.defaultProps = {
|
||||||
|
isNfpLine: false
|
||||||
|
}
|
||||||
|
|
||||||
export default ChartLine
|
export default ChartLine
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, FlatList, ActivityIndicator } from 'react-native'
|
import { ActivityIndicator, FlatList, Dimensions, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppLoadingView from '../common/app-loading'
|
||||||
|
import AppPage from '../common/app-page'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import NoData from './no-data'
|
|
||||||
import AppLoadingView from '../app-loading'
|
|
||||||
import YAxis from './y-axis'
|
|
||||||
import nfpLines from './nfp-lines'
|
|
||||||
import DayColumn from './day-column'
|
import DayColumn from './day-column'
|
||||||
import HorizontalGrid from './horizontal-grid'
|
import HorizontalGrid from './horizontal-grid'
|
||||||
import AppText from '../app-text'
|
import NoData from './no-data'
|
||||||
|
import Tutorial from './tutorial'
|
||||||
|
import YAxis from './y-axis'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { navigate } from '../../slices/navigation'
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
import { getCycleDaysSortedByDate } from '../../db'
|
import { getCycleDaysSortedByDate } from '../../db'
|
||||||
import nothingChanged from '../../db/db-unchanged'
|
import nothingChanged from '../../db/db-unchanged'
|
||||||
import { scaleObservable } from '../../local-storage'
|
import { getChartFlag, scaleObservable, setChartFlag } from '../../local-storage'
|
||||||
import { makeColumnInfo } from '../helpers/chart'
|
import { makeColumnInfo, nfpLines } from '../helpers/chart'
|
||||||
|
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
SYMPTOMS,
|
||||||
|
CHART_SYMPTOM_HEIGHT_RATIO,
|
||||||
|
CHART_XAXIS_HEIGHT_RATIO
|
||||||
|
} from '../../config'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
import { Colors, Spacing } from '../../styles'
|
||||||
|
|
||||||
class CycleChart extends Component {
|
class CycleChart extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -36,13 +43,37 @@ class CycleChart extends Component {
|
|||||||
this.getFhmAndLtlInfo = nfpLines()
|
this.getFhmAndLtlInfo = nfpLines()
|
||||||
this.shouldShowTemperatureColumn = false
|
this.shouldShowTemperatureColumn = false
|
||||||
|
|
||||||
|
this.checkShouldShowHint()
|
||||||
this.prepareSymptomData()
|
this.prepareSymptomData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
||||||
|
this.removeObvListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
checkShouldShowHint = async () => {
|
||||||
|
const flag = await getChartFlag()
|
||||||
|
const shouldShowHint = flag === 'true' ? true : false
|
||||||
|
this.setState({ shouldShowHint })
|
||||||
|
}
|
||||||
|
|
||||||
|
setShouldShowHint = async () => {
|
||||||
|
await setChartFlag()
|
||||||
|
this.setState({ shouldShowHint: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayout = () => {
|
||||||
|
if (this.state.chartHeight) return false
|
||||||
|
|
||||||
|
this.reCalculateChartInfo()
|
||||||
|
this.updateListeners(this.reCalculateChartInfo)
|
||||||
|
}
|
||||||
|
|
||||||
prepareSymptomData = () => {
|
prepareSymptomData = () => {
|
||||||
this.symptomRowSymptoms = config.symptoms.filter((symptomName) => {
|
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
|
||||||
return this.cycleDaysSortedByDate.some(cycleDay => {
|
return this.cycleDaysSortedByDate.some(cycleDay => {
|
||||||
return cycleDay[symptomName]
|
return (symptomName !== 'temperature') && cycleDay[symptomName]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.chartSymptoms = [...this.symptomRowSymptoms]
|
this.chartSymptoms = [...this.symptomRowSymptoms]
|
||||||
@@ -69,35 +100,23 @@ class CycleChart extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reCalculateChartInfo = (nativeEvent) => {
|
reCalculateChartInfo = () => {
|
||||||
const { height, width } = nativeEvent.layout
|
const { width, height } = Dimensions.get('window')
|
||||||
const xAxisCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.xAxisHeightPercentage : config.xAxisHeightPercentageLarge
|
|
||||||
const symptomCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.symptomHeightPercentage : config.symptomHeightPercentageLarge
|
|
||||||
|
|
||||||
this.xAxisHeight = height * xAxisCoefficient
|
this.xAxisHeight = height * 0.7 * CHART_XAXIS_HEIGHT_RATIO
|
||||||
const remainingHeight = height - this.xAxisHeight
|
const remainingHeight = height * 0.7 - this.xAxisHeight
|
||||||
this.symptomHeight = remainingHeight * symptomCoefficient
|
this.symptomHeight = remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO
|
||||||
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
||||||
this.symptomHeight
|
this.symptomHeight
|
||||||
this.columnHeight = remainingHeight - this.symptomRowHeight
|
this.columnHeight = remainingHeight - this.symptomRowHeight
|
||||||
const chartHeight = this.shouldShowTemperatureColumn ?
|
const chartHeight = this.shouldShowTemperatureColumn ?
|
||||||
height : (this.symptomRowHeight + this.xAxisHeight)
|
height * 0.7 : (this.symptomRowHeight + this.xAxisHeight)
|
||||||
|
const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH)
|
||||||
const numberOfColumnsToRender = Math.round(width / config.columnWidth)
|
|
||||||
const columns = makeColumnInfo()
|
const columns = makeColumnInfo()
|
||||||
|
|
||||||
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayout = ({ nativeEvent }) => {
|
|
||||||
if (this.state.chartHeight) return
|
|
||||||
|
|
||||||
this.reCalculateChartInfo(nativeEvent)
|
|
||||||
this.updateListeners(this.reCalculateChartInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateListeners(dataUpdateHandler) {
|
updateListeners(dataUpdateHandler) {
|
||||||
// remove existing listeners
|
// remove existing listeners
|
||||||
if(this.handleDbChange) {
|
if(this.handleDbChange) {
|
||||||
@@ -114,38 +133,47 @@ class CycleChart extends Component {
|
|||||||
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
|
||||||
this.removeObvListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { chartHeight, chartLoaded, numberOfColumnsToRender } = this.state
|
const {
|
||||||
const shouldShowChart = this.chartSymptoms.length > 0 ? true : false
|
chartHeight,
|
||||||
|
chartLoaded,
|
||||||
|
shouldShowHint,
|
||||||
|
numberOfColumnsToRender
|
||||||
|
} = this.state
|
||||||
|
const hasDataToDisplay = this.chartSymptoms.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View onLayout={this.onLayout} style={styles.container}>
|
<AppPage
|
||||||
{!shouldShowChart && <NoData navigate={this.props.navigate}/>}
|
contentContainerStyle={styles.pageContainer}
|
||||||
{shouldShowChart && !chartHeight && !chartLoaded && <AppLoadingView />}
|
onLayout={this.onLayout}
|
||||||
|
scrollViewStyle={styles.page}
|
||||||
|
>
|
||||||
|
{!hasDataToDisplay && <NoData />}
|
||||||
|
{hasDataToDisplay && !chartHeight && !chartLoaded && <AppLoadingView />}
|
||||||
<View style={styles.chartContainer}>
|
<View style={styles.chartContainer}>
|
||||||
{shouldShowChart && (
|
{shouldShowHint && chartLoaded &&
|
||||||
|
<Tutorial onClose={this.setShouldShowHint} />
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && chartLoaded &&
|
||||||
|
!this.shouldShowTemperatureColumn &&
|
||||||
|
<View style={styles.centerItem}>
|
||||||
|
<AppText style={styles.warning}>
|
||||||
|
{shared.noTemperatureWarning}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && (
|
||||||
<View style={styles.chartArea}>
|
<View style={styles.chartArea}>
|
||||||
|
|
||||||
{chartHeight && chartLoaded && (
|
{chartHeight && chartLoaded && (
|
||||||
<React.Fragment>
|
<YAxis
|
||||||
<YAxis
|
height={this.columnHeight}
|
||||||
height={this.columnHeight}
|
symptomsToDisplay={this.symptomRowSymptoms}
|
||||||
symptomsToDisplay={this.symptomRowSymptoms}
|
symptomsSectionHeight={this.symptomRowHeight}
|
||||||
symptomsSectionHeight={this.symptomRowHeight}
|
shouldShowTemperatureColumn=
|
||||||
shouldShowTemperatureColumn=
|
{this.shouldShowTemperatureColumn}
|
||||||
{this.shouldShowTemperatureColumn}
|
xAxisHeight={this.xAxisHeight}
|
||||||
xAxisHeight={this.xAxisHeight}
|
/>
|
||||||
/>
|
|
||||||
{this.shouldShowTemperatureColumn && (<HorizontalGrid
|
|
||||||
height={this.columnHeight}
|
|
||||||
startPosition={this.symptomRowHeight}
|
|
||||||
/>)}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chartHeight &&
|
{chartHeight &&
|
||||||
@@ -162,27 +190,28 @@ class CycleChart extends Component {
|
|||||||
onEndReached={() => this.setState({end: true})}
|
onEndReached={() => this.setState({end: true})}
|
||||||
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
||||||
updateCellsBatchingPeriod={800}
|
updateCellsBatchingPeriod={800}
|
||||||
contentContainerStyle={{height: chartHeight}}
|
contentContainerStyle={{ height: chartHeight }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{chartHeight && chartLoaded && (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.shouldShowTemperatureColumn &&
|
||||||
|
<HorizontalGrid height={this.columnHeight} />
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowChart && chartLoaded && !this.shouldShowTemperatureColumn
|
</AppPage>
|
||||||
&& (
|
|
||||||
<View style={styles.centerItem}>
|
|
||||||
<AppText style={{textAlign: 'center'}}>{shared.noTemperatureWarning}</AppText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadingMoreView({ end }) {
|
function LoadingMoreView({ end }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingMore}>
|
<View style={styles.loadingContainer}>
|
||||||
{!end && <ActivityIndicator size={'large'} color={'white'}/>}
|
{!end && <ActivityIndicator size={'large'} color={Colors.orange}/>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,6 +220,29 @@ LoadingMoreView.propTypes = {
|
|||||||
end: PropTypes.bool
|
end: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
chartArea: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
chartContainer: {
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
marginVertical: Spacing.small
|
||||||
|
},
|
||||||
|
pageContainer: {
|
||||||
|
paddingHorizontal: Spacing.base
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
padding: Spacing.large
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return({
|
return({
|
||||||
navigate: (page) => dispatch(navigate(page)),
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Text, View } from 'react-native'
|
|
||||||
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
|
|
||||||
import styles from './styles'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
|
import { getOrdinalSuffix } from '../helpers/home'
|
||||||
|
import { Containers, Typography, Sizes } from '../../styles'
|
||||||
|
|
||||||
const CycleDayLabel = ({ height, date }) => {
|
const CycleDayLabel = ({ height, date }) => {
|
||||||
const { label } = styles.column
|
|
||||||
const dayDate = LocalDate.parse(date)
|
|
||||||
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
||||||
|
const cycleDayLabel = cycleDayNumber ? cycleDayNumber : ' '
|
||||||
|
|
||||||
const isFirstDayOfMonth = dayDate.dayOfMonth() === 1
|
const momentDate = moment(date)
|
||||||
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'Do'
|
const dayOfMonth = momentDate.date()
|
||||||
const shortDate = moment(date, "YYYY-MM-DD").format(dateFormatting)
|
const isFirstDayOfMonth = dayOfMonth === 1
|
||||||
const boldDateLabel = isFirstDayOfMonth ? {fontWeight: 'bold'} : {}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.chartLegend, { height }]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<Text style={label.number}>
|
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
|
||||||
{cycleDayNumber ? cycleDayNumber : ' '}
|
<View style={styles.dateLabel}>
|
||||||
</Text>
|
<AppText style={styles.text}>
|
||||||
<Text style={[label.date, boldDateLabel]}>
|
{isFirstDayOfMonth ? momentDate.format('MMM') : dayOfMonth}
|
||||||
{shortDate}
|
</AppText>
|
||||||
</Text>
|
{!isFirstDayOfMonth &&
|
||||||
|
<AppText style={styles.textLight}>
|
||||||
|
{getOrdinalSuffix(dayOfMonth)}
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -36,4 +39,30 @@ CycleDayLabel.propTypes = {
|
|||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
left: 4,
|
||||||
|
},
|
||||||
|
containerRow: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label,
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
},
|
||||||
|
textLight: {
|
||||||
|
...Typography.labelLight,
|
||||||
|
},
|
||||||
|
dateLabel: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default CycleDayLabel
|
export default CycleDayLabel
|
||||||
|
|||||||
@@ -92,21 +92,6 @@ class DayColumn extends Component {
|
|||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
>
|
>
|
||||||
|
|
||||||
{ symptomRowSymptoms.map(symptom => {
|
|
||||||
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
|
||||||
return (
|
|
||||||
<SymptomCell
|
|
||||||
key={symptom}
|
|
||||||
symptom={symptom}
|
|
||||||
symptomValue={hasSymptomData && this.data[symptom]}
|
|
||||||
isSymptomDataComplete={
|
|
||||||
hasSymptomData && isSymptomDataComplete(symptom, dateString)
|
|
||||||
}
|
|
||||||
height={symptomHeight}
|
|
||||||
/>)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldShowTemperatureColumn && <TemperatureColumn
|
{shouldShowTemperatureColumn && <TemperatureColumn
|
||||||
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
||||||
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
||||||
@@ -119,6 +104,22 @@ class DayColumn extends Component {
|
|||||||
date={dateString}
|
date={dateString}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{ symptomRowSymptoms.map((symptom, i) => {
|
||||||
|
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
||||||
|
return (
|
||||||
|
<SymptomCell
|
||||||
|
index={i}
|
||||||
|
key={symptom}
|
||||||
|
symptom={symptom}
|
||||||
|
symptomValue={hasSymptomData && this.data[symptom]}
|
||||||
|
isSymptomDataComplete={
|
||||||
|
hasSymptomData && isSymptomDataComplete(symptom, dateString)
|
||||||
|
}
|
||||||
|
height={symptomHeight}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
import config from '../../config'
|
|
||||||
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_COLUMN_MIDDLE,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_STROKE_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
export default class DotAndLine extends Component {
|
export default class DotAndLine extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -20,48 +26,62 @@ export default class DotAndLine extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const y = this.props.y
|
const {
|
||||||
const exclude = this.props.exclude
|
exclude,
|
||||||
let lineToRight
|
leftTemperatureExclude,
|
||||||
let lineToLeft
|
leftY,
|
||||||
|
rightTemperatureExclude,
|
||||||
|
rightY,
|
||||||
|
y
|
||||||
|
} = this.props
|
||||||
|
let excludeLeftLine, excludeRightLine, lineLeft, lineRight
|
||||||
|
|
||||||
if (this.props.leftY) {
|
if (leftY) {
|
||||||
const middleY = ((this.props.leftY - y) / 2) + y
|
const middleY = ((leftY - y) / 2) + y
|
||||||
const excludedLine = this.props.leftTemperatureExclude || exclude
|
excludeLeftLine = leftTemperatureExclude || exclude
|
||||||
lineToLeft = makeLine(y, middleY, 0, excludedLine)
|
lineLeft = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE, y)
|
||||||
|
.lineTo(0, middleY)
|
||||||
}
|
}
|
||||||
if (this.props.rightY) {
|
if (rightY) {
|
||||||
const middleY = ((y - this.props.rightY) / 2) + this.props.rightY
|
const middleY = ((y - rightY) / 2) + rightY
|
||||||
const excludedLine = this.props.rightTemperatureExclude || exclude
|
excludeRightLine = rightTemperatureExclude || exclude
|
||||||
lineToRight = makeLine(y, middleY, config.columnWidth, excludedLine)
|
lineRight = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE, y)
|
||||||
|
.lineTo(CHART_COLUMN_WIDTH, middleY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots
|
const dot = new Path().moveTo(CHART_COLUMN_MIDDLE , y - CHART_DOT_RADIUS)
|
||||||
const radius = dotStyle.r
|
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
|
||||||
const dot = (
|
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
|
||||||
<Shape
|
const dotColor = exclude ? Colors.turquoise : Colors.turquoiseDark
|
||||||
d={new Path()
|
const lineColorLeft = excludeLeftLine ?
|
||||||
.moveTo(config.columnMiddle, y - radius)
|
Colors.turquoise : Colors.turquoiseDark
|
||||||
.arc(0, radius * 2, radius)
|
const lineColorRight = excludeRightLine ?
|
||||||
.arc(0, radius * -2, radius)
|
Colors.turquoise : Colors.turquoiseDark
|
||||||
}
|
|
||||||
fill={dotStyle.fill}
|
return(
|
||||||
key='dot'
|
<React.Fragment>
|
||||||
/>
|
<Shape
|
||||||
|
d={lineLeft}
|
||||||
|
stroke={lineColorLeft}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y}
|
||||||
|
/>
|
||||||
|
<Shape
|
||||||
|
d={lineRight}
|
||||||
|
stroke={lineColorRight}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y + CHART_DOT_RADIUS}
|
||||||
|
/>
|
||||||
|
<Shape
|
||||||
|
d={dot}
|
||||||
|
stroke={dotColor}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
fill="white"
|
||||||
|
key='dot'
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
return [lineToLeft, lineToRight, dot]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLine(currY, middleY, x, excludeLine) {
|
|
||||||
const lineStyle = excludeLine ? styles.curveExcluded : styles.curve
|
|
||||||
|
|
||||||
return <Shape
|
|
||||||
stroke={lineStyle.stroke}
|
|
||||||
d={new Path()
|
|
||||||
.moveTo(config.columnMiddle, currY)
|
|
||||||
.lineTo(x, middleY)
|
|
||||||
}
|
|
||||||
key={x.toString()}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import { getTickPositions } from '../helpers/chart'
|
import { getTickPositions } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
|
import { CHART_GRID_LINE_HORIZONTAL_WIDTH, CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const HorizontalGrid = ({ height, startPosition }) => {
|
const HorizontalGrid = ({ height }) => {
|
||||||
return getTickPositions(height).map(tick => {
|
return getTickPositions(height).map(tick => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View key={tick} top={tick} {...styles.line}/>
|
||||||
top={startPosition + tick}
|
|
||||||
{...styles.horizontalGrid}
|
|
||||||
key={tick}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalGrid.propTypes = {
|
HorizontalGrid.propTypes = {
|
||||||
height: PropTypes.number,
|
height: PropTypes.number
|
||||||
startPosition: PropTypes.number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
line: {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderBottomColor: Colors.grey,
|
||||||
|
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
left: CHART_YAXIS_WIDTH,
|
||||||
|
position:'absolute',
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default HorizontalGrid
|
export default HorizontalGrid
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
|
||||||
import { normalizeToScale } from '../helpers/chart'
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
const cycle = {
|
|
||||||
status: null
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentCycle(dateString) {
|
|
||||||
// for the NFP lines, we don't care about potentially extending the
|
|
||||||
// preOvu phase, so we don't include all earlier cycles, as that is
|
|
||||||
// an expensive db operation at the moment
|
|
||||||
cycle.status = getCycleStatusForDay(
|
|
||||||
dateString, { excludeEarlierCycles: true }
|
|
||||||
)
|
|
||||||
if(!cycle.status) {
|
|
||||||
cycle.noMoreCycles = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (cycle.status.phases.preOvulatory) {
|
|
||||||
cycle.startDate = cycle.status.phases.preOvulatory.start.date
|
|
||||||
} else {
|
|
||||||
cycle.startDate = cycle.status.phases.periOvulatory.start.date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateIsInPeriOrPostPhase(dateString) {
|
|
||||||
return (
|
|
||||||
dateString >= cycle.status.phases.periOvulatory.start.date
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function precededByAnotherTempValue(dateString) {
|
|
||||||
return (
|
|
||||||
// we are only interested in days that have a preceding
|
|
||||||
// temp
|
|
||||||
Object.keys(cycle.status.phases).some(phaseName => {
|
|
||||||
return cycle.status.phases[phaseName].cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date < dateString
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// and also a following temp, so we don't draw the line
|
|
||||||
// longer than necessary
|
|
||||||
&&
|
|
||||||
cycle.status.phases.postOvulatory.cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date > dateString
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInTempMeasuringPhase(temperature, dateString) {
|
|
||||||
return (
|
|
||||||
temperature || precededByAnotherTempValue(dateString)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(dateString, temperature, columnHeight) {
|
|
||||||
const ret = {
|
|
||||||
drawLtlAt: null,
|
|
||||||
drawFhmLine: false
|
|
||||||
}
|
|
||||||
if (!cycle.status && !cycle.noMoreCycles) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
if (dateString < cycle.startDate) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
const tempShift = cycle.status.temperatureShift
|
|
||||||
|
|
||||||
if (tempShift) {
|
|
||||||
if (tempShift.firstHighMeasurementDay.date === dateString) {
|
|
||||||
ret.drawFhmLine = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
dateIsInPeriOrPostPhase(dateString) &&
|
|
||||||
isInTempMeasuringPhase(temperature, dateString)
|
|
||||||
) {
|
|
||||||
ret.drawLtlAt = normalizeToScale(tempShift.ltl, columnHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
import SettingsButton from '../settings/shared/settings-button'
|
import Button from '../common/button'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const NoData = ({ navigate }) => {
|
const NoData = ({ navigate }) => {
|
||||||
return (
|
return (
|
||||||
<View flex={1}>
|
<View style={styles.container}>
|
||||||
<View style={styles.centerItem}>
|
<AppText>{shared.noDataWarning}</AppText>
|
||||||
<AppText>{shared.noDataWarning}</AppText>
|
<Button isCTA onPress={() => {navigate('CycleDay')}}>
|
||||||
<SettingsButton
|
{shared.noDataButtonText}
|
||||||
onPress={() => {navigate('CycleDay')}}
|
</Button>
|
||||||
style={{marginHorizontal: 40}}
|
|
||||||
>
|
|
||||||
{shared.noDataButtonText}
|
|
||||||
</SettingsButton>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoData.propTypes = {
|
NoData.propTypes = {
|
||||||
navigate: PropTypes.func,
|
navigate: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoData
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return({
|
||||||
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(NoData)
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import config from '../../config'
|
|
||||||
import { shadesOfRed, cycleDayColor } from '../../styles/index'
|
|
||||||
|
|
||||||
const colorTemperature = '#765285'
|
|
||||||
const colorTemperatureLight = '#a67fb5'
|
|
||||||
export const dotRadius = 5
|
|
||||||
const lineWidth = 1.5
|
|
||||||
const colorLtl = '#feb47b'
|
|
||||||
const gridColor = '#d3d3d3'
|
|
||||||
const gridLineWidthVertical = 0.6
|
|
||||||
const gridLineWidthHorizontal = 0.3
|
|
||||||
const numberLabelFontSize = 13
|
|
||||||
|
|
||||||
const redColor = '#c3000d'
|
|
||||||
const violetColor = '#6a7b98'
|
|
||||||
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', violetColor] // light to dark
|
|
||||||
const yellowColor = '#dbb40c'
|
|
||||||
const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
|
|
||||||
const magentaColor = '#6f2565'
|
|
||||||
const shadesOfMagenta = ['#a87ca2', '#8b5083', magentaColor] // light to dark
|
|
||||||
const pinkColor = '#9e346c'
|
|
||||||
const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
|
|
||||||
const lightGreenColor = '#bccd67'
|
|
||||||
const orangeColor = '#bc6642'
|
|
||||||
const mintColor = '#6ca299'
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
container: { flex: 1 },
|
|
||||||
chartContainer: { flexDirection: 'column' },
|
|
||||||
chartArea: { flexDirection: 'row' },
|
|
||||||
centerItem: {
|
|
||||||
flex:1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginHorizontal: 25,
|
|
||||||
},
|
|
||||||
curve: {
|
|
||||||
stroke: colorTemperature,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
curveExcluded: {
|
|
||||||
stroke: colorTemperatureLight,
|
|
||||||
strokeWidth: lineWidth
|
|
||||||
},
|
|
||||||
curveDots: {
|
|
||||||
fill: colorTemperature,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
curveDotsExcluded: {
|
|
||||||
fill: colorTemperatureLight,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
label: {
|
|
||||||
date: {
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingTop: 2.5
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
color: cycleDayColor,
|
|
||||||
fontSize: numberLabelFontSize,
|
|
||||||
textAlign: 'center',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
color: gridColor,
|
|
||||||
width: gridLineWidthVertical,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomDot: {
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
borderRadius: 50,
|
|
||||||
},
|
|
||||||
iconColors: {
|
|
||||||
'bleeding': {
|
|
||||||
color: redColor,
|
|
||||||
shades: shadesOfRed,
|
|
||||||
},
|
|
||||||
'mucus': {
|
|
||||||
color: violetColor,
|
|
||||||
shades: shadesOfViolet,
|
|
||||||
},
|
|
||||||
'cervix': {
|
|
||||||
color: yellowColor,
|
|
||||||
shades: shadesOfYellow,
|
|
||||||
},
|
|
||||||
'sex': {
|
|
||||||
color: magentaColor,
|
|
||||||
shades: shadesOfMagenta,
|
|
||||||
},
|
|
||||||
'desire': {
|
|
||||||
color: pinkColor,
|
|
||||||
shades: shadesOfPink,
|
|
||||||
},
|
|
||||||
'pain': {
|
|
||||||
color: lightGreenColor,
|
|
||||||
shades: [lightGreenColor],
|
|
||||||
},
|
|
||||||
'mood': {
|
|
||||||
color: orangeColor,
|
|
||||||
shades: [orangeColor],
|
|
||||||
},
|
|
||||||
'note': {
|
|
||||||
color: mintColor,
|
|
||||||
shades: [mintColor],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
width: 27,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
borderColor: 'lightgrey',
|
|
||||||
borderStyle: 'solid'
|
|
||||||
},
|
|
||||||
yAxisLabels: {
|
|
||||||
tempScale: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 2,
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
textAlign: 'left'
|
|
||||||
},
|
|
||||||
cycleDayLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: Math.ceil(numberLabelFontSize / 2)
|
|
||||||
},
|
|
||||||
dateLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomIcon: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
chartLegend: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
boldTick: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 11,
|
|
||||||
},
|
|
||||||
horizontalGrid: {
|
|
||||||
position:'absolute',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderBottomColor: gridColor,
|
|
||||||
borderBottomWidth: gridLineWidthHorizontal,
|
|
||||||
width: '100%',
|
|
||||||
left: config.columnWidth
|
|
||||||
},
|
|
||||||
nfpLine: {
|
|
||||||
stroke: colorLtl,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
symptomRow: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
loadingMore: {
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'lightgrey',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styles
|
|
||||||
@@ -1,47 +1,49 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_GRID_LINE_HORIZONTAL_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
const SymptomCell = ({
|
const SymptomCell = ({
|
||||||
height,
|
height,
|
||||||
|
index,
|
||||||
symptom,
|
symptom,
|
||||||
symptomValue,
|
symptomValue,
|
||||||
isSymptomDataComplete
|
isSymptomDataComplete
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const shouldDrawDot = symptomValue !== false
|
const shouldDrawDot = symptomValue !== false
|
||||||
const styleParent = [styles.symptomRow, { height, width: config.columnWidth }]
|
const styleCell = index !== 0
|
||||||
let styleChild
|
? [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
|
||||||
|
: [styles.cell, { height, width: CHART_COLUMN_WIDTH }, styles.topBorder]
|
||||||
|
let styleDot
|
||||||
|
|
||||||
if (shouldDrawDot) {
|
if (shouldDrawDot) {
|
||||||
const styleSymptom = styles.iconColors[symptom]
|
const styleSymptom = Colors.iconColors[symptom]
|
||||||
const symptomColor = styleSymptom.shades[symptomValue]
|
const symptomColor = styleSymptom.shades[symptomValue]
|
||||||
|
|
||||||
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
||||||
|
|
||||||
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
||||||
'white' : symptomColor
|
'white' : symptomColor
|
||||||
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
||||||
const borderColor = symptomColor
|
const borderColor = symptomColor
|
||||||
styleChild = [styles.symptomDot, {
|
styleDot = [styles.dot, { backgroundColor, borderColor, borderWidth }]
|
||||||
backgroundColor,
|
|
||||||
borderColor,
|
|
||||||
borderWidth
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styleParent} key={symptom}>
|
<View style={styleCell} key={symptom}>
|
||||||
{shouldDrawDot && <View style={styleChild} />}
|
{shouldDrawDot && <View style={styleDot} />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SymptomCell.propTypes = {
|
SymptomCell.propTypes = {
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
index: PropTypes.number.isRequired,
|
||||||
symptom: PropTypes.string,
|
symptom: PropTypes.string,
|
||||||
symptomValue: PropTypes.oneOfType([
|
symptomValue: PropTypes.oneOfType([
|
||||||
PropTypes.bool,
|
PropTypes.bool,
|
||||||
@@ -50,4 +52,23 @@ SymptomCell.propTypes = {
|
|||||||
isSymptomDataComplete: PropTypes.bool,
|
isSymptomDataComplete: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
cell: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderBottomColor: Colors.grey,
|
||||||
|
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
borderLeftColor: Colors.grey,
|
||||||
|
borderLeftWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
...Containers.centerItems
|
||||||
|
},
|
||||||
|
topBorder: {
|
||||||
|
borderTopColor: Colors.grey,
|
||||||
|
borderTopWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
},
|
||||||
|
dot: {
|
||||||
|
width: CHART_DOT_RADIUS * 2,
|
||||||
|
height: CHART_DOT_RADIUS * 2,
|
||||||
|
borderRadius: 50
|
||||||
|
}
|
||||||
|
})
|
||||||
export default SymptomCell
|
export default SymptomCell
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet , View } from 'react-native'
|
||||||
|
|
||||||
import DripIcon from '../../assets/drip-icons'
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
import { CHART_YAXIS_WIDTH, CHART_ICON_SIZE } from '../../config'
|
||||||
|
|
||||||
const SymptomIcon = ({ symptom, height }) => {
|
const SymptomIcon = ({ symptom, height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.symptomIcon} width={styles.yAxis.width} height={height}>
|
<View style={styles.container} width={CHART_YAXIS_WIDTH} height={height}>
|
||||||
<DripIcon
|
<DripIcon
|
||||||
size={16}
|
size={CHART_ICON_SIZE}
|
||||||
name={`drip-icon-${symptom}`}
|
name={`drip-icon-${symptom}`}
|
||||||
color={styles.iconColors[symptom].color}
|
color={Colors.iconColors[symptom].color}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -23,4 +24,10 @@ SymptomIcon.propTypes = {
|
|||||||
symptom: PropTypes.string,
|
symptom: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default SymptomIcon
|
export default SymptomIcon
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import ChartLine from './chart-line'
|
import ChartLine from './chart-line'
|
||||||
import DotAndLine from './dot-and-line'
|
import DotAndLine from './dot-and-line'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_COLUMN_WIDTH, CHART_STROKE_WIDTH } from '../../config'
|
||||||
import config from '../../config'
|
|
||||||
|
|
||||||
const TemperatureColumn = ({
|
const TemperatureColumn = ({
|
||||||
horizontalLinePosition,
|
horizontalLinePosition,
|
||||||
@@ -15,20 +15,21 @@ const TemperatureColumn = ({
|
|||||||
data,
|
data,
|
||||||
columnHeight
|
columnHeight
|
||||||
}) => {
|
}) => {
|
||||||
|
const x = CHART_STROKE_WIDTH / 2
|
||||||
const x = styles.nfpLine.strokeWidth / 2
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Surface width={config.columnWidth} height={columnHeight}>
|
<Surface
|
||||||
|
width={CHART_COLUMN_WIDTH}
|
||||||
|
height={columnHeight}
|
||||||
|
style={styles.container}
|
||||||
|
>
|
||||||
|
|
||||||
<ChartLine
|
<ChartLine path={new Path().lineTo(0, columnHeight)}/>
|
||||||
path={new Path().lineTo(0, columnHeight)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{horizontalLinePosition && <ChartLine
|
{horizontalLinePosition && <ChartLine
|
||||||
path={new Path()
|
path={new Path()
|
||||||
.moveTo(0, horizontalLinePosition)
|
.moveTo(0, horizontalLinePosition)
|
||||||
.lineTo(config.columnWidth, horizontalLinePosition)
|
.lineTo(CHART_COLUMN_WIDTH, horizontalLinePosition)
|
||||||
}
|
}
|
||||||
isNfpLine={true}
|
isNfpLine={true}
|
||||||
key='ltl'
|
key='ltl'
|
||||||
@@ -40,7 +41,7 @@ const TemperatureColumn = ({
|
|||||||
key='fhm'
|
key='fhm'
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{data && data.y && <DotAndLine
|
{data && typeof(data.y) !== 'undefined' && <DotAndLine
|
||||||
y={data.y}
|
y={data.y}
|
||||||
exclude={data.temperatureExclude}
|
exclude={data.temperatureExclude}
|
||||||
rightY={data.rightY}
|
rightY={data.rightY}
|
||||||
@@ -61,4 +62,10 @@ TemperatureColumn.propTypes = {
|
|||||||
columnHeight: PropTypes.number,
|
columnHeight: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TemperatureColumn
|
export default TemperatureColumn
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import Tick from './tick'
|
import Tick from './tick'
|
||||||
|
|
||||||
import { getTickList } from '../helpers/chart'
|
import { getTickList } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const TickList = ({ height }) => {
|
const TickList = ({ height }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, { height }]}>{
|
<View style={[styles.container, height]}>
|
||||||
getTickList(height)
|
{
|
||||||
.map(({ label, position, isBold, shouldShowLabel}) => {
|
getTickList(height)
|
||||||
return (
|
.map(({ isBold, label, position, shouldShowLabel, tickHeight}) => {
|
||||||
<Tick
|
return (
|
||||||
key={label}
|
<Tick
|
||||||
yPosition={position}
|
height={tickHeight}
|
||||||
isBold={isBold}
|
isBold={isBold}
|
||||||
shouldShowLabel={shouldShowLabel}
|
key={label}
|
||||||
label={label}
|
label={label}
|
||||||
/>
|
shouldShowLabel={shouldShowLabel}
|
||||||
)
|
yPosition={position}
|
||||||
})
|
/>
|
||||||
}</View>
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,4 +33,10 @@ TickList.propTypes = {
|
|||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TickList
|
export default TickList
|
||||||
|
|||||||
@@ -1,29 +1,53 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Sizes } from '../../styles'
|
||||||
|
|
||||||
const Tick = ({ yPosition, isBold, shouldShowLabel, label }) => {
|
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
|
||||||
// this eyeballing is sadly necessary because RN does not
|
const top = yPosition - height / 2
|
||||||
// support percentage values for transforms, which we'd need
|
const containerStyle = [ styles.container, { flexBasis: height, height, top }]
|
||||||
// to reliably place the label vertically centered to the grid
|
const textStyle = isBold ? styles.textBold : styles.textNormal
|
||||||
const topPosition = yPosition - 8
|
|
||||||
const style = [
|
|
||||||
styles.yAxisLabels.tempScale,
|
|
||||||
{top: topPosition},
|
|
||||||
isBold && styles.boldTick
|
|
||||||
]
|
|
||||||
|
|
||||||
return <AppText style={style}>{shouldShowLabel && label}</AppText>
|
return(
|
||||||
|
<View style={containerStyle}>
|
||||||
|
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tick.propTypes = {
|
Tick.propTypes = {
|
||||||
yPosition: PropTypes.number,
|
yPosition: PropTypes.number,
|
||||||
|
height: PropTypes.number.isRequired,
|
||||||
isBold: PropTypes.bool,
|
isBold: PropTypes.bool,
|
||||||
shouldShowLabel: PropTypes.bool,
|
shouldShowLabel: PropTypes.bool,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
lineHeight: Sizes.base,
|
||||||
|
right: 4,
|
||||||
|
textAlign: 'right'
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
width: 40
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
fontSize: Sizes.base,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
...text
|
||||||
|
},
|
||||||
|
textNormal: {
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
...text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default Tick
|
export default Tick
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import CloseIcon from '../common/close-icon'
|
||||||
|
|
||||||
|
import { Containers, Spacing } from '../../styles'
|
||||||
|
import { chart } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
const image = require('../../assets/swipe.png')
|
||||||
|
|
||||||
|
const Tutorial = ({ onClose }) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Image resizeMode='contain' source={image} style={styles.image} />
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText>{chart.tutorial}</AppText>
|
||||||
|
</View>
|
||||||
|
<CloseIcon onClose={onClose} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tutorial.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer,
|
||||||
|
padding: Spacing.large
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Tutorial
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import SymptomIcon from './symptom-icon'
|
import SymptomIcon from './symptom-icon'
|
||||||
import TickList from './tick-list'
|
import TickList from './tick-list'
|
||||||
import ChartLegend from './chart-legend'
|
import ChartLegend from './chart-legend'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const YAxis = ({
|
const YAxis = ({
|
||||||
height,
|
height,
|
||||||
@@ -19,6 +19,8 @@ const YAxis = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
{shouldShowTemperatureColumn && <TickList height={height} />}
|
||||||
|
<ChartLegend height={xAxisHeight} />
|
||||||
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
||||||
{symptomsToDisplay.map(symptom => (
|
{symptomsToDisplay.map(symptom => (
|
||||||
<SymptomIcon
|
<SymptomIcon
|
||||||
@@ -29,8 +31,6 @@ const YAxis = ({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowTemperatureColumn && <TickList height={height} />}
|
|
||||||
<ChartLegend xAxisHeight={xAxisHeight} />
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -43,4 +43,10 @@ YAxis.propTypes = {
|
|||||||
xAxisHeight: PropTypes.number.isRequired
|
xAxisHeight: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
yAxis: {
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default YAxis
|
export default YAxis
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
import Icon from 'react-native-vector-icons/Entypo'
|
||||||
|
|
||||||
|
import { Sizes } from '../../styles'
|
||||||
|
|
||||||
|
const AppIcon = ({ color, name, style, ...props }) => {
|
||||||
|
const iconStyle = [styles.icon, style, { color }]
|
||||||
|
|
||||||
|
return <Icon name={name} style={iconStyle} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
AppIcon.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
AppIcon.defaultProps = {
|
||||||
|
color: 'black'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
icon: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppIcon
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
|
|
||||||
|
import { shared } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
const AppLoadingView = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<AppText>{shared.loading}</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppLoadingView
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Modal, StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
const AppModal = ({ children, onClose }) => {
|
||||||
|
return(
|
||||||
|
<Modal
|
||||||
|
animationType='fade'
|
||||||
|
onRequestClose={onClose}
|
||||||
|
transparent={true}
|
||||||
|
visible={true}
|
||||||
|
>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.blackBackground} />
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModal.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
onClose: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
blackBackground: {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
flex: 1,
|
||||||
|
opacity: 0.5,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppModal
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { ScrollView, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppPage = ({
|
||||||
|
children,
|
||||||
|
contentContainerStyle,
|
||||||
|
scrollViewStyle,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return(
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={[styles.scrollView, contentContainerStyle]}
|
||||||
|
style={scrollViewStyle}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppPage.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
contentContainerStyle: PropTypes.object,
|
||||||
|
scrollViewStyle: PropTypes.object,
|
||||||
|
title: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
scrollView: {
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppPage
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, Switch, View } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
|
|
||||||
|
const AppSwitch = ({ onToggle, text, value }) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText>{text}</AppText>
|
||||||
|
</View>
|
||||||
|
<Switch onValueChange={onToggle} style={styles.switch} value={value} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSwitch.propTypes = {
|
||||||
|
onToggle: PropTypes.func.isRequired,
|
||||||
|
text: PropTypes.string,
|
||||||
|
value: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
switch: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flex: 4,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppSwitch
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { KeyboardAvoidingView, StyleSheet, TextInput } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import { Colors, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppTextInput = ({ style, isKeyboardOffset, ...props }) => {
|
||||||
|
const behavior = isKeyboardOffset ? "padding" : "height"
|
||||||
|
const keyboardVerticalOffset = isKeyboardOffset ? 300 : 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior={behavior}
|
||||||
|
keyboardVerticalOffset={keyboardVerticalOffset}
|
||||||
|
>
|
||||||
|
<TextInput style={[styles.input, style]} {...props} />
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTextInput.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
|
isKeyboardOffset: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTextInput.defultProps = {
|
||||||
|
isKeyboardOffset: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderColor: Colors.grey,
|
||||||
|
borderRadius: 5,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
color: Colors.greyDark,
|
||||||
|
marginTop: Spacing.base,
|
||||||
|
minWidth: '80%',
|
||||||
|
paddingHorizontal: Spacing.base,
|
||||||
|
...Typography.mainText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppTextInput
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
|
||||||
|
import Link from './link'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppText = ({ children, linkStyle, style, ...props }) => {
|
||||||
|
// we parse for links in case the text contains any
|
||||||
|
return (
|
||||||
|
<Link style={linkStyle}>
|
||||||
|
<Text style={[styles.text, style]} {...props}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppText.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
linkStyle: PropTypes.object,
|
||||||
|
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
...Typography.mainText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppText
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from './app-icon'
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Colors, Fonts, Spacing } from '../../styles'
|
||||||
|
|
||||||
|
const Button = ({
|
||||||
|
children,
|
||||||
|
iconName,
|
||||||
|
isCTA,
|
||||||
|
isSmall,
|
||||||
|
onPress,
|
||||||
|
testID,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const buttonStyle = isCTA ? styles.cta : styles.regular
|
||||||
|
const textCTA = isCTA ? styles.buttonTextBold : styles.buttonTextRegular
|
||||||
|
const textStyle = [textCTA, isSmall ? textSmall : text]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onPress}
|
||||||
|
style={buttonStyle}
|
||||||
|
testID={testID}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<AppText style={textStyle}>{children}</AppText>
|
||||||
|
{iconName && <AppIcon color={Colors.orange} name={iconName} />}
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
iconName: PropTypes.string,
|
||||||
|
isCTA: PropTypes.bool,
|
||||||
|
isSmall: PropTypes.bool,
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
testID: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
isSmall: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
padding: Spacing.base,
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSmall = {
|
||||||
|
fontSize: Fonts.small,
|
||||||
|
padding: Spacing.small,
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = {
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: Spacing.base,
|
||||||
|
minWidth: '15%'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
regular: {
|
||||||
|
...button
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
borderRadius: 25,
|
||||||
|
...button
|
||||||
|
},
|
||||||
|
buttonTextBold: {
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: Fonts.bold
|
||||||
|
},
|
||||||
|
buttonTextRegular: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
fontFamily: Fonts.main
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Button
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from './app-icon'
|
||||||
|
|
||||||
|
import { Colors, Sizes } from '../../styles'
|
||||||
|
|
||||||
|
const CloseIcon = ({ onClose, ...props }) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onClose}
|
||||||
|
style={styles.container}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<AppIcon name='cross' color={Colors.orange} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseIcon.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
marginBottom: Sizes.base
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default CloseIcon
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Hyperlink from 'react-native-hyperlink'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
import links from '../../i18n/en/links'
|
||||||
|
|
||||||
|
const Link = ({ children, style }) => {
|
||||||
|
return (
|
||||||
|
<Hyperlink
|
||||||
|
linkStyle={[styles.link, style]}
|
||||||
|
linkText={replaceUrlWithText}
|
||||||
|
linkDefault
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Hyperlink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Link.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
link: {
|
||||||
|
color: Colors.purple,
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
...Typography.mainText,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function replaceUrlWithText(url) {
|
||||||
|
const link = Object.values(links).find(l => l.url === url)
|
||||||
|
return (link && link.text) || url
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Link
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { createIconSetFromIcoMoon } from 'react-native-vector-icons'
|
||||||
|
import iconConfig from '../../selection.json'
|
||||||
|
|
||||||
|
import { Colors, Sizes } from '../../styles'
|
||||||
|
|
||||||
|
const Icon = createIconSetFromIcoMoon(iconConfig, '', 'Menu')
|
||||||
|
|
||||||
|
const MenuIcon = ({ isActive, name }) => {
|
||||||
|
const color = isActive ? Colors.greyDark : Colors.grey
|
||||||
|
|
||||||
|
return <Icon name={name} size={Sizes.icon} color={color} />
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuIcon.propTypes = {
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuIcon
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Colors, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const Segment = ({ children, last, title }) => {
|
||||||
|
const containerStyle = last ? styles.containerLast : styles.container
|
||||||
|
const commonStyle = Object.assign({}, containerStyle)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={commonStyle}>
|
||||||
|
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
last: PropTypes.bool,
|
||||||
|
style: PropTypes.object,
|
||||||
|
title: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const segmentContainer = {
|
||||||
|
marginHorizontal: Spacing.base,
|
||||||
|
marginBottom: Spacing.base,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: Colors.greyLight,
|
||||||
|
paddingBottom: Spacing.base,
|
||||||
|
...segmentContainer,
|
||||||
|
},
|
||||||
|
containerLast: {
|
||||||
|
...segmentContainer,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.subtitle,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Segment
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Sizes, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const Table = ({ tableContent }) => {
|
||||||
|
return (
|
||||||
|
tableContent.map((rowContent, i) => <Row key={i} rowContent={rowContent} />)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Table.propTypes = {
|
||||||
|
tableContent: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = ({ rowContent }) => {
|
||||||
|
return(
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Cell content={rowContent[0]} isLeft />
|
||||||
|
<Cell content={rowContent[1]} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row.propTypes = {
|
||||||
|
rowContent: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cell = ({ content, isLeft }) => {
|
||||||
|
const styleContainer = isLeft ? styles.cellLeft : styles.cellRight
|
||||||
|
const styleText = isLeft ? styles.accentPurpleBig : styles.accentOrange
|
||||||
|
const numberOfLines = isLeft ? 1 : 2
|
||||||
|
const ellipsizeMode = isLeft ? 'clip' : 'tail'
|
||||||
|
|
||||||
|
return(
|
||||||
|
<View style={styleContainer}>
|
||||||
|
<AppText
|
||||||
|
numberOfLines={numberOfLines}
|
||||||
|
ellipsizeMode={ellipsizeMode}
|
||||||
|
style={styleText}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell.propTypes = {
|
||||||
|
content: PropTypes.node.isRequired,
|
||||||
|
isLeft: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
accentOrange: {
|
||||||
|
...Typography.accentOrange,
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
},
|
||||||
|
accentPurpleBig: {
|
||||||
|
...Typography.accentPurpleBig,
|
||||||
|
marginRight: Spacing.small,
|
||||||
|
},
|
||||||
|
cellLeft: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
flex: 5,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
cellRight: {
|
||||||
|
flex: 6,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: Spacing.tiny,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Table
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { View, Dimensions } from 'react-native'
|
|
||||||
import styles from '../../styles'
|
|
||||||
|
|
||||||
export default class FillerBoxes extends Component {
|
|
||||||
render() {
|
|
||||||
const n = Dimensions.get('window').width / styles.symptomBox.width
|
|
||||||
const fillerBoxes = []
|
|
||||||
for (let i = 0; i < Math.ceil(n); i++) {
|
|
||||||
fillerBoxes.push(
|
|
||||||
<View
|
|
||||||
width={styles.symptomBox.width}
|
|
||||||
height={0}
|
|
||||||
key={i.toString()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fillerBoxes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from '../app-text'
|
|
||||||
import DripIcon from '../../assets/drip-icons'
|
|
||||||
|
|
||||||
import styles from '../../styles'
|
|
||||||
|
|
||||||
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
|
||||||
import * as labels from '../../i18n/en/cycle-day'
|
|
||||||
const bleedingLabels = labels.bleeding.labels
|
|
||||||
const intensityLabels = labels.intensity
|
|
||||||
const sexLabels = labels.sex.categories
|
|
||||||
const contraceptiveLabels = labels.contraceptives.categories
|
|
||||||
const painLabels = labels.pain.categories
|
|
||||||
const moodLabels = labels.mood.categories
|
|
||||||
|
|
||||||
function isNumber(val) {
|
|
||||||
return typeof val === 'number'
|
|
||||||
}
|
|
||||||
|
|
||||||
const l = {
|
|
||||||
bleeding: ({ value, exclude }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
const bleedingLabel = bleedingLabels[value]
|
|
||||||
return exclude ? `(${bleedingLabel})` : bleedingLabel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
temperature: ({ value, time, exclude }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
let temperatureLabel = `${value} °C`
|
|
||||||
if (time) {
|
|
||||||
temperatureLabel += ` - ${time}`
|
|
||||||
}
|
|
||||||
if (exclude) {
|
|
||||||
temperatureLabel = `(${temperatureLabel})`
|
|
||||||
}
|
|
||||||
return temperatureLabel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mucus: mucus => {
|
|
||||||
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 => {
|
|
||||||
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 => note.value,
|
|
||||||
desire: ({ value }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
return intensityLabels[value]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sex: sex => {
|
|
||||||
const sexLabel = []
|
|
||||||
if (sex && Object.values({...sex}).some(val => val)){
|
|
||||||
Object.keys(sex).forEach(key => {
|
|
||||||
if(sex[key] && key !== 'other' && key !== 'note') {
|
|
||||||
sexLabel.push(
|
|
||||||
sexLabels[key] ||
|
|
||||||
contraceptiveLabels[key]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(key === 'other' && sex.other) {
|
|
||||||
let label = contraceptiveLabels[key]
|
|
||||||
if(sex.note) {
|
|
||||||
label = `${label} (${sex.note})`
|
|
||||||
}
|
|
||||||
sexLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return sexLabel.join(', ')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pain: pain => {
|
|
||||||
const painLabel = []
|
|
||||||
if (pain && Object.values({...pain}).some(val => val)){
|
|
||||||
Object.keys(pain).forEach(key => {
|
|
||||||
if(pain[key] && key !== 'other' && key !== 'note') {
|
|
||||||
painLabel.push(painLabels[key])
|
|
||||||
}
|
|
||||||
if(key === 'other' && pain.other) {
|
|
||||||
let label = painLabels[key]
|
|
||||||
if(pain.note) {
|
|
||||||
label = `${label} (${pain.note})`
|
|
||||||
}
|
|
||||||
painLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return painLabel.join(', ')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mood: mood => {
|
|
||||||
const moodLabel = []
|
|
||||||
if (mood && Object.values({...mood}).some(val => val)){
|
|
||||||
Object.keys(mood).forEach(key => {
|
|
||||||
if(mood[key] && key !== 'other' && key !== 'note') {
|
|
||||||
moodLabel.push(moodLabels[key])
|
|
||||||
}
|
|
||||||
if(key === 'other' && mood.other) {
|
|
||||||
let label = moodLabels[key]
|
|
||||||
if(mood.note) {
|
|
||||||
label = `${label} (${mood.note})`
|
|
||||||
}
|
|
||||||
moodLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return moodLabel.join(', ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLabel = (symptom, symptomData) => {
|
|
||||||
return symptomData && l[symptom](symptomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SymptomBox(
|
|
||||||
{ disabled, onPress, symptom, symptomData }) {
|
|
||||||
|
|
||||||
const data = getLabel(symptom, symptomData)
|
|
||||||
const iconName = `drip-icon-${symptom}`
|
|
||||||
|
|
||||||
const disabledStyle = disabled ? styles.symptomInFuture : null
|
|
||||||
const containerStyle = [
|
|
||||||
styles.symptomBox,
|
|
||||||
data && styles.symptomBoxActive,
|
|
||||||
disabledStyle
|
|
||||||
]
|
|
||||||
const titleStyle = [
|
|
||||||
data && styles.symptomTextActive,
|
|
||||||
disabledStyle,
|
|
||||||
{fontSize: 15}
|
|
||||||
]
|
|
||||||
const dataBoxStyle = [styles.symptomDataBox, disabledStyle]
|
|
||||||
const iconColor = data ? 'white' : 'black'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={onPress} disabled={disabled} testID={iconName}>
|
|
||||||
<View style={containerStyle}>
|
|
||||||
<DripIcon name={iconName} size={50} color={iconColor} />
|
|
||||||
<AppText style={titleStyle} numberOfLines={1}>
|
|
||||||
{symptomTitles[symptom].toLowerCase()}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
|
||||||
<View style={dataBoxStyle}>
|
|
||||||
<AppText style={styles.symptomDataText} numberOfLines={3}>
|
|
||||||
{data}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SymptomBox.propTypes = {
|
|
||||||
disabled: PropTypes.bool.isRequired,
|
|
||||||
onPress: PropTypes.func.isRequired,
|
|
||||||
symptom: PropTypes.string.isRequired,
|
|
||||||
symptomData: PropTypes.object
|
|
||||||
}
|
|
||||||
@@ -1,118 +1,98 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { ScrollView, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { LocalDate } from 'js-joda'
|
||||||
|
|
||||||
|
import AppPage from '../common/app-page'
|
||||||
|
import SymptomBox from './symptom-box'
|
||||||
|
import SymptomPageTitle from './symptom-page-title'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { getDate, setDate } from '../../slices/date'
|
import { getDate, setDate } from '../../slices/date'
|
||||||
import { navigate } from '../../slices/navigation'
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
import Header from '../header'
|
|
||||||
import FillerBoxes from './FillerBoxes'
|
|
||||||
import SymptomBox from './SymptomBox'
|
|
||||||
|
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
import formatDate from '../helpers/format-date'
|
import { dateToTitle } from '../helpers/format-date'
|
||||||
import { getCycleDay } from '../../db'
|
import { getCycleDay } from '../../db'
|
||||||
import styles from '../../styles'
|
import { getData } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { general as labels} from '../../i18n/en/cycle-day'
|
||||||
|
import { Spacing } from '../../styles'
|
||||||
|
import { SYMPTOMS } from '../../config'
|
||||||
|
|
||||||
class CycleDayOverView extends Component {
|
class CycleDayOverView extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigate: PropTypes.func,
|
navigate: PropTypes.func,
|
||||||
setDate: PropTypes.func,
|
setDate: PropTypes.func,
|
||||||
// The following are not being used,
|
|
||||||
// we could see if it's possible to not pass them from the <App />
|
|
||||||
cycleDay: PropTypes.object,
|
cycleDay: PropTypes.object,
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
|
isTemperatureEditView: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
|
||||||
cycleDay: getCycleDay(props.date)
|
this.state = { cycleDay: getCycleDay(props.date), data: null }
|
||||||
|
if (props.isTemperatureEditView) {
|
||||||
|
const todayDateString = LocalDate.now().toString()
|
||||||
|
props.setDate(todayDateString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCycleDay = (date) => {
|
updateCycleDay = (date) => {
|
||||||
this.props.setDate(date)
|
this.props.setDate(date)
|
||||||
this.setState({
|
this.setState({ cycleDay: getCycleDay(date) })
|
||||||
cycleDay: getCycleDay(date)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
goToPrevDay = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const prevDate = LocalDate.parse(date).minusDays(1).toString()
|
|
||||||
this.updateCycleDay(prevDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
goToNextDay = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const nextDate = LocalDate.parse(date).plusDays(1).toString()
|
|
||||||
this.updateCycleDay(nextDate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { cycleDay } = this.state
|
const { cycleDay } = this.state
|
||||||
const { date } = this.props
|
const { date, isTemperatureEditView } = this.props
|
||||||
|
|
||||||
const dateInFuture = LocalDate.now().isBefore(LocalDate.parse(date))
|
|
||||||
|
|
||||||
const symptomBoxesList = [
|
|
||||||
'bleeding',
|
|
||||||
'temperature',
|
|
||||||
'mucus',
|
|
||||||
'cervix',
|
|
||||||
'desire',
|
|
||||||
'sex',
|
|
||||||
'pain',
|
|
||||||
'mood',
|
|
||||||
'note',
|
|
||||||
]
|
|
||||||
|
|
||||||
const { getCycleDayNumber } = cycleModule()
|
const { getCycleDayNumber } = cycleModule()
|
||||||
const cycleDayNumber = getCycleDayNumber(date)
|
const cycleDayNumber = getCycleDayNumber(date)
|
||||||
const headerSubtitle = cycleDayNumber && `Cycle day ${cycleDayNumber}`
|
const subtitle = cycleDayNumber && `${labels.cycleDayNumber}${cycleDayNumber}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<AppPage>
|
||||||
<Header
|
<SymptomPageTitle
|
||||||
handleBack={this.goToPrevDay}
|
reloadSymptomData={this.updateCycleDay}
|
||||||
handleNext={this.goToNextDay}
|
subtitle={subtitle}
|
||||||
title={formatDate(date)}
|
title={dateToTitle(date)}
|
||||||
subtitle={headerSubtitle}
|
|
||||||
/>
|
/>
|
||||||
<ScrollView>
|
<View style={styles.container}>
|
||||||
<View style={styles.symptomBoxesView}>
|
{SYMPTOMS.map(symptom => {
|
||||||
{
|
const symptomData = cycleDay && cycleDay[symptom]
|
||||||
symptomBoxesList.map(symptom => {
|
? cycleDay[symptom] : null
|
||||||
const symptomEditView =
|
|
||||||
`${symptom[0].toUpperCase() + symptom.substring(1)}EditView`
|
const isSymptomEdited = isTemperatureEditView && symptom === 'temperature'
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : null
|
return(
|
||||||
return(
|
<SymptomBox
|
||||||
<SymptomBox
|
key={symptom}
|
||||||
key={symptom}
|
symptom={symptom}
|
||||||
symptom={symptom}
|
symptomData={symptomData}
|
||||||
symptomData={symptomData}
|
symptomDataToDisplay={getData(symptom, symptomData)}
|
||||||
onPress={() => this.props.navigate(symptomEditView)}
|
updateCycleDayData={this.updateCycleDay}
|
||||||
disabled={dateInFuture && symptom !== 'note'}
|
isSymptomEdited={isSymptomEdited}
|
||||||
/>)
|
/>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
{
|
</View>
|
||||||
// this is just to make the last row adhere to the grid
|
</AppPage>
|
||||||
// (and) because there are no pseudo properties in RN
|
|
||||||
}
|
|
||||||
<FillerBoxes />
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: Spacing.base
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return({
|
return({
|
||||||
date: getDate(state),
|
date: getDate(state),
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
|
||||||
export default function SelectBoxGroup({ labels, onSelect, optionsState }) {
|
const SelectBoxGroup = ({ labels, optionsState, onSelect }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.selectBoxSection}>
|
<View style={styles.container}>
|
||||||
{Object.keys(labels).map(key => {
|
{Object.keys(labels).map(key => {
|
||||||
const style = [styles.selectBox]
|
const isActive = optionsState[key]
|
||||||
const textStyle = []
|
const boxStyle = [styles.box, isActive && styles.boxActive]
|
||||||
if (optionsState[key]) {
|
const textStyle = [styles.text, isActive && styles.textActive]
|
||||||
style.push(styles.selectBoxActive)
|
|
||||||
textStyle.push(styles.selectBoxTextActive)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => onSelect(key)}
|
|
||||||
key={key}
|
key={key}
|
||||||
|
onPress={() => onSelect(key)}
|
||||||
|
style={boxStyle}
|
||||||
>
|
>
|
||||||
<View style={style}>
|
<AppText style={textStyle}>{labels[key]}</AppText>
|
||||||
<AppText style={textStyle}>{labels[key]}</AppText>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -36,3 +33,23 @@ SelectBoxGroup.propTypes = {
|
|||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
optionsState: PropTypes.object.isRequired
|
optionsState: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
box: {
|
||||||
|
...Containers.box
|
||||||
|
},
|
||||||
|
boxActive: {
|
||||||
|
...Containers.boxActive
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
...Containers.selectGroupContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: Colors.orange
|
||||||
|
},
|
||||||
|
textActive: {
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SelectBoxGroup
|
||||||
|
|||||||
@@ -1,40 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
|
||||||
export default function SelectTabGroup({ active, buttons, onSelect }) {
|
export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.selectTabGroup}>
|
<View style={styles.container}>
|
||||||
{
|
{
|
||||||
buttons.map(({ label, value }, i) => {
|
buttons.map(({ label, value }, i) => {
|
||||||
let firstOrLastStyle
|
const isActive = value === activeButton
|
||||||
if (i === buttons.length - 1) {
|
const boxStyle = [styles.box, isActive && styles.boxActive]
|
||||||
firstOrLastStyle = styles.selectTabLast
|
const textStyle = [styles.text, isActive && styles.textActive]
|
||||||
} else if (i === 0) {
|
|
||||||
firstOrLastStyle = styles.selectTabFirst
|
|
||||||
}
|
|
||||||
let activeStyle
|
|
||||||
const isActive = value === active
|
|
||||||
if (isActive) activeStyle = styles.selectTabActive
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => onSelect(isActive ? null : value)}
|
onPress={() => onSelect(value)}
|
||||||
key={i}
|
key={i}
|
||||||
activeOpacity={1}
|
style={boxStyle}
|
||||||
>
|
>
|
||||||
<View>
|
<AppText style={textStyle}>{label}</AppText>
|
||||||
<View style={[
|
|
||||||
styles.selectTab,
|
|
||||||
firstOrLastStyle,
|
|
||||||
activeStyle
|
|
||||||
]}>
|
|
||||||
<AppText style={activeStyle}>{label}</AppText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -44,7 +31,25 @@ export default function SelectTabGroup({ active, buttons, onSelect }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectTabGroup.propTypes = {
|
SelectTabGroup.propTypes = {
|
||||||
active: PropTypes.number,
|
activeButton: PropTypes.number,
|
||||||
buttons: PropTypes.array.isRequired,
|
buttons: PropTypes.array.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired
|
onSelect: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
box: {
|
||||||
|
...Containers.box
|
||||||
|
},
|
||||||
|
boxActive: {
|
||||||
|
...Containers.boxActive
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
...Containers.selectGroupContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: Colors.orange
|
||||||
|
},
|
||||||
|
textActive: {
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
import SymptomEditView from './symptom-edit-view'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate } from '../../slices/date'
|
||||||
|
import { isDateInFuture } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { Colors, Sizes, Spacing } from '../../styles'
|
||||||
|
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
class SymptomBox extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
isSymptomEdited: PropTypes.bool,
|
||||||
|
symptom: PropTypes.string.isRequired,
|
||||||
|
symptomData: PropTypes.object,
|
||||||
|
symptomDataToDisplay: PropTypes.string,
|
||||||
|
updateCycleDayData: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
isSymptomEdited: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isSymptomEdited: props.isSymptomEdited
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinishEditing = () => {
|
||||||
|
const { date, updateCycleDayData } = this.props
|
||||||
|
|
||||||
|
updateCycleDayData(date)
|
||||||
|
this.setState({ isSymptomEdited: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditSymptom = () => {
|
||||||
|
this.setState({ isSymptomEdited: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { date, symptom, symptomData, symptomDataToDisplay } = this.props
|
||||||
|
const { isSymptomEdited } = this.state
|
||||||
|
const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
|
||||||
|
const isExcluded = symptomData !== null ? symptomData.exclude : false
|
||||||
|
|
||||||
|
const iconColor = isSymptomDisabled ? Colors.greyLight : Colors.grey
|
||||||
|
const iconName = `drip-icon-${symptom}`
|
||||||
|
const symptomNameStyle = [
|
||||||
|
styles.symptomName,
|
||||||
|
(isSymptomDisabled && styles.symptomNameDisabled),
|
||||||
|
(isExcluded && styles.symptomNameExcluded)
|
||||||
|
]
|
||||||
|
const textStyle = [
|
||||||
|
styles.text,
|
||||||
|
(isSymptomDisabled && styles.textDisabled),
|
||||||
|
(isExcluded && styles.textExcluded)
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isSymptomEdited &&
|
||||||
|
<SymptomEditView
|
||||||
|
symptom={symptom}
|
||||||
|
symptomData={symptomData}
|
||||||
|
onClose={this.onFinishEditing}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
disabled={isSymptomDisabled}
|
||||||
|
onPress={this.onEditSymptom}
|
||||||
|
style={styles.container}
|
||||||
|
testID={iconName}
|
||||||
|
>
|
||||||
|
<DripIcon
|
||||||
|
color={iconColor}
|
||||||
|
isActive={!isSymptomDisabled}
|
||||||
|
name={iconName}
|
||||||
|
size={40}
|
||||||
|
/>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText style={symptomNameStyle}>
|
||||||
|
{symptomTitles[symptom].toLowerCase()}
|
||||||
|
</AppText>
|
||||||
|
{symptomDataToDisplay &&
|
||||||
|
<AppText style={textStyle} numberOfLines={4}>
|
||||||
|
{symptomDataToDisplay}
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hint = {
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
fontStyle: 'italic'
|
||||||
|
}
|
||||||
|
|
||||||
|
const main = {
|
||||||
|
fontSize: Sizes.base,
|
||||||
|
height: Sizes.base * 2,
|
||||||
|
lineHeight: Sizes.base,
|
||||||
|
marginBottom: (-1) * Sizes.tiny,
|
||||||
|
textAlignVertical: 'center'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 10,
|
||||||
|
elevation: 4,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 110,
|
||||||
|
marginBottom: Spacing.base,
|
||||||
|
paddingHorizontal: Spacing.small,
|
||||||
|
paddingVertical: Spacing.base,
|
||||||
|
width: Spacing.symptomTileWidth
|
||||||
|
},
|
||||||
|
symptomName: {
|
||||||
|
color: Colors.purple,
|
||||||
|
...main
|
||||||
|
},
|
||||||
|
symptomNameDisabled: {
|
||||||
|
color: Colors.grey
|
||||||
|
},
|
||||||
|
symptomNameExcluded: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginLeft: Spacing.small,
|
||||||
|
maxWidth: Spacing.textWidth
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...hint
|
||||||
|
},
|
||||||
|
textDisabled: {
|
||||||
|
color: Colors.greyLight
|
||||||
|
},
|
||||||
|
textExcluded: {
|
||||||
|
color: Colors.grey,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomBox)
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppModal from '../common/app-modal'
|
||||||
|
import AppSwitch from '../common/app-switch'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import AppTextInput from '../common/app-text-input'
|
||||||
|
import Button from '../common/button'
|
||||||
|
import CloseIcon from '../common/close-icon'
|
||||||
|
import Segment from '../common/segment'
|
||||||
|
import SelectBoxGroup from './select-box-group'
|
||||||
|
import SelectTabGroup from './select-tab-group'
|
||||||
|
import Temperature from './temperature'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate } from '../../slices/date'
|
||||||
|
import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { shared as sharedLabels } from '../../i18n/en/labels'
|
||||||
|
import info from '../../i18n/en/symptom-info'
|
||||||
|
import { Colors, Containers, Sizes, Spacing } from '../../styles'
|
||||||
|
|
||||||
|
class SymptomEditView extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
symptom: PropTypes.string.isRequired,
|
||||||
|
symptomData: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
const { symptomData, symptom } = this.props
|
||||||
|
const data = symptomData ? symptomData : blank[symptom]
|
||||||
|
|
||||||
|
const symptomConfig = symtomPage[symptom]
|
||||||
|
const shouldShowExclude = shouldShow(symptomConfig.excludeText)
|
||||||
|
const shouldShowNote = shouldShow(symptomConfig.note)
|
||||||
|
const shouldBoxGroup = shouldShow(symptomConfig.selectBoxGroups)
|
||||||
|
const shouldTabGroup = shouldShow(symptomConfig.selectTabGroups)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data,
|
||||||
|
shouldShowExclude,
|
||||||
|
shouldShowInfo: false,
|
||||||
|
shouldShowNote,
|
||||||
|
shouldBoxGroup,
|
||||||
|
shouldTabGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.saveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
getParsedData = () => JSON.parse(JSON.stringify(this.state.data))
|
||||||
|
|
||||||
|
onEditNote = (note) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
const { symptom } = this.props
|
||||||
|
|
||||||
|
if (symptom === 'note') {
|
||||||
|
Object.assign(data, { value: note })
|
||||||
|
} else {
|
||||||
|
data['note'] = note
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onExcludeToggle = () => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { exclude: !data.exclude })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressLearnMore = () => {
|
||||||
|
this.setState({ shouldShowInfo: !this.state.shouldShowInfo })
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove = () => {
|
||||||
|
this.saveData(true)
|
||||||
|
this.props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave = () => {
|
||||||
|
this.saveData()
|
||||||
|
this.props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveTemperature = (value, field) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
const dataToSave = field === 'value'
|
||||||
|
? { [field]: Number(value) } : { [field]: value }
|
||||||
|
Object.assign(data, { ...dataToSave })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectBox = (key) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
if (key === "other") {
|
||||||
|
Object.assign(data, {
|
||||||
|
note: null,
|
||||||
|
[key]: !this.state.data[key]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.assign(data, { [key]: !this.state.data[key] })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectBoxNote= (value) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { note: value !== '' ? value : null })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectTab = (group, value) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { [group.key]: value })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
saveData = (shouldDeleteData) => {
|
||||||
|
const { date, symptom } = this.props
|
||||||
|
const { data } = this.state
|
||||||
|
save[symptom](data, date, shouldDeleteData)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onClose, symptom } = this.props
|
||||||
|
const { data,
|
||||||
|
shouldShowExclude,
|
||||||
|
shouldShowInfo,
|
||||||
|
shouldShowNote,
|
||||||
|
shouldBoxGroup,
|
||||||
|
shouldTabGroup
|
||||||
|
} = this.state
|
||||||
|
const iconName = shouldShowInfo ? "chevron-down" : "chevron-up"
|
||||||
|
const noteText = symptom === 'note' ? data.value : data.note
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppModal onClose={onClose}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.modalContainer}
|
||||||
|
style={styles.modalWindow}
|
||||||
|
>
|
||||||
|
<View style={styles.headerContainer}>
|
||||||
|
<CloseIcon onClose={onClose} />
|
||||||
|
</View>
|
||||||
|
{symptom === 'temperature' &&
|
||||||
|
<Temperature
|
||||||
|
data={data}
|
||||||
|
save={(value, field) => this.onSaveTemperature(value, field)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{shouldTabGroup && symtomPage[symptom].selectTabGroups.map(group => {
|
||||||
|
return (
|
||||||
|
<Segment key={group.key} style={styles.segmentBorder}>
|
||||||
|
<AppText style={styles.title}>{group.title}</AppText>
|
||||||
|
<SelectTabGroup
|
||||||
|
activeButton={data[group.key]}
|
||||||
|
buttons={group.options}
|
||||||
|
onSelect={value => this.onSelectTab(group, value)}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{shouldBoxGroup && symtomPage[symptom].selectBoxGroups.map(group => {
|
||||||
|
const isOtherSelected =
|
||||||
|
data['other'] !== null
|
||||||
|
&& data['other'] !== false
|
||||||
|
&& Object.keys(group.options).includes('other')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Segment key={group.key} style={styles.segmentBorder} >
|
||||||
|
<AppText style={styles.title}>{group.title}</AppText>
|
||||||
|
<SelectBoxGroup
|
||||||
|
labels={group.options}
|
||||||
|
onSelect={value => this.onSelectBox(value)}
|
||||||
|
optionsState={data}
|
||||||
|
/>
|
||||||
|
{isOtherSelected &&
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
value={data.note}
|
||||||
|
onChangeText={value => this.onSelectBoxNote(value)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{shouldShowExclude &&
|
||||||
|
<Segment style={styles.segmentBorder} >
|
||||||
|
<AppSwitch
|
||||||
|
onToggle={this.onExcludeToggle}
|
||||||
|
text={symtomPage[symptom].excludeText}
|
||||||
|
value={data.exclude}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
{shouldShowNote &&
|
||||||
|
<Segment style={styles.segmentBorder} >
|
||||||
|
<AppText>{symtomPage[symptom].note}</AppText>
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
numberOfLines={3}
|
||||||
|
onChangeText={this.onEditNote}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
testID='noteInput'
|
||||||
|
value={noteText !== null ? noteText : ''}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
<View style={styles.buttonsContainer}>
|
||||||
|
<Button iconName={iconName} isSmall onPress={this.onPressLearnMore}>
|
||||||
|
{sharedLabels.learnMore}
|
||||||
|
</Button>
|
||||||
|
<Button isSmall onPress={this.onRemove}>
|
||||||
|
{sharedLabels.remove}
|
||||||
|
</Button>
|
||||||
|
<Button isCTA isSmall onPress={this.onSave}>
|
||||||
|
{sharedLabels.save}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
{shouldShowInfo &&
|
||||||
|
<Segment last style={styles.segmentBorder} >
|
||||||
|
<AppText>{info[symptom].text}</AppText>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
</ScrollView>
|
||||||
|
</AppModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttonsContainer: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
headerContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingVertical: Spacing.tiny,
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.base,
|
||||||
|
},
|
||||||
|
modalWindow: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 10,
|
||||||
|
marginVertical: Sizes.huge * 2,
|
||||||
|
position: 'absolute',
|
||||||
|
minHeight: '40%',
|
||||||
|
maxHeight: Dimensions.get('window').height * 0.7
|
||||||
|
},
|
||||||
|
segmentBorder: {
|
||||||
|
borderBottomColor: Colors.greyLight
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomEditView)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from '../common/app-icon'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate, setDate } from '../../slices/date'
|
||||||
|
|
||||||
|
import { nextDate, prevDate } from '../helpers/cycle-day'
|
||||||
|
import { Colors, Containers, Spacing, Typography } from '../../styles'
|
||||||
|
import { HIT_SLOP } from '../../config'
|
||||||
|
|
||||||
|
const SymptomPageTitle = ({
|
||||||
|
date,
|
||||||
|
reloadSymptomData,
|
||||||
|
setDate,
|
||||||
|
subtitle,
|
||||||
|
title,
|
||||||
|
}) => {
|
||||||
|
const navigate = (isForward) => {
|
||||||
|
const nextDay = isForward ? nextDate(date) : prevDate(date)
|
||||||
|
reloadSymptomData(nextDay)
|
||||||
|
setDate(nextDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<TouchableOpacity onPress={() => navigate(false)} hitSlop={HIT_SLOP}>
|
||||||
|
<AppIcon name='chevron-left' color={Colors.orange}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText style={styles.title}>{title}</AppText>
|
||||||
|
{subtitle && <AppText style={styles.subtitle}>{subtitle}</AppText>}
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={() => navigate(true)} hitSlop={HIT_SLOP}>
|
||||||
|
<AppIcon name='chevron-right' color={Colors.orange}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SymptomPageTitle.propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
reloadSymptomData: PropTypes.func.isRequired,
|
||||||
|
setDate: PropTypes.func.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
height: Spacing.base * 4,
|
||||||
|
marginHorizontal: Spacing.base,
|
||||||
|
marginTop: Spacing.base,
|
||||||
|
...Containers.rowContainer,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.titleWithoutMargin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
date: getDate(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
setDate: (date) => dispatch(setDate(date)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SymptomPageTitle)
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { bleeding } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Bleeding extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'bleeding'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {
|
|
||||||
value: null,
|
|
||||||
exclude: false
|
|
||||||
}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.bleedingRadioProps = getLabelsList(bleeding.labels)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = { ...this.state }
|
|
||||||
const hasValueToSave = typeof this.state.value === 'number'
|
|
||||||
saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={bleeding.heaviness.header}
|
|
||||||
explainer={bleeding.heaviness.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.bleedingRadioProps}
|
|
||||||
active={this.state.value}
|
|
||||||
onSelect={val => this.setState({ value: 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>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Bleeding
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { cervix as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Cervix extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'cervix'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.cervixOpeningRadioProps = getLabelsList(labels.opening.categories)
|
|
||||||
this.cervixFirmnessRadioProps = getLabelsList(labels.firmness.categories)
|
|
||||||
this.cervixPositionRadioProps = getLabelsList(labels.position.categories)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const { opening, firmness, position, exclude } = this.state
|
|
||||||
const valuesToSave = {
|
|
||||||
opening,
|
|
||||||
firmness,
|
|
||||||
position,
|
|
||||||
exclude: Boolean(exclude)
|
|
||||||
}
|
|
||||||
const nothingEntered = ['opening', 'firmness', 'position'].every(
|
|
||||||
val => typeof this.state[val] !== 'number')
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// TODO saving this info for notice when leaving incomplete data
|
|
||||||
// const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header="Opening"
|
|
||||||
explainer={labels.opening.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.cervixOpeningRadioProps}
|
|
||||||
active={this.state.opening}
|
|
||||||
onSelect={val => this.setState({ opening: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Firmness"
|
|
||||||
explainer={labels.firmness.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.cervixFirmnessRadioProps}
|
|
||||||
active={this.state.firmness}
|
|
||||||
onSelect={val => this.setState({ firmness: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Position"
|
|
||||||
explainer={labels.position.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.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>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Cervix
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { intensity, desire } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Desire extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'desire'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = { value: null }
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
|
|
||||||
this.desireRadioProps = getLabelsList(intensity)
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = { ...this.state }
|
|
||||||
const hasValueToSave = typeof this.state.value === 'number'
|
|
||||||
saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={desire.header}
|
|
||||||
explainer={desire.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.desireRadioProps}
|
|
||||||
active={this.state.value}
|
|
||||||
onSelect={val => this.setState({ value: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Desire
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import BleedingEditView from './bleeding'
|
|
||||||
import TemperatureEditView from './temperature'
|
|
||||||
import MucusEditView from './mucus'
|
|
||||||
import CervixEditView from './cervix'
|
|
||||||
import NoteEditView from './note'
|
|
||||||
import DesireEditView from './desire'
|
|
||||||
import SexEditView from './sex'
|
|
||||||
import PainEditView from './pain'
|
|
||||||
import MoodEditView from './mood'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
BleedingEditView,
|
|
||||||
TemperatureEditView,
|
|
||||||
MucusEditView,
|
|
||||||
CervixEditView,
|
|
||||||
NoteEditView,
|
|
||||||
DesireEditView,
|
|
||||||
SexEditView,
|
|
||||||
PainEditView,
|
|
||||||
MoodEditView
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
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 styles, {iconStyles} from '../../../styles/index'
|
|
||||||
|
|
||||||
export default function InfoSymptom({ close, symptom }) {
|
|
||||||
return (
|
|
||||||
<View style={styles.infoPopUpWrapper}>
|
|
||||||
<View style={styles.dimmed}></View>
|
|
||||||
<View style={styles.infoPopUp} testID="symptomInfoPopup">
|
|
||||||
<TouchableOpacity onPress={close} style={styles.infoSymptomClose}>
|
|
||||||
<Icon name='close' {...iconStyles.infoPopUpClose}/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<ScrollView style={styles.infoSymptomText}>
|
|
||||||
<AppText>{labels[symptom].text}</AppText>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoSymptom.propTypes = {
|
|
||||||
close: PropTypes.func.isRequired,
|
|
||||||
symptom: PropTypes.string.isRequired
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { mood as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Mood extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'mood'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
// We make sure other is always true when there is a note,
|
|
||||||
// e.g. when import is messed up.
|
|
||||||
if (this.state.note) this.state.other = true
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = Object.assign({}, this.state)
|
|
||||||
if (!valuesToSave.other) {
|
|
||||||
valuesToSave.note = null
|
|
||||||
}
|
|
||||||
const nothingEntered = Object.values(this.state).every(val => !val)
|
|
||||||
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleState = (key) => {
|
|
||||||
const curr = this.state[key]
|
|
||||||
this.setState({[key]: !curr})
|
|
||||||
if (key === 'other' && !curr) {
|
|
||||||
this.setState({focusTextArea: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
explainer={labels.explainer}
|
|
||||||
>
|
|
||||||
<SelectBoxGroup
|
|
||||||
labels={labels.categories}
|
|
||||||
onSelect={this.toggleState}
|
|
||||||
optionsState={this.state}
|
|
||||||
/>
|
|
||||||
{ this.state.other &&
|
|
||||||
<TextInput
|
|
||||||
autoFocus={this.state.focusTextArea}
|
|
||||||
multiline={true}
|
|
||||||
placeholder="Enter"
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
this.setState({note: val})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Mood
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { mucus as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import computeNfpValue from '../../../lib/nfp-mucus'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Mucus extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'mucus'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.mucusFeeling = getLabelsList(labels.feeling.categories)
|
|
||||||
this.mucusTexture = getLabelsList(labels.texture.categories)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldAutoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const nothingEntered = ['feeling', 'texture'].every(
|
|
||||||
val => typeof this.state[val] !== 'number'
|
|
||||||
)
|
|
||||||
const { feeling, texture, exclude} = this.state
|
|
||||||
const valuesToSave = {
|
|
||||||
feeling,
|
|
||||||
texture,
|
|
||||||
value: computeNfpValue(feeling, texture),
|
|
||||||
exclude: Boolean(exclude)
|
|
||||||
}
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.shouldAutoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// TODO leaving this info for notice when leaving incomplete data
|
|
||||||
// const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header='Feeling'
|
|
||||||
explainer={labels.feeling.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.mucusFeeling}
|
|
||||||
onSelect={val => this.setState({ feeling: val })}
|
|
||||||
active={this.state.feeling}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header='Texture'
|
|
||||||
explainer={labels.texture.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.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>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Mucus
|
|
||||||