diff --git a/android/app/src/main/assets/fonts/Prompt-Light.ttf b/android/app/src/main/assets/fonts/Prompt-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e00a747c0ce227f6bcfdfbfb2e89b75d37295775 Binary files /dev/null and b/android/app/src/main/assets/fonts/Prompt-Light.ttf differ diff --git a/android/app/src/main/assets/fonts/Prompt-Thin.ttf b/android/app/src/main/assets/fonts/Prompt-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..27679994ae658e151594ed8c90dd50341b7ab703 Binary files /dev/null and b/android/app/src/main/assets/fonts/Prompt-Thin.ttf differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 2f5e5f86f3e4bf505b5fa75194a22fb8da092afa..6f1943f11f511295454f0e9be46644b56ab324ff 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -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> \ No newline at end of file diff --git a/assets/bleeding.js b/assets/bleeding.js new file mode 100644 index 0000000000000000000000000000000000000000..d2f6772732d8f5cda4d2788648c5ead1b8180c9b --- /dev/null +++ b/assets/bleeding.js @@ -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> + ) +} diff --git a/assets/cervix.js b/assets/cervix.js new file mode 100644 index 0000000000000000000000000000000000000000..bab3667dc217cc33fec8bd45015311eab254b63e --- /dev/null +++ b/assets/cervix.js @@ -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> + ) +} diff --git a/assets/desire.js b/assets/desire.js new file mode 100644 index 0000000000000000000000000000000000000000..aba2415f38072b60737b16ffdf22e8288939d465 --- /dev/null +++ b/assets/desire.js @@ -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> + ) +} diff --git a/assets/fonts/Prompt-Light.ttf b/assets/fonts/Prompt-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e00a747c0ce227f6bcfdfbfb2e89b75d37295775 Binary files /dev/null and b/assets/fonts/Prompt-Light.ttf differ diff --git a/assets/fonts/Prompt-Thin.ttf b/assets/fonts/Prompt-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..27679994ae658e151594ed8c90dd50341b7ab703 Binary files /dev/null and b/assets/fonts/Prompt-Thin.ttf differ diff --git a/assets/mucus.js b/assets/mucus.js new file mode 100644 index 0000000000000000000000000000000000000000..cc721afef8f04e648b4d7b69200cfba0428e6626 --- /dev/null +++ b/assets/mucus.js @@ -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> + ) +} \ No newline at end of file diff --git a/assets/note.js b/assets/note.js new file mode 100644 index 0000000000000000000000000000000000000000..3413143bf755bddf8ea0c56d0c48d2f106a4048e --- /dev/null +++ b/assets/note.js @@ -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> + ) +} diff --git a/assets/pain.js b/assets/pain.js new file mode 100644 index 0000000000000000000000000000000000000000..dae6ea8fc7584d0fa0d1c9494cfe5fe4c3c84eeb --- /dev/null +++ b/assets/pain.js @@ -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> + ) +} diff --git a/assets/sex.js b/assets/sex.js new file mode 100644 index 0000000000000000000000000000000000000000..db8e175309ad67204ef0f4d3bdd1717293438b42 --- /dev/null +++ b/assets/sex.js @@ -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> + ) +} diff --git a/assets/temperature.js b/assets/temperature.js new file mode 100644 index 0000000000000000000000000000000000000000..403d88ed23ef1e68311dbdc8155800692c0619a7 --- /dev/null +++ b/assets/temperature.js @@ -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> + ) +} diff --git a/components/app.js b/components/app.js index 94472a1114118c9a2ad59e28ee5c0b30e6831848..aea1c6f3311a027e64f57d5243c309c03f0ffd35 100644 --- a/components/app.js +++ b/components/app.js @@ -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> ) diff --git a/components/chart/chart.js b/components/chart/chart.js index 201f0f866a29805107e8890f73ae1fcf37b31282..ccffbba3b9f7096f1450f38fcf914305996bd68d 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -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) } diff --git a/components/chart/day-column.js b/components/chart/day-column.js index f2ed95b54bc2ff783e76b03fcd80ed55ad9ca0f9..ec3dd9595407a35c92c48ec6dc1dcf800ee68fb4 100644 --- a/components/chart/day-column.js +++ b/components/chart/day-column.js @@ -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> ) diff --git a/components/chart/styles.js b/components/chart/styles.js index 8441f9b2db7ea78de4d4a00cb9fc8164982cb8cb..8cc044fbd854b4021743923511acf6ed42f1f0c4 100644 --- a/components/chart/styles.js +++ b/components/chart/styles.js @@ -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', diff --git a/components/chart/y-axis.js b/components/chart/y-axis.js index 12fa08c9a230ac7fe196fac400d41ae78fc7ce5c..e1e0d6f0a1d9a6dffa238adb345d013ed694e7e1 100644 --- a/components/chart/y-axis.js +++ b/components/chart/y-axis.js @@ -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 diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js index 98231651d3f553fe2bb4c8194c04c0b178cc3408..775e299c213c8501033aba00927ddfd42161ee53 100644 --- a/components/cycle-day/cycle-day-overview.js +++ b/components/cycle-day/cycle-day-overview.js @@ -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> diff --git a/components/cycle-day/labels/labels.js b/components/cycle-day/labels/labels.js index 0758cddfcaeeda1a3d48b702bf5a44e3b701acdb..aeff7bc6e1ae1b79a4d4d594d1d132d6b012c869 100644 --- a/components/cycle-day/labels/labels.js +++ b/components/cycle-day/labels/labels.js @@ -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?' diff --git a/components/cycle-day/symptoms/action-button-footer.js b/components/cycle-day/symptoms/action-button-footer.js index d990f480ebe47577730d3054cd9ea7204fa1d73e..15f22eb730733574cf40ed08c6279721866d1e0a 100644 --- a/components/cycle-day/symptoms/action-button-footer.js +++ b/components/cycle-day/symptoms/action-button-footer.js @@ -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 diff --git a/components/cycle-day/symptoms/sex.js b/components/cycle-day/symptoms/sex.js index a51b5c02336ce67a683be36bd33234539e3af663..1e14b0698e35b9e0b15f3a9c9c069c22671973ce 100644 --- a/components/cycle-day/symptoms/sex.js +++ b/components/cycle-day/symptoms/sex.js @@ -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' diff --git a/components/header.js b/components/header.js index 94a01191dfebe37986a27b1e13ff5ef1126738a3..c9da0c734bd802d59e8dfd1b571ac7749c1c949a 100644 --- a/components/header.js +++ b/components/header.js @@ -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 > diff --git a/components/labels.js b/components/labels.js index 7fde2b803482f9b78b683c4352d2e8bda8605400..e728bd52df564ac89b2d338e4d3740bbfe5c049c 100644 --- a/components/labels.js +++ b/components/labels.js @@ -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.', diff --git a/components/menu.js b/components/menu.js index 737203baf8c94e29834457cac0a7489f763408a7..8bdbb43213d5d96098a7b0dcd0a1a17b818df2e8 100644 --- a/components/menu.js +++ b/components/menu.js @@ -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 > ) diff --git a/components/settings/index.js b/components/settings/index.js index e0828d8042e638cc1cf9818097f4c56e56446d35..1180f240d96b51fb3db10e0ab35384a47a1ec094 100644 --- a/components/settings/index.js +++ b/components/settings/index.js @@ -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}> diff --git a/components/settings/period-reminder.js b/components/settings/period-reminder.js new file mode 100644 index 0000000000000000000000000000000000000000..70ec0fcf7bd0c3ac48f0c756212bbe2318de25f6 --- /dev/null +++ b/components/settings/period-reminder.js @@ -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> + ) + } +} \ No newline at end of file diff --git a/db/fixtures.js b/db/fixtures.js index fd4a9492c2627c450048d16499bfe92e2f5fd3c1..421bcb83257526cb43d98733d410c8c43626fa84 100644 --- a/db/fixtures.js +++ b/db/fixtures.js @@ -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 }, diff --git a/db/index.js b/db/index.js index 6dc4feb537bd72e5839e76079d743aba01f0854c..34afd2370b52ac7038eff5687d1c21d593e8fb9e 100644 --- a/db/index.js +++ b/db/index.js @@ -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) diff --git a/db/schema.js b/db/schemas/0.js similarity index 91% rename from db/schema.js rename to db/schemas/0.js index 8e8792ac0e67adc96bb51a96e92f8ed666f888ed..138719582c18dda14fd706ace7fa6a6ee794a981 100644 --- a/db/schema.js +++ b/db/schemas/0.js @@ -127,14 +127,17 @@ const CycleDaySchema = { } } -export default [ - CycleDaySchema, - TemperatureSchema, - BleedingSchema, - MucusSchema, - CervixSchema, - NoteSchema, - DesireSchema, - SexSchema, - PainSchema -] \ No newline at end of file +export default { + schema: [ + CycleDaySchema, + TemperatureSchema, + BleedingSchema, + MucusSchema, + CervixSchema, + NoteSchema, + DesireSchema, + SexSchema, + PainSchema + ], + schemaVersion: 0 +} diff --git a/db/schemas/1.js b/db/schemas/1.js new file mode 100644 index 0000000000000000000000000000000000000000..572a2218a1755cccb0a0b948dc23f03a80bdcc69 --- /dev/null +++ b/db/schemas/1.js @@ -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 + }) + } +} diff --git a/db/schemas/index.js b/db/schemas/index.js new file mode 100644 index 0000000000000000000000000000000000000000..eacf7dd8d7e9439fedfd3c68390f4f9e974625b2 --- /dev/null +++ b/db/schemas/index.js @@ -0,0 +1,4 @@ +import schema0 from './0.js' +import schema1 from './1.js' + +export default [schema0, schema1] \ No newline at end of file diff --git a/ios/drip.xcodeproj/project.pbxproj b/ios/drip.xcodeproj/project.pbxproj index c6a9665d4c43aa6826e3bbf31f5fc17bad00de8a..ff698e3942ce5346b1a5ecd7edb7ae201f4c5c7d 100644 --- a/ios/drip.xcodeproj/project.pbxproj +++ b/ios/drip.xcodeproj/project.pbxproj @@ -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 = ( diff --git a/lib/notifications.js b/lib/notifications.js index dc1a7a52ce2c5f792eabca6b8166471566e20fdd..74427d2dd46c7df63fc5774cdedaa648bd47901d 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -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 + }) + } + } } \ No newline at end of file diff --git a/lib/sympto/index.js b/lib/sympto/index.js index 575b608ea0a28f4235053d93839b607a9df5b737..96e2b49151ab9ae68130d1d969571694bce3a689 100644 --- a/lib/sympto/index.js +++ b/lib/sympto/index.js @@ -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 diff --git a/lib/sympto/minus-8-day-rule.js b/lib/sympto/minus-8-day-rule.js index 2964b373b518fcbe5030d38558d601e73ce3cbe8..5a07d765b9a0852e725f0b06c58f621698f83d52 100644 --- a/lib/sympto/minus-8-day-rule.js +++ b/lib/sympto/minus-8-day-rule.js @@ -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) diff --git a/lib/sympto/pre-ovulatory.js b/lib/sympto/pre-ovulatory.js index 874e2bc911958ca72137ebac1319866991c623ce..236eefdc0a283e604dde03e410c4a17ff91e67d0 100644 --- a/lib/sympto/pre-ovulatory.js +++ b/lib/sympto/pre-ovulatory.js @@ -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 } }) diff --git a/local-storage/index.js b/local-storage/index.js index d41d4d7f23fa8f089f87c7d66fbae7fcc265f5bc..5f4af70e252bc350db736a5729fbb8279b611879 100644 --- a/local-storage/index.js +++ b/local-storage/index.js @@ -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) diff --git a/styles/index.js b/styles/index.js index 463a4c714f8c522ba32b9108379d95867f9cc417..10c90c98bf397a1b1433abef6dc97e3d06febf42 100644 --- a/styles/index.js +++ b/styles/index.js @@ -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, }, } \ No newline at end of file diff --git a/test/sympto/cervix-temp-fixtures.js b/test/sympto/cervix-temp-fixtures.js index 58ff6954b52b6bc2b792bf939018869c0883e30e..18adfe63c38a37fff5ddf9750117591475a3838c 100644 --- a/test/sympto/cervix-temp-fixtures.js +++ b/test/sympto/cervix-temp-fixtures.js @@ -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) diff --git a/test/sympto/cervix-temp.spec.js b/test/sympto/cervix-temp.spec.js index e2d8fa04126a0a3c57dbc59f2fc02b15bf886cf2..964b1d6d007dc1fd455ec0a68aa6e4bf2450933b 100644 --- a/test/sympto/cervix-temp.spec.js +++ b/test/sympto/cervix-temp.spec.js @@ -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) + }) + }) }) })