merges master into branch

This commit is contained in:
tina
2018-09-28 17:07:27 +02:00
41 changed files with 1322 additions and 165 deletions
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- color for the app bar and other primary UI elements -->
<color name="colorPrimary">#ff7e5f</color>
<color name="colorPrimary">#000D19</color>
<!-- a darker variant of the primary color, used for
the status bar (on Android 5.0+) and contextual app bars -->
<color name="colorPrimaryDark">#c74e34</color>
<color name="colorPrimaryDark">#000D19</color>
<!-- a secondary color for controls like checkboxes and text fields -->
<color name="colorAccent">#351c4d</color>
<color name="colorAccent">#4FAFA7</color>
</resources>
+30
View File
@@ -0,0 +1,30 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function BleedingIcon() {
return (
<G>
<G>
<G>
<Path d="M339.933,239.523c-0.208,44.132-21.328,86.257-57.085,112.321c-36.515,26.616-84.173,33.427-126.899,19.16
C114.761,357.25,82,323.167,68.684,282.032c-5.737-17.721-8.567-38.792-5.272-56.193c3-15.845,9.583-30.846,16.998-45.069
c17.539-33.641,40.871-64.264,65.143-93.281c17.537-20.967,35.965-41.232,55.182-60.673c1.843-1.865,3.691-3.726,5.568-5.557
c-3.536,0-7.071,0-10.607,0c38.715,37.75,74.873,79.651,104.944,124.587c10.68,15.96,20.522,32.59,28.319,50.162
C335.139,209.936,339.848,224.981,339.933,239.523c0.057,9.67,15.057,9.675,15,0c-0.197-33.557-18.743-66.013-36.262-93.48
c-20.353-31.911-44.383-61.53-69.441-89.84c-13.814-15.606-27.999-30.995-42.927-45.551c-2.953-2.88-7.653-2.88-10.607,0
c-39.08,38.106-75.435,80.474-105.99,125.685c-19.151,28.338-38.238,60.475-42.184,95.092c-2.349,20.61,2.114,42.9,9.338,62.151
c7.866,20.961,20.329,39.958,36.22,55.709c32.547,32.26,79.142,48.166,124.605,43.225c45.358-4.929,86.11-30.178,111.58-67.88
c16.871-24.972,25.525-55.072,25.666-85.113C354.979,229.848,339.979,229.852,339.933,239.523z"/>
</G>
</G>
<G>
<G>
<Path d="M302.951,204.766c6.324,15.655,9.089,30.834,6.668,47.63c-2.071,14.37-7.057,28.083-14.407,40.572
c-15.176,25.785-40.825,43.865-69.771,50.779c-9.406,2.247-5.416,16.71,3.988,14.464c32.459-7.753,60.431-28.167,78.015-56.482
c8.531-13.738,14.139-29.398,16.639-45.346c3.044-19.418,0.692-37.384-6.668-55.605c-1.53-3.787-4.94-6.417-9.226-5.238
C304.59,196.53,301.417,200.969,302.951,204.766L302.951,204.766z"/>
</G>
</G>
</G>
)
}
+52
View File
@@ -0,0 +1,52 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function CervixIcon() {
return (
<G>
<G>
<G>
<Path d="M308.051,59.094c39.578,30.329,64.919,77.001,68.909,126.668c3.615,44.995-10.341,90.543-38.73,125.662
c-27.793,34.381-67.601,57.036-111.162,64.074c-44.332,7.163-90.389-3.98-127.397-29.031
c-36.121-24.449-61.843-62.515-72.239-104.726c-10.767-43.717-3.77-90.765,18.421-129.809
c20.088-35.343,52.666-63.227,90.617-77.762c45.784-17.534,97.925-15.081,141.926,6.515c8.649,4.245,16.254-8.69,7.571-12.952
C238.27,4.324,182.323,1.033,132.483,19.707c-41.849,15.679-77.244,45.998-99.581,84.655
C9.697,144.519,2.505,192.391,11.226,237.777c8.407,43.756,33.198,83.943,67.763,111.884
c35.673,28.837,80.807,43.954,126.641,42.755c45.594-1.192,89.443-19.237,123.365-49.541
c33.349-29.793,55.423-71.657,61.564-115.891c6.985-50.32-6.116-102.061-36.747-142.666
c-10.949-14.514-23.773-27.128-38.19-38.176c-3.254-2.494-8.389-0.506-10.261,2.691C303.099,52.693,304.813,56.613,308.051,59.094
L308.051,59.094z"/>
</G>
</G>
<G>
<G>
<Path d="M54.752,200c0.173-38.308,15.22-74.92,42.08-102.242C123.924,70.201,161.473,54.926,200,54.752
c9.671-0.044,9.675-15.044,0-15c-42.217,0.191-82.743,16.877-112.848,46.473C56.831,116.034,39.944,157.615,39.752,200
C39.708,209.675,54.708,209.671,54.752,200L54.752,200z"/>
</G>
</G>
<G>
<G>
<Path d="M322.047,215.376c-5.429,38.56-27.688,72.188-61.525,91.627c-8.383,4.816-0.831,17.779,7.571,12.952
c36.857-21.174,62.482-58.425,68.419-100.592c0.569-4.045-0.921-8.038-5.238-9.226C327.72,209.16,322.618,211.32,322.047,215.376
L322.047,215.376z"/>
</G>
</G>
<G>
<G>
<Path d="M166.83,221.667c11.597,0.446,19.283-7.922,28.09-14.041c11.514-8,24.581-4.834,37.425-2.873
c4.038,0.616,8.048-0.955,9.226-5.238c0.987-3.588-1.191-8.608-5.238-9.226c-13.631-2.081-27.518-5.324-40.809,0.137
c-5.743,2.359-10.473,5.812-15.314,9.637c-3.944,3.116-8.096,6.807-13.379,6.604C157.154,206.295,157.19,221.297,166.83,221.667
L166.83,221.667z"/>
</G>
</G>
<G>
<G>
<Path d="M178.012,217.621c7.537,9.326,18.196,12.651,29.168,7.402c8.973-4.293,15.14-14.52,18.008-23.655
c2.906-9.255-11.577-13.183-14.464-3.988c-1.606,5.114-4.67,10.317-9.098,13.487c-5.416,3.878-9.313,0.717-13.007-3.853
c-2.573-3.184-7.946-2.661-10.607,0C174.894,210.132,175.445,214.445,178.012,217.621L178.012,217.621z"/>
</G>
</G>
</G>
)
}
+35
View File
@@ -0,0 +1,35 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function DesireIcon() {
return (
<G>
<G>
<G>
<Path d="M203.217,178.64c9.173-15.347,24.414-28.187,41.641-33.383c6.98-2.105,15.02-2.824,21.802,0.123
c8.023,3.485,13.388,10.999,16.303,19.038c6.212,17.134-4.114,35.27-13.764,48.897c-12.53,17.695-28.989,32.729-45.389,46.756
c-7.586,6.489-15.379,12.73-23.207,18.924c3.03-0.391,6.059-0.782,9.089-1.173c-12.856-8.973-25.145-18.727-36.774-29.243
c-15.386-13.913-30.054-29.425-39.856-47.864c-8.169-15.367-11.017-34.555,1.174-48.753c5.941-6.919,14.435-10.99,23.528-11.317
c11.303-0.407,21.355,5.611,29.156,13.298c6.893,6.792,17.505-3.81,10.607-10.607c-16.84-16.593-41.119-23.435-62.536-11.276
c-18.831,10.691-26.485,32.708-23.17,53.282c3.24,20.105,16.667,38.067,29.744,52.973c13.393,15.265,28.902,28.628,44.96,40.999
c5.112,3.938,10.305,7.767,15.597,11.46c2.965,2.069,6.576,0.816,9.089-1.173c15.706-12.426,31.169-25.166,45.442-39.239
c15.265-15.051,30.064-32.135,38.232-52.195c7.78-19.105,5.775-38.526-6.986-54.946c-11.975-15.409-32.734-17.365-50.118-11.436
c-19.62,6.691-36.938,21.584-47.517,39.283C185.29,179.393,198.261,186.931,203.217,178.64L203.217,178.64z"/>
</G>
</G>
<G>
<G>
<Path d="M21.476,214.886c0.459-0.653,0.932-1.296,1.405-1.938c0.543-0.737,1.093-1.47,1.646-2.2c0.349-0.461,0.7-0.92,1.052-1.379
c0.847-1.104-1.113,1.424,0.462-0.591c5.085-6.506,10.428-12.81,15.936-18.96c17.11-19.105,36.389-37.027,56.801-51.734
c25.034-18.037,52.751-32.165,83.118-38.193c33.298-6.609,67.771-0.477,97.773,15.655c41.656,22.398,73.297,60.394,98.855,99.34
c0.391-3.03,0.782-6.059,1.173-9.089c-30.898,31.006-64.431,60.841-104.143,80.054c-33.093,16.011-69.808,21.035-105.751,12.864
c-57.848-13.151-107.277-53.225-149.499-92.918c-7.04-6.618-17.667,3.969-10.607,10.607
c38.602,36.29,81.508,70.531,131.592,89.357c36.282,13.638,75.632,15.82,112.823,4.534
c53.375-16.197,97.598-55.162,136.192-93.891c2.287-2.295,3.012-6.285,1.173-9.089c-27.657-42.145-62.001-82.757-107.396-106.394
C252.073,84.256,216.24,78.708,180.733,84.9C123.129,94.946,73,133.847,34.346,175.93c-9.13,9.94-18.057,20.325-25.822,31.385
C2.951,215.253,15.964,222.737,21.476,214.886z"/>
</G>
</G>
</G>
)
}
Binary file not shown.
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function MucusIcon() {
return (
<G>
<G>
<G>
<Path d="M207.504,377.768c-16.918-6.602-30.629-12.56-47.482-20.145c-29.7-13.368-59.341-27.672-86.712-45.414
c-10.757-6.973-20.373-13.618-28.819-23.55c-1.796-2.112-4.217-6.198-4.845-8.679c-0.623-2.459-0.043,0.955-0.142-0.908
c-0.022-0.404-0.006-0.809,0.01-1.213c0.061-1.529-0.334,1.911-0.075,0.407c0.183-1.064,0.547-2.101,0.915-3.111
c-0.564,1.549,0.946-1.677,1.371-2.326c1.045-1.597-1.02,1.182,0.198-0.278c0.362-0.433,0.759-0.835,1.156-1.236
c0.399-0.404,0.831-0.774,1.261-1.143c-1.406,1.207,0.036-0.012,0.323-0.211c0.977-0.678,2.026-1.248,3.085-1.785
c0.242-0.122,2.114-0.919,0.469-0.247c0.791-0.323,1.603-0.594,2.415-0.859c6.172-2.014,10.073-2.144,16.908-2.153
c15.189-0.02,29.715,3.067,44.602,6.814c17.158,4.318,34.009,9.892,50.565,16.111c3.092,1.161,6.171,2.357,9.24,3.577
c1.318,0.524,2.633,1.057,3.946,1.594c0.678,0.277,1.354,0.557,2.03,0.838c2.003,0.832-0.633-0.273,0.502,0.208
c1.709,0.725,3.412,1.466,5.099,2.243c4.186,1.93-0.628,0.893-1.316-3.372c1.085,6.731,10.647,6.89,13.708,1.792
c1.927-3.209,1.77-8.323,2.179-11.819c0.882-7.538,1.359-15.124,1.567-22.709c0.675-24.576-1.794-49.434-7.885-73.268
c-7.63-29.857-21.829-57.994-42.894-80.625c-26.798-28.79-62.263-43.932-99.84-52.979c-3.466-0.834-6.436-1.899-9.209-3.302
c-3.389-1.715-4.487-2.523-6.811-4.868c-0.458-0.462-0.878-0.96-1.297-1.457c1.088,1.293-0.135-0.265-0.284-0.49
c-0.327-0.497-0.607-1.022-0.886-1.547c-0.911-1.717,0.513,1.473-0.147-0.312c-0.239-0.645-0.755-3.902-0.539-1.757
c-0.054-0.537-0.048-1.079-0.041-1.618c0.028-2.179-0.19,0.6,0.076-0.691c0.112-0.542,0.285-1.069,0.456-1.593
c0.587-1.797-0.713,1.432,0.146-0.299c0.264-0.531,0.582-1.033,0.899-1.533c1.073-1.696-1.001,1.128,0.325-0.469
c2.265-2.728,3.244-3.251,6.459-5.04c6.907-3.842,15.884-5.172,24.052-5.671c40.533-2.473,83.251,12.46,114.672,37.781
c40.478,32.619,65.735,80.863,84.886,128.217c1.531,3.787,4.938,6.418,9.226,5.238c3.598-0.99,6.774-5.429,5.238-9.226
c-17.593-43.5-39.484-86.479-72.692-120.257c-28.74-29.233-65.75-48.066-106.208-54.423c-21.216-3.334-44.412-4.779-64.473,4.251
c-11.728,5.279-21.003,17.252-17.123,30.545c4.302,14.741,20.418,21.001,33.917,24.032c19.569,4.393,38.447,11.404,55.719,21.644
c27.691,16.417,48.496,41.228,61.637,70.423c10.732,23.844,15.847,50.772,17.292,76.289c0.719,12.703,0.548,25.469-0.491,38.149
c-0.17,2.08-0.372,4.156-0.598,6.231c-0.093,0.858-0.194,1.716-0.299,2.573c-0.046,0.381-0.095,0.762-0.144,1.143
c-0.221,1.728,0.288-2.098,0.064-0.464c-0.135,0.981-0.281,1.961-0.441,2.939c-0.238,1.447-0.776,1.58,0.539-0.61
c4.569,0.597,9.139,1.194,13.708,1.792c-0.546-3.387-3.938-4.858-6.662-6.087c-4.064-1.833-8.192-3.53-12.326-5.2
c-12.525-5.059-25.265-9.604-38.14-13.689c-15.428-4.894-31.144-9.083-47.13-11.695c-14.167-2.314-29.086-3.777-43.211-0.387
c-11.295,2.711-21.776,10.5-24.231,22.393c-2.17,10.514,4.193,19.954,10.978,27.32c9.158,9.943,20.772,17.804,32.093,25.048
c13.837,8.854,28.323,16.691,42.953,24.145c30.069,15.321,61.075,29.17,92.518,41.44c3.814,1.488,8.226-1.604,9.226-5.238
C213.912,382.743,211.309,379.253,207.504,377.768L207.504,377.768z"/>
</G>
</G>
<G>
<G>
<Path d="M337.602,177.211c-3.923-15.29-11.505-30.082-21.181-42.47c-7.644-9.786-17.909-17.577-29.86-21.204
c-15.183-4.608-31.142-0.523-44.907,6.372c-8.65,4.333-1.051,17.271,7.571,12.952c11.236-5.629,23.378-8.128,34.988-4.344
c8.528,2.779,16.658,10.243,21.918,17.258c2.988,3.985,5.574,8.265,7.921,12.653c1.02,1.907,1.968,3.851,2.883,5.81
c0.473,1.013,0.925,2.037,1.371,3.063c0.343,0.802,0.34,0.792-0.01-0.031c0.235,0.562,0.462,1.128,0.689,1.693
c1.599,3.982,3.085,8.074,4.153,12.236C325.54,190.563,340.008,186.588,337.602,177.211L337.602,177.211z"/>
</G>
</G>
<G>
<G>
<Path d="M370.175,172.282c0,0.002,0,0.005,0,0.007c4.911-0.665,9.821-1.329,14.732-1.994c-2.262-10.021-6.785-19.12-13.425-26.983
c-15.155-17.949-39.359-20.996-61.329-17.931c-4.057,0.566-6.215,5.676-5.238,9.226c1.188,4.321,5.18,5.803,9.226,5.238
c18.149-2.532,36.136,1.101,47.695,15.228c-0.483-0.59,1.374,1.938,1.778,2.549c0.855,1.292,1.627,2.638,2.364,4
c0.668,1.234,1.266,2.505,1.837,3.787c-0.799-1.795,0.56,1.488,0.775,2.09c0.793,2.226,1.336,4.487,1.854,6.784
c1.896,8.402,14.689,6.339,14.732-1.994c0-0.002,0-0.005,0-0.007C385.225,162.607,370.225,162.611,370.175,172.282
L370.175,172.282z"/>
</G>
</G>
<G>
<G>
<Path d="M43.712,68.819c0.239,0.272,0.47,0.553,0.7,0.833c0.279,0.34,0.553,0.685,0.826,1.03c0.969,1.228-1.249-1.655,0.135,0.189
c2.498,3.328,4.78,6.815,6.936,10.372c7.22,11.912,12.26,23.965,16.03,37.321c13.048,46.228,1.749,94.154-21.682,135.069
c-4.815,8.408,8.147,15.961,12.952,7.571c24.635-43.017,36.253-92.632,24.536-141.488c-5.226-21.789-15.029-43.954-29.582-61.219
c-0.081-0.096-0.162-0.191-0.244-0.285C47.921,50.934,37.354,61.585,43.712,68.819L43.712,68.819z"/>
</G>
</G>
<G>
<G>
<Path d="M113.897,88.799c-12.33,13.358-20.381,31.576-25.213,48.833c-11.953,42.691-1.173,87.256,20.026,125.151
c4.722,8.441,17.679,0.88,12.952-7.571c-19.563-34.971-29.245-75.265-18.238-114.578c3.124-11.157,7.474-20.948,13.615-30.881
c0.989-1.6,2.028-3.168,3.098-4.715c0.544-0.788,3.524-4.756,1.855-2.606c0.802-1.034,1.623-2.065,2.511-3.027
C131.052,92.311,120.471,81.677,113.897,88.799L113.897,88.799z"/>
</G>
</G>
</G>
)
}
+41
View File
@@ -0,0 +1,41 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function NoteIcon() {
return (
<G>
<G>
<G>
<Path d="M293.542,218.325c0,48.787,0,97.574,0,146.361c0,4.704,1.495,12.814-5.511,12.814c-5.347,0-10.694,0-16.041,0
c-26.52,0-53.039,0-79.559,0c-51.177,0-102.355,0-153.532,0c-4.509,0-6.931-2.332-6.931-6.795c0-5.898,0-11.796,0-17.694
c0-24.998,0-49.997,0-74.995c0-61.13,0-122.26,0-183.39c0-12.317,0-24.634,0-36.952c0-6.772,11.938-4.818,16.129-4.818
c12.288,0,24.575,0,36.863,0c41.778,0,83.556,0,125.334,0c9.673,0,9.673-15,0-15c-49.939,0-99.878,0-149.817,0
c-7.557,0-15.113,0-22.67,0c-12.23,0-20.839,9.339-20.839,21.298c0,16.1,0,32.2,0,48.301c0,62.266,0,124.532,0,186.798
c0,22.351,0,44.703,0,67.054c0,3.771,0,7.542,0,11.313c0,11.327,9.264,19.311,20.09,19.881c1.045,0.055,2.108,0,3.153,0
c52.674,0,105.348,0,158.023,0c25.592,0,51.185,0,76.777,0c4.465,0,8.929,0,13.394,0c10.815,0,20.137-8.91,20.137-19.823
c0-1.791,0-3.582,0-5.373c0-49.558,0-99.116,0-148.675c0-0.101,0-0.203,0-0.304C308.542,208.652,293.542,208.652,293.542,218.325
L293.542,218.325z"/>
</G>
</G>
<G>
<G>
<Path d="M180.488,246.618c2.36-2.075,1.046-0.828,0.099-0.322c-1.058,0.566-2.14,1.091-3.218,1.617
c-3.32,1.621-6.674,3.175-10.027,4.728c-9.967,4.616-19.983,9.125-30.002,13.625c-14.011,6.293-28.041,12.543-42.089,18.752
c3.42,3.42,6.841,6.841,10.261,10.261c10.211-23.101,20.465-46.189,31.014-69.138c2.249-4.893,4.494-9.794,6.896-14.615
c0.479-0.962,0.943-1.962,1.545-2.856c-1.484,2.202-1.225,1.541-0.553,0.869c3.921-3.921,7.841-7.841,11.762-11.762
c41.471-41.471,82.943-82.943,124.414-124.414c15.218-15.218,30.436-30.436,45.654-45.654c3.057-3.057,5.967-7.153,10.013-3.107
c9.662,9.662,19.323,19.323,28.985,28.985c2.142,2.142,4.213,4.062,1.287,6.988c-1.093,1.093-2.186,2.186-3.279,3.279
c-5.732,5.732-11.464,11.464-17.196,17.196c-20.4,20.4-40.8,40.8-61.2,61.2c-34.562,34.562-69.125,69.125-103.687,103.687
C180.943,246.164,180.716,246.391,180.488,246.618c-6.846,6.845,3.761,17.452,10.607,10.607
c31.506-31.506,63.012-63.012,94.519-94.519c21.391-21.391,42.781-42.781,64.172-64.172c6.857-6.857,13.714-13.714,20.571-20.571
c3.653-3.653,8.009-7.271,10.522-11.847c8.424-15.34-9.811-27.92-19.178-37.286S339.755,1.229,324.415,9.653
c-4.575,2.513-8.193,6.868-11.847,10.522c-6.857,6.857-13.714,13.714-20.571,20.571c-43.021,43.021-86.042,86.042-129.064,129.064
c-9.876,9.876-19.751,19.751-29.627,29.627c-2.387,2.387-3.674,6.196-5.086,9.165c-2.85,5.992-5.601,12.032-8.35,18.072
c-6.662,14.634-13.218,29.317-19.754,44.008c-2.524,5.673-5.045,11.348-7.555,17.028c-3.135,7.092,3.169,13.396,10.261,10.261
c14.357-6.346,28.694-12.736,43.013-19.167c9.948-4.468,19.89-8.954,29.786-13.537c5.026-2.328,11.24-4.319,15.473-8.04
C198.374,250.827,187.722,240.26,180.488,246.618z"/>
</G>
</G>
</G>
)
}
+25
View File
@@ -0,0 +1,25 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function PainIcon() {
return (
<G>
<G>
<G>
<Path d="M213.298,12.215c-22.873,41.561-45.747,83.121-68.62,124.682c-13.959,25.363-27.918,50.726-41.877,76.09
c-2.538,4.611,0.559,11.793,6.476,11.285c36.832-3.161,73.664-6.321,110.497-9.482c-2.411-3.165-4.821-6.329-7.232-9.494
c-16.814,46.834-33.628,93.669-50.443,140.503c-4.572,12.736-9.145,25.471-13.717,38.207c-2.963,8.254,9.387,12.79,13.708,5.779
c23.572-38.249,47.145-76.498,70.717-114.747c21.827-35.416,43.653-70.833,65.48-106.249c2.819-4.574-0.763-11.76-6.476-11.285
c-37.177,3.086-74.353,6.173-111.53,9.259c2.411,3.165,4.821,6.329,7.232,9.494c11.995-48.069,23.99-96.138,35.985-144.207
c1.169-4.685,2.338-9.371,3.508-14.056c2.343-9.388-12.123-13.369-14.464-3.988c-11.995,48.069-23.99,96.138-35.985,144.207
c-1.169,4.685-2.338,9.371-3.508,14.056c-1.1,4.41,2.221,9.91,7.232,9.494c37.177-3.086,74.353-6.173,111.53-9.259
c-2.159-3.762-4.317-7.524-6.476-11.285c-23.572,38.249-47.145,76.498-70.717,114.747c-21.827,35.416-43.653,70.833-65.48,106.249
c4.569,1.926,9.139,3.853,13.708,5.779c16.814-46.834,33.628-93.669,50.443-140.503c4.572-12.736,9.145-25.471,13.717-38.207
c1.567-4.366-2.567-9.894-7.232-9.494c-36.832,3.161-73.664,6.321-110.497,9.482c2.159,3.762,4.317,7.524,6.476,11.285
c22.873-41.561,45.747-83.121,68.62-124.682c13.959-25.363,27.918-50.726,41.877-76.09
C230.917,11.306,217.962,3.74,213.298,12.215z"/>
</G>
</G>
</G>
)
}
+52
View File
@@ -0,0 +1,52 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function SexIcon() {
return (
<G>
<G>
<G>
<Path d="M223.866,273.491c-9.27,7.609-18.641,15.09-28.046,22.531c3.03-0.391,6.059-0.782,9.089-1.173
c-26.973-18.809-52.745-39.401-76.814-61.811c-30.072-27.999-59.349-59.879-75.868-98.006
c-13.533-31.236-13.079-70.686,17.15-92.034c14.76-10.424,32.854-13.86,50.619-12.012c21.845,2.272,41.738,14.412,57.002,29.646
c6.852,6.838,17.459-3.768,10.607-10.607C156.498,18.981,108.865,4.191,68.337,26.005c-17.135,9.223-29.809,24.986-36.018,43.306
c-6.061,17.884-4.621,37.712,0.475,55.62c11.307,39.732,39.837,73.783,68.23,102.702c26.066,26.548,55.008,50.314,85.079,72.172
c3.718,2.703,7.465,5.366,11.236,7.995c2.966,2.068,6.575,0.816,9.089-1.173c9.405-7.441,18.776-14.922,28.046-22.531
c3.164-2.597,2.677-7.929,0-10.607C231.371,270.39,227.023,270.9,223.866,273.491L223.866,273.491z"/>
</G>
</G>
<G>
<G>
<Path d="M190.303,98.582c18.489-31.197,50.263-57.513,85.587-66.889c15.203-4.035,31.746-4.146,45.972,3.065
c16.886,8.559,28.07,26.445,32.812,44.318c5.721,21.564-1.335,44.336-11.075,63.597c-12.146,24.019-29.543,45.148-47.821,64.72
c-6.593,7.06,3.992,17.69,10.607,10.607c19.882-21.29,38.645-44.401,51.608-70.641c11.051-22.37,17.178-47.569,11.145-72.27
c-4.848-19.85-17.504-38.907-34.535-50.262c-14.4-9.601-32.507-12.314-49.424-10.111c-44.387,5.78-85.437,38.514-107.829,76.297
C172.408,99.353,185.377,106.894,190.303,98.582L190.303,98.582z"/>
</G>
</G>
<G>
<G>
<Path d="M247.096,386.054c11.872-0.436,21.781-7.984,24.818-19.576c1.141-4.356,0.702-9.345,0.702-13.811
c0-14.763,0-29.527,0-44.29c0-19.838,0-39.676,0-59.514c0-9.673-15-9.673-15,0c0,18.218,0,36.436,0,54.655
c0,13.762,0,27.523,0,41.285c0,9.062,2.942,25.758-10.521,26.252C237.453,371.408,237.419,386.41,247.096,386.054L247.096,386.054
z"/>
</G>
</G>
<G>
<G>
<Path d="M221.575,279.871c0,16.563,0,33.127,0,49.69c0,9.676,0,19.351,0,29.027c0,14.967,10.158,26.901,25.521,27.466
c9.677,0.356,9.643-14.646,0-15c-9.815-0.361-10.521-8.931-10.521-16.443c0-10.329,0-20.659,0-30.988c0-14.584,0-29.168,0-43.752
C236.575,270.198,221.575,270.198,221.575,279.871L221.575,279.871z"/>
</G>
</G>
<G>
<G>
<Path d="M257.617,250.967c0,12.121,0,24.242,0,36.363c0,9.67,3.981,17.6,11.923,23.169c10.062,7.057,24.478,4.133,32.502-4.467
c5.634-6.039,6.617-13.633,6.617-21.463c0-7.622,0-15.245,0-22.868c0-10.295,0-20.589,0-30.884c0-5.734,0-11.469,0-17.203
c0-9.673-15-9.673-15,0c0,23.183,0,46.366,0,69.549c0,5.962,0.313,11.492-5.577,15.035c-7.257,4.365-15.464-2.233-15.464-9.828
c0-12.367,0-24.733,0-37.1c0-0.101,0-0.203,0-0.304C272.617,241.294,257.617,241.294,257.617,250.967L257.617,250.967z"/>
</G>
</G>
</G>
)
}
+48
View File
@@ -0,0 +1,48 @@
import React from 'react'
import { G, Path } from 'react-native-svg'
export default function TemperatureIcon() {
return (
<G>
<G>
<G>
<Path d="M191.099,160.115c10.413,0,20.827,0,31.24,0c9.673,0,9.673-15,0-15c-10.413,0-20.827,0-31.24,0
C181.426,145.115,181.426,160.115,191.099,160.115L191.099,160.115z"/>
</G>
</G>
<G>
<G>
<Path d="M132.196,212.267c10.413,0,20.827,0,31.24,0c9.673,0,9.673-15,0-15c-10.413,0-20.827,0-31.24,0
C122.523,197.267,122.523,212.267,132.196,212.267L132.196,212.267z"/>
</G>
</G>
<G>
<G>
<Path d="M132.196,160.115c10.413,0,20.827,0,31.24,0c9.673,0,9.673-15,0-15c-10.413,0-20.827,0-31.24,0
C122.523,145.115,122.523,160.115,132.196,160.115L132.196,160.115z"/>
</G>
</G>
<G>
<G>
<Path d="M132.196,107.963c10.413,0,20.827,0,31.24,0c9.673,0,9.673-15,0-15c-10.413,0-20.827,0-31.24,0
C122.523,92.963,122.523,107.963,132.196,107.963L132.196,107.963z"/>
</G>
</G>
<G>
<G>
<Path d="M229.929,30.683c-0.553-12.998-10.185-22.63-23.183-23.183c-12.982-0.552-22.666,11.017-23.183,23.183
c-0.178,4.184-0.029,8.401-0.027,12.588c0.014,25.986,0.027,51.973,0.041,77.959c0.025,47.489,0.05,94.978,0.075,142.468
c0,0.002,0,0.004,0,0.006c1.835-2.411,3.671-4.821,5.506-7.232c-23.353,6.161-41.614,23.799-48.998,46.725
c-7.336,22.777-1.066,47.767,14.135,65.751c15.043,17.796,39.364,26.045,62.168,22.738c22.465-3.258,42.218-17.985,52.043-38.41
c18.566-38.597-3.421-85.086-44.264-96.557c1.835,2.411,3.671,4.821,5.506,7.232c0.045-48.952,0.09-97.904,0.134-146.856
c0.018-19.561,0.036-39.123,0.054-58.684c0.009-9.673-14.991-9.673-15,0c-0.045,48.952-0.09,97.904-0.134,146.856
c-0.018,19.561-0.036,39.123-0.054,58.684c-0.003,3.349,2.293,6.33,5.506,7.232c21.292,5.98,37.485,24.326,39.787,46.517
c2.267,21.854-9.039,42.595-28.034,53.276c-18.698,10.514-43.135,8.034-59.709-5.391c-17.347-14.051-23.985-37.156-17.673-58.4
c5.347-17.999,20.617-31.525,38.521-36.249c3.22-0.85,5.508-3.924,5.506-7.232c-0.025-47.166-0.05-94.331-0.075-141.497
c-0.013-24.032-0.025-48.065-0.038-72.097c-0.003-5.739-0.006-11.479-0.009-17.218c-0.001-1.88,0.084-3.568,0.648-5.417
c2.686-8.801,15.408-4.86,15.751,3.208C215.339,40.319,230.341,40.359,229.929,30.683L229.929,30.683z"/>
</G>
</G>
</G>
)
}
+19 -5
View File
@@ -9,9 +9,20 @@ import symptomViews from './cycle-day/symptoms'
import Chart from './chart/chart'
import Settings from './settings'
import Stats from './stats'
import {headerTitles as titles} from './labels'
import {headerTitles, menuTitles} from './labels'
import setupNotifications from '../lib/notifications'
// design wants everyhting lowercased, but we don't
// have CSS pseudo properties
const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
acc[curr] = headerTitles[curr].toLowerCase()
return acc
}, {})
const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => {
acc[curr] = menuTitles[curr].toLowerCase()
return acc
}, {})
const isSymptomView = name => Object.keys(symptomViews).indexOf(name) > -1
export default class App extends Component {
@@ -55,25 +66,28 @@ export default class App extends Component {
}[this.state.currentPage]
return (
<View style={{flex: 1}}>
{this.state.currentPage != 'CycleDay' && !isSymptomView(this.state.currentPage) &&
<Header
title={titles[this.state.currentPage]}
title={headerTitlesLowerCase[this.state.currentPage]}
/>}
{isSymptomView(this.state.currentPage) &&
<Header
title={titles[this.state.currentPage]}
title={headerTitlesLowerCase[this.state.currentPage]}
isSymptomView={true}
goBack={this.handleBackButtonPress}
/>}
{React.createElement(page, {
navigate: this.navigate,
...this.state.currentProps
})}
{!isSymptomView(this.state.currentPage) &&
<Menu navigate={this.navigate} />
<Menu
navigate={this.navigate}
titles={menuTitlesLowerCase}
/>
}
</View>
)
+23 -9
View File
@@ -10,6 +10,7 @@ import styles from './styles'
import { scaleObservable } from '../../local-storage'
import config from '../../config'
import { AppText } from '../app-text'
import { shared as labels } from '../labels'
export default class CycleChart extends Component {
constructor(props) {
@@ -111,7 +112,7 @@ export default class CycleChart extends Component {
(cycleDay.cervix.opening + cycleDay.cervix.firmness)
} else if (symptom === 'sex') {
// solo = 1 + partner = 2
acc.sex = cycleDay.sex && (cycleDay.sex.solo + cycleDay.sex.partner)
acc.sex = cycleDay.sex && (cycleDay.sex.solo + 2 * cycleDay.sex.partner)
} else if (symptom === 'pain') {
// is any pain documented?
acc.pain = cycleDay.pain &&
@@ -144,20 +145,33 @@ export default class CycleChart extends Component {
>
{!this.state.chartLoaded &&
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
<AppText>Loading...</AppText>
<AppText>{labels.loading}</AppText>
</View>
}
{this.state.chartHeight && this.state.chartLoaded &&
<View
style={[styles.yAxis, {
height: this.columnHeight,
marginTop: this.symptomRowHeight
}]}
>
{makeYAxisLabels(this.columnHeight)}
<View>
<View style={[styles.yAxis, {height: this.symptomRowHeight}]}>
{this.symptomRowSymptoms.map(symptomName => {
return <View key={symptomName} style={{flex: 1}}>
<AppText>{symptomName[0]}</AppText>
</View>
})}
</View>
<View style={[styles.yAxis, {height: this.columnHeight}]}>
{makeYAxisLabels(this.columnHeight)}
</View>
<View style={[styles.yAxis, {height: this.xAxisHeight}]}>
<AppText style = {[styles.column.label.number, styles.yAxisLabels.cycleDayLabel]}>
{labels.cycleDayWithLinebreak}
</AppText>
<AppText style={[styles.column.label.date,styles.yAxisLabels.dateLabel]}>
{labels.date}
</AppText>
</View>
</View>}
{this.state.chartHeight && this.state.chartLoaded &&
makeHorizontalGrid(this.columnHeight, this.symptomRowHeight)
}
+24 -19
View File
@@ -3,7 +3,8 @@ import {
Text, View, TouchableOpacity
} from 'react-native'
import Svg,{ G, Rect, Line } from 'react-native-svg'
import Icon from 'react-native-vector-icons/Entypo'
import { LocalDate } from 'js-joda'
import moment from 'moment'
import styles from './styles'
import config from '../../config'
import { getOrCreateCycleDay } from '../../db'
@@ -88,13 +89,18 @@ export default class DayColumn extends Component {
}
const cycleDayNumber = this.getCycleDayNumber(dateString)
const shortDate = dateString.split('-').slice(1).join('-')
const dayDate = LocalDate.parse(dateString)
const shortDate = dayDate.dayOfMonth() === 1 ?
moment(dateString, "YYYY-MM-DD").format('MMM')
:
moment(dateString, "YYYY-MM-DD").format('Do')
const boldDateLabel = dayDate.dayOfMonth() === 1 ? {fontWeight: 'bold'} : {}
const cycleDayLabel = (
<Text style = {label.number}>
{cycleDayNumber ? cycleDayNumber : ' '}
</Text>)
const dateLabel = (
<Text style = {label.date}>
<Text style = {[label.date, boldDateLabel]}>
{shortDate}
</Text>
)
@@ -116,10 +122,9 @@ export default class DayColumn extends Component {
symptomHeight={symptomHeight}
key='bleeding'
>
<Icon
name='drop'
size={12}
color={styles.bleedingIconShades[this.props.bleeding]}
<View
{...styles.symptomIcon}
backgroundColor={styles.iconShades.bleeding[this.props.bleeding]}
/>
</SymptomIconView>
),
@@ -130,8 +135,8 @@ export default class DayColumn extends Component {
key='mucus'
>
<View
{...styles.mucusIcon}
backgroundColor={styles.mucusIconShades[this.props.mucus]}
{...styles.symptomIcon}
backgroundColor={styles.iconShades.mucus[this.props.mucus]}
/>
</SymptomIconView>
),
@@ -142,9 +147,9 @@ export default class DayColumn extends Component {
key='cervix'
>
<View
{...styles.mucusIcon}
{...styles.symptomIcon}
// cervix is sum of openess and firmness - fertile only when closed and hard (=0)
backgroundColor={this.props.cervix > 0 ? 'blue' : 'green'}
backgroundColor={this.props.cervix > 0 ? styles.iconShades.cervix[2] : styles.iconShades.cervix[0]}
/>
</SymptomIconView>
),
@@ -155,8 +160,8 @@ export default class DayColumn extends Component {
key='sex'
>
<View
{...styles.mucusIcon}
backgroundColor='orange'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.sex[this.props.sex - 1]}
/>
</SymptomIconView>
),
@@ -167,8 +172,8 @@ export default class DayColumn extends Component {
key='desire'
>
<View
{...styles.mucusIcon}
backgroundColor='red'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.desire[this.props.desire]}
/>
</SymptomIconView>
),
@@ -179,8 +184,8 @@ export default class DayColumn extends Component {
key='pain'
>
<View
{...styles.mucusIcon}
backgroundColor='blue'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.pain}
/>
</SymptomIconView>
),
@@ -191,8 +196,8 @@ export default class DayColumn extends Component {
key='note'
>
<View
{...styles.mucusIcon}
backgroundColor='green'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.note}
/>
</SymptomIconView>
)
+50 -23
View File
@@ -8,6 +8,7 @@ const lineWidth = 1.5
const colorLtl = '#feb47b'
const gridColor = 'lightgrey'
const gridLineWidth = 0.5
const numberLabelFontSize = 13
const styles = {
curve: {
@@ -32,10 +33,11 @@ const styles = {
color: 'grey',
fontSize: 9,
fontWeight: '100',
textAlign: 'center',
},
number: {
color: primaryColor,
fontSize: 13,
fontSize: numberLabelFontSize,
textAlign: 'center',
}
},
@@ -48,37 +50,62 @@ const styles = {
fill: 'transparent'
}
},
bleedingIcon: {
fill: '#fb2e01',
scale: 0.6,
x: 6,
y: 3
},
bleedingIconShades: shadesOfRed,
mucusIcon: {
symptomIcon: {
width: 12,
height: 12,
borderRadius: 50,
},
mucusIconShades: [
'#fef0e4',
'#fee1ca',
'#fed2af',
'#fec395',
'#feb47b'
],
iconShades: {
'bleeding': shadesOfRed,
'mucus': [
'#e3e7ed',
'#c8cfdc',
'#acb8cb',
'#91a0ba',
'#7689a9'
],
'cervix': [
'#f0e19d',
'#e9d26d',
'#e2c33c',
'#dbb40c',
],
'sex': [
'#a87ca2',
'#8b5083',
'#6f2565',
],
'desire': [
'#c485a6',
'#b15c89',
'#9e346c',
],
'pain': ['#bccd67'],
'note': ['#6CA299']
},
yAxis: {
width: 27,
borderRightWidth: 0.5,
borderRightWidth: 1,
borderColor: 'lightgrey',
borderStyle: 'solid'
},
yAxisLabel: {
position: 'absolute',
left: 3,
color: 'grey',
fontSize: 11,
textAlign: 'left'
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'
}
},
horizontalGrid: {
position:'absolute',
+3 -3
View File
@@ -8,7 +8,7 @@ import { AppText } from '../app-text'
export function makeYAxisLabels(columnHeight) {
const units = unitObservable.value
const scaleMax = scaleObservable.value.max
const style = styles.yAxisLabel
const style = styles.yAxisLabels.tempScale
return getTickPositions(columnHeight).map((y, i) => {
const tick = scaleMax - i * units
@@ -17,10 +17,10 @@ export function makeYAxisLabels(columnHeight) {
let tickBold
if (units === 0.1) {
showTick = (tick * 10 % 2) ? false : true
tickBold = tick * 10 % 5 ? {} : {fontWeight: 'bold'}
tickBold = tick * 10 % 5 ? {} : {fontWeight: 'bold', fontSize: 11}
} else {
showTick = (tick * 10 % 5) ? false : true
tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold'}
tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold', fontSize: 11}
}
// this eyeballing is sadly necessary because RN does not
// support percentage values for transforms, which we'd need
+49 -18
View File
@@ -6,13 +6,21 @@ import {
Dimensions
} from 'react-native'
import { LocalDate } from 'js-joda'
import Svg, { G } from 'react-native-svg'
import Header from '../header'
import { getOrCreateCycleDay } from '../../db'
import cycleModule from '../../lib/cycle'
import Icon from 'react-native-vector-icons/FontAwesome'
import styles, { iconStyles } from '../../styles'
import styles from '../../styles'
import * as labels from './labels/labels'
import { AppText } from '../app-text'
import BleedingIcon from '../../assets/bleeding'
import CervixIcon from '../../assets/cervix'
import DesireIcon from '../../assets/desire'
import MucusIcon from '../../assets/mucus'
import NoteIcon from '../../assets/note'
import PainIcon from '../../assets/pain'
import SexIcon from '../../assets/sex'
import TemperatureIcon from '../../assets/temperature'
const bleedingLabels = labels.bleeding
const feelingLabels = labels.mucus.feeling.categories
@@ -68,47 +76,64 @@ export default class CycleDayOverView extends Component {
onPress={() => this.navigate('BleedingEditView')}
data={getLabel('bleeding', cycleDay.bleeding)}
disabled={dateInFuture}
/>
>
<BleedingIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Temperature'
onPress={() => this.navigate('TemperatureEditView')}
data={getLabel('temperature', cycleDay.temperature)}
disabled={dateInFuture}
/>
>
<TemperatureIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Mucus'
onPress={() => this.navigate('MucusEditView')}
data={getLabel('mucus', cycleDay.mucus)}
disabled={dateInFuture}
/>
>
<MucusIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Cervix'
onPress={() => this.navigate('CervixEditView')}
data={getLabel('cervix', cycleDay.cervix)}
disabled={dateInFuture}
/>
>
<CervixIcon viewBox='10 10 320 440' />
</SymptomBox>
<SymptomBox
title='Desire'
onPress={() => this.navigate('DesireEditView')}
data={getLabel('desire', cycleDay.desire)}
disabled={dateInFuture}
/>
>
<DesireIcon viewBox='10 10 320 380' />
</SymptomBox>
<SymptomBox
title='Sex'
onPress={() => this.navigate('SexEditView')}
data={getLabel('sex', cycleDay.sex)}
disabled={dateInFuture}
/>
>
<SexIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Pain'
onPress={() => this.navigate('PainEditView')}
data={getLabel('pain', cycleDay.pain)}
/>
disabled={dateInFuture}
>
<PainIcon viewBox='10 10 300 400' />
</SymptomBox>
<SymptomBox
title='Note'
onPress={() => this.navigate('NoteEditView')}
data={getLabel('note', cycleDay.note)}
/>
>
<NoteIcon viewBox='10 10 270 400' />
</SymptomBox>
{/* this is just to make the last row adhere to the grid
(and) because there are no pseudo properties in RN */}
<FillerBoxes />
@@ -221,10 +246,6 @@ class SymptomBox extends Component {
render() {
const d = this.props.data
const boxActive = d ? styles.symptomBoxActive : {}
const iconActive = d ? iconStyles.symptomBoxActive : {}
const iconStyle = Object.assign(
{}, iconStyles.symptomBox, iconActive, disabledStyle
)
const textActive = d ? styles.symptomTextActive : {}
const disabledStyle = this.props.disabled ? styles.symptomInFuture : {}
@@ -234,10 +255,20 @@ class SymptomBox extends Component {
disabled={this.props.disabled}
>
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
<Icon
name='thermometer'
{...iconStyle}
/>
{this.props.children ?
React.Children.map(this.props.children, child => {
return (
<Svg width={100} height={50} viewBox={child.props.viewBox}>
<G fill={d ? 'white' : 'black'}>
{child}
</G>
</Svg>
)
})
: null
}
<AppText style={[textActive, disabledStyle]}>
{this.props.title}
</AppText>
+2
View File
@@ -43,6 +43,8 @@ export const sex = {
patch: 'Patch',
ring: 'Ring',
implant: 'Implant',
diaphragm: 'Diaphragm',
none: 'None',
other: 'Other',
activityExplainer: 'Were you sexually active today?',
contraceptiveExplainer: 'Did you use contraceptives?'
@@ -53,7 +53,8 @@ export default class ActionButtonFooter extends Component {
return (
<View style={styles.menu}>
{buttons.map(({ title, action, disabledCondition, icon }, i) => {
const textStyle = disabledCondition ? styles.menuTextInActive : styles.menuText
const textStyle = [styles.menuText]
if (disabledCondition) textStyle.push(styles.menuTextInActive)
const iconStyle = disabledCondition ?
Object.assign({}, iconStyles.menuIcon, iconStyles.menuIconInactive) :
iconStyles.menuIcon
+6
View File
@@ -37,6 +37,12 @@ const contraceptiveBoxes = [{
}, {
label: labels.implant,
stateKey: 'implant'
}, {
label: labels.diaphragm,
stateKey: 'diaphragm'
}, {
label: labels.none,
stateKey: 'none'
}, {
label: labels.other,
stateKey: 'other'
+7 -2
View File
@@ -1,7 +1,8 @@
import React, { Component } from 'react'
import {
View,
Text
Text,
Dimensions
} from 'react-native'
import styles, { iconStyles } from '../styles'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
@@ -10,9 +11,11 @@ import { formatDateForViewHeader } from '../components/cycle-day/labels/format'
export default class Header extends Component {
render() {
const middle = Dimensions.get('window').width / 2
return (
this.props.isCycleDayOverView ?
<View style={[styles.header, styles.headerCycleDay]}>
<View style={styles.accentCircle} left={middle - styles.accentCircle.width / 2}/>
<Icon
name='arrow-left-drop-circle'
{...iconStyles.navigationArrow}
@@ -35,6 +38,7 @@ export default class Header extends Component {
</View >
: this.props.isSymptomView ?
<View style={[styles.header, styles.headerSymptom]}>
<View style={styles.accentCircle} left={middle - styles.accentCircle.width / 2}/>
<Icon
name='keyboard-backspace'
{...iconStyles.symptomHeaderIcons}
@@ -53,7 +57,8 @@ export default class Header extends Component {
</View>
:
<View style={styles.header}>
<Text style={styles.dateHeader}>
<View style={styles.accentCircle} />
<Text style={styles.headerText}>
{this.props.title}
</Text>
</View >
+17 -1
View File
@@ -8,7 +8,10 @@ export const shared = {
incorrectPasswordMessage: 'That password is incorrect.',
tryAgain: 'Try again',
ok: 'OK',
unlock: 'Unlock'
unlock: 'Unlock',
date: 'Date',
cycleDayWithLinebreak: 'Cycle\nday',
loading: 'Loading ...'
}
export const settings = {
@@ -54,6 +57,11 @@ export const settings = {
timeSet: time => `Daily reminder set for ${time}`,
notification: 'Record your morning temperature'
},
periodReminder: {
title: 'Next period reminder',
reminderText: 'Get a notification 3 days before your next period is likely to start.',
notification: daysToEndOfPrediction => `Your next period is likely to start in 3 to ${daysToEndOfPrediction} days.`
},
passwordSettings: {
title: 'App password',
explainerDisabled: "Encrypt the app's database with a password. You need to enter the password every time the app is started.",
@@ -86,6 +94,14 @@ export const headerTitles = {
PainEditView: 'Pain'
}
export const menuTitles = {
Home: 'Home',
Calendar: 'Calendar',
Chart: 'Chart',
Stats: 'Stats',
Settings: 'Settings',
}
export const stats = {
cycleLengthTitle: 'Cycle length',
cycleLengthExplainer: 'Basic statistics about the length of your cycles.',
+6 -5
View File
@@ -28,14 +28,15 @@ export default class Menu extends Component {
}
render() {
const t = this.props.titles
return (
<View style={styles.menu}>
{[
{ title: 'Home', icon: 'home', onPress: () => this.goTo('Home') },
{ title: 'Calendar', icon: 'calendar-range', onPress: () => this.goTo('Calendar') },
{ title: 'Chart', icon: 'chart-line', onPress: () => this.goTo('Chart') },
{ title: 'Stats', icon: 'chart-pie', onPress: () => this.goTo('Stats') },
{ title: 'Settings', icon: 'settings', onPress: () => this.goTo('Settings') },
{ title: t.Home, icon: 'home', onPress: () => this.goTo('Home') },
{ title: t.Calendar, icon: 'calendar-range', onPress: () => this.goTo('Calendar') },
{ title: t.Chart, icon: 'chart-line', onPress: () => this.goTo('Chart') },
{ title: t.Stats, icon: 'chart-pie', onPress: () => this.goTo('Stats') },
{ title: t.Settings, icon: 'settings', onPress: () => this.goTo('Settings') },
].map(this.makeMenuItem)}
</View >
)
+2
View File
@@ -8,6 +8,7 @@ import styles from '../../styles/index'
import { settings as labels } from '../labels'
import { AppText } from '../app-text'
import TempReminderPicker from './temp-reminder-picker'
import PeriodReminderPicker from './period-reminder'
import TempSlider from './temp-slider'
import openImportDialogAndImport from './import-dialog'
import openShareDialogAndExport from './export-dialog'
@@ -30,6 +31,7 @@ export default class Settings extends Component {
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TempSlider/>
</View>
<PeriodReminderPicker/>
<PasswordSetting />
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
+41
View File
@@ -0,0 +1,41 @@
import React, { Component } from 'react'
import {
View,
Switch
} from 'react-native'
import { AppText } from '../app-text'
import {
periodReminderObservable,
savePeriodReminder
} from '../../local-storage'
import styles from '../../styles/index'
import { settings as labels } from '../labels'
export default class PeriodReminderPicker extends Component {
constructor(props) {
super(props)
this.state = periodReminderObservable.value
}
render() {
return (
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
{labels.periodReminder.title}
</AppText>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ flex: 1 }}>
<AppText>{labels.periodReminder.reminderText}</AppText>
</View>
<Switch
value={this.state.enabled}
onValueChange={switchOn => {
this.setState({ enabled: switchOn })
savePeriodReminder({enabled: switchOn})
}}
/>
</View>
</View>
)
}
}
+13 -4
View File
@@ -1,16 +1,25 @@
function convertToSymptoFormat(val) {
const sympto = { date: val.date }
if (val.bleeding) sympto.bleeding = {
value: val.bleeding,
exclude: false
}
if (val.temperature) sympto.temperature = {
value: val.temperature,
time: '08:00',
exclude: false
}
if (val.mucus) sympto.mucus = {
value: val.mucus,
exclude: false,
feeling: val.mucus,
texture: val.mucus
texture: val.mucus,
exclude: false
}
if (val.cervix && typeof val.cervix.opening === 'number' && typeof val.cervix.firmness === 'number') sympto.cervix = {
opening: val.cervix.opening,
firmness: val.cervix.firmness,
exclude: false
}
if (val.bleeding) sympto.bleeding = { value: val.bleeding, exclude: false }
return sympto
}
@@ -75,7 +84,7 @@ export const cycleWithTempAndNoMucusShift = [
{ date: '2018-05-27', temperature: 36.9, mucus: 4 }
].map(convertToSymptoFormat).reverse()
export const cycleWithFhmCervix = [
export const cervixShiftAndFhmOnSameDay = [
{ date: '2018-08-01', bleeding: 2 },
{ date: '2018-08-02', bleeding: 1 },
{ date: '2018-08-03', bleeding: 0 },
+30 -15
View File
@@ -7,17 +7,42 @@ import {
cycleWithFhmMucus,
longAndComplicatedCycleWithMucus,
cycleWithTempAndNoMucusShift,
cycleWithFhmCervix,
cervixShiftAndFhmOnSameDay,
longAndComplicatedCycleWithCervix,
cycleWithTempAndNoCervixShift
} from './fixtures'
import dbSchema from './schema'
import schemas from './schemas'
let db
const realmConfig = {
schema: dbSchema
export async function openDb ({ hash, persistConnection }) {
const realmConfig = {}
if (hash) {
realmConfig.encryptionKey = hashToInt8Array(hash)
}
// perform migrations if necessary, see https://realm.io/docs/javascript/2.8.0/#migrations
let nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath)
while (nextSchemaIndex < schemas.length - 1) {
const tempConfig = Object.assign(
realmConfig,
schemas[nextSchemaIndex++]
)
const migratedRealm = new Realm(tempConfig)
migratedRealm.close()
}
// open the Realm with the latest schema
realmConfig.schema = schemas[schemas.length - 1]
const connection = await Realm.open(Object.assign(
realmConfig,
schemas[schemas.length - 1]
))
if (persistConnection) db = connection
}
export function getBleedingDaysSortedByDate() {
return db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
}
@@ -77,7 +102,7 @@ export function fillWithMucusDummyData() {
export function fillWithCervixDummyData() {
const dummyCycles = [
cycleWithFhmCervix,
cervixShiftAndFhmOnSameDay,
longAndComplicatedCycleWithCervix,
cycleWithTempAndNoCervixShift
]
@@ -160,16 +185,6 @@ export function requestHash(type, pw) {
}))
}
export async function openDb ({ hash, persistConnection }) {
if (hash) {
realmConfig.encryptionKey = hashToInt8Array(hash)
}
const connection = await Realm.open(realmConfig)
if (persistConnection) db = connection
}
export async function changeEncryptionAndRestartApp(hash) {
let key
if (hash) key = hashToInt8Array(hash)
+14 -11
View File
@@ -127,14 +127,17 @@ const CycleDaySchema = {
}
}
export default [
CycleDaySchema,
TemperatureSchema,
BleedingSchema,
MucusSchema,
CervixSchema,
NoteSchema,
DesireSchema,
SexSchema,
PainSchema
]
export default {
schema: [
CycleDaySchema,
TemperatureSchema,
BleedingSchema,
MucusSchema,
CervixSchema,
NoteSchema,
DesireSchema,
SexSchema,
PainSchema
],
schemaVersion: 0
}
+156
View File
@@ -0,0 +1,156 @@
const TemperatureSchema = {
name: 'Temperature',
properties: {
value: 'double',
exclude: 'bool',
time: {
type: 'string',
optional: true
},
note: {
type: 'string',
optional: true
}
}
}
const BleedingSchema = {
name: 'Bleeding',
properties: {
value: 'int',
exclude: 'bool'
}
}
const MucusSchema = {
name: 'Mucus',
properties: {
feeling: 'int',
texture: 'int',
value: 'int',
exclude: 'bool'
}
}
const CervixSchema = {
name: 'Cervix',
properties: {
opening: 'int',
firmness: 'int',
position: {type: 'int', optional: true },
exclude: 'bool'
}
}
const NoteSchema = {
name: 'Note',
properties: {
value: 'string'
}
}
const DesireSchema = {
name: 'Desire',
properties: {
value: 'int'
}
}
const SexSchema = {
name: 'Sex',
properties: {
solo: { type: 'bool', optional: true },
partner: { type: 'bool', optional: true },
condom: { type: 'bool', optional: true },
pill: { type: 'bool', optional: true },
iud: { type: 'bool', optional: true },
patch: { type: 'bool', optional: true },
ring: { type: 'bool', optional: true },
implant: { type: 'bool', optional: true },
diaphragm: { type: 'bool', optional: true },
none: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const PainSchema = {
name: 'Pain',
properties: {
cramps: { type: 'bool', optional: true },
ovulationPain: { type: 'bool', optional: true },
headache: { type: 'bool', optional: true },
backache: { type: 'bool', optional: true },
nausea: { type: 'bool', optional: true },
tenderBreasts: { type: 'bool', optional: true },
migraine: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const CycleDaySchema = {
name: 'CycleDay',
primaryKey: 'date',
properties: {
date: 'string',
temperature: {
type: 'Temperature',
optional: true
},
bleeding: {
type: 'Bleeding',
optional: true
},
mucus: {
type: 'Mucus',
optional: true
},
cervix: {
type: 'Cervix',
optional: true
},
note: {
type: 'Note',
optional: true
},
desire: {
type: 'Desire',
optional: true
},
sex: {
type: 'Sex',
optional: true
},
pain: {
type: 'Pain',
optional: true
}
}
}
export default {
schema: [
CycleDaySchema,
TemperatureSchema,
BleedingSchema,
MucusSchema,
CervixSchema,
NoteSchema,
DesireSchema,
SexSchema,
PainSchema
],
schemaVersion: 1,
migration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion >= 1) return
const oldCycleDays = oldRealm.objects('CycleDay')
const newCycleDays = newRealm.objects('CycleDay')
oldCycleDays.forEach((day, i) => {
if (!day.sex) return
newCycleDays[i].sex.diaphragm = null
newCycleDays[i].sex.none = null
})
}
}
+4
View File
@@ -0,0 +1,4 @@
import schema0 from './0.js'
import schema1 from './1.js'
export default [schema0, schema1]
+6 -6
View File
@@ -747,9 +747,9 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
2B572382D4504B8FB4B9D251 /* Embed Frameworks */,
7DDFD19623084447885928A6 /* Build NodeJS Mobile Native Modules */,
554E2494DF2646B083F4BD1D /* Sign NodeJS Mobile Native Modules */,
8F5D6E75B7D344BD80BC6EC0 /* Remove NodeJS Mobile Framework Simulator Strips */,
2916172A40DD44AE85EB76AF /* Build NodeJS Mobile Native Modules */,
E93078AE736B464D9A7409A4 /* Sign NodeJS Mobile Native Modules */,
E95128D078C34495AFAAA808 /* Remove NodeJS Mobile Framework Simulator Strips */,
);
buildRules = (
);
@@ -1232,7 +1232,7 @@
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
7DDFD19623084447885928A6 /* Build NodeJS Mobile Native Modules */ = {
2916172A40DD44AE85EB76AF /* Build NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1300,7 +1300,7 @@ fi
popd
";
};
554E2494DF2646B083F4BD1D /* Sign NodeJS Mobile Native Modules */ = {
E93078AE736B464D9A7409A4 /* Sign NodeJS Mobile Native Modules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1360,7 +1360,7 @@ find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -path \"*/*.framework/*\" -del
find \"$CODESIGNING_FOLDER_PATH/nodejs-project/\" -name \"*.framework\" -type d -delete
";
};
8F5D6E75B7D344BD80BC6EC0 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
E95128D078C34495AFAAA808 /* Remove NodeJS Mobile Framework Simulator Strips */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
+49 -7
View File
@@ -1,21 +1,26 @@
import {tempReminderObservable} from '../local-storage'
import {tempReminderObservable, periodReminderObservable} from '../local-storage'
import Notification from 'react-native-push-notification'
import { LocalDate } from 'js-joda'
import Moment from 'moment'
import { settings as labels } from '../components/labels'
import { getOrCreateCycleDay } from '../db'
import { getOrCreateCycleDay, getBleedingDaysSortedByDate } from '../db'
import cycleModule from './cycle'
export default function setupNotifications(navigate) {
Notification.configure({
onNotification: () => {
const todayDateString = LocalDate.now().toString()
const cycleDay = getOrCreateCycleDay(todayDateString)
navigate('TemperatureEditView', { cycleDay })
onNotification: (notification) => {
if (notification.id === '1') {
const todayDateString = LocalDate.now().toString()
const cycleDay = getOrCreateCycleDay(todayDateString)
navigate('TemperatureEditView', { cycleDay })
} else {
navigate('Home')
}
}
})
tempReminderObservable(reminder => {
Notification.cancelAllLocalNotifications()
Notification.cancelLocalNotifications({id: '1'})
if (reminder.enabled) {
const [hours, minutes] = reminder.time.split(':')
let target = new Moment()
@@ -28,6 +33,7 @@ export default function setupNotifications(navigate) {
}
Notification.localNotificationSchedule({
id: '1',
message: labels.tempReminder.notification,
date: target.toDate(),
vibrate: false,
@@ -35,4 +41,40 @@ export default function setupNotifications(navigate) {
})
}
})
periodReminderObservable(reminder => {
Notification.cancelLocalNotifications({id: '2'})
if (reminder.enabled) setupPeriodReminder()
})
getBleedingDaysSortedByDate().addListener(() => {
Notification.cancelLocalNotifications({id: '2'})
if (periodReminderObservable.value.enabled) setupPeriodReminder()
})
}
function setupPeriodReminder() {
const bleedingPrediction = cycleModule().getPredictedMenses()
if (bleedingPrediction.length > 0) {
const bleedingStart = Moment(bleedingPrediction[0][0], "YYYY-MM-DD")
// 3 days before and at 6 am
const reminderDate = bleedingStart
.subtract(3, 'days')
.hours(6)
.minutes(0)
.seconds(0)
if (reminderDate.isAfter()) {
// period is likely to start in 3 to 3 + (length of prediction - 1) days
const daysToEndOfPrediction = bleedingPrediction[0].length + 2
Notification.localNotificationSchedule({
id: '2',
message: labels.periodReminder.notification(daysToEndOfPrediction),
date: reminderDate.toDate(),
vibrate: false
})
}
}
}
+2 -1
View File
@@ -23,7 +23,8 @@ export default function getSymptoThermalStatus(cycleInfo) {
if (statusForLast.temperatureShift) {
const preOvuPhase = getPreOvulatoryPhase(
cycle,
[previousCycle, ...earlierCycles]
[previousCycle, ...earlierCycles],
secondarySymptom
)
if (preOvuPhase) {
status.phases.preOvulatory = preOvuPhase
+2 -2
View File
@@ -1,10 +1,10 @@
import { LocalDate } from 'js-joda'
import getNfpStatus from './index'
export default function (previousCycles) {
export default function (previousCycles, secondarySymptom) {
const fhms = previousCycles
.map(cycle => {
const status = getNfpStatus({ cycle })
const status = getNfpStatus({ cycle, secondarySymptom })
if (status.temperatureShift) {
const day = status.temperatureShift.firstHighMeasurementDay
const firstCycleDayDate = LocalDate.parse(cycle[0].date)
+5 -4
View File
@@ -1,10 +1,10 @@
import { LocalDate } from "js-joda"
import apply8DayRule from './minus-8-day-rule'
export default function(cycle, previousCycles) {
export default function(cycle, previousCycles, secondarySymptom) {
let preOvuPhaseLength = 5
const minus8DayRuleResult = apply8DayRule(previousCycles)
const minus8DayRuleResult = apply8DayRule(previousCycles, secondarySymptom)
if (minus8DayRuleResult) preOvuPhaseLength = minus8DayRuleResult
const startDate = LocalDate.parse(cycle[0].date)
@@ -12,7 +12,7 @@ export default function(cycle, previousCycles) {
const maybePreOvuDays = cycle.slice(0, preOvuPhaseLength).filter(d => {
return d.date <= preOvuEndDate
})
const preOvulatoryDays = getDaysUntilFertileSecondarySymptom(maybePreOvuDays)
const preOvulatoryDays = getDaysUntilFertileSecondarySymptom(maybePreOvuDays, secondarySymptom)
// if fertile mucus or cervix occurs on the 1st cycle day, there is no pre-ovu phase
if (!preOvulatoryDays.length) return null
@@ -39,7 +39,8 @@ function getDaysUntilFertileSecondarySymptom(days, secondarySymptom = 'mucus') {
if (secondarySymptom === 'mucus') {
return day.mucus && day.mucus.value > 1
} else if (secondarySymptom === 'cervix') {
return day.cervix && !day.cervix.isClosedAndHard
return day.cervix && day.cervix.opening > 0
|| day.cervix && day.cervix.firmness > 0
}
})
+10
View File
@@ -34,6 +34,16 @@ export async function saveTempReminder(reminder) {
tempReminderObservable.set(reminder)
}
export const periodReminderObservable = Observable()
setObvWithInitValue('periodReminder', periodReminderObservable, {
enabled: false
})
export async function savePeriodReminder(reminder) {
await AsyncStorage.setItem('periodReminder', JSON.stringify(reminder))
periodReminderObservable.set(reminder)
}
export const hasEncryptionObservable = Observable()
setObvWithInitValue('hasEncryption', hasEncryptionObservable, false)
+42 -15
View File
@@ -1,18 +1,31 @@
import { StyleSheet } from 'react-native'
export const primaryColor = '#ff7e5f'
export const secondaryColor = '#351c4d'
export const primaryColor = '#000D19'
export const secondaryColor = '#4FAFA7'
export const secondaryColorLight = '#91749d'
export const fontOnPrimaryColor = 'white'
export const shadesOfRed = ['#ffcbbf', '#ffb19f', '#ff977e', '#ff7e5f'] // light to dark
export const shadesOfRed = [
'#e7999e',
'#db666d',
'#cf323d',
'#c3000d'
] // light to dark
const fontRegular = 'Prompt-Light'
const fontLight = 'Prompt-Thin'
const regularSize = 16
const defaultBottomMargin = 5
const defaultIndentation = 10
const defaultTopMargin = 10
const colorInActive = '#666666'
export default StyleSheet.create({
appText: {
color: 'black'
color: 'black',
fontFamily: fontRegular,
fontSize: regularSize
},
paragraph: {
marginBottom: defaultBottomMargin
@@ -31,21 +44,36 @@ export default StyleSheet.create({
},
welcome: {
fontSize: 20,
fontFamily: 'serif',
margin: 30,
textAlign: 'center',
textAlignVertical: 'center'
},
dateHeader: {
fontSize: 22,
fontWeight: 'bold',
fontSize: 20,
fontFamily: fontLight,
color: fontOnPrimaryColor,
textAlign: 'center',
},
headerText: {
fontSize: 30,
fontFamily: fontLight,
color: fontOnPrimaryColor,
textAlign: 'center',
},
accentCircle: {
borderColor: secondaryColor,
borderWidth: 0.5,
width: 40,
height: 40,
borderRadius: 100,
position: 'absolute'
},
cycleDayNumber: {
fontSize: 15,
color: fontOnPrimaryColor,
textAlign: 'center',
marginLeft: 15
fontFamily: fontLight
},
symptomViewHeading: {
fontSize: 20,
@@ -107,14 +135,14 @@ export default StyleSheet.create({
paddingHorizontal: 15,
alignItems: 'center',
justifyContent: 'center',
height: '10%'
height: 80
},
menu: {
backgroundColor: primaryColor,
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
height: '12%'
height: 60
},
menuItem: {
alignItems: 'center',
@@ -122,20 +150,19 @@ export default StyleSheet.create({
paddingVertical: 15
},
menuText: {
color: fontOnPrimaryColor
color: fontOnPrimaryColor,
fontFamily: fontLight
},
menuTextInActive: {
color: 'lightgrey'
color: colorInActive
},
headerCycleDay: {
flexDirection: 'row',
justifyContent: 'space-between',
height: '15%'
},
headerSymptom: {
flexDirection: 'row',
justifyContent: 'space-between',
height: '12%'
},
navigationArrow: {
fontSize: 60,
@@ -299,7 +326,7 @@ export const iconStyles = {
color: fontOnPrimaryColor
},
symptomHeaderIcons: {
size: 30,
size: 20,
color: fontOnPrimaryColor
},
symptomBox: {
@@ -313,6 +340,6 @@ export const iconStyles = {
color: fontOnPrimaryColor
},
menuIconInactive: {
color: 'lightgrey',
color: colorInActive,
},
}
+86 -7
View File
@@ -2,9 +2,9 @@ function convertToSymptoFormat(val) {
const sympto = { date: val.date }
if (val.temperature) sympto.temperature = {
value: val.temperature,
time: '08:00',
exclude: false
}
if (val.cervix && typeof val.cervix.opening === 'number' && typeof val.cervix.firmness === 'number') sympto.cervix = {
opening: val.cervix.opening,
firmness: val.cervix.firmness,
@@ -18,11 +18,11 @@ function convertToSymptoFormat(val) {
}
export const cervixShiftAndFhmOnSameDay = [
{ date: '2018-08-01', bleeding: 1, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-08-02', bleeding: 2, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-08-03', temperature: 36.6, bleeding: 2, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-08-04', temperature: 36.55, bleeding: 1, cervix: { opening: 2, firmness: 0 } },
{ date: '2018-08-05', temperature: 36.6, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-08-01', bleeding: 1 },
{ date: '2018-08-02', bleeding: 2 },
{ date: '2018-08-03', temperature: 36.6, bleeding: 2 },
{ date: '2018-08-04', temperature: 36.55, bleeding: 1 },
{ date: '2018-08-05', temperature: 36.6, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-08-06', temperature: 36.65, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-08-07', temperature: 36.71, cervix: { opening: 1, firmness: 0 } },
{ date: '2018-08-08', temperature: 36.69, cervix: { opening: 1, firmness: 0 } },
@@ -99,6 +99,8 @@ export const longAndComplicatedCycle = [
{ date: '2018-06-04', temperature: 36.6 },
{ date: '2018-06-05', temperature: 36.55 },
{ date: '2018-06-06', temperature: 36.7, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-07', temperature: 36.5, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-08', temperature: 36.52, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-09', temperature: 36.5, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-10', temperature: 36.4, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-13', temperature: 36.45, cervix: { opening: 1, firmness: 1 } },
@@ -110,7 +112,7 @@ export const longAndComplicatedCycle = [
{ date: '2018-06-19', temperature: 36.8, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-20', temperature: 36.85, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-21', temperature: 36.8, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-22', temperature: 36.9, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-22', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-25', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-26', temperature: 36.8, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-27', temperature: 36.9, cervix: { opening: 0, firmness: 0 } }
@@ -186,3 +188,80 @@ export const fiveDayCycle = [
{ date: '2018-08-01', bleeding: 2 },
{ date: '2018-08-03', bleeding: 3 }
].map(convertToSymptoFormat)
export const fhmOnDay12 = [
{ date: '2018-06-01', temperature: 36.6, bleeding: 2 },
{ date: '2018-06-02', temperature: 36.65 },
{ date: '2018-06-04', temperature: 36.6 },
{ date: '2018-06-05', temperature: 36.55 },
{ date: '2018-06-06', temperature: 36.7, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-09', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-10', temperature: 36.4, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-12', temperature: 36.8, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-14', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-17', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-18', temperature: 36.9, cervix: { opening: 0, firmness: 0 } }
].map(convertToSymptoFormat)
export const fhmOnDay15 = [
{ date: '2018-06-01', temperature: 36.6, bleeding: 2 },
{ date: '2018-06-02', temperature: 36.65 },
{ date: '2018-06-04', temperature: 36.6 },
{ date: '2018-06-05', temperature: 36.55 },
{ date: '2018-06-06', temperature: 36.7, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-09', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-10', temperature: 36.4, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-11', temperature: 36.4, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-12', temperature: 36.4, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-14', temperature: 36.4, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-15', temperature: 36.8, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-16', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-17', temperature: 36.9, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-18', temperature: 36.9, cervix: { opening: 0, firmness: 0 } }
].map(convertToSymptoFormat)
export const cycleWithEarlyCervix = [
{ date: '2018-06-01', temperature: 36.6, bleeding: 2 },
{ date: '2018-06-02', temperature: 36.65, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-05', temperature: 36.55 },
{ date: '2018-06-06', temperature: 36.7, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-08', temperature: 36.45, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-09', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-10', temperature: 36.4, cervix: { opening: 2, firmness: 0 } },
{ date: '2018-06-11', temperature: 36.5, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-13', temperature: 36.45, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-14', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-15', temperature: 36.55, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-16', temperature: 36.7, cervix: { opening: 1, firmness: 0 } },
{ date: '2018-06-17', temperature: 36.65, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-06-18', temperature: 36.75, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-19', temperature: 36.8, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-20', temperature: 36.85, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-23', temperature: 36.9, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-06-24', temperature: 36.85, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-26', temperature: 36.8, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-27', temperature: 36.9, cervix: { opening: 1, firmness: 1 } }
].map(convertToSymptoFormat)
export const cycleWithCervixOnFirstDay = [
{ date: '2018-06-01', temperature: 36.6, bleeding: 2, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-02', temperature: 36.65 },
{ date: '2018-06-05', temperature: 36.55 },
{ date: '2018-06-06', temperature: 36.7, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-08', temperature: 36.45, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-09', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-10', temperature: 36.4, cervix: { opening: 2, firmness: 0 } },
{ date: '2018-06-11', temperature: 36.5, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-13', temperature: 36.45, cervix: { opening: 2, firmness: 1 } },
{ date: '2018-06-14', temperature: 36.5, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-15', temperature: 36.55, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-16', temperature: 36.7, cervix: { opening: 1, firmness: 0 } },
{ date: '2018-06-17', temperature: 36.65, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-06-18', temperature: 36.75, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-19', temperature: 36.8, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-20', temperature: 36.85, cervix: { opening: 0, firmness: 0 } },
{ date: '2018-06-23', temperature: 36.9, cervix: { opening: 0, firmness: 1 } },
{ date: '2018-06-24', temperature: 36.85, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-26', temperature: 36.8, cervix: { opening: 1, firmness: 1 } },
{ date: '2018-06-27', temperature: 36.9, cervix: { opening: 1, firmness: 1 } }
].map(convertToSymptoFormat)
+291 -4
View File
@@ -1,14 +1,20 @@
import chai from 'chai'
import getSensiplanStatus from '../../lib/sympto'
import { AssertionError } from 'assert'
import {
cervixShiftAndFhmOnSameDay,
cycleWithFhmNoCervixShift,
cycleWithoutFhm,
cycleWithoutFhmNoCervixShift,
longCycleWithoutAnyShifts,
longAndComplicatedCycle,
tempShift3DaysAfterCervixShift,
cervixShift2DaysAfterTempShift,
noOvulationDetected,
fiveDayCycle
fiveDayCycle,
fhmOnDay12,
fhmOnDay15,
cycleWithEarlyCervix,
cycleWithCervixOnFirstDay
} from './cervix-temp-fixtures'
const expect = chai.expect
@@ -19,7 +25,7 @@ describe('sympto', () => {
it('with no temp or cervix shifts detects only peri-ovulatory', () => {
const status = getSensiplanStatus({
cycle: longCycleWithoutAnyShifts,
previousCycle: cycleWithoutFhm,
previousCycle: cycleWithoutFhmNoCervixShift,
secondarySymptom: 'cervix'
})
expect(Object.keys(status.phases).length).to.eql(1)
@@ -35,7 +41,7 @@ describe('sympto', () => {
it('with temp but no cervix shift detects only peri-ovulatory', () => {
const status = getSensiplanStatus({
cycle: cycleWithFhmNoCervixShift,
previousCycle: cycleWithoutFhm,
previousCycle: cycleWithoutFhmNoCervixShift,
secondarySymptom: 'cervix'
})
expect(Object.keys(status.phases).length).to.eql(1)
@@ -217,5 +223,286 @@ describe('sympto', () => {
})
})
})
describe('applying the minus-8 rule', () => {
it('shortens the pre-ovu phase if there is a previous < 13 fhm', () => {
const status = getSensiplanStatus({
cycle: longAndComplicatedCycle,
previousCycle: fhmOnDay15,
earlierCycles: [fhmOnDay12, ...Array(10).fill(fhmOnDay15)],
secondarySymptom: 'cervix'
})
expect(status.temperatureShift).to.be.an('object')
expect(status.cervixShift).to.be.an('object')
expect(Object.keys(status.phases).length).to.eql(3)
expect(status.phases.preOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-04' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => date <= '2018-06-04')
})
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-05' },
end: { date: '2018-06-26', time: '18:00' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => {
return date > '2018-06-04' && date <= '2018-06-26'
})
})
expect(status.phases.postOvulatory).to.eql({
start: {
date: '2018-06-26',
time: '18:00'
},
cycleDays: longAndComplicatedCycle
.filter(({date}) => date >= '2018-06-26')
})
})
it('shortens pre-ovu phase with prev < 13 fhm even with < 12 cycles', () => {
const status = getSensiplanStatus({
cycle: longAndComplicatedCycle,
previousCycle: fhmOnDay12,
earlierCycles: Array(10).fill(fhmOnDay12),
secondarySymptom: 'cervix'
})
expect(status.temperatureShift).to.be.an('object')
expect(status.cervixShift).to.be.an('object')
expect(Object.keys(status.phases).length).to.eql(3)
expect(status.phases.preOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-04' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => date <= '2018-06-04')
})
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-05' },
end: { date: '2018-06-26', time: '18:00' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => {
return date > '2018-06-04' && date <= '2018-06-26'
})
})
expect(status.phases.postOvulatory).to.eql({
start: {
date: '2018-06-26',
time: '18:00'
},
cycleDays: longAndComplicatedCycle
.filter(({date}) => date >= '2018-06-26')
})
})
it('shortens the pre-ovu phase if early fertile cervix occurs', () => {
const status = getSensiplanStatus({
cycle: cycleWithEarlyCervix,
previousCycle: fhmOnDay12,
earlierCycles: Array(10).fill(fhmOnDay12),
secondarySymptom: 'cervix'
})
expect(status.temperatureShift).to.be.an('object')
expect(status.cervixShift).to.be.an('object')
expect(Object.keys(status.phases).length).to.eql(3)
expect(status.phases.preOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-01' },
cycleDays: cycleWithEarlyCervix
.filter(({date}) => date <= '2018-06-01')
})
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-02' },
end: { date: '2018-06-20', time: '18:00'},
cycleDays: cycleWithEarlyCervix
.filter(({date}) => {
return date > '2018-06-01' && date <= '2018-06-20'
})
})
expect(status.phases.postOvulatory).to.eql({
start: { date: '2018-06-20', time: '18:00' },
cycleDays: cycleWithEarlyCervix
.filter(({date}) => date >= '2018-06-20')
})
})
it('shortens the pre-ovu phase if cervix occurs even on the first day', () => {
const status = getSensiplanStatus({
cycle: cycleWithCervixOnFirstDay,
previousCycle: fhmOnDay12,
earlierCycles: Array(10).fill(fhmOnDay12),
secondarySymptom: 'cervix'
})
expect(Object.keys(status.phases).length).to.eql(2)
expect(status.temperatureShift).to.be.an('object')
expect(status.cervixShift).to.be.an('object')
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-20', time: '18:00'},
cycleDays: cycleWithCervixOnFirstDay
.filter(({date}) => {
return date >= '2018-06-01' && date <= '2018-06-20'
})
})
expect(status.phases.postOvulatory).to.eql({
start: { date: '2018-06-20', time: '18:00' },
cycleDays: cycleWithCervixOnFirstDay
.filter(({date}) => date >= '2018-06-20')
})
})
it('lengthens the pre-ovu phase if >= 12 cycles with fhm > 13', () => {
const status = getSensiplanStatus({
cycle: longAndComplicatedCycle,
previousCycle: fhmOnDay15,
earlierCycles: Array(11).fill(fhmOnDay15),
secondarySymptom: 'cervix'
})
expect(status.temperatureShift).to.be.an('object')
expect(status.cervixShift).to.be.an('object')
expect(Object.keys(status.phases).length).to.eql(3)
expect(status.phases.preOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-07' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => date <= '2018-06-07')
})
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-08' },
end: { date: '2018-06-26', time: '18:00'},
cycleDays: longAndComplicatedCycle
.filter(({date}) => {
return date >= '2018-06-08' && date <= '2018-06-26'
})
})
expect(status.phases.postOvulatory).to.eql({
start: { date: '2018-06-26', time: '18:00' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => date >= '2018-06-26')
})
})
it('does not lengthen the pre-ovu phase if < 12 cycles', () => {
const status = getSensiplanStatus({
cycle: longAndComplicatedCycle,
previousCycle: fhmOnDay15,
earlierCycles: Array(10).fill(fhmOnDay15),
secondarySymptom: 'cervix'
})
expect(Object.keys(status.phases).length).to.eql(3)
expect(status.phases.preOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-05' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => date <= '2018-06-05')
})
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-06' },
end: { date: '2018-06-26', time: '18:00' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => {
return date > '2018-06-05' && date <= '2018-06-26'
})
})
expect(status.phases.postOvulatory).to.eql({
start: {
date: '2018-06-26',
time: '18:00'
},
cycleDays: longAndComplicatedCycle
.filter(({date}) => date >= '2018-06-26')
})
})
it('does not detect any pre-ovu phase if prev cycle had no fhm', () => {
const status = getSensiplanStatus({
cycle: longAndComplicatedCycle,
previousCycle: cycleWithoutFhmNoCervixShift,
earlierCycles: [...Array(12).fill(fhmOnDay15)],
secondarySymptom: 'cervix'
})
expect(Object.keys(status.phases).length).to.eql(2)
expect(status.phases.periOvulatory).to.eql({
start: { date: '2018-06-01' },
end: { date: '2018-06-26', time: '18:00' },
cycleDays: longAndComplicatedCycle
.filter(({date}) => {
return date >= '2018-06-01' && date <= '2018-06-26'
})
})
expect(status.phases.postOvulatory).to.eql({
start: {
date: '2018-06-26',
time: '18:00'
},
cycleDays: longAndComplicatedCycle
.filter(({date}) => date >= '2018-06-26')
})
})
})
describe('when args are wrong', () => {
it('throws when arg object is not in right format', () => {
const wrongObject = { ada: 'lovelace' }
expect(() => getSensiplanStatus(wrongObject)).to.throw(AssertionError)
})
it('throws if cycle array is empty', () => {
expect(() => getSensiplanStatus({cycle: []})).to.throw(AssertionError)
})
it('throws if cycle days are not in right format', () => {
expect(() => getSensiplanStatus({
cycle: [{
Ilike: "you"
}],
})).to.throw(AssertionError)
expect(() => getSensiplanStatus({
cycle: [{
date: '01.09.2018',
bleeding: { value: 0 },
cervix: {opening: 0, firmness: 0}
}],
})).to.throw(AssertionError)
expect(() => getSensiplanStatus({
cycle: [{
date: '2018-01-01',
bleeding: { value: 'medium' },
temperature: { value: 36.6 }
}],
})).to.throw(AssertionError)
expect(() => getSensiplanStatus({
cycle: [{
date: '2018-09-01',
bleeding: { value: 0 },
temperature: { value: '36.6' }
}],
})).to.throw(AssertionError)
expect(() => getSensiplanStatus({
cycle: [{
date: '2018-09-01',
bleeding: { value: 0 },
temperature: { value: 36.6 },
cervix: {opening: 4, firmness: 18}
}],
})).to.throw(AssertionError)
expect(() => getSensiplanStatus({
cycle: [{
date: '2018-09-01',
bleeding: { value: 0 },
temperature: 37.9
}],
})).to.throw(AssertionError)
})
it('throws if first cycle day does not have valid bleeding value', () => {
expect(() => getSensiplanStatus({
cycle: [{
date: '2018-09-01',
bleeding: { value: 0 }
}],
earlierCycles: [[{
date: '2017-09-23',
bleeding: { value: '1' }
}]]
})).to.throw(AssertionError)
})
})
})
})