Compare commits

..

7 Commits

Author SHA1 Message Date
bl00dymarie a152ca0f40 Adapt note field for temperature and note 2024-02-20 13:38:03 +01:00
bl00dymarie 60010f9760 Allow more lines for "other" under mood, pain, sex 2024-02-15 18:22:12 +01:00
bl00dymarie 2ee08c9769 Limit lines to 3 for symptom day boxes 2024-02-15 18:20:10 +01:00
bl00dymarie 3020967ed2 Add multiline to notes in edit view 2024-02-15 18:19:47 +01:00
bl00dymarie dc2f867c2c Update version to 1.2402.15 2024-02-15 18:07:22 +01:00
bl00dymarie 90ad1cb12f Add KeyboardAvoidingView for visible TextInput 2024-02-15 17:38:08 +01:00
bl00dymarie 010cabcefb Allow scrolling in note text field 2024-02-15 17:37:15 +01:00
39 changed files with 350 additions and 864 deletions
+1 -2
View File
@@ -59,9 +59,8 @@ buck-out/
# Bundle artifact # Bundle artifact
*.jsbundle *.jsbundle
# Ruby / CocoaPods # CocoaPods
/ios/Pods/ /ios/Pods/
/vendor/bundle/
# RN android release # RN android release
android/app/bin/ android/app/bin/
+2 -2
View File
@@ -33,10 +33,9 @@ We are an open source project and we highly appreciate contributions. At the sam
- 🔮 open source - 🔮 open source
- 🩸 feminist and gender inclusive - 🩸 feminist and gender inclusive
- 🔒 secure: data entered stays with that person/on their device - 🔒 secure: data entered stays with that person/on their device
- 🔬 science-based: we implemented the sympto-thermal method - 🔬 science based: we implemented the symptothermal method
This means that we will never implement anything that contradicts these core values. Some examples: We will never build a cloud integration, we will never make an ovulation prediction. This means that we will never implement anything that contradicts these core values. Some examples: We will never build a cloud integration, we will never make an ovulation prediction.
- If you would like to make a sustainable contribution to the project, we would be happy to join the game. - If you would like to make a sustainable contribution to the project, we would be happy to join the game.
### Reporting Bugs or Making Suggestions ### Reporting Bugs or Making Suggestions
@@ -49,6 +48,7 @@ If you found a bug or have suggestions, please :one: first review the [list of e
- If you want to open a merge request, yeah :tada: exciting! We are using a template for merge requests to make sure we explain what we have done and why. - If you want to open a merge request, yeah :tada: exciting! We are using a template for merge requests to make sure we explain what we have done and why.
- Keep in mind that people who will review your merge request are more motivated to do so when the merge request is well explained and ideally not too big. - Keep in mind that people who will review your merge request are more motivated to do so when the merge request is well explained and ideally not too big.
### Thank you ### Thank you
![](https://media.giphy.com/media/kPA88elN9kYco/giphy.gif) ![](https://media.giphy.com/media/kPA88elN9kYco/giphy.gif)
-20
View File
@@ -25,15 +25,6 @@ yarn release
The versionName and versionCode [are defined here](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/android/app/build.gradle#L137-138) and in [package.json](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/package.json#L3). The versionName and versionCode [are defined here](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/android/app/build.gradle#L137-138) and in [package.json](https://gitlab.com/bloodyhealth/drip/-/blob/5401789c46f4a02915ab900ef284581be420451c/package.json#L3).
**Note for iOS**
Update the version number for iOS in `ios/drip/Info.plist` under:
```
<key>CFBundleShortVersionString</key>
<string>1.2403.19</string>
```
## Building in Android ## Building in Android
APK versus AAB APK versus AAB
@@ -84,17 +75,6 @@ yarn sign-android-aab-release
_which is a shortcut for:_ `jarsigner -keystore ./android/app/drip-release-key.keystore ./android/app/build/outputs/bundle/release/app-release.aab drip-release-key` _which is a shortcut for:_ `jarsigner -keystore ./android/app/drip-release-key.keystore ./android/app/build/outputs/bundle/release/app-release.aab drip-release-key`
## Building in iOS
To build an .ipa archive file for an upload to the AppStore you need to go to xCode and select Build -> "Any iOS Device" and under "Product" -> "Archive".
Once the archiving process has completed you can chose to do the following:
"Distribute the app"
- TestFlight & App Store for when you want to upload it for external testing and/or production release
- TestFlight Internal Only for when you want to upload it for internal testing
## Share the release ## Share the release
### Gitlab repository ### Gitlab repository
+2 -2
View File
@@ -134,8 +134,8 @@ android {
applicationId "com.drip" applicationId "com.drip"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 33 versionCode 27
versionName "1.2403.19" versionName "1.2402.15"
ndk { ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
} }
+1 -1
View File
@@ -8,6 +8,6 @@
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:targetApi="28" tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application> </application>
</manifest> </manifest>
@@ -1,10 +1,10 @@
/** /**
* Copyright (c) Meta Platforms, Inc. and affiliates. * Copyright (c) Facebook, Inc. and its affiliates.
* *
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root * <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree. * directory of this source tree.
*/ */
package com.drip; package com.rndiffapp;
import android.content.Context; import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient; import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils; import com.facebook.flipper.android.utils.FlipperUtils;
@@ -18,7 +18,6 @@ import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.network.NetworkingModule;
@@ -47,7 +46,7 @@ public class ReactNativeFlipper {
ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) { if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener( reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() { new ReactInstanceManager.ReactInstanceEventListener() {
@Override @Override
public void onReactContextInitialized(ReactContext reactContext) { public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this); reactInstanceManager.removeReactInstanceEventListener(this);
+3 -3
View File
@@ -57,12 +57,12 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan" android:windowSoftInputMode="adjustPan"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -1,8 +1,6 @@
package com.drip; package com.drip;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
@@ -14,27 +12,4 @@ public class MainActivity extends ReactActivity {
protected String getMainComponentName() { protected String getMainComponentName() {
return "drip"; return "drip";
} }
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
* you can specify the rendered you wish to use (Fabric or the older renderer).
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new MainActivityDelegate(this, getMainComponentName());
} }
public static class MainActivityDelegate extends ReactActivityDelegate {
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
super(activity, mainComponentName);
}
@Override
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
return reactRootView;
}
}
}
@@ -8,7 +8,6 @@ import com.facebook.react.ReactInstanceManager;
import cl.json.ShareApplication; import cl.json.ShareApplication;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
@@ -63,7 +62,7 @@ public class MainApplication extends Application implements ReactApplication, Sh
We use reflection here to pick up the class that initializes Flipper, We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode since Flipper library is not available in release mode
*/ */
Class<?> aClass = Class.forName("com.drip.ReactNativeFlipper"); Class<?> aClass = Class.forName("com.rndiffapp.ReactNativeFlipper");
aClass aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager); .invoke(null, context, reactInstanceManager);
+2 -12
View File
@@ -1,5 +1,3 @@
import org.apache.tools.ant.taskdefs.condition.Os
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
@@ -9,9 +7,8 @@ buildscript {
} }
ext.kotlinVersion = '1.3.40' ext.kotlinVersion = '1.3.40'
dependencies { dependencies {
classpath('com.android.tools.build:gradle:7.0.4') classpath('com.android.tools.build:gradle:7.0.3')
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("de.undercouch:gradle-download-task:4.1.2")
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
@@ -53,12 +50,5 @@ ext {
minSdkVersion = 21 minSdkVersion = 21
compileSdkVersion = 33 compileSdkVersion = 33
targetSdkVersion = 33 targetSdkVersion = 33
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529" ndkVersion = "21.4.7075529"
} }
}
+5 -14
View File
@@ -9,8 +9,8 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m # Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
@@ -25,16 +25,7 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Version of flipper SDK to use with React Native # Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0 FLIPPER_VERSION=0.99.0
# Use this property to specify which architecture you want to build. # https://github.com/facebook/react-native/issues/30729
# You can also override it from the CLI using org.gradle.jvmargs=-Xmx4g
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,6 @@
#Wed Oct 11 14:45:21 CEST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
+112 -160
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/usr/bin/env bash
# #
# Copyright © 2015-2021 the original authors. # Copyright 2015 the original author or authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -17,103 +17,70 @@
# #
############################################################################## ##############################################################################
# ##
# Gradle start up script for POSIX generated by Gradle. ## Gradle start up script for UN*X
# ##
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD="maximum"
warn ( ) { warn ( ) {
echo "$*" echo "$*"
} >&2 }
die ( ) { die ( ) {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} >&2 }
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false case "`uname`" in
case "$( uname )" in #( CYGWIN* )
CYGWIN* ) cygwin=true ;; #( cygwin=true
Darwin* ) darwin=true ;; #( ;;
MSYS* | MINGW* ) msys=true ;; #( Darwin* )
NONSTOP* ) nonstop=true ;; darwin=true
;;
MINGW* )
msys=true
;;
esac esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -121,9 +88,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java JAVACMD="$JAVA_HOME/jre/sh/java"
else else
JAVACMD=$JAVA_HOME/bin/java JAVACMD="$JAVA_HOME/bin/java"
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -132,7 +99,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -140,95 +107,80 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
case $MAX_FD in #( MAX_FD_LIMIT=`ulimit -H -n`
max*) if [ $? -eq 0 ] ; then
MAX_FD=$( ulimit -H -n ) || if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
warn "Could not query maximum file descriptor limit" MAX_FD="$MAX_FD_LIMIT"
esac fi
case $MAX_FD in #( ulimit -n $MAX_FD
'' | soft) :;; #( if [ $? -ne 0 ] ; then
*) warn "Could not set maximum file descriptor limit: $MAX_FD"
ulimit -n "$MAX_FD" || fi
warn "Could not set maximum file descriptor limit to $MAX_FD" else
esac warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi fi
# Collect all arguments for the java command, stacking in reverse order: # For Darwin, add options to specify how the application appears in the dock
# * args from the command line if $darwin; then
# * the main class name GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
# * -classpath fi
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh # We build the pattern for arguments to be converted via cygpath
for arg do ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
if SEP=""
case $arg in #( for dir in $ROOTDIRSRAW ; do
-*) false ;; # don't mess with options #( ROOTDIRS="$ROOTDIRS$SEP$dir"
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath SEP="|"
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi fi
# Collect all arguments for the java command; # Escape application args
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of save () {
# shell script including quotes and variable substitutions, so put them in for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
# double quotes to make sure that they get re-expanded; and echo " "
# * put everything else in single quotes, so that it's not re-expanded. }
APP_ARGS=`save "$@"`
set -- \ # Collect all arguments for the java command, following the shell quoting and substitution rules
"-Dorg.gradle.appname=$APP_BASE_NAME" \ eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
-7
View File
@@ -3,10 +3,3 @@ rootProject.name = 'drip'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app' include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
include(":ReactAndroid")
project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid')
}
+3 -16
View File
@@ -14,10 +14,7 @@ import {
determinePredictionText, determinePredictionText,
formatWithOrdinalSuffix, formatWithOrdinalSuffix,
} from './helpers/home' } from './helpers/home'
import { import { periodPredictionObservable } from '../local-storage'
fertilityTrackingObservable,
periodPredictionObservable,
} from '../local-storage'
import { Colors, Fonts, Sizes, Spacing } from '../styles' import { Colors, Fonts, Sizes, Spacing } from '../styles'
import { LocalDate } from '@js-joda/core' import { LocalDate } from '@js-joda/core'
@@ -31,12 +28,11 @@ const Home = ({ navigate, setDate }) => {
navigate('CycleDay') navigate('CycleDay')
} }
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
const todayDateString = LocalDate.now().toString() const todayDateString = LocalDate.now().toString()
const { getCycleDayNumber, getPredictedMenses } = cycleModule() const { getCycleDayNumber, getPredictedMenses } = cycleModule()
const cycleDayNumber = getCycleDayNumber(todayDateString) const cycleDayNumber = getCycleDayNumber(todayDateString)
const { status, phase, statusText } = const { status, phase, statusText } =
isFertilityTrackingEnabled && getFertilityStatusForDay(todayDateString) getFertilityStatusForDay(todayDateString)
const isPeriodPredictionEnabled = periodPredictionObservable.value const isPeriodPredictionEnabled = periodPredictionObservable.value
const prediction = determinePredictionText(getPredictedMenses(), t) const prediction = determinePredictionText(getPredictedMenses(), t)
@@ -51,7 +47,6 @@ const Home = ({ navigate, setDate }) => {
> >
<AppText style={styles.title}>{moment().format('MMM Do YYYY')}</AppText> <AppText style={styles.title}>{moment().format('MMM Do YYYY')}</AppText>
{/* display if at least 1 bleeding day has been entered */}
{cycleDayNumber && ( {cycleDayNumber && (
<View style={styles.line}> <View style={styles.line}>
<AppText style={styles.whiteSubtitle}>{cycleDayText}</AppText> <AppText style={styles.whiteSubtitle}>{cycleDayText}</AppText>
@@ -60,9 +55,7 @@ const Home = ({ navigate, setDate }) => {
</AppText> </AppText>
</View> </View>
)} )}
{phase && (
{/* display if fertility tracking enabled and if phase 1, 2 or 3 has been identified */}
{isFertilityTrackingEnabled && phase && (
<View style={styles.line}> <View style={styles.line}>
<AppText style={styles.whiteSubtitle}> <AppText style={styles.whiteSubtitle}>
{formatWithOrdinalSuffix(phase)} {formatWithOrdinalSuffix(phase)}
@@ -74,14 +67,11 @@ const Home = ({ navigate, setDate }) => {
<Asterisk /> <Asterisk />
</View> </View>
)} )}
{isPeriodPredictionEnabled && ( {isPeriodPredictionEnabled && (
<View style={styles.line}> <View style={styles.line}>
<AppText style={styles.turquoiseText}>{prediction}</AppText> <AppText style={styles.turquoiseText}>{prediction}</AppText>
</View> </View>
)} )}
{!isFertilityTrackingEnabled && <View style={styles.largePadding}></View>}
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}> <Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')} {t('labels.home.addDataForToday')}
</Button> </Button>
@@ -120,9 +110,6 @@ const styles = StyleSheet.create({
color: 'white', color: 'white',
fontSize: Sizes.subtitle, fontSize: Sizes.subtitle,
}, },
largePadding: {
padding: Spacing.large,
},
}) })
Home.propTypes = { Home.propTypes = {
-6
View File
@@ -22,8 +22,6 @@ import {
painTrackingCategoryObservable, painTrackingCategoryObservable,
sexTrackingCategoryObservable, sexTrackingCategoryObservable,
temperatureTrackingCategoryObservable, temperatureTrackingCategoryObservable,
mucusTrackingCategoryObservable,
cervixTrackingCategoryObservable,
} from '../../local-storage' } from '../../local-storage'
import { makeColumnInfo } from '../helpers/chart' import { makeColumnInfo } from '../helpers/chart'
@@ -74,10 +72,6 @@ const CycleChart = ({ navigate, setDate }) => {
const symptomRowEnabledSymptoms = symptomRowSymptoms.filter((symptom) => { const symptomRowEnabledSymptoms = symptomRowSymptoms.filter((symptom) => {
if (symptom === 'sex') { if (symptom === 'sex') {
return sexTrackingCategoryObservable.value ? symptom : null return sexTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'mucus') {
return mucusTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'cervix') {
return cervixTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'desire') { } else if (symptom === 'desire') {
return desireTrackingCategoryObservable.value ? symptom : null return desireTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'pain') { } else if (symptom === 'pain') {
+8
View File
@@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import { import {
Dimensions, Dimensions,
KeyboardAvoidingView,
Modal, Modal,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
@@ -19,6 +20,7 @@ const AppModal = ({ children, onClose }) => (
transparent={true} transparent={true}
visible={true} visible={true}
> >
<KeyboardAvoidingView behavior={'padding'} style={styles.safeAreaView}>
<TouchableOpacity onPress={onClose} style={styles.blackBackground} /> <TouchableOpacity onPress={onClose} style={styles.blackBackground} />
<View style={styles.modalWindow}> <View style={styles.modalWindow}>
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
@@ -26,6 +28,7 @@ const AppModal = ({ children, onClose }) => (
</View> </View>
{children} {children}
</View> </View>
</KeyboardAvoidingView>
</Modal> </Modal>
) )
@@ -62,6 +65,11 @@ const styles = StyleSheet.create({
elevation: 2, // works on android elevation: 2, // works on android
minWidth: '80%', minWidth: '80%',
}, },
safeAreaView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
}) })
export default AppModal export default AppModal
+4 -9
View File
@@ -1,13 +1,12 @@
import React from 'react' import React from 'react'
import { Platform, StyleSheet, Switch, View } from 'react-native' import { StyleSheet, Switch, View } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import AppText from './app-text' import AppText from './app-text'
import { Colors, Containers, Spacing } from '../../styles' import { Containers } from '../../styles'
const AppSwitch = ({ onToggle, text, value, disabled }) => { const AppSwitch = ({ onToggle, text, value, trackColor, disabled }) => {
const trackColor = { true: Colors.turquoiseDark }
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.textContainer}> <View style={styles.textContainer}>
@@ -28,20 +27,16 @@ AppSwitch.propTypes = {
onToggle: PropTypes.func.isRequired, onToggle: PropTypes.func.isRequired,
text: PropTypes.string, text: PropTypes.string,
value: PropTypes.bool, value: PropTypes.bool,
trackColor: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
...Containers.rowContainer, ...Containers.rowContainer,
marginTop: Spacing.tiny,
}, },
switch: { switch: {
flex: 1, flex: 1,
transform:
Platform.OS === 'ios'
? [{ scaleX: 0.8 }, { scaleY: 0.8 }]
: [{ scaleX: 1 }, { scaleY: 1 }],
}, },
textContainer: { textContainer: {
flex: 4, flex: 4,
+1 -8
View File
@@ -6,14 +6,13 @@ import AppText from './app-text'
import { Colors, Containers, Spacing, Typography } from '../../styles' import { Colors, Containers, Spacing, Typography } from '../../styles'
const Segment = ({ children, last, title, subheader }) => { const Segment = ({ children, last, title }) => {
const containerStyle = last ? styles.containerLast : styles.container const containerStyle = last ? styles.containerLast : styles.container
const commonStyle = Object.assign({}, containerStyle) const commonStyle = Object.assign({}, containerStyle)
return ( return (
<View style={commonStyle}> <View style={commonStyle}>
{title && <AppText style={styles.title}>{title}</AppText>} {title && <AppText style={styles.title}>{title}</AppText>}
{subheader && <AppText style={styles.subheader}>{subheader}</AppText>}
{children} {children}
</View> </View>
) )
@@ -24,7 +23,6 @@ Segment.propTypes = {
last: PropTypes.bool, last: PropTypes.bool,
style: PropTypes.object, style: PropTypes.object,
title: PropTypes.string, title: PropTypes.string,
subheader: PropTypes.string,
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -41,11 +39,6 @@ const styles = StyleSheet.create({
title: { title: {
...Typography.subtitle, ...Typography.subtitle,
}, },
subheader: {
...Typography.subtitle,
fontWeight: 'bold',
marginBottom: Spacing.zero,
},
}) })
export default Segment export default Segment
@@ -1,63 +0,0 @@
import React from 'react'
import { Platform, StyleSheet, Switch, View } from 'react-native'
import PropTypes from 'prop-types'
import AppText from './app-text'
import DripIcon from '../../assets/drip-icons'
import { Colors, Containers, Sizes, Spacing } from '../../styles'
const TrackingCategorySwitch = ({ onToggle, symptom, text, value }) => {
const trackColor = { true: Colors.turquoiseDark }
const iconColor = value ? Colors.iconColors[symptom].color : Colors.grey
return (
<View style={styles.container}>
<View style={styles.iconContainer}>
<DripIcon
color={iconColor}
name={`drip-icon-${symptom}`}
size={Sizes.title}
/>
</View>
<View style={styles.textContainer}>
<AppText>{text}</AppText>
</View>
<Switch
onValueChange={onToggle}
style={styles.appSwitch}
value={value}
trackColor={trackColor}
/>
</View>
)
}
TrackingCategorySwitch.propTypes = {
onToggle: PropTypes.func.isRequired,
symptom: PropTypes.string,
text: PropTypes.string,
value: PropTypes.bool,
}
const styles = StyleSheet.create({
container: {
...Containers.rowContainer,
marginVertical: Spacing.tiny,
},
iconContainer: {
marginRight: Spacing.tiny,
flex: 1,
},
textContainer: {
flex: 5,
},
appSwitch: {
flex: 2,
transform:
Platform.OS === 'ios'
? [{ scaleX: 0.8 }, { scaleY: 0.8 }]
: [{ scaleX: 1 }, { scaleY: 1 }],
},
})
export default TrackingCategorySwitch
@@ -16,8 +16,6 @@ import {
painTrackingCategoryObservable, painTrackingCategoryObservable,
sexTrackingCategoryObservable, sexTrackingCategoryObservable,
temperatureTrackingCategoryObservable, temperatureTrackingCategoryObservable,
mucusTrackingCategoryObservable,
cervixTrackingCategoryObservable,
} from '../../local-storage' } from '../../local-storage'
import { Spacing } from '../../styles' import { Spacing } from '../../styles'
import { SYMPTOMS } from '../../config' import { SYMPTOMS } from '../../config'
@@ -42,10 +40,6 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
return temperatureTrackingCategoryObservable.value ? symptom : null return temperatureTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'sex') { } else if (symptom === 'sex') {
return sexTrackingCategoryObservable.value ? symptom : null return sexTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'mucus') {
return mucusTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'cervix') {
return cervixTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'desire') { } else if (symptom === 'desire') {
return desireTrackingCategoryObservable.value ? symptom : null return desireTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'pain') { } else if (symptom === 'pain') {
+6 -62
View File
@@ -1,61 +1,22 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native' import { StyleSheet, TouchableOpacity, View } from 'react-native'
import AppText from '../common/app-text' import AppText from '../common/app-text'
import { Colors, Containers } from '../../styles' import { Colors, Containers } from '../../styles'
import labels from '../../i18n/en/settings'
export default function SelectTabGroup({
activeButton,
buttons,
onSelect,
disabled,
}) {
// TODO https://gitlab.com/bloodyhealth/drip/-/issues/707
const oneTimeTransformIntoNumber =
typeof activeButton === 'boolean' && Number(activeButton)
const isSecondarySymptomSwitch =
buttons[0]['label'] === labels.secondarySymptom.mucus
// Disable is only used for secondarySymptom in customization, if more come up maybe consider more tidy solution
const showDisabledAlert = (label) => {
if (
label === labels.secondarySymptom.cervix ||
label === labels.secondarySymptom.mucus
) {
Alert.alert(
labels.secondarySymptom.disabled.title,
labels.secondarySymptom.disabled.message
)
}
}
export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
return ( return (
<View style={styles.container}> <View style={styles.container}>
{buttons.map(({ label, value }, i) => { {buttons.map(({ label, value }, i) => {
const isActive = const isActive = value === activeButton
value === activeButton || value === oneTimeTransformIntoNumber const boxStyle = [styles.box, isActive && styles.boxActive]
const boxStyle = [ const textStyle = [styles.text, isActive && styles.textActive]
styles.box,
isActive && styles.boxActive,
isSecondarySymptomSwitch && styles.purpleBox,
isSecondarySymptomSwitch && isActive && styles.activePurpleBox,
disabled && styles.disabledBox,
]
const textStyle = [
styles.text,
isSecondarySymptomSwitch && styles.purpleText,
isActive && styles.textActive,
disabled && styles.greyText,
]
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => onPress={() => onSelect(value)}
!disabled ? onSelect(value) : showDisabledAlert(label)
}
key={i} key={i}
style={boxStyle} style={boxStyle}
> >
@@ -71,7 +32,6 @@ SelectTabGroup.propTypes = {
activeButton: PropTypes.number, activeButton: PropTypes.number,
buttons: PropTypes.array.isRequired, buttons: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
disabled: PropTypes.bool,
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -90,20 +50,4 @@ const styles = StyleSheet.create({
textActive: { textActive: {
color: 'white', color: 'white',
}, },
purpleBox: {
borderColor: Colors.purple,
},
activePurpleBox: {
backgroundColor: Colors.purple,
},
purpleText: {
color: Colors.purple,
},
greyText: {
color: Colors.grey,
},
disabledBox: {
borderColor: Colors.grey,
backgroundColor: Colors.turquoiseLight,
},
}) })
+1 -1
View File
@@ -20,7 +20,7 @@ const SymptomBox = ({
editedSymptom, editedSymptom,
setEditedSymptom, setEditedSymptom,
}) => { }) => {
const { t } = useTranslation(null, { keyPrefix: 'symptoms' }) const { t } = useTranslation(null, { keyPrefix: 'cycleDay.symptomBox' })
const isSymptomEdited = editedSymptom === symptom const isSymptomEdited = editedSymptom === symptom
const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note' const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
const isExcluded = symptomData !== null ? symptomData.exclude : false const isExcluded = symptomData !== null ? symptomData.exclude : false
+5 -8
View File
@@ -15,7 +15,6 @@ import Temperature from './temperature'
import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day' import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day'
import { showToast } from '../helpers/general' import { showToast } from '../helpers/general'
import { fertilityTrackingObservable } from '../../local-storage'
import { shared as sharedLabels } from '../../i18n/en/labels' import { shared as sharedLabels } from '../../i18n/en/labels'
import info from '../../i18n/en/symptom-info' import info from '../../i18n/en/symptom-info'
import { Colors, Containers, Sizes, Spacing } from '../../styles' import { Colors, Containers, Sizes, Spacing } from '../../styles'
@@ -26,7 +25,6 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
const [shouldShowInfo, setShouldShowInfo] = useState(false) const [shouldShowInfo, setShouldShowInfo] = useState(false)
const getParsedData = () => JSON.parse(JSON.stringify(data)) const getParsedData = () => JSON.parse(JSON.stringify(data))
const onPressLearnMore = () => setShouldShowInfo(!shouldShowInfo) const onPressLearnMore = () => setShouldShowInfo(!shouldShowInfo)
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
const onEditNote = (note) => { const onEditNote = (note) => {
const parsedData = getParsedData() const parsedData = getParsedData()
@@ -112,7 +110,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
const inputProps = { const inputProps = {
multiline: true, multiline: true,
numberOfLines: 3, numberOfLines: 3,
scrollEnabled: false, scrollEnabled: true,
style: styles.input, style: styles.input,
textAlignVertical: 'top', textAlignVertical: 'top',
} }
@@ -169,10 +167,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
</Segment> </Segment>
) )
})} })}
{/* show exclude AppSwitch for bleeding, mucus, cervix, temperature */} {shouldShow(symptomConfig.excludeText) && (
{/* but if fertility is off only for bleeding */}
{shouldShow(symptomConfig.excludeText) &&
(symptom === 'bleeding' || isFertilityTrackingEnabled) && (
<Segment style={styles.segmentBorder}> <Segment style={styles.segmentBorder}>
<AppSwitch <AppSwitch
onToggle={onExcludeToggle} onToggle={onExcludeToggle}
@@ -181,11 +176,13 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
/> />
</Segment> </Segment>
)} )}
{/* this code below applies to the note field in temperature and the note tracking category */}
{shouldShow(symptomConfig.note) && ( {shouldShow(symptomConfig.note) && (
<Segment style={styles.segmentBorder}> <Segment style={styles.segmentBorder}>
<AppText>{symtomPage[symptom].note}</AppText> <AppText>{symtomPage[symptom].note}</AppText>
<AppTextInput <AppTextInput
{...inputProps} {...(symptom === 'temperature' ? { inputProps } : {})}
multiline={true}
onChangeText={onEditNote} onChangeText={onEditNote}
placeholder={sharedLabels.enter} placeholder={sharedLabels.enter}
testID="noteInput" testID="noteInput"
+2 -7
View File
@@ -1,10 +1,6 @@
import { LocalDate } from '@js-joda/core' import { LocalDate } from '@js-joda/core'
import { import { scaleObservable, unitObservable } from '../../local-storage'
fertilityTrackingObservable,
scaleObservable,
unitObservable,
} from '../../local-storage'
import { getCycleStatusForDay } from '../../lib/sympto-adapter' import { getCycleStatusForDay } from '../../lib/sympto-adapter'
import { getCycleDay, getAmountOfCycleDays } from '../../db' import { getCycleDay, getAmountOfCycleDays } from '../../db'
@@ -274,8 +270,7 @@ export function nfpLines() {
if (dateString < cycle.startDate) updateCurrentCycle(dateString) if (dateString < cycle.startDate) updateCurrentCycle(dateString)
if (cycle.noMoreCycles) return ret if (cycle.noMoreCycles) return ret
const tempShift = const tempShift = cycle.status.temperatureShift
fertilityTrackingObservable.value && cycle.status.temperatureShift
if (tempShift) { if (tempShift) {
if (tempShift.firstHighMeasurementDay.date === dateString) { if (tempShift.firstHighMeasurementDay.date === dateString) {
+77 -219
View File
@@ -1,49 +1,38 @@
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { Alert, Pressable, StyleSheet, View } from 'react-native' import { Alert, Pressable } from 'react-native'
import { useTranslation } from 'react-i18next'
import AppIcon from '../../common/app-icon'
import AppPage from '../../common/app-page' import AppPage from '../../common/app-page'
import AppSwitch from '../../common/app-switch' import AppSwitch from '../../common/app-switch'
import AppText from '../../common/app-text' import AppText from '../../common/app-text'
import { Colors, Spacing, Typography } from '../../../styles'
import TemperatureSlider from './temperature-slider' import TemperatureSlider from './temperature-slider'
import Segment from '../../common/segment' import Segment from '../../common/segment'
import TrackingCategorySwitch from '../../common/tracking-category-switch'
import SelectTabGroup from '../../cycle-day/select-tab-group'
import { import {
desireTrackingCategoryObservable, desireTrackingCategoryObservable,
fertilityTrackingObservable,
moodTrackingCategoryObservable, moodTrackingCategoryObservable,
noteTrackingCategoryObservable, noteTrackingCategoryObservable,
painTrackingCategoryObservable, painTrackingCategoryObservable,
sexTrackingCategoryObservable, sexTrackingCategoryObservable,
temperatureTrackingCategoryObservable, temperatureTrackingCategoryObservable,
mucusTrackingCategoryObservable,
cervixTrackingCategoryObservable,
periodPredictionObservable,
useCervixAsSecondarySymptomObservable,
saveDesireTrackingCategory, saveDesireTrackingCategory,
saveFertilityTrackingEnabled,
saveMoodTrackingCategory, saveMoodTrackingCategory,
saveNoteTrackingCategory, saveNoteTrackingCategory,
savePainTrackingCategory, savePainTrackingCategory,
saveMucusTrackingCategory,
saveCervixTrackingCategory,
savePeriodPrediction, savePeriodPrediction,
saveSexTrackingCategory, saveSexTrackingCategory,
saveTemperatureTrackingCategory, saveTemperatureTrackingCategory,
saveUseCervixAsSecondarySymptom, saveUseCervix,
periodPredictionObservable,
useCervixObservable,
} from '../../../local-storage' } from '../../../local-storage'
import { Colors } from '../../../styles'
import labels from '../../../i18n/en/settings' import labels from '../../../i18n/en/settings'
import { SYMPTOMS } from '../../../config' import { SYMPTOMS } from '../../../config'
const Settings = () => { const Settings = () => {
const { t } = useTranslation(null, { keyPrefix: 'symptoms' }) const [shouldUseCervix, setShouldUseCervix] = useState(
useCervixObservable.value
const [useCervixAsSecondarySymptom, setUseCervixAsSecondarySymptom] = )
useState(useCervixAsSecondarySymptomObservable.value)
const [isPeriodPredictionEnabled, setPeriodPrediction] = useState( const [isPeriodPredictionEnabled, setPeriodPrediction] = useState(
periodPredictionObservable.value periodPredictionObservable.value
@@ -52,14 +41,6 @@ const Settings = () => {
const [isTemperatureTrackingCategoryEnabled, setTemperatureTrackingCategory] = const [isTemperatureTrackingCategoryEnabled, setTemperatureTrackingCategory] =
useState(temperatureTrackingCategoryObservable.value) useState(temperatureTrackingCategoryObservable.value)
const [isMucusTrackingCategoryEnabled, setMucusTrackingCategory] = useState(
mucusTrackingCategoryObservable.value
)
const [isCervixTrackingCategoryEnabled, setCervixTrackingCategory] = useState(
cervixTrackingCategoryObservable.value
)
const [isSexTrackingCategoryEnabled, setSexTrackingCategory] = useState( const [isSexTrackingCategoryEnabled, setSexTrackingCategory] = useState(
sexTrackingCategoryObservable.value sexTrackingCategoryObservable.value
) )
@@ -80,33 +61,19 @@ const Settings = () => {
noteTrackingCategoryObservable.value noteTrackingCategoryObservable.value
) )
const [isFertilityTrackingEnabled, setFertilityTrackingEnabled] = useState( const [isEnabled, setIsEnabled] = useState(false)
fertilityTrackingObservable.value const toggleSwitch = () => setIsEnabled((previousState) => !previousState)
)
const fertilityTrackingToggle = (value) => {
setFertilityTrackingEnabled(value)
saveFertilityTrackingEnabled(value)
}
const temperatureTrackingCategoryToggle = (value) => { const temperatureTrackingCategoryToggle = (value) => {
setTemperatureTrackingCategory(value) setTemperatureTrackingCategory(value)
saveTemperatureTrackingCategory(value) saveTemperatureTrackingCategory(value)
if (!value) {
setFertilityTrackingEnabled(false)
saveFertilityTrackingEnabled(false)
}
}
const mucusTrackingCategoryToggle = (value) => {
manageSecondarySymptom(cervixTrackingCategoryObservable.value, value)
}
const cervixTrackingCategoryToggle = (value) => {
manageSecondarySymptom(value, mucusTrackingCategoryObservable.value)
} }
const sexTrackingCategoryToggle = (value) => { const sexTrackingCategoryToggle = (value) => {
setSexTrackingCategory(value) setSexTrackingCategory(value)
saveSexTrackingCategory(value) saveSexTrackingCategory(value)
} }
const desireTrackingCategoryToggle = (value) => { const desireTrackingCategoryToggle = (value) => {
setDesireTrackingCategory(value) setDesireTrackingCategory(value)
saveDesireTrackingCategory(value) saveDesireTrackingCategory(value)
@@ -123,229 +90,120 @@ const Settings = () => {
setNoteTrackingCategory(value) setNoteTrackingCategory(value)
saveNoteTrackingCategory(value) saveNoteTrackingCategory(value)
} }
const onPeriodPredictionToggle = (value) => { const onPeriodPredictionToggle = (value) => {
setPeriodPrediction(value) setPeriodPrediction(value)
savePeriodPrediction(value) savePeriodPrediction(value)
} }
const fertilityTrackingText = isFertilityTrackingEnabled
? labels.fertilityTracking.on
: labels.fertilityTracking.off
const periodPredictionText = isPeriodPredictionEnabled const periodPredictionText = isPeriodPredictionEnabled
? labels.periodPrediction.on ? labels.periodPrediction.on
: labels.periodPrediction.off : labels.periodPrediction.off
const secondarySymptomButtons = [ const onCervixToggle = (value) => {
{ setShouldUseCervix(value)
label: labels.secondarySymptom.mucus, saveUseCervix(value)
value: 0,
},
{
label: labels.secondarySymptom.cervix,
value: 1,
},
]
const onSelectTab = (value) => {
if (isMucusTrackingCategoryEnabled && isCervixTrackingCategoryEnabled) {
setUseCervixAsSecondarySymptom(value)
saveUseCervixAsSecondarySymptom(value)
} else {
secondarySymptomDisabledPrompt()
}
} }
// is needed so secondary symptom is set correct on load const cervixText = shouldUseCervix
useEffect(() => { ? labels.useCervix.cervixModeOn
manageSecondarySymptom( : labels.useCervix.cervixModeOff
cervixTrackingCategoryObservable.value,
mucusTrackingCategoryObservable.value
)
}, [])
const manageSecondarySymptom = (cervix, mucus) => {
if (!cervix && mucus) {
setUseCervixAsSecondarySymptom(0)
saveUseCervixAsSecondarySymptom(0)
} else if (cervix && !mucus) {
setUseCervixAsSecondarySymptom(1)
saveUseCervixAsSecondarySymptom(1)
} else if (!cervix && !mucus) {
setFertilityTrackingEnabled(false)
saveFertilityTrackingEnabled(false)
}
setMucusTrackingCategory(mucus)
saveMucusTrackingCategory(mucus)
setCervixTrackingCategory(cervix)
saveCervixTrackingCategory(cervix)
}
const secondarySymptomDisabledPrompt = () => {
if (!isFertilityTrackingEnabled) {
Alert.alert(
labels.secondarySymptom.disabled.title,
labels.secondarySymptom.disabled.message
)
} else if (
!isMucusTrackingCategoryEnabled == isCervixTrackingCategoryEnabled
) {
Alert.alert(
labels.secondarySymptom.disabled.title,
labels.secondarySymptom.disabled.noSecondaryEnabled
)
}
}
const manageFertilityFeature =
isTemperatureTrackingCategoryEnabled &&
(isMucusTrackingCategoryEnabled || isCervixTrackingCategoryEnabled)
const cervixText = useCervixAsSecondarySymptom
? labels.secondarySymptom.cervixModeOn
: labels.secondarySymptom.cervixModeOff
const sliderDisabledPrompt = () => { const sliderDisabledPrompt = () => {
if (!isTemperatureTrackingCategoryEnabled) { if (!isTemperatureTrackingCategoryEnabled) {
Alert.alert(labels.tempScale.disabled, labels.tempScale.disabledMessage) Alert.alert(labels.disabled.title, labels.disabled.message)
} }
} }
const fertilityDisabledPrompt = () => {
if (!manageFertilityFeature) {
Alert.alert(
labels.fertilityTracking.disabledTitle,
labels.fertilityTracking.disabled
)
}
}
return ( return (
<AppPage title={labels.customization.title}> <AppPage title={'Customization'}>
<Segment title={labels.customization.trackingCategories}> <Segment title={'Tracking categories'}>
<TrackingCategorySwitch <AppSwitch
onToggle={temperatureTrackingCategoryToggle} onToggle={temperatureTrackingCategoryToggle}
text={t(SYMPTOMS[1])} text={SYMPTOMS[1]}
value={isTemperatureTrackingCategoryEnabled} value={isTemperatureTrackingCategoryEnabled}
symptom={SYMPTOMS[1]} trackColor={{ true: Colors.turquoiseDark }}
/> />
<TrackingCategorySwitch <AppSwitch
onToggle={(enabled) => {
mucusTrackingCategoryToggle(enabled)
}}
text={t(SYMPTOMS[2])}
value={isMucusTrackingCategoryEnabled}
symptom={SYMPTOMS[2]}
/>
<TrackingCategorySwitch
onToggle={(enabled) => {
cervixTrackingCategoryToggle(enabled)
}}
text={t(SYMPTOMS[3])}
value={isCervixTrackingCategoryEnabled}
symptom={SYMPTOMS[3]}
/>
<TrackingCategorySwitch
onToggle={sexTrackingCategoryToggle} onToggle={sexTrackingCategoryToggle}
text={t(SYMPTOMS[4])} text={SYMPTOMS[4]}
value={isSexTrackingCategoryEnabled} value={isSexTrackingCategoryEnabled}
symptom={SYMPTOMS[4]} trackColor={{ true: Colors.turquoiseDark }}
/> />
<TrackingCategorySwitch <AppSwitch
onToggle={desireTrackingCategoryToggle} onToggle={desireTrackingCategoryToggle}
text={t(SYMPTOMS[5])} text={SYMPTOMS[5]}
value={isDesireTrackingCategoryEnabled} value={isDesireTrackingCategoryEnabled}
symptom={SYMPTOMS[5]} trackColor={{ true: Colors.turquoiseDark }}
/> />
<TrackingCategorySwitch <AppSwitch
onToggle={painTrackingCategoryToggle} onToggle={painTrackingCategoryToggle}
text={t(SYMPTOMS[6])} text={SYMPTOMS[6]}
value={isPainTrackingCategoryEnabled} value={isPainTrackingCategoryEnabled}
symptom={SYMPTOMS[6]} trackColor={{ true: Colors.turquoiseDark }}
/> />
<TrackingCategorySwitch <AppSwitch
onToggle={moodTrackingCategoryToggle} onToggle={moodTrackingCategoryToggle}
text={t(SYMPTOMS[7])} text={SYMPTOMS[7]}
value={isMoodTrackingCategoryEnabled} value={isMoodTrackingCategoryEnabled}
symptom={SYMPTOMS[7]} trackColor={{ true: Colors.turquoiseDark }}
/> />
<TrackingCategorySwitch <AppSwitch
onToggle={noteTrackingCategoryToggle} onToggle={noteTrackingCategoryToggle}
text={t(SYMPTOMS[8])} text={SYMPTOMS[8]}
value={isNoteTrackingCategoryEnabled} value={isNoteTrackingCategoryEnabled}
symptom={SYMPTOMS[8]} trackColor={{ true: Colors.turquoiseDark }}
/> />
</Segment> </Segment>
<Pressable onPress={fertilityDisabledPrompt}> <Segment title={'Fertility feature'}>
<Segment title={labels.fertilityTracking.title}>
<AppText>{labels.fertilityTracking.message}</AppText>
<AppSwitch <AppSwitch
onToggle={fertilityTrackingToggle} onToggle={toggleSwitch}
text={fertilityTrackingText} text={'If turned on ...'}
value={isFertilityTrackingEnabled} value={isEnabled}
disabled={!manageFertilityFeature} trackColor={{ true: Colors.turquoiseDark }}
/> />
</Segment> </Segment>
<Pressable onPress={sliderDisabledPrompt}>
<Segment title={labels.tempScale.segmentTitle}>
{isTemperatureTrackingCategoryEnabled && (
<>
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TemperatureSlider />
</>
)}
{!isTemperatureTrackingCategoryEnabled && (
<AppText>{labels.disabled.message}</AppText>
)}
</Segment>
</Pressable> </Pressable>
<Segment title={labels.periodPrediction.title}> <Pressable onPress={sliderDisabledPrompt}>
<Segment title={labels.useCervix.title}>
{isTemperatureTrackingCategoryEnabled && (
<AppSwitch
onToggle={onCervixToggle}
text={cervixText}
value={shouldUseCervix}
trackColor={{ true: Colors.turquoiseDark }}
/>
)}
{!isTemperatureTrackingCategoryEnabled && (
<AppText>{labels.disabled.message}</AppText>
)}
</Segment>
</Pressable>
<Segment title={labels.periodPrediction.title} last>
<AppSwitch <AppSwitch
onToggle={onPeriodPredictionToggle} onToggle={onPeriodPredictionToggle}
text={periodPredictionText} text={periodPredictionText}
value={isPeriodPredictionEnabled} value={isPeriodPredictionEnabled}
trackColor={{ true: Colors.turquoiseDark }}
/> />
</Segment> </Segment>
<Segment
subheader={labels.customization.subheaderSymptoThermalMethod}
last
></Segment>
<Pressable onPress={sliderDisabledPrompt}>
<Segment title={labels.tempScale.segmentTitle}>
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TemperatureSlider disabled={!isTemperatureTrackingCategoryEnabled} />
</Segment>
</Pressable>
<Pressable onPress={secondarySymptomDisabledPrompt}>
<Segment title={labels.secondarySymptom.title}>
<AppText>{cervixText}</AppText>
<SelectTabGroup
activeButton={useCervixAsSecondarySymptom}
buttons={secondarySymptomButtons}
onSelect={(value) => onSelectTab(value)}
disabled={!isFertilityTrackingEnabled}
/>
</Segment>
</Pressable>
<Segment last>
<View style={styles.line}>
<AppIcon
color={Colors.purple}
name="info-with-circle"
style={styles.icon}
/>
<AppText style={styles.title}>{labels.preOvu.title}</AppText>
</View>
<AppText>{labels.preOvu.note}</AppText>
</Segment>
</AppPage> </AppPage>
) )
} }
export default Settings export default Settings
const styles = StyleSheet.create({
icon: {
marginRight: Spacing.base,
},
line: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
...Typography.subtitle,
},
})
@@ -1,6 +1,5 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import Slider from '@ptomasroos/react-native-multi-slider' import Slider from '@ptomasroos/react-native-multi-slider'
import alertError from '../common/alert-error' import alertError from '../common/alert-error'
@@ -11,7 +10,7 @@ import { Colors, Sizes } from '../../../styles'
import labels from '../../../i18n/en/settings' import labels from '../../../i18n/en/settings'
import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config' import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config'
const TemperatureSlider = ({ disabled }) => { const TemperatureSlider = () => {
const savedValue = scaleObservable.value const savedValue = scaleObservable.value
const [minTemperature, setMinTemperature] = useState(savedValue.min) const [minTemperature, setMinTemperature] = useState(savedValue.min)
const [maxTemperature, setMaxTemperature] = useState(savedValue.max) const [maxTemperature, setMaxTemperature] = useState(savedValue.max)
@@ -26,14 +25,6 @@ const TemperatureSlider = ({ disabled }) => {
} }
} }
const sliderAccentBackground = disabled
? styles.disabledSliderAccentBackground
: styles.sliderAccentBackground
const sliderBackground = disabled
? styles.disabledSliderBackground
: styles.sliderBackground
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Slider <Slider
@@ -44,13 +35,11 @@ const TemperatureSlider = ({ disabled }) => {
max={TEMP_MAX} max={TEMP_MAX}
min={TEMP_MIN} min={TEMP_MIN}
onValuesChange={onTemperatureSliderChange} onValuesChange={onTemperatureSliderChange}
selectedStyle={styles.sliderAccentBackground}
step={TEMP_SLIDER_STEP} step={TEMP_SLIDER_STEP}
trackStyle={styles.slider} trackStyle={styles.slider}
unselectedStyle={styles.sliderBackground}
values={[minTemperature, maxTemperature]} values={[minTemperature, maxTemperature]}
enabledOne={!disabled}
enabledTwo={!disabled}
selectedStyle={sliderAccentBackground}
unselectedStyle={sliderBackground}
/> />
</View> </View>
) )
@@ -58,10 +47,6 @@ const TemperatureSlider = ({ disabled }) => {
export default TemperatureSlider export default TemperatureSlider
TemperatureSlider.propTypes = {
disabled: PropTypes.bool,
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
alignItems: 'center', alignItems: 'center',
@@ -69,7 +54,6 @@ const styles = StyleSheet.create({
}, },
marker: { marker: {
backgroundColor: Colors.turquoiseDark, backgroundColor: Colors.turquoiseDark,
borderRadius: 50, borderRadius: 50,
elevation: 4, elevation: 4,
height: Sizes.subtitle, height: Sizes.subtitle,
@@ -82,13 +66,7 @@ const styles = StyleSheet.create({
sliderAccentBackground: { sliderAccentBackground: {
backgroundColor: Colors.turquoiseDark, backgroundColor: Colors.turquoiseDark,
}, },
disabledSliderAccentBackground: {
backgroundColor: Colors.grey,
},
sliderBackground: { sliderBackground: {
backgroundColor: Colors.turquoise, backgroundColor: Colors.turquoise,
}, },
disabledSliderBackground: {
backgroundColor: Colors.greyLight,
},
}) })
+1
View File
@@ -11,6 +11,7 @@ const menuItems = [
{ label: 'reminders', componentName: 'Reminders' }, { label: 'reminders', componentName: 'Reminders' },
{ label: 'dataManagement', componentName: 'DataManagement' }, { label: 'dataManagement', componentName: 'DataManagement' },
{ label: 'password', componentName: 'Password' }, { label: 'password', componentName: 'Password' },
{ label: 'info', componentName: 'Info' },
] ]
const SettingsMenu = ({ navigate }) => { const SettingsMenu = ({ navigate }) => {
+7 -5
View File
@@ -7,7 +7,8 @@
"chart": { "chart": {
"tutorial": "You can swipe the chart to view more dates." "tutorial": "You can swipe the chart to view more dates."
}, },
"symptoms": { "cycleDay": {
"symptomBox": {
"bleeding": "bleeding", "bleeding": "bleeding",
"temperature": "temperature", "temperature": "temperature",
"mucus": "cervical mucus", "mucus": "cervical mucus",
@@ -17,6 +18,7 @@
"sex": "sex", "sex": "sex",
"pain": "pain", "pain": "pain",
"mood": "mood" "mood": "mood"
}
}, },
"labels": { "labels": {
"bleedingPrediction": { "bleedingPrediction": {
@@ -112,19 +114,19 @@
"menuItem": { "menuItem": {
"dataManagement": { "dataManagement": {
"name": "Data", "name": "Data",
"text": "Import, export or delete your data" "text": "import, export or delete your data"
}, },
"customization": { "customization": {
"name": "Customization", "name": "Customization",
"text": "Define how you want to use drip" "text": "define how you want to use drip"
}, },
"password": { "password": {
"name": "Password", "name": "Password",
"text": "Set, edit or delete your password" "text": "set or edit your password"
}, },
"reminders": { "reminders": {
"name": "Reminders", "name": "Reminders",
"text": "Turn on/off reminders" "text": "turn on/off reminders"
}, },
"info": { "info": {
"name": "Info", "name": "Info",
+38 -44
View File
@@ -4,58 +4,57 @@ export const intensity = ['low', 'medium', 'high']
export const bleeding = { export const bleeding = {
labels: ['spotting', 'light', 'medium', 'heavy'], labels: ['spotting', 'light', 'medium', 'heavy'],
heaviness: { heaviness: {
header: 'Heaviness', header: "Heaviness",
explainer: 'How heavy is the bleeding?', explainer: "How heavy is the bleeding?",
}, },
exclude: { exclude: {
header: 'Exclude', header: "Exclude",
explainer: "You can exclude this value if it's not menstrual bleeding", explainer: "You can exclude this value if it's not menstrual bleeding"
}, }
} }
export const cervix = { export const cervix = {
subcategories: { subcategories: {
opening: 'opening', opening: 'opening',
firmness: 'firmness', firmness: 'firmness',
position: 'position', position: 'position'
}, },
opening: { opening: {
categories: ['closed', 'medium', 'open'], categories: ['closed', 'medium', 'open'],
explainer: 'Is your cervix open or closed?', explainer: 'Is your cervix open or closed?'
}, },
firmness: { firmness: {
categories: ['hard', 'soft'], categories: ['hard', 'soft'],
explainer: "When it's hard, it might feel like the tip of your nose", explainer: "When it's hard, it might feel like the tip of your nose"
}, },
position: { position: {
categories: ['low', 'medium', 'high'], categories: ['low', 'medium', 'high'],
explainer: 'How high up in the vagina is the cervix?', explainer: 'How high up in the vagina is the cervix?'
}, },
excludeExplainer: excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection.",
"You can exclude this value if you don't want to use it for fertility detection.", actionHint: 'Choose values for at least "Opening" and "Firmness" to save.'
} }
export const mucus = { export const mucus = {
subcategories: { subcategories: {
feeling: 'feeling', feeling: 'feeling',
texture: 'texture', texture: 'texture'
}, },
feeling: { feeling: {
categories: ['dry', 'nothing', 'wet', 'slippery'], categories: ['dry', 'nothing', 'wet', 'slippery'],
explainer: 'What does your vaginal entrance feel like?', explainer: 'What does your vaginal entrance feel like?'
}, },
texture: { texture: {
categories: ['nothing', 'creamy', 'egg white'], categories: ['nothing', 'creamy', 'egg white'],
explainer: explainer: "Looking at and touching your cervical mucus, which describes it best?"
'Looking at and touching your cervical mucus, which describes it best?',
}, },
excludeExplainer: excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection",
"You can exclude this value if you don't want to use it for fertility detection", actionHint: 'Choose values for both "Feeling" and "Texture" to save.'
} }
export const desire = { export const desire = {
header: 'Intensity', header: 'Intensity',
explainer: 'How would you rate your sexual desire?', explainer: 'How would you rate your sexual desire?'
} }
export const sex = { export const sex = {
@@ -63,7 +62,7 @@ export const sex = {
solo: 'solo', solo: 'solo',
partner: 'partner', partner: 'partner',
}, },
header: 'Activity', header: "Activity",
explainer: 'Were you sexually active today?', explainer: 'Were you sexually active today?',
} }
@@ -79,8 +78,8 @@ export const contraceptives = {
none: 'none', none: 'none',
other: 'other', other: 'other',
}, },
header: 'Contraceptives', header: "Contraceptives",
explainer: 'Did you use contraceptives?', explainer: 'Did you use contraceptives?'
} }
export const pain = { export const pain = {
@@ -92,9 +91,9 @@ export const pain = {
nausea: 'nausea', nausea: 'nausea',
tenderBreasts: 'tender breasts', tenderBreasts: 'tender breasts',
migraine: 'migraine', migraine: 'migraine',
other: 'other', other: 'other'
}, },
explainer: 'How did your body feel today?', explainer: 'How did your body feel today?'
} }
export const mood = { export const mood = {
@@ -108,39 +107,34 @@ export const mood = {
energetic: 'energetic', energetic: 'energetic',
fatigue: 'fatigue', fatigue: 'fatigue',
angry: 'angry', angry: 'angry',
other: 'other', other: 'other'
}, },
explainer: 'How did you feel today?', explainer: 'How did you feel today?'
} }
export const temperature = { export const temperature = {
outOfRangeWarning: outOfRangeWarning: 'This temperature value is out of the current range for the temperature chart. You can change the range in the settings.',
'This temperature value is out of the current range for the temperature chart. You can change the range in the settings.', outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
outOfAbsoluteRangeWarning:
'This temperature value is too high or low to be shown on the temperature chart.',
temperature: { temperature: {
header: 'Temperature', header: "Temperature",
explainer: explainer: 'Take your temperature right after waking up, before getting out of bed'
'Take your temperature right after waking up, before getting out of bed',
}, },
time: 'Time', time: "Time",
note: { note: {
header: 'Note', header: "Note",
explainer: explainer: 'Is there anything that could have influenced this value, such as bad sleep or alcohol consumption?'
'Is there anything that could have influenced this value, such as bad sleep or alcohol consumption?',
}, },
exclude: { exclude: {
header: 'Exclude', header: "Exclude",
explainer: explainer: "You can exclude this value if you don't want to use it for fertility detection"
"You can exclude this value if you don't want to use it for fertility detection", }
},
} }
export const noteExplainer = 'Anything you want to add for the day?' export const noteExplainer = "Anything you want to add for the day?"
export const general = { export const general = {
cycleDayNumber: 'Cycle day ', cycleDayNumber: "Cycle day ",
today: 'Today', today: "Today"
} }
export const sharedDialogs = { export const sharedDialogs = {
@@ -150,5 +144,5 @@ export const sharedDialogs = {
reallyDeleteData: 'Yes, I am sure', reallyDeleteData: 'Yes, I am sure',
save: 'Save', save: 'Save',
delete: 'Delete', delete: 'Delete',
disabledInfo: 'There is some data missing', disabledInfo: 'There is some data missing'
} }
+12 -35
View File
@@ -1,11 +1,6 @@
import links from './links' import links from './links'
export default { export default {
customization: {
title: 'Customization',
trackingCategories: 'Tracking categories',
subheaderSymptoThermalMethod: 'Sympto-thermal method settings',
},
export: { export: {
errors: { errors: {
noData: 'There is no data to export', noData: 'There is no data to export',
@@ -37,16 +32,17 @@ export default {
tempScale: { tempScale: {
segmentTitle: 'Temperature scale', segmentTitle: 'Temperature scale',
segmentExplainer: segmentExplainer:
'Change the minimum and maximum value for the temperature chart.', 'Change the minimum and maximum value for the temperature chart',
min: 'Min', min: 'Min',
max: 'Max', max: 'Max',
loadError: 'Could not load saved temperature scale settings', loadError: 'Could not load saved temperature scale settings',
saveError: 'Could not save temperature scale settings', saveError: 'Could not save temperature scale settings',
disabled: 'Disabled',
disabledMessage:
'To use the temperature scale please first enable temperature tracking above.',
}, },
disabled: {
title: 'This feature is turned off',
message:
'Please first enable the temperature tracking category in the customization settings.',
},
tempReminder: { tempReminder: {
title: 'Temperature reminder', title: 'Temperature reminder',
noTimeSet: 'Set a time for a daily reminder to take your temperature', noTimeSet: 'Set a time for a daily reminder to take your temperature',
@@ -70,36 +66,17 @@ export default {
'To use the period reminder please first enable period predictions in the customization settings.', 'To use the period reminder please first enable period predictions in the customization settings.',
}, },
}, },
fertilityTracking: { useCervix: {
title: 'Fertility phases calculation',
disabledTitle: 'Disabled',
disabled:
'To use fertility phases calculation please enable both temperature tracking and either cervical mucus or cervix tracking above.',
message:
'If you enter menstrual bleeding, temperature and cervical mucus or cervix data according to the sympto-thermal method, drip will calculate cycle phases with the provided data.',
on: 'If you switch this off, drip will not show fertility related information.',
off: 'If you switch this on, drip will show fertility related information.',
},
secondarySymptom: {
title: 'Secondary symptom', title: 'Secondary symptom',
cervixModeOn: cervixModeOn:
'Cervix values are being used for fertility detection according to the sympto-thermal method.', 'Cervix values are being used for symptothermal fertility detection. You can switch here to use cervical mucus values for symptothermal fertility detection',
cervixModeOff: cervixModeOff:
'Cervical mucus values are being used for fertility detection according to the sympto-thermal method.', 'By default, cervical mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection',
disabled: {
title: 'Disabled',
message:
'To set a secondary symptom please first enable the cervical mucus or cervix tracking category as well as temperature and fertility phases calculation above.',
noSecondaryEnabled:
'To switch the secondary symptom both cervical mucus and cervix need to be enabled above.',
},
mucus: 'cervical mucus',
cervix: 'cervix',
}, },
periodPrediction: { periodPrediction: {
title: 'Period predictions', title: 'Period predictions',
on: 'drip predicts your 3 next menstrual bleedings based on statistics if you previously tracked at least 3 complete cycles.', on: 'drip predicts your 3 next menstrual bleedings based on the statistics of your previously tracked cycles, min 3 complete cycles.',
off: 'There are no predictions for menstrual cycles displayed. If turned on, the calendar and the home screen will display period predictions.', off: 'There are no predictions for menstrual cycles displayed. If turned on the calendar and the home screen will display period predictions.',
}, },
passwordSettings: { passwordSettings: {
title: 'App password', title: 'App password',
@@ -142,6 +119,6 @@ Making any changes to your password setting will keep your data as it was before
}, },
preOvu: { preOvu: {
title: 'Infertile days at cycle start', title: 'Infertile days at cycle start',
note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past sympto-thermal info is available.`, note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past symptothermal info is available.`,
}, },
} }
+2 -2
View File
@@ -25,7 +25,7 @@ export default {
After tracking at least 3 menstrual cycles, drip. will give you an overview of After tracking at least 3 menstrual cycles, drip. will give you an overview of
· how long your cycles last on average (in "stats"), · how long your cycles last on average (in "stats"),
· whether the length of your cycles varied significantly (in "stats" and in bleeding predictions) · whether the length of your cycles varied significantly (in "stats" and in bleeding predictions)
· and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar") if this functionality is enabled in the customization settings. · and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar").
The app allows you to track different intensities of bleeding. On the chart and on the calendar, bleeding values are colored in different shades of red. The darker, the more intense your bleeding. Every bleeding value that is not excluded is taken into account for fertility calculation and period predictions. The app allows you to track different intensities of bleeding. On the chart and on the calendar, bleeding values are colored in different shades of red. The darker, the more intense your bleeding. Every bleeding value that is not excluded is taken into account for fertility calculation and period predictions.
@@ -74,7 +74,7 @@ ${generalInfo.curiousNfp}`,
title: 'Tracking cervical mucus', title: 'Tracking cervical mucus',
text: `Cervical mucus can help determine in which phase of the menstrual cycle you are. text: `Cervical mucus can help determine in which phase of the menstrual cycle you are.
By default the secondary symptom the app uses for the sympto-thermal method is cervical mucus. You can change this in the customization settings. By default the secondary symptom the app uses for the sympto-thermal method is cervical mucus.
· How to identify fertile cervical mucus? · How to identify fertile cervical mucus?
Tracking the feeling and the texture of your cervical mucus on a daily basis helps you identify changes of the quality of the cervical mucus. The values you enter for both feeling and texture of your cervical mucus are combined by drip. into one of five values following the sympto-thermal method. Tracking the feeling and the texture of your cervical mucus on a daily basis helps you identify changes of the quality of the cervical mucus. The values you enter for both feeling and texture of your cervical mucus are combined by drip. into one of five values following the sympto-thermal method.
+1 -1
View File
@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.2403.19</string> <string>1.2402.15</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
+2 -12
View File
@@ -1,15 +1,9 @@
import getFertilityStatus from 'sympto' import getFertilityStatus from 'sympto'
import cycleModule from './cycle' import cycleModule from './cycle'
import { fertilityTrackingObservable, useCervixAsSecondarySymptomObservable } from '../local-storage' import { useCervixObservable } from '../local-storage'
import { fertilityStatus as labels } from '../i18n/en/labels' import { fertilityStatus as labels } from '../i18n/en/labels'
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
export function getFertilityStatusForDay(dateString) { export function getFertilityStatusForDay(dateString) {
if (!isFertilityTrackingEnabled) {
return
}
const status = getCycleStatusForDay(dateString) const status = getCycleStatusForDay(dateString)
if (!status) return { if (!status) return {
status: labels.fertile, status: labels.fertile,
@@ -40,10 +34,6 @@ export function getFertilityStatusForDay(dateString) {
} }
export function getCycleStatusForDay(dateString, opts = {}) { export function getCycleStatusForDay(dateString, opts = {}) {
if (!isFertilityTrackingEnabled) {
return
}
const { const {
getCycleForDay, getCycleForDay,
getCyclesBefore, getCyclesBefore,
@@ -67,7 +57,7 @@ export function getCycleStatusForDay(dateString, opts = {}) {
} }
} }
cycleInfo.secondarySymptom = useCervixAsSecondarySymptomObservable.value ? 'cervix' : 'mucus' cycleInfo.secondarySymptom = useCervixObservable.value ? 'cervix' : 'mucus'
return getFertilityStatus(cycleInfo) return getFertilityStatus(cycleInfo)
} }
+7 -39
View File
@@ -59,19 +59,12 @@ export async function savePeriodPrediction(bool) {
} }
} }
export const useCervixAsSecondarySymptomObservable = Observable() export const useCervixObservable = Observable()
setObvWithInitValue( setObvWithInitValue('useCervix', useCervixObservable, false)
'useCervixAsSecondarySymptom',
useCervixAsSecondarySymptomObservable,
0
)
export async function saveUseCervixAsSecondarySymptom(value) { export async function saveUseCervix(bool) {
await AsyncStorage.setItem( await AsyncStorage.setItem('useCervix', JSON.stringify(bool))
'useCervixAsSecondarySymptom', useCervixObservable.set(bool)
JSON.stringify(value)
)
useCervixAsSecondarySymptomObservable.set(value)
} }
export const hasEncryptionObservable = Observable() export const hasEncryptionObservable = Observable()
@@ -107,30 +100,13 @@ export async function saveTemperatureTrackingCategory(bool) {
temperatureTrackingCategoryObservable.set(bool) temperatureTrackingCategoryObservable.set(bool)
if (!temperatureTrackingCategoryObservable.value) { if (!temperatureTrackingCategoryObservable.value) {
// if temperature tracking is turned off, the temperature reminder gets disabled const result = await AsyncStorage.getItem('tempReminder')
const tempReminderResult = await AsyncStorage.getItem('tempReminder') if (JSON.parse(result).enabled) {
if (tempReminderResult && JSON.parse(tempReminderResult).enabled) {
tempReminderObservable.set(false) tempReminderObservable.set(false)
} }
} }
} }
export const mucusTrackingCategoryObservable = Observable()
setObvWithInitValue('mucus', mucusTrackingCategoryObservable, true)
export async function saveMucusTrackingCategory(bool) {
await AsyncStorage.setItem('mucus', JSON.stringify(bool))
mucusTrackingCategoryObservable.set(bool)
}
export const cervixTrackingCategoryObservable = Observable()
setObvWithInitValue('cervix', cervixTrackingCategoryObservable, true)
export async function saveCervixTrackingCategory(bool) {
await AsyncStorage.setItem('cervix', JSON.stringify(bool))
cervixTrackingCategoryObservable.set(bool)
}
export const sexTrackingCategoryObservable = Observable() export const sexTrackingCategoryObservable = Observable()
setObvWithInitValue('sex', sexTrackingCategoryObservable, true) setObvWithInitValue('sex', sexTrackingCategoryObservable, true)
@@ -171,14 +147,6 @@ export async function saveNoteTrackingCategory(bool) {
noteTrackingCategoryObservable.set(bool) noteTrackingCategoryObservable.set(bool)
} }
export const fertilityTrackingObservable = Observable()
setObvWithInitValue('fertilityTracking', fertilityTrackingObservable, true)
export async function saveFertilityTrackingEnabled(bool) {
await AsyncStorage.setItem('fertilityTracking', JSON.stringify(bool))
fertilityTrackingObservable.set(bool)
}
async function setObvWithInitValue(key, obv, defaultValue) { async function setObvWithInitValue(key, obv, defaultValue) {
const result = await AsyncStorage.getItem(key) const result = await AsyncStorage.getItem(key)
let value let value
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "drip.", "name": "drip.",
"version": "1.2403.19", "version": "1.2402.15",
"contributors": [ "contributors": [
"Julia Friesel <julia.friesel@gmail.com>", "Julia Friesel <julia.friesel@gmail.com>",
"Marie Kochsiek", "Marie Kochsiek",
+1 -5
View File
@@ -18,7 +18,6 @@ const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
const lightGreenColor = '#bccd67' const lightGreenColor = '#bccd67'
const orangeColor = '#bc6642' const orangeColor = '#bc6642'
const mintColor = '#6ca299' const mintColor = '#6ca299'
const turquoiseDark = '#69CBC1'
export default { export default {
greyDark: '#555', greyDark: '#555',
@@ -28,7 +27,7 @@ export default {
orange: '#F38337', orange: '#F38337',
purple: '#3A2671', purple: '#3A2671',
purpleLight: '#938EB2', purpleLight: '#938EB2',
turquoiseDark: turquoiseDark, turquoiseDark: '#69CBC1',
turquoise: '#CFECEA', turquoise: '#CFECEA',
turquoiseLight: '#E9F2ED', turquoiseLight: '#E9F2ED',
iconColors: { iconColors: {
@@ -36,9 +35,6 @@ export default {
color: redColor, color: redColor,
shades: shadesOfRed, shades: shadesOfRed,
}, },
temperature: {
color: turquoiseDark,
},
mucus: { mucus: {
color: violetColor, color: violetColor,
shades: shadesOfViolet, shades: shadesOfViolet,
+1 -2
View File
@@ -1,11 +1,10 @@
import { scale } from 'react-native-size-matters' import { scale } from 'react-native-size-matters'
export default { export default {
zero: '0%',
tiny: scale(4), tiny: scale(4),
small: scale(10), small: scale(10),
base: scale(16), base: scale(16),
large: scale(20), large: scale(20),
symptomTileWidth: '48%', symptomTileWidth: '48%',
textWidth: '70%', textWidth: '70%'
} }