Browse Source

Applicacion completa atemperada a android 34

Carlos J Corrada Bravo 5 months ago
commit
2c60688eb5
100 changed files with 6985 additions and 0 deletions
  1. 10
    0
      .gitignore
  2. 3
    0
      .idea/.gitignore
  3. 1
    0
      .idea/.name
  4. BIN
      .idea/caches/build_file_checksums.ser
  5. BIN
      .idea/caches/gradle_models.ser
  6. 116
    0
      .idea/codeStyles/Project.xml
  7. 6
    0
      .idea/compiler.xml
  8. 7
    0
      .idea/dictionaries/coralyscubero.xml
  9. 21
    0
      .idea/gradle.xml
  10. 60
    0
      .idea/jarRepositories.xml
  11. 54
    0
      .idea/misc.xml
  12. 55
    0
      .idea/navEditor.xml
  13. 1
    0
      app/.gitignore
  14. 130
    0
      app/build.gradle
  15. 40
    0
      app/google-services.json
  16. 21
    0
      app/proguard-rules.pro
  17. BIN
      app/release/app-release.apk
  18. 20
    0
      app/release/output-metadata.json
  19. 28
    0
      app/src/androidTest/java/uprrp/tania/ExampleInstrumentedTest.java
  20. 131
    0
      app/src/main/AndroidManifest.xml
  21. 105
    0
      app/src/main/java/uprrp/tania/App.java
  22. 64
    0
      app/src/main/java/uprrp/tania/GlobalValues.java
  23. 140
    0
      app/src/main/java/uprrp/tania/SNSRegister.java
  24. 193
    0
      app/src/main/java/uprrp/tania/activities/AccountRecoveryActivity.java
  25. 176
    0
      app/src/main/java/uprrp/tania/activities/ExperienceRegistrationActivity.java
  26. 84
    0
      app/src/main/java/uprrp/tania/activities/ForgotPasswordActivity.java
  27. 692
    0
      app/src/main/java/uprrp/tania/activities/GettingStartedActivity.java
  28. 97
    0
      app/src/main/java/uprrp/tania/activities/MainActivity.java
  29. 102
    0
      app/src/main/java/uprrp/tania/activities/ResetPasswordActivity.java
  30. 224
    0
      app/src/main/java/uprrp/tania/activities/UserRegistrationActivity.java
  31. 492
    0
      app/src/main/java/uprrp/tania/fragments/AssessmentsFragment.java
  32. 95
    0
      app/src/main/java/uprrp/tania/fragments/ConsentFragment.java
  33. 42
    0
      app/src/main/java/uprrp/tania/fragments/NotificationPermissionHandler.java
  34. 199
    0
      app/src/main/java/uprrp/tania/fragments/WithdrawFragment.java
  35. 16
    0
      app/src/main/java/uprrp/tania/models/AnsweredAssessmentModel.java
  36. 16
    0
      app/src/main/java/uprrp/tania/models/AnsweredQuestionModel.java
  37. 34
    0
      app/src/main/java/uprrp/tania/models/AssessmentModel.java
  38. 9
    0
      app/src/main/java/uprrp/tania/models/EmptyAssessmentModel.java
  39. 8
    0
      app/src/main/java/uprrp/tania/models/EmptyUserStatusModel.java
  40. 50
    0
      app/src/main/java/uprrp/tania/models/QuestionModel.java
  41. 23
    0
      app/src/main/java/uprrp/tania/models/UserModel.java
  42. 115
    0
      app/src/main/java/uprrp/tania/models/UserStatusModel.java
  43. 145
    0
      app/src/main/java/uprrp/tania/networking/FetchAssessment.java
  44. 153
    0
      app/src/main/java/uprrp/tania/networking/FetchUserStatus.java
  45. 187
    0
      app/src/main/java/uprrp/tania/networking/SendAnswers.java
  46. 219
    0
      app/src/main/java/uprrp/tania/networking/SendConsentForm.java
  47. 142
    0
      app/src/main/java/uprrp/tania/networking/SendExperienceRegistration.java
  48. 144
    0
      app/src/main/java/uprrp/tania/networking/SendLoginCredentials.java
  49. 138
    0
      app/src/main/java/uprrp/tania/networking/SendRecoveryEmail.java
  50. 146
    0
      app/src/main/java/uprrp/tania/networking/SendResetPassword.java
  51. 146
    0
      app/src/main/java/uprrp/tania/networking/SendUserRegistration.java
  52. 140
    0
      app/src/main/java/uprrp/tania/networking/SendWithdrawal.java
  53. 201
    0
      app/src/main/java/uprrp/tania/services/MyFirebaseMessagingService.java
  54. 52
    0
      app/src/main/java/uprrp/tania/unused/AppPrefs.java
  55. 40
    0
      app/src/main/java/uprrp/tania/unused/CognitoSettings.java
  56. 12
    0
      app/src/main/java/uprrp/tania/utils/URLEventListener.java
  57. 7
    0
      app/src/main/res/color/bottom_nav_selector.xml
  58. BIN
      app/src/main/res/drawable-v24/baseline_assessment_white_24dp.png
  59. BIN
      app/src/main/res/drawable-v24/baseline_file_copy_white_24dp.png
  60. 34
    0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  61. BIN
      app/src/main/res/drawable/baseline_clear_white_24dp.png
  62. BIN
      app/src/main/res/drawable/baseline_home_white_24dp.png
  63. BIN
      app/src/main/res/drawable/baseline_info_white_24dp.png
  64. BIN
      app/src/main/res/drawable/baseline_settings_white_24dp.png
  65. 14
    0
      app/src/main/res/drawable/button_background.xml
  66. 12
    0
      app/src/main/res/drawable/button_disabled.xml
  67. 12
    0
      app/src/main/res/drawable/button_press.xml
  68. 12
    0
      app/src/main/res/drawable/button_unfocused.xml
  69. 12
    0
      app/src/main/res/drawable/circle.xml
  70. 9
    0
      app/src/main/res/drawable/ic_home_black_24dp.xml
  71. 170
    0
      app/src/main/res/drawable/ic_launcher_background.xml
  72. 9
    0
      app/src/main/res/drawable/ic_notifications_black_24dp.xml
  73. BIN
      app/src/main/res/drawable/ic_stat_ic_notification.png
  74. 12
    0
      app/src/main/res/drawable/login_rectangle.xml
  75. 7
    0
      app/src/main/res/font/abril_fatface.xml
  76. 7
    0
      app/src/main/res/font/acme.xml
  77. 7
    0
      app/src/main/res/font/amaranth.xml
  78. 7
    0
      app/src/main/res/font/andada.xml
  79. 7
    0
      app/src/main/res/font/arbutus_slab.xml
  80. 7
    0
      app/src/main/res/font/cabin.xml
  81. 118
    0
      app/src/main/res/layout/activity_account_recovery.xml
  82. 65
    0
      app/src/main/res/layout/activity_experience_registration.xml
  83. 57
    0
      app/src/main/res/layout/activity_forgot_password.xml
  84. 141
    0
      app/src/main/res/layout/activity_getting_started.xml
  85. 81
    0
      app/src/main/res/layout/activity_main.xml
  86. 75
    0
      app/src/main/res/layout/activity_reset_password.xml
  87. 308
    0
      app/src/main/res/layout/activity_user_registration.xml
  88. 102
    0
      app/src/main/res/layout/fragment_assessments.xml
  89. 21
    0
      app/src/main/res/layout/fragment_consent.xml
  90. 63
    0
      app/src/main/res/layout/fragment_withdrawal.xml
  91. 6
    0
      app/src/main/res/menu/activity_main.xml
  92. 19
    0
      app/src/main/res/menu/bottom_navigation.xml
  93. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  94. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  95. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  96. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  97. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  98. 25
    0
      app/src/main/res/navigation/mobile_navigation.xml
  99. 14
    0
      app/src/main/res/values/colors.xml
  100. 0
    0
      app/src/main/res/values/dimens.xml

+ 10
- 0
.gitignore View File

@@ -0,0 +1,10 @@
1
+*.iml
2
+.gradle
3
+/local.properties
4
+/.idea/libraries
5
+/.idea/modules.xml
6
+/.idea/workspace.xml
7
+.DS_Store
8
+/build
9
+/captures
10
+.externalNativeBuild

+ 3
- 0
.idea/.gitignore View File

@@ -0,0 +1,3 @@
1
+# Default ignored files
2
+/shelf/
3
+/workspace.xml

+ 1
- 0
.idea/.name View File

@@ -0,0 +1 @@
1
+Tania

BIN
.idea/caches/build_file_checksums.ser View File


BIN
.idea/caches/gradle_models.ser View File


+ 116
- 0
.idea/codeStyles/Project.xml View File

@@ -0,0 +1,116 @@
1
+<component name="ProjectCodeStyleConfiguration">
2
+  <code_scheme name="Project" version="173">
3
+    <codeStyleSettings language="XML">
4
+      <indentOptions>
5
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
6
+      </indentOptions>
7
+      <arrangement>
8
+        <rules>
9
+          <section>
10
+            <rule>
11
+              <match>
12
+                <AND>
13
+                  <NAME>xmlns:android</NAME>
14
+                  <XML_ATTRIBUTE />
15
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
16
+                </AND>
17
+              </match>
18
+            </rule>
19
+          </section>
20
+          <section>
21
+            <rule>
22
+              <match>
23
+                <AND>
24
+                  <NAME>xmlns:.*</NAME>
25
+                  <XML_ATTRIBUTE />
26
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
27
+                </AND>
28
+              </match>
29
+              <order>BY_NAME</order>
30
+            </rule>
31
+          </section>
32
+          <section>
33
+            <rule>
34
+              <match>
35
+                <AND>
36
+                  <NAME>.*:id</NAME>
37
+                  <XML_ATTRIBUTE />
38
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
39
+                </AND>
40
+              </match>
41
+            </rule>
42
+          </section>
43
+          <section>
44
+            <rule>
45
+              <match>
46
+                <AND>
47
+                  <NAME>.*:name</NAME>
48
+                  <XML_ATTRIBUTE />
49
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
50
+                </AND>
51
+              </match>
52
+            </rule>
53
+          </section>
54
+          <section>
55
+            <rule>
56
+              <match>
57
+                <AND>
58
+                  <NAME>name</NAME>
59
+                  <XML_ATTRIBUTE />
60
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
61
+                </AND>
62
+              </match>
63
+            </rule>
64
+          </section>
65
+          <section>
66
+            <rule>
67
+              <match>
68
+                <AND>
69
+                  <NAME>style</NAME>
70
+                  <XML_ATTRIBUTE />
71
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
72
+                </AND>
73
+              </match>
74
+            </rule>
75
+          </section>
76
+          <section>
77
+            <rule>
78
+              <match>
79
+                <AND>
80
+                  <NAME>.*</NAME>
81
+                  <XML_ATTRIBUTE />
82
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
83
+                </AND>
84
+              </match>
85
+              <order>BY_NAME</order>
86
+            </rule>
87
+          </section>
88
+          <section>
89
+            <rule>
90
+              <match>
91
+                <AND>
92
+                  <NAME>.*</NAME>
93
+                  <XML_ATTRIBUTE />
94
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
95
+                </AND>
96
+              </match>
97
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
98
+            </rule>
99
+          </section>
100
+          <section>
101
+            <rule>
102
+              <match>
103
+                <AND>
104
+                  <NAME>.*</NAME>
105
+                  <XML_ATTRIBUTE />
106
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
107
+                </AND>
108
+              </match>
109
+              <order>BY_NAME</order>
110
+            </rule>
111
+          </section>
112
+        </rules>
113
+      </arrangement>
114
+    </codeStyleSettings>
115
+  </code_scheme>
116
+</component>

+ 6
- 0
.idea/compiler.xml View File

@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="CompilerConfiguration">
4
+    <bytecodeTargetLevel target="11" />
5
+  </component>
6
+</project>

+ 7
- 0
.idea/dictionaries/coralyscubero.xml View File

@@ -0,0 +1,7 @@
1
+<component name="ProjectDictionaryState">
2
+  <dictionary name="coralyscubero">
3
+    <words>
4
+      <w>subquestionnair</w>
5
+    </words>
6
+  </dictionary>
7
+</component>

+ 21
- 0
.idea/gradle.xml View File

@@ -0,0 +1,21 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="GradleMigrationSettings" migrationVersion="1" />
4
+  <component name="GradleSettings">
5
+    <option name="linkedExternalProjectsSettings">
6
+      <GradleProjectSettings>
7
+        <option name="testRunner" value="GRADLE" />
8
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
9
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
10
+        <option name="gradleHome" value="/usr/local/Cellar/gradle/8.8/libexec" />
11
+        <option name="gradleJvm" value="Embedded JDK" />
12
+        <option name="modules">
13
+          <set>
14
+            <option value="$PROJECT_DIR$" />
15
+            <option value="$PROJECT_DIR$/app" />
16
+          </set>
17
+        </option>
18
+      </GradleProjectSettings>
19
+    </option>
20
+  </component>
21
+</project>

+ 60
- 0
.idea/jarRepositories.xml View File

@@ -0,0 +1,60 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="RemoteRepositoriesConfiguration">
4
+    <remote-repository>
5
+      <option name="id" value="central" />
6
+      <option name="name" value="Maven Central repository" />
7
+      <option name="url" value="https://repo1.maven.org/maven2" />
8
+    </remote-repository>
9
+    <remote-repository>
10
+      <option name="id" value="jboss.community" />
11
+      <option name="name" value="JBoss Community repository" />
12
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
13
+    </remote-repository>
14
+    <remote-repository>
15
+      <option name="id" value="BintrayJCenter" />
16
+      <option name="name" value="BintrayJCenter" />
17
+      <option name="url" value="https://jcenter.bintray.com/" />
18
+    </remote-repository>
19
+    <remote-repository>
20
+      <option name="id" value="maven" />
21
+      <option name="name" value="maven" />
22
+      <option name="url" value="https://jitpack.io" />
23
+    </remote-repository>
24
+    <remote-repository>
25
+      <option name="id" value="Google" />
26
+      <option name="name" value="Google" />
27
+      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
28
+    </remote-repository>
29
+    <remote-repository>
30
+      <option name="id" value="MavenRepo" />
31
+      <option name="name" value="MavenRepo" />
32
+      <option name="url" value="https://repo.maven.apache.org/maven2/" />
33
+    </remote-repository>
34
+    <remote-repository>
35
+      <option name="id" value="maven2" />
36
+      <option name="name" value="maven2" />
37
+      <option name="url" value="https://mvnrepository.com/artifact/org.sagebionetworks.bridge/researchstack-sdk" />
38
+    </remote-repository>
39
+    <remote-repository>
40
+      <option name="id" value="maven2" />
41
+      <option name="name" value="maven2" />
42
+      <option name="url" value="https://mvnrepository.com/artifact/org.researchstack" />
43
+    </remote-repository>
44
+    <remote-repository>
45
+      <option name="id" value="maven2" />
46
+      <option name="name" value="maven2" />
47
+      <option name="url" value="https://repo.spring.io/plugins-release/" />
48
+    </remote-repository>
49
+    <remote-repository>
50
+      <option name="id" value="maven2" />
51
+      <option name="name" value="maven2" />
52
+      <option name="url" value="https://mvnrepository.com/artifact/org.researchstack/skin" />
53
+    </remote-repository>
54
+    <remote-repository>
55
+      <option name="id" value="maven3" />
56
+      <option name="name" value="maven3" />
57
+      <option name="url" value="https://mvnrepository.com/artifact/org.researchstack/backbone" />
58
+    </remote-repository>
59
+  </component>
60
+</project>

+ 54
- 0
.idea/misc.xml View File

@@ -0,0 +1,54 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="NullableNotNullManager">
4
+    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
5
+    <option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" />
6
+    <option name="myNullables">
7
+      <value>
8
+        <list size="15">
9
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
10
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
11
+          <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
12
+          <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
13
+          <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
14
+          <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
15
+          <item index="6" class="java.lang.String" itemvalue="android.annotation.Nullable" />
16
+          <item index="7" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
17
+          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
18
+          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
19
+          <item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
20
+          <item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
21
+          <item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
22
+          <item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
23
+          <item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
24
+        </list>
25
+      </value>
26
+    </option>
27
+    <option name="myNotNulls">
28
+      <value>
29
+        <list size="14">
30
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
31
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
32
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
33
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
34
+          <item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
35
+          <item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
36
+          <item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
37
+          <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
38
+          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
39
+          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
40
+          <item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
41
+          <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
42
+          <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
43
+          <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
44
+        </list>
45
+      </value>
46
+    </option>
47
+  </component>
48
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
49
+    <output url="file://$PROJECT_DIR$/build/classes" />
50
+  </component>
51
+  <component name="ProjectType">
52
+    <option name="id" value="Android" />
53
+  </component>
54
+</project>

+ 55
- 0
.idea/navEditor.xml View File

@@ -0,0 +1,55 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="navEditor-manualLayoutAlgorithm2">
4
+    <option name="myPositions">
5
+      <map>
6
+        <entry key="mobile_navigation.xml">
7
+          <value>
8
+            <LayoutPositions>
9
+              <option name="myPositions">
10
+                <map>
11
+                  <entry key="navigation_dashboard">
12
+                    <value>
13
+                      <LayoutPositions>
14
+                        <option name="myPosition">
15
+                          <Point>
16
+                            <option name="x" value="256" />
17
+                            <option name="y" value="12" />
18
+                          </Point>
19
+                        </option>
20
+                      </LayoutPositions>
21
+                    </value>
22
+                  </entry>
23
+                  <entry key="navigation_home">
24
+                    <value>
25
+                      <LayoutPositions>
26
+                        <option name="myPosition">
27
+                          <Point>
28
+                            <option name="x" value="12" />
29
+                            <option name="y" value="-5" />
30
+                          </Point>
31
+                        </option>
32
+                      </LayoutPositions>
33
+                    </value>
34
+                  </entry>
35
+                  <entry key="navigation_notifications">
36
+                    <value>
37
+                      <LayoutPositions>
38
+                        <option name="myPosition">
39
+                          <Point>
40
+                            <option name="x" value="12" />
41
+                            <option name="y" value="368" />
42
+                          </Point>
43
+                        </option>
44
+                      </LayoutPositions>
45
+                    </value>
46
+                  </entry>
47
+                </map>
48
+              </option>
49
+            </LayoutPositions>
50
+          </value>
51
+        </entry>
52
+      </map>
53
+    </option>
54
+  </component>
55
+</project>

+ 1
- 0
app/.gitignore View File

@@ -0,0 +1 @@
1
+/build

+ 130
- 0
app/build.gradle View File

@@ -0,0 +1,130 @@
1
+plugins {
2
+    id 'com.android.application'
3
+    id 'com.google.gms.google-services'
4
+//    id 'io.fabric'
5
+    id 'com.google.firebase.crashlytics'
6
+//    id 'kotlin-android'
7
+//    id 'kotlin-kapt'
8
+}
9
+
10
+android {
11
+    namespace 'uprrp.tania'
12
+    compileSdkVersion 34
13
+
14
+    defaultConfig {
15
+        applicationId "uprrp.tania"
16
+        minSdkVersion 30
17
+        targetSdkVersion 34
18
+        versionCode 16
19
+        versionName "4.4.1"
20
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
21
+        vectorDrawables.useSupportLibrary = true
22
+    }
23
+//    configurations.all {
24
+//        resolutionStrategy.eachDependency { details ->
25
+//            if (details.requested.group == 'androidx.appcompat' &&
26
+//                    !details.requested.name.contains('multidex') ) {
27
+//                details.useVersion "1.7.0"
28
+//            }
29
+//        }
30
+//    }
31
+//
32
+    buildTypes {
33
+        release {
34
+            minifyEnabled false
35
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
36
+        }
37
+    }
38
+
39
+    compileOptions {
40
+        sourceCompatibility JavaVersion.VERSION_1_8
41
+        targetCompatibility JavaVersion.VERSION_1_8
42
+    }
43
+
44
+    bundle {
45
+        density {
46
+            enableSplit true
47
+        }
48
+        abi {
49
+            enableSplit true
50
+        }
51
+        language {
52
+            enableSplit false
53
+        }
54
+    }
55
+
56
+    packagingOptions {
57
+        exclude 'META-INF/DEPENDENCIES'
58
+        exclude 'META-INF/LICENSE'
59
+        exclude 'META-INF/LICENSE.txt'
60
+        exclude 'META-INF/license.txt'
61
+        exclude 'META-INF/NOTICE'
62
+        exclude 'META-INF/NOTICE.txt'
63
+        exclude 'META-INF/notice.txt'
64
+        exclude 'META-INF/ASL2.0'
65
+    }
66
+}
67
+
68
+dependencies {
69
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
70
+
71
+    implementation 'androidx.appcompat:appcompat:1.6.1'
72
+    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
73
+    implementation 'androidx.appcompat:appcompat-resources:1.6.1'
74
+
75
+//    implementation 'androidx.appcompat:appcompat:1.7.0'
76
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
77
+    implementation 'com.google.android.material:material:1.12.0'
78
+    implementation 'androidx.navigation:navigation-fragment:2.7.7'
79
+    implementation 'androidx.navigation:navigation-ui:2.7.7'
80
+    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
81
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
82
+
83
+    testImplementation 'junit:junit:4.13.2'
84
+    androidTestImplementation 'androidx.test.ext:junit:1.2.1'
85
+    androidTestImplementation 'androidx.test:rules:1.6.1'
86
+
87
+    implementation platform('com.google.firebase:firebase-bom:33.1.2')
88
+//    implementation 'com.google.firebase:firebase-core:'
89
+    implementation 'com.google.firebase:firebase-analytics'
90
+    implementation 'com.google.firebase:firebase-messaging'
91
+    implementation 'com.google.android.gms:play-services-gcm:17.0.0'
92
+
93
+//    implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'
94
+
95
+    implementation 'org.apache.httpcomponents:httpcore:4.4.16'
96
+    implementation 'org.apache.httpcomponents:httpclient:4.5.14'
97
+
98
+    implementation 'com.amazonaws:aws-android-sdk-sns:2.76.1'
99
+    implementation 'com.amazonaws:aws-android-sdk-core:2.76.1'
100
+    implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.76.1'
101
+
102
+//    implementation 'com.google.android.play:core:1.10.3'
103
+
104
+    implementation 'com.itextpdf:itextg:5.5.10'
105
+    // ResearchStack dependencies
106
+//    implementation 'org.researchstack:skin:1.1.2'
107
+//    implementation 'org.researchstack:backbone:1.1.2'
108
+//    implementation 'com.github.ResearchStack.ResearchStack:backbone:1.1.1'
109
+//    implementation 'com.github.ResearchStack.ResearchStack:skin:1.1.1'
110
+//    implementation 'com.github.ResearchStack:ResearchStack:skin'
111
+//    implementation 'com.github.kpgalligan.Squeaky:squeaky:0.3.1.0'
112
+//    implementation 'com.github.touchlab:squeaky:0.4.0.0'
113
+//    implementation 'com.google.firebase:firebase-crashlytics-gradle:2.9.5'
114
+    implementation 'com.google.gms:google-services:4.4.2'
115
+//    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
116
+//    implementation fileTree(dir: 'libs', include: ['*.jar'])
117
+    implementation('com.github.ResearchStack.ResearchStack:backbone:1.1.1') {
118
+        exclude group: 'co.touchlab.squeaky', module: 'squeaky-query'
119
+        exclude group: 'net.zetetic', module: 'android-database-sqlcipher'
120
+    }
121
+    implementation 'net.zetetic:android-database-sqlcipher:4.5.3' // or the latest version that works
122
+    implementation 'androidx.sqlite:sqlite:2.1.0'
123
+    implementation 'com.github.touchlab:squeaky:0.4.0.2'
124
+//    implementation 'com.github.touchlab:squeaky:0.4.0.2'
125
+
126
+    // Updated PDF viewer library
127
+    implementation 'com.github.mhiew:AndroidPdfViewer:3.1.0-beta.1'
128
+//    implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'
129
+
130
+}

+ 40
- 0
app/google-services.json View File

@@ -0,0 +1,40 @@
1
+{
2
+  "project_info": {
3
+    "project_number": "987669930181",
4
+    "firebase_url": "https://marle-233119.firebaseio.com",
5
+    "project_id": "marle-233119",
6
+    "storage_bucket": "marle-233119.appspot.com"
7
+  },
8
+  "client": [
9
+    {
10
+      "client_info": {
11
+        "mobilesdk_app_id": "1:987669930181:android:a77462b0265ff59fa050ac",
12
+        "android_client_info": {
13
+          "package_name": "uprrp.tania"
14
+        }
15
+      },
16
+      "oauth_client": [
17
+        {
18
+          "client_id": "987669930181-1s4otmblup6puhjqllj3a8efe2a48dg4.apps.googleusercontent.com",
19
+          "client_type": 3
20
+        }
21
+      ],
22
+      "api_key": [
23
+        {
24
+          "current_key": "AIzaSyB8jMdFdMGcCypkiDonLi52nxIHaX8GKik"
25
+        }
26
+      ],
27
+      "services": {
28
+        "appinvite_service": {
29
+          "other_platform_oauth_client": [
30
+            {
31
+              "client_id": "987669930181-1s4otmblup6puhjqllj3a8efe2a48dg4.apps.googleusercontent.com",
32
+              "client_type": 3
33
+            }
34
+          ]
35
+        }
36
+      }
37
+    }
38
+  ],
39
+  "configuration_version": "1"
40
+}

+ 21
- 0
app/proguard-rules.pro View File

@@ -0,0 +1,21 @@
1
+# Add project specific ProGuard rules here.
2
+# You can control the set of applied configuration files using the
3
+# proguardFiles setting in build.gradle.
4
+#
5
+# For more details, see
6
+#   http://developer.android.com/guide/developing/tools/proguard.html
7
+
8
+# If your project uses WebView with JS, uncomment the following
9
+# and specify the fully qualified class name to the JavaScript interface
10
+# class:
11
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12
+#   public *;
13
+#}
14
+
15
+# Uncomment this to preserve the line number information for
16
+# debugging stack traces.
17
+#-keepattributes SourceFile,LineNumberTable
18
+
19
+# If you keep the line number information, uncomment this to
20
+# hide the original source file name.
21
+#-renamesourcefileattribute SourceFile

BIN
app/release/app-release.apk View File


+ 20
- 0
app/release/output-metadata.json View File

@@ -0,0 +1,20 @@
1
+{
2
+  "version": 3,
3
+  "artifactType": {
4
+    "type": "APK",
5
+    "kind": "Directory"
6
+  },
7
+  "applicationId": "uprrp.tania",
8
+  "variantName": "release",
9
+  "elements": [
10
+    {
11
+      "type": "SINGLE",
12
+      "filters": [],
13
+      "attributes": [],
14
+      "versionCode": 16,
15
+      "versionName": "4.4.1",
16
+      "outputFile": "app-release.apk"
17
+    }
18
+  ],
19
+  "elementType": "File"
20
+}

+ 28
- 0
app/src/androidTest/java/uprrp/tania/ExampleInstrumentedTest.java View File

@@ -0,0 +1,28 @@
1
+package uprrp.tania;
2
+
3
+import android.content.Context;
4
+
5
+
6
+import androidx.test.InstrumentationRegistry;
7
+import androidx.test.runner.AndroidJUnit4;
8
+
9
+import org.junit.Test;
10
+import org.junit.runner.RunWith;
11
+
12
+import static org.junit.Assert.assertEquals;
13
+
14
+/**
15
+ * Instrumented test, which will execute on an Android device.
16
+ *
17
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
18
+ */
19
+@RunWith(AndroidJUnit4.class)
20
+public class ExampleInstrumentedTest {
21
+    @Test
22
+    public void useAppContext() {
23
+        // Context of the app under test.
24
+        Context appContext = InstrumentationRegistry.getTargetContext();
25
+
26
+        assertEquals("uprrp.tania", appContext.getPackageName());
27
+    }
28
+}

+ 131
- 0
app/src/main/AndroidManifest.xml View File

@@ -0,0 +1,131 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+    <uses-permission android:name="android.permission.INTERNET" />
5
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
7
+
8
+    <application
9
+        android:name=".App"
10
+        android:allowBackup="true"
11
+        android:icon="@mipmap/ic_launcher"
12
+        android:label="TANIA"
13
+        android:roundIcon="@mipmap/ic_launcher"
14
+        android:supportsRtl="true"
15
+        android:theme="@style/Theme.TANIA"
16
+        android:usesCleartextTraffic="true"
17
+        android:requestLegacyExternalStorage="true">
18
+
19
+        <activity android:name=".activities.GettingStartedActivity"
20
+            android:exported="true">
21
+            <intent-filter>
22
+                <category android:name="android.intent.category.LAUNCHER" />
23
+                <action android:name="android.intent.action.MAIN" />
24
+            </intent-filter>
25
+        </activity>
26
+
27
+        <activity android:name=".activities.MainActivity"
28
+            android:exported="true">
29
+            <intent-filter>
30
+                <action android:name="android.intent.action.VIEW" />
31
+                <category android:name="android.intent.category.DEFAULT" />
32
+                <category android:name="android.intent.category.BROWSABLE" />
33
+                <data
34
+                    android:host="main_activity"
35
+                    android:scheme="tania" />
36
+            </intent-filter>
37
+        </activity>
38
+
39
+        <activity
40
+            android:name="org.researchstack.backbone.ui.ViewTaskActivity"
41
+            android:parentActivityName=".activities.MainActivity"
42
+            android:theme="@style/Theme.TANIA"
43
+            android:windowSoftInputMode="adjustResize"
44
+            android:exported="false" />
45
+
46
+        <activity
47
+            android:name="org.researchstack.backbone.ui.ViewWebDocumentActivity"
48
+            android:label="@string/appName"
49
+            android:parentActivityName=".activities.MainActivity"
50
+            android:theme="@style/Theme.TANIA"
51
+            android:exported="false" />
52
+
53
+        <activity
54
+            android:name=".activities.UserRegistrationActivity"
55
+            android:parentActivityName=".activities.GettingStartedActivity"
56
+            android:exported="true">
57
+            <intent-filter>
58
+                <category android:name="android.intent.category.DEFAULT" />
59
+            </intent-filter>
60
+        </activity>
61
+
62
+        <activity
63
+            android:name=".activities.AccountRecoveryActivity"
64
+            android:parentActivityName=".activities.GettingStartedActivity"
65
+            android:exported="true">
66
+            <intent-filter>
67
+                <category android:name="android.intent.category.DEFAULT" />
68
+            </intent-filter>
69
+        </activity>
70
+
71
+        <activity
72
+            android:name=".activities.ForgotPasswordActivity"
73
+            android:parentActivityName=".activities.AccountRecoveryActivity"
74
+            android:theme="@style/Theme.TANIA"
75
+            android:exported="true">
76
+            <intent-filter>
77
+                <category android:name="android.intent.category.DEFAULT" />
78
+            </intent-filter>
79
+        </activity>
80
+
81
+        <activity
82
+            android:name=".activities.ResetPasswordActivity"
83
+            android:parentActivityName=".activities.ResetPasswordActivity"
84
+            android:theme="@style/Theme.TANIA"
85
+            android:exported="true">
86
+            <intent-filter>
87
+                <action android:name="android.intent.action.VIEW" />
88
+                <category android:name="android.intent.category.DEFAULT" />
89
+                <category android:name="android.intent.category.BROWSABLE" />
90
+                <data
91
+                    android:host="reset_password"
92
+                    android:scheme="tania" />
93
+            </intent-filter>
94
+        </activity>
95
+
96
+        <activity
97
+            android:name=".activities.ExperienceRegistrationActivity"
98
+            android:parentActivityName=".activities.ExperienceRegistrationActivity"
99
+            android:theme="@style/Theme.TANIA"
100
+            android:exported="true">
101
+            <intent-filter>
102
+                <action android:name="android.intent.action.VIEW" />
103
+                <category android:name="android.intent.category.DEFAULT" />
104
+                <category android:name="android.intent.category.BROWSABLE" />
105
+                <data
106
+                    android:host="experiencia"
107
+                    android:scheme="tania" />
108
+            </intent-filter>
109
+        </activity>
110
+
111
+        <meta-data
112
+            android:name="preloaded_fonts"
113
+            android:resource="@array/preloaded_fonts" />
114
+
115
+        <meta-data
116
+            android:name="com.google.firebase.messaging.default_notification_color"
117
+            android:resource="@color/colorAccent" />
118
+        <meta-data
119
+            android:name="com.google.firebase.messaging.default_notification_channel_id"
120
+            android:value="default_notification_channel_id"/>
121
+
122
+        <service
123
+            android:name=".services.MyFirebaseMessagingService"
124
+            android:exported="false">
125
+            <intent-filter>
126
+                <action android:name="com.google.firebase.MESSAGING_EVENT" />
127
+            </intent-filter>
128
+        </service>
129
+    </application>
130
+
131
+</manifest>

+ 105
- 0
app/src/main/java/uprrp/tania/App.java View File

@@ -0,0 +1,105 @@
1
+package uprrp.tania;
2
+
3
+import android.app.Application;
4
+import android.content.Intent;
5
+import android.content.SharedPreferences;
6
+import android.util.Log;
7
+import android.widget.Toast;
8
+
9
+import androidx.annotation.NonNull;
10
+
11
+import com.amazonaws.auth.CognitoCachingCredentialsProvider;
12
+import com.amazonaws.regions.Regions;
13
+import com.google.android.gms.tasks.OnCompleteListener;
14
+import com.google.android.gms.tasks.Task;
15
+import com.google.firebase.messaging.FirebaseMessaging;
16
+
17
+import org.researchstack.backbone.StorageAccess;
18
+import org.researchstack.backbone.storage.database.AppDatabase;
19
+import org.researchstack.backbone.storage.database.sqlite.DatabaseHelper;
20
+import org.researchstack.backbone.storage.file.EncryptionProvider;
21
+import org.researchstack.backbone.storage.file.FileAccess;
22
+import org.researchstack.backbone.storage.file.PinCodeConfig;
23
+import org.researchstack.backbone.storage.file.SimpleFileAccess;
24
+import org.researchstack.backbone.storage.file.UnencryptedProvider;
25
+
26
+import uprrp.tania.activities.GettingStartedActivity;
27
+
28
+public class App extends Application {
29
+
30
+    private static final String TAG = "App";
31
+    private static final String IDENTITY_POOL_ID = "us-east-1:574094cd-0784-4e26-bd14-4fa72ae63579";
32
+    private static final String PREFS_NAME = "prefs";
33
+    private static final String NEEDS_TO_REGISTER_KEY = "needsToRegister";
34
+
35
+    @Override
36
+    public void onCreate() {
37
+        super.onCreate();
38
+
39
+        initializeStorageAccess();
40
+        retrieveFirebaseToken();
41
+        checkAndRedirectForRegistration();
42
+    }
43
+
44
+    private void initializeStorageAccess() {
45
+        PinCodeConfig pinCodeConfig = new PinCodeConfig();
46
+        EncryptionProvider encryptionProvider = new UnencryptedProvider();
47
+        FileAccess fileAccess = new SimpleFileAccess();
48
+        AppDatabase database = new DatabaseHelper(this,
49
+                DatabaseHelper.DEFAULT_NAME,
50
+                null,
51
+                DatabaseHelper.DEFAULT_VERSION);
52
+
53
+        StorageAccess.getInstance().init(pinCodeConfig, encryptionProvider, fileAccess, database);
54
+    }
55
+
56
+    private void retrieveFirebaseToken() {
57
+        Toast.makeText(getApplicationContext(), "Waiting for token...", Toast.LENGTH_LONG).show();
58
+
59
+        FirebaseMessaging.getInstance().getToken()
60
+                .addOnCompleteListener(new OnCompleteListener<String>() {
61
+                    @Override
62
+                    public void onComplete(@NonNull Task<String> task) {
63
+                        if (!task.isSuccessful()) {
64
+                            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
65
+                            Toast.makeText(getApplicationContext(), "Couldn't retrieve token!", Toast.LENGTH_LONG).show();
66
+                            return;
67
+                        }
68
+
69
+                        String token = task.getResult();
70
+                        GlobalValues.getInstance().setDeviceToken(token);
71
+                        Log.d(TAG, "Got token: " + token);
72
+
73
+                        Toast.makeText(getApplicationContext(), "Got token!", Toast.LENGTH_LONG).show();
74
+
75
+                        registerWithSNS();
76
+                    }
77
+                });
78
+    }
79
+
80
+    private void registerWithSNS() {
81
+        new Thread(new Runnable() {
82
+            @Override
83
+            public void run() {
84
+                CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
85
+                        getApplicationContext(),
86
+                        IDENTITY_POOL_ID,
87
+                        Regions.US_EAST_1
88
+                );
89
+                SNSRegister snsRegister = new SNSRegister(credentialsProvider);
90
+                snsRegister.execute();
91
+            }
92
+        }).start();
93
+    }
94
+
95
+    private void checkAndRedirectForRegistration() {
96
+        SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
97
+        boolean needsToRegister = prefs.getBoolean(NEEDS_TO_REGISTER_KEY, true);
98
+        if (needsToRegister) {
99
+            Log.d(TAG, "Redirecting to GettingStarted");
100
+            Intent intent = new Intent(this, GettingStartedActivity.class);
101
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
102
+            startActivity(intent);
103
+        }
104
+    }
105
+}

+ 64
- 0
app/src/main/java/uprrp/tania/GlobalValues.java View File

@@ -0,0 +1,64 @@
1
+package uprrp.tania;
2
+
3
+import androidx.annotation.Nullable;
4
+
5
+import java.util.Observable;
6
+
7
+import uprrp.tania.models.UserStatusModel;
8
+
9
+// TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
10
+public class GlobalValues extends Observable {
11
+
12
+    private static GlobalValues mInstance = null;
13
+
14
+    public static GlobalValues getInstance() {
15
+        if(mInstance == null) {
16
+            mInstance = new GlobalValues();
17
+        }
18
+        return mInstance;
19
+    }
20
+
21
+    // WHENEVER ADDING ANOTHER GLOBAL VARIABLE...
22
+    // 1. ADD ITS NAME IN ValueName
23
+    // 2. ADD A DECLARATION (AND INITIALIZATION; OPTIONAL)
24
+    // 3. ADD A GETTER AND A SETTER (TAKE A GOOD LOOK AT THE SETTER)
25
+    public enum ValueName {
26
+        DEVICE_TOKEN,
27
+        USER_STATUS,
28
+    }
29
+
30
+    public static class ValueKey {
31
+        private final ValueName mValueName;
32
+        public ValueKey(ValueName valueName) {
33
+            mValueName = valueName;
34
+        }
35
+        public ValueName getKey() {
36
+            return mValueName;
37
+        }
38
+    }
39
+
40
+    // DEVICE TOKEN
41
+    @Nullable
42
+    private String mDeviceToken;
43
+    public String getDeviceToken() {
44
+        return mDeviceToken;
45
+    }
46
+    public void setDeviceToken(@Nullable String value) {
47
+        mDeviceToken = value;
48
+        this.setChanged();
49
+        this.notifyObservers(new ValueKey(ValueName.DEVICE_TOKEN));
50
+    }
51
+
52
+    // USER STATUS
53
+    @Nullable
54
+    private UserStatusModel mUserStatus;
55
+    public UserStatusModel getUserStatus() {
56
+        return mUserStatus;
57
+    }
58
+    public void setUserStatus(UserStatusModel value) {
59
+        mUserStatus = value;
60
+        this.setChanged();
61
+        this.notifyObservers(new ValueKey(ValueName.USER_STATUS));
62
+    }
63
+
64
+}

+ 140
- 0
app/src/main/java/uprrp/tania/SNSRegister.java View File

@@ -0,0 +1,140 @@
1
+package uprrp.tania;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import com.amazonaws.auth.CognitoCachingCredentialsProvider;
7
+import com.amazonaws.services.sns.AmazonSNSClient;
8
+import com.amazonaws.services.sns.model.CreatePlatformEndpointRequest;
9
+import com.amazonaws.services.sns.model.CreatePlatformEndpointResult;
10
+import com.amazonaws.services.sns.model.GetEndpointAttributesRequest;
11
+import com.amazonaws.services.sns.model.GetEndpointAttributesResult;
12
+import com.amazonaws.services.sns.model.InvalidParameterException;
13
+import com.amazonaws.services.sns.model.NotFoundException;
14
+import com.amazonaws.services.sns.model.SetEndpointAttributesRequest;
15
+
16
+import java.util.HashMap;
17
+import java.util.Map;
18
+import java.util.regex.Matcher;
19
+import java.util.regex.Pattern;
20
+
21
+public class SNSRegister extends AsyncTask <String, Void, String> {
22
+
23
+    private static final String TAG = "SNSRegister";
24
+    AmazonSNSClient client;
25
+    String arnStorage = null;
26
+
27
+    public SNSRegister (CognitoCachingCredentialsProvider credentialsProvider){
28
+        client = new AmazonSNSClient(credentialsProvider);
29
+    }
30
+
31
+    @Override
32
+    protected void onPostExecute(String s) {
33
+        super.onPostExecute(s);
34
+    }
35
+
36
+    @Override
37
+    protected String doInBackground(String... strings) {
38
+        String endpointArn = retrieveEndpointArn();
39
+        String token = GlobalValues.getInstance().getDeviceToken(); // FirebaseInstanceId.getInstance().getToken()
40
+
41
+        boolean updateNeeded = false;
42
+        boolean createNeeded = (null == endpointArn);
43
+
44
+        if (createNeeded) {
45
+            // No platform endpoint ARN is stored; need to call createEndpoint.
46
+            endpointArn = createEndpoint(token);
47
+            createNeeded = false;
48
+        }
49
+
50
+        Log.i(TAG, "Retrieving platform endpoint data...");
51
+        // Look up the platform endpoint and make sure the data in it is current, even if
52
+        // it was just created.
53
+        try {
54
+            GetEndpointAttributesRequest geaReq =
55
+                    new GetEndpointAttributesRequest()
56
+                            .withEndpointArn(endpointArn);
57
+
58
+            GetEndpointAttributesResult geaRes =
59
+                    client.getEndpointAttributes(geaReq);
60
+
61
+            updateNeeded = !geaRes.getAttributes().get("Token").equals(token)
62
+                    || !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
63
+
64
+        } catch (NotFoundException nfe) {
65
+            // We had a stored ARN, but the platform endpoint associated with it
66
+            // disappeared. Recreate it.
67
+            createNeeded = true;
68
+        }
69
+
70
+        if (createNeeded) {
71
+            createEndpoint(token);
72
+        }
73
+
74
+        Log.d(TAG, "updateNeeded = " + updateNeeded);
75
+
76
+        if (updateNeeded) {
77
+            // The platform endpoint is out of sync with the current data;
78
+            // update the token and enable it.
79
+            Log.d(TAG, "Updating platform endpoint " + endpointArn);
80
+            Map attribs = new HashMap();
81
+            attribs.put("Token", token);
82
+            attribs.put("Enabled", "true");
83
+            SetEndpointAttributesRequest saeReq =
84
+                    new SetEndpointAttributesRequest()
85
+                            .withEndpointArn(endpointArn)
86
+                            .withAttributes(attribs);
87
+            client.setEndpointAttributes(saeReq);
88
+        }
89
+
90
+        return endpointArn;
91
+    }
92
+
93
+    /**
94
+     * @return never null
95
+     * */
96
+    private String createEndpoint(String token) {
97
+
98
+        String endpointArn = null;
99
+        try {
100
+            Log.d(TAG, "Creating platform endpoint with token " + token);
101
+            CreatePlatformEndpointRequest cpeReq =
102
+                    new CreatePlatformEndpointRequest()
103
+                            .withPlatformApplicationArn("arn:aws:sns:us-east-1:227586183436:app/GCM/TANIA_Android")
104
+                            .withToken(token);
105
+            CreatePlatformEndpointResult cpeRes = client
106
+                    .createPlatformEndpoint(cpeReq);
107
+            endpointArn = cpeRes.getEndpointArn();
108
+        } catch (InvalidParameterException ipe) {
109
+            String message = ipe.getErrorMessage();
110
+            Log.d(TAG, "Exception message: " + message);
111
+            Pattern p = Pattern
112
+                    .compile(".*Endpoint (arn:aws:sns[^ ]+) already exists " +
113
+                            "with the same token.*");
114
+            Matcher m = p.matcher(message);
115
+            if (m.matches()) {
116
+                // The platform endpoint already exists for this token, but with
117
+                // additional custom data that
118
+                // createEndpoint doesn't want to overwrite. Just use the
119
+                // existing platform endpoint.
120
+                endpointArn = m.group(1);
121
+            } else {
122
+                // Rethrow the exception, the input is actually bad.
123
+                throw ipe;
124
+            }
125
+        }
126
+        storeEndpointArn(endpointArn);
127
+        return endpointArn;
128
+    }
129
+
130
+
131
+    private String retrieveEndpointArn() {
132
+        return arnStorage;
133
+    }
134
+
135
+    private void storeEndpointArn(String endpointArn) {
136
+        arnStorage = endpointArn;
137
+    }
138
+
139
+
140
+}

+ 193
- 0
app/src/main/java/uprrp/tania/activities/AccountRecoveryActivity.java View File

@@ -0,0 +1,193 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.app.ProgressDialog;
4
+import android.content.Context;
5
+import android.content.Intent;
6
+import android.content.SharedPreferences;
7
+import android.os.Bundle;
8
+import android.os.Environment;
9
+import android.util.Base64;
10
+import android.util.Log;
11
+import android.view.View;
12
+import android.widget.Button;
13
+import android.widget.EditText;
14
+import android.widget.Toast;
15
+
16
+import androidx.appcompat.app.AppCompatActivity;
17
+
18
+import java.io.FileNotFoundException;
19
+import java.io.FileOutputStream;
20
+import java.io.IOException;
21
+
22
+import uprrp.tania.GlobalValues;
23
+import uprrp.tania.R;
24
+import uprrp.tania.networking.SendLoginCredentials;
25
+import uprrp.tania.utils.URLEventListener;
26
+
27
+// For basic Android functionality
28
+import android.content.Context;
29
+import android.os.Bundle;
30
+import android.util.Log;
31
+import android.view.LayoutInflater;
32
+import android.view.View;
33
+import android.view.ViewGroup;
34
+import android.widget.Toast;
35
+
36
+// For file operations
37
+import java.io.File;
38
+import java.io.FileOutputStream;
39
+import java.io.IOException;
40
+import java.io.BufferedInputStream;
41
+import java.io.FileInputStream;
42
+
43
+// For Base64 encoding/decoding
44
+import android.util.Base64;
45
+
46
+// For PDF creation (using iText)
47
+import com.itextpdf.text.Document;
48
+import com.itextpdf.text.DocumentException;
49
+import com.itextpdf.text.Paragraph;
50
+import com.itextpdf.text.pdf.PdfWriter;
51
+import com.itextpdf.text.Font;
52
+import com.itextpdf.text.BaseColor;
53
+import com.itextpdf.text.Element;
54
+import com.itextpdf.text.Chunk;
55
+import com.itextpdf.text.Image;
56
+
57
+// For PDF viewing (using AndroidPdfViewer)
58
+import com.github.barteksc.pdfviewer.PDFView;
59
+import com.github.barteksc.pdfviewer.listener.OnDrawListener;
60
+import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
61
+import com.github.barteksc.pdfviewer.listener.OnPageChangeListener;
62
+import com.github.barteksc.pdfviewer.listener.OnPageErrorListener;
63
+import com.github.barteksc.pdfviewer.listener.OnTapListener;
64
+import com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle;
65
+import com.github.barteksc.pdfviewer.util.FitPolicy;
66
+
67
+// For Fragment
68
+import androidx.annotation.NonNull;
69
+import androidx.annotation.Nullable;
70
+import androidx.fragment.app.Fragment;
71
+
72
+// For drawing and touch events
73
+import android.graphics.Canvas;
74
+import android.view.MotionEvent;
75
+
76
+// Your app's R class for resources
77
+//import your.package.name.R;
78
+public class AccountRecoveryActivity extends AppCompatActivity {
79
+
80
+    private static final String TAG = "AccountRecoveryActivity";
81
+
82
+    @Override
83
+    protected void onCreate(Bundle savedInstanceState) {
84
+
85
+        // Start constructor
86
+        super.onCreate(savedInstanceState);
87
+        setContentView(R.layout.activity_account_recovery);
88
+
89
+        // Change all caps text to normal capitalization and add onClick listeners
90
+        final Button recoverButton = findViewById(R.id.recoverButton);
91
+        final Button forgotPasswordButton = findViewById(R.id.forgotPasswordButton);
92
+        recoverButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
93
+        forgotPasswordButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
94
+        recoverButton.setOnClickListener(new View.OnClickListener() {
95
+            @Override
96
+            public void onClick(View v) {
97
+                sendAccountRecoveryRequest();
98
+            }
99
+        });
100
+        forgotPasswordButton.setOnClickListener(new View.OnClickListener() {
101
+            @Override
102
+            public void onClick(View v) {
103
+                Intent intent = new Intent(AccountRecoveryActivity.this, ForgotPasswordActivity.class);
104
+                startActivity(intent);
105
+            }
106
+        });
107
+
108
+    }
109
+
110
+    private void sendAccountRecoveryRequest() {
111
+
112
+        final EditText emailText = findViewById(R.id.emailTextRecover);
113
+        final EditText passwordText = findViewById(R.id.passwordTextRecover);
114
+
115
+        // Email and password are needed (make sure user knows); else, proceed
116
+        if(emailText.getText().toString().equals("") || passwordText.getText().toString().equals("")) {
117
+            Toast.makeText(AccountRecoveryActivity.this, "All fields are needed!", Toast.LENGTH_LONG).show();
118
+        } else {
119
+
120
+            // Initiate progress dialog
121
+            // TODO: find substitute for this deprecated dialog box
122
+            final ProgressDialog progressDialog = ProgressDialog.show(AccountRecoveryActivity.this,
123
+                    "Recovering Account",
124
+                    getString(R.string.progressDialogDescriptionText));
125
+
126
+            // Send request to server
127
+            SendLoginCredentials accountRecoveryTask = new SendLoginCredentials(new URLEventListener() {
128
+
129
+                @Override
130
+                public void onSuccess(String response) {
131
+
132
+                    progressDialog.dismiss();
133
+                    restoreConsentForm(response);
134
+
135
+                    // Change user preferences to reflect login
136
+                    Context context = AccountRecoveryActivity.this;
137
+                    SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
138
+                    SharedPreferences.Editor editor = prefs.edit();
139
+                    editor.putBoolean("needsToRegister", false);
140
+                    editor.apply();
141
+
142
+                    // Start MainActivity
143
+                    Intent intent = new Intent(context, MainActivity.class);
144
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
145
+                    context.startActivity(intent);
146
+                    Toast.makeText(getApplicationContext(), "Login successful!", Toast.LENGTH_LONG).show();
147
+
148
+                }
149
+
150
+                @Override
151
+                public void onFailure(Exception e) {
152
+                    progressDialog.dismiss();
153
+                    Toast.makeText(AccountRecoveryActivity.this, "Couldn't recover account!\nTry again later with the correct credentials...", Toast.LENGTH_LONG).show();
154
+                    Log.e(TAG, "Error occurred while sending account recovery request to server...");
155
+                    e.printStackTrace();
156
+                }
157
+
158
+            });
159
+
160
+            // Start task
161
+            String deviceToken = GlobalValues.getInstance().getDeviceToken();
162
+            String email = emailText.getText().toString();
163
+            String password = passwordText.getText().toString();
164
+            accountRecoveryTask.execute(deviceToken, email, password);
165
+
166
+        }
167
+
168
+    }
169
+
170
+    // TODO: IO should be moved to a background task
171
+    private void restoreConsentForm(String response) {
172
+        FileOutputStream consentForm = null;
173
+        try {
174
+            byte[] consentBytes = Base64.decode(response, Base64.NO_WRAP);
175
+            File file = new File(getFilesDir(), getString(R.string.consentFileName));
176
+            consentForm = new FileOutputStream(file);
177
+            consentForm.write(consentBytes);
178
+            Log.d(TAG, "Consent form restored successfully");
179
+        } catch (IllegalArgumentException e) {
180
+            Log.e(TAG, "Invalid Base64 string", e);
181
+        } catch (IOException e) {
182
+            Log.e(TAG, "Error writing consent form", e);
183
+        } finally {
184
+            if (consentForm != null) {
185
+                try {
186
+                    consentForm.close();
187
+                } catch (IOException e) {
188
+                    Log.e(TAG, "Error closing FileOutputStream", e);
189
+                }
190
+            }
191
+        }
192
+    }
193
+}

+ 176
- 0
app/src/main/java/uprrp/tania/activities/ExperienceRegistrationActivity.java View File

@@ -0,0 +1,176 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.app.ProgressDialog;
4
+import android.content.Context;
5
+import android.content.Intent;
6
+import android.net.Uri;
7
+import android.os.Bundle;
8
+import android.util.Log;
9
+import android.view.View;
10
+import android.widget.Button;
11
+import android.widget.TextView;
12
+import android.widget.Toast;
13
+
14
+import androidx.annotation.Nullable;
15
+import androidx.appcompat.app.AppCompatActivity;
16
+
17
+import java.util.Observable;
18
+import java.util.Observer;
19
+
20
+import uprrp.tania.GlobalValues;
21
+import uprrp.tania.R;
22
+import uprrp.tania.networking.SendExperienceRegistration;
23
+import uprrp.tania.utils.URLEventListener;
24
+
25
+public class ExperienceRegistrationActivity  extends AppCompatActivity implements Observer {
26
+
27
+    private static final String TAG = "ExperienceRegistrationActivity";
28
+    private String EXPERIENCE_TOKEN;
29
+
30
+    @Override
31
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
32
+
33
+        // Start constructor
34
+        super.onCreate(savedInstanceState);
35
+        setContentView(R.layout.activity_experience_registration);
36
+
37
+
38
+        // Fetch experience token from URL (only if we launched this Activity from Browser)
39
+        Intent intent = getIntent();
40
+        if(Intent.ACTION_VIEW.equals(intent.getAction())) {
41
+            Uri uri = intent.getData();
42
+            assert uri != null; // TODO: figure out if this causes crashes...
43
+            EXPERIENCE_TOKEN = uri.getQueryParameter("id");
44
+            Log.d(TAG, "Experience Token is " + EXPERIENCE_TOKEN);
45
+        } else {
46
+            Log.wtf(TAG, "Activity started from somewhere other than the browser!");
47
+            disableInteraction();
48
+        }
49
+
50
+
51
+        // Listen for device token changes
52
+        this.startObservingGlobals();
53
+
54
+
55
+        // Disable interaction initially while token is being fetched
56
+        if(GlobalValues.getInstance().getDeviceToken() == null) {
57
+            disableInteractionTemporarily();
58
+        } else {
59
+            enableInteraction();
60
+        }
61
+
62
+
63
+        // Change all caps text to normal capitalization and add onClick listener
64
+        final Button experienceRegistrationButton = findViewById(R.id.buttonEnterExperience);
65
+        experienceRegistrationButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
66
+        experienceRegistrationButton.setOnClickListener(new View.OnClickListener() {
67
+            @Override
68
+            public void onClick(View v) {
69
+                sendRegistrationRequest();
70
+            }
71
+        });
72
+
73
+    }
74
+
75
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
76
+    private void startObservingGlobals() {
77
+        GlobalValues.getInstance().addObserver(this);
78
+    }
79
+
80
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
81
+    @Override
82
+    public void update(Observable observable, Object o) {
83
+        if(observable instanceof GlobalValues) {
84
+            if(o instanceof GlobalValues.ValueKey) {
85
+                if(((GlobalValues.ValueKey) o).getKey() == GlobalValues.ValueName.DEVICE_TOKEN) {
86
+                    if(GlobalValues.getInstance().getDeviceToken() == null) {
87
+                        disableInteraction();
88
+                    } else {
89
+                        enableInteraction();
90
+                    }
91
+                }
92
+            }
93
+        }
94
+    }
95
+
96
+    private void enableInteraction() {
97
+        final Button experienceRegistrationButton = findViewById(R.id.buttonEnterExperience);
98
+        final TextView experienceRegistrationTextView = findViewById(R.id.experienceRegistrationText);
99
+
100
+        experienceRegistrationButton.setEnabled(true);
101
+        experienceRegistrationTextView.setText(R.string.experienceRegistrationDescriptionText);
102
+    }
103
+
104
+    private void disableInteraction() {
105
+        final Button experienceRegistrationButton = findViewById(R.id.buttonEnterExperience);
106
+        final TextView experienceRegistrationTextView = findViewById(R.id.experienceRegistrationText);
107
+
108
+        experienceRegistrationButton.setEnabled(false);
109
+        experienceRegistrationTextView.setText(R.string.tokenErrorText);
110
+    }
111
+
112
+    private void disableInteractionTemporarily() {
113
+        final Button experienceRegistrationButton = findViewById(R.id.buttonEnterExperience);
114
+        final TextView experienceRegistrationTextView = findViewById(R.id.experienceRegistrationText);
115
+
116
+        experienceRegistrationButton.setEnabled(false);
117
+        experienceRegistrationTextView.setText(R.string.loadingText);
118
+    }
119
+
120
+    private void sendRegistrationRequest() {
121
+
122
+        // Initiate progress dialog
123
+        // TODO: find substitute for this deprecated dialog box
124
+        final ProgressDialog progressDialog = ProgressDialog.show(ExperienceRegistrationActivity.this,
125
+                "Registering in Experience",
126
+                getString(R.string.progressDialogDescriptionText));
127
+
128
+        // Send registration request to server
129
+        SendExperienceRegistration experienceRegistrationTask = new SendExperienceRegistration(new URLEventListener() {
130
+            @Override
131
+            public void onSuccess() {
132
+                progressDialog.dismiss();
133
+                Context context = ExperienceRegistrationActivity.this;
134
+                Intent intent = new Intent(context, MainActivity.class);
135
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
136
+                context.startActivity(intent);
137
+                Toast.makeText(getApplicationContext(), "You have been registered!", Toast.LENGTH_LONG).show();
138
+            }
139
+
140
+            @Override
141
+            public void onFailure(Exception e) {
142
+                progressDialog.dismiss();
143
+                Toast.makeText(ExperienceRegistrationActivity.this, "Oops! Something went wrong...\nPlease try again", Toast.LENGTH_LONG).show();
144
+                Log.e(TAG, "Error occurred while sending registration request to server...");
145
+                e.printStackTrace();
146
+            }
147
+        });
148
+
149
+        // Start task
150
+        String deviceToken = GlobalValues.getInstance().getDeviceToken();
151
+        String experienceToken = this.EXPERIENCE_TOKEN;
152
+        experienceRegistrationTask.execute(deviceToken, experienceToken);
153
+
154
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
155
+        /*
156
+        Handler handler = new Handler();
157
+        handler.postDelayed(new Runnable() {
158
+            public void run() {
159
+                progressDialog.dismiss();
160
+                if(false) { // Imitate success
161
+                    Context context = ExperienceRegistrationActivity.this;
162
+                    Intent intent = new Intent(context, MainActivity.class);
163
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
164
+                    context.startActivity(intent);
165
+                    Toast.makeText(getApplicationContext(), "You have been registered!", Toast.LENGTH_LONG).show();
166
+                } else { // Imitate failure
167
+                    Toast.makeText(ExperienceRegistrationActivity.this, "Oops! Something went wrong...", Toast.LENGTH_LONG).show();
168
+                }
169
+            }
170
+        }, 5000);
171
+        */
172
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
173
+
174
+    }
175
+
176
+}

+ 84
- 0
app/src/main/java/uprrp/tania/activities/ForgotPasswordActivity.java View File

@@ -0,0 +1,84 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.app.ProgressDialog;
4
+import android.content.Intent;
5
+import android.os.Bundle;
6
+import android.util.Log;
7
+import android.view.View;
8
+import android.widget.Button;
9
+import android.widget.EditText;
10
+import android.widget.Toast;
11
+
12
+import androidx.annotation.Nullable;
13
+import androidx.appcompat.app.AppCompatActivity;
14
+
15
+import uprrp.tania.R;
16
+import uprrp.tania.networking.SendRecoveryEmail;
17
+import uprrp.tania.utils.URLEventListener;
18
+
19
+public class ForgotPasswordActivity extends AppCompatActivity {
20
+
21
+    private static final String TAG = "ForgotPasswordActivity";
22
+
23
+    @Override
24
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
25
+
26
+        // Constructor stuff
27
+        super.onCreate(savedInstanceState);
28
+        setContentView(R.layout.activity_forgot_password);
29
+
30
+        // Change all caps text to normal capitalization and add onClick listeners
31
+        final Button sendEmailRecoveryButton = findViewById(R.id.buttonSendEmail);
32
+        sendEmailRecoveryButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
33
+        sendEmailRecoveryButton.setOnClickListener(new View.OnClickListener() {
34
+            @Override
35
+            public void onClick(View v) {
36
+                sendRecoveryEmail();
37
+            }
38
+        });
39
+
40
+    }
41
+
42
+    private void sendRecoveryEmail() {
43
+
44
+        final EditText emailRecoveryEditText = findViewById(R.id.editTextEmailRecovery);
45
+        String email = emailRecoveryEditText.getText().toString();
46
+
47
+        // Email and password are needed (make sure user knows); else, proceed
48
+        if(email.equals("")) {
49
+            Toast.makeText(getApplicationContext(), "Please enter your email to reset your password.", Toast.LENGTH_LONG).show();
50
+        } else {
51
+
52
+            // Initiate progress dialog
53
+            // TODO: find substitute for this deprecated dialog box
54
+            final ProgressDialog progressDialog = ProgressDialog.show(ForgotPasswordActivity.this,
55
+                    "Verifying Email",
56
+                    getString(R.string.progressDialogDescriptionText));
57
+
58
+            // Define task
59
+            SendRecoveryEmail sendRecoveryEmailTask = new SendRecoveryEmail(new URLEventListener() {
60
+                @Override
61
+                public void onSuccess() {
62
+                    progressDialog.dismiss();
63
+                    Toast.makeText(getApplicationContext(), "Thanks!\nWe'll send you an email to reset your password.", Toast.LENGTH_SHORT).show();
64
+                    Intent intent = new Intent(ForgotPasswordActivity.this, GettingStartedActivity.class);
65
+                    startActivity(intent);
66
+                }
67
+
68
+                @Override
69
+                public void onFailure(Exception e) {
70
+                    progressDialog.dismiss();
71
+                    Toast.makeText(getApplicationContext(), "Error!\nPlease try again (make sure to write your email correctly)", Toast.LENGTH_SHORT).show();
72
+                    Log.e(TAG, "Couldn't send recovery email to server!");
73
+                    e.printStackTrace();
74
+                }
75
+            });
76
+
77
+            // Start task
78
+            sendRecoveryEmailTask.execute(email);
79
+
80
+        }
81
+
82
+    }
83
+
84
+}

+ 692
- 0
app/src/main/java/uprrp/tania/activities/GettingStartedActivity.java View File

@@ -0,0 +1,692 @@
1
+package uprrp.tania.activities;
2
+
3
+//import android.app.ProgressDialog;
4
+import android.content.Intent;
5
+import android.content.SharedPreferences;
6
+import android.content.pm.PackageManager;
7
+import android.net.Uri;
8
+import android.os.Bundle;
9
+        import android.util.Base64;
10
+import android.util.Log;
11
+        import android.widget.Button;
12
+import android.widget.TextView;
13
+import android.widget.Toast;
14
+
15
+import androidx.annotation.Nullable;
16
+import androidx.appcompat.app.AppCompatActivity;
17
+
18
+        import com.itextpdf.text.BaseColor;
19
+import com.itextpdf.text.Chunk;
20
+import com.itextpdf.text.Document;
21
+        import com.itextpdf.text.Element;
22
+import com.itextpdf.text.Font;
23
+import com.itextpdf.text.Image;
24
+import com.itextpdf.text.Paragraph;
25
+import com.itextpdf.text.pdf.PdfWriter;
26
+
27
+import org.researchstack.backbone.StorageAccess;
28
+import org.researchstack.backbone.model.ConsentDocument;
29
+import org.researchstack.backbone.model.ConsentSection;
30
+import org.researchstack.backbone.model.ConsentSignature;
31
+import org.researchstack.backbone.result.TaskResult;
32
+import org.researchstack.backbone.step.ConsentDocumentStep;
33
+import org.researchstack.backbone.step.ConsentSignatureStep;
34
+import org.researchstack.backbone.step.ConsentVisualStep;
35
+import org.researchstack.backbone.task.OrderedTask;
36
+import org.researchstack.backbone.task.Task;
37
+import org.researchstack.backbone.ui.ViewTaskActivity;
38
+import org.researchstack.backbone.ui.step.layout.ConsentSignatureStepLayout;
39
+
40
+import java.io.BufferedInputStream;
41
+import java.io.File;
42
+import java.io.FileInputStream;
43
+        import java.io.FileOutputStream;
44
+import java.io.IOException;
45
+import java.util.Observable;
46
+import java.util.Observer;
47
+
48
+import uprrp.tania.GlobalValues;
49
+import uprrp.tania.R;
50
+import uprrp.tania.networking.SendConsentForm;
51
+import uprrp.tania.utils.URLEventListener;
52
+
53
+public class GettingStartedActivity extends AppCompatActivity implements Observer {
54
+
55
+    private static final String TAG = "GettingStartedActivity";
56
+    private static final int REQUEST_CONSENT = 0;
57
+    public static final String VISUAL_CONSENT_IDENTIFIER = "visual_consent_identifier";
58
+    public static final String CONSENT_DOC = "consent_doc";
59
+    //    public static final String SIGNATURE_FORM_STEP = "form_step";
60
+//    public static final String NAME = "name";
61
+    public static final String CONSENT = "consent";
62
+    public static final String SIGNATURE = "signature";
63
+//    private static final int STORAGE_PERMISSION_CODE = 1;
64
+
65
+    @Override
66
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
67
+
68
+        // Constructor stuff
69
+        super.onCreate(savedInstanceState);
70
+        setContentView(R.layout.activity_getting_started);
71
+
72
+
73
+        // If user is already logged in, go to MainActivity
74
+        final SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
75
+        boolean needsToRegister = prefs.getBoolean("needsToRegister", true);
76
+        if(!needsToRegister) {
77
+            Intent intent = new Intent(GettingStartedActivity.this, MainActivity.class);
78
+            startActivity(intent);
79
+            return;
80
+        }
81
+
82
+
83
+//        // Get permission to write to external storage - files
84
+//        String[] permissions = { Manifest.permission.WRITE_EXTERNAL_STORAGE };
85
+//        requestPermissions(permissions, 1);
86
+
87
+
88
+        // Listen for device token changes
89
+        this.startObservingGlobals();
90
+
91
+
92
+        // Disable interaction initially while token is being fetched
93
+        if(GlobalValues.getInstance().getDeviceToken() == null) {
94
+            disableInteraction();
95
+        } else {
96
+            enableInteraction();
97
+        }
98
+
99
+        TextView privacyPolicyLink = findViewById(R.id.privacy_policy_link);
100
+        privacyPolicyLink.setOnClickListener(v -> openPrivacyPolicy());
101
+
102
+        // Attach onClick listeners to buttons
103
+        final Button createAccountButton = findViewById(R.id.buttonCreateAccount);
104
+        final Button loginAccountButton = findViewById(R.id.buttonRecoverAccount);
105
+        createAccountButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
106
+        loginAccountButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
107
+        createAccountButton.setOnClickListener(v -> {
108
+            boolean alreadyConsented = prefs.getBoolean("alreadyConsented", false);
109
+
110
+            if(alreadyConsented) {
111
+                Intent intent = new Intent(GettingStartedActivity.this, UserRegistrationActivity.class);
112
+                startActivity(intent);
113
+            } else {
114
+                launchConsent();
115
+            }
116
+        });
117
+        loginAccountButton.setOnClickListener(v -> {
118
+            Intent intent = new Intent(GettingStartedActivity.this, AccountRecoveryActivity.class);
119
+            startActivity(intent);
120
+        });
121
+
122
+    }
123
+
124
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
125
+    private void startObservingGlobals() {
126
+        GlobalValues.getInstance().addObserver(this);
127
+    }
128
+
129
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
130
+    @Override
131
+    public void update(Observable observable, Object o) {
132
+        if(observable instanceof GlobalValues) {
133
+            if(o instanceof GlobalValues.ValueKey) {
134
+                if(((GlobalValues.ValueKey) o).getKey() == GlobalValues.ValueName.DEVICE_TOKEN) {
135
+                    if(GlobalValues.getInstance().getDeviceToken() == null) {
136
+                        disableInteraction();
137
+                    } else {
138
+                        enableInteraction();
139
+                    }
140
+                }
141
+            }
142
+        }
143
+    }
144
+
145
+    private void enableInteraction() {
146
+        Button createAccountButton = findViewById(R.id.buttonCreateAccount);
147
+        Button loginAccountButton = findViewById(R.id.buttonRecoverAccount);
148
+        createAccountButton.setEnabled(true);
149
+        loginAccountButton.setEnabled(true);
150
+    }
151
+
152
+    private void disableInteraction() {
153
+        Button createAccountButton = findViewById(R.id.buttonCreateAccount);
154
+        Button loginAccountButton = findViewById(R.id.buttonRecoverAccount);
155
+        createAccountButton.setEnabled(false);
156
+        loginAccountButton.setEnabled(false);
157
+    }
158
+
159
+    @Override
160
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
161
+        super.onActivityResult(requestCode, resultCode, data);
162
+        if(requestCode == REQUEST_CONSENT && resultCode == RESULT_OK) {
163
+            TaskResult taskResult = (TaskResult) data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT);
164
+            assert taskResult != null;
165
+            this.processConsentResult(taskResult);
166
+        }
167
+    }
168
+
169
+
170
+    @Override
171
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
172
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
173
+        if (requestCode == 1) {
174
+            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
175
+                Toast.makeText(getApplicationContext(), "Permission for writing to external storage: DENIED", Toast.LENGTH_LONG).show();
176
+            }
177
+        }
178
+    }
179
+
180
+
181
+    private void processConsentResult(TaskResult result) {
182
+
183
+        boolean consented = (boolean) result.getStepResult(CONSENT_DOC).getResult();
184
+        StorageAccess.getInstance().getAppDatabase().saveTaskResult(result);
185
+
186
+        if(consented) {
187
+
188
+            // Get signature in base64
189
+            TaskResult taskResult = StorageAccess.getInstance()
190
+                    .getAppDatabase()
191
+                    .loadLatestTaskResult(CONSENT);
192
+            String signatureBase64 = (String) taskResult.getStepResult(SIGNATURE)
193
+                    .getResultForIdentifier(ConsentSignatureStepLayout.KEY_SIGNATURE);
194
+
195
+            // Get signature date
196
+            String signatureDate = (String) result.getStepResult(SIGNATURE)
197
+                    .getResultForIdentifier(ConsentSignatureStepLayout.KEY_SIGNATURE_DATE);
198
+
199
+            // Use signature to get encoded consent form
200
+            byte[] signatureBytes = Base64.decode(signatureBase64, Base64.DEFAULT);
201
+            String consentFormBase64 = this.createPdf(signatureBytes, signatureDate);
202
+            restoreConsentForm(consentFormBase64);
203
+            // Send consent form to backend
204
+            this.sendConsentForm(signatureBase64, consentFormBase64, signatureDate);
205
+
206
+        }
207
+
208
+    }
209
+
210
+    private void sendConsentForm(final String signatureBase64, String consentFormBase64, final String signatureDate) {
211
+        if (consentFormBase64 == null || consentFormBase64.isEmpty()) {
212
+            Log.e(TAG, "sendConsentForm: consentFormBase64 is null or empty");
213
+            // Handle this error appropriately, maybe show an error message to the user
214
+            return;
215
+        }
216
+
217
+        Toast.makeText(getApplicationContext(), "Sending consent form...", Toast.LENGTH_LONG).show();
218
+
219
+        // Initiate progress dialog
220
+        // TODO: find substitute for this deprecated dialog box
221
+//        final ProgressDialog progressDialog = ProgressDialog.show(getApplicationContext(),
222
+//                "Sending Consent Form",
223
+//                getString(R.string.progressDialogDescriptionText));
224
+
225
+        SendConsentForm sendConsentFormTask = new SendConsentForm(new URLEventListener() {
226
+            @Override
227
+            public void onSuccess() {
228
+                runOnUiThread(() -> {
229
+                    Toast.makeText(getApplicationContext(), "Consent form sent!", Toast.LENGTH_LONG).show();
230
+
231
+                // Save consent signature, consent date and consented boolean on preferences
232
+                SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
233
+                SharedPreferences.Editor editor = prefs.edit();
234
+                editor.putString("consentSignature", signatureBase64);
235
+                editor.putString("consentDate", signatureDate);
236
+                editor.putBoolean("alreadyConsented", true);
237
+                editor.apply();
238
+
239
+                // Redirect to UserRegistrationActivity
240
+                Intent intent = new Intent(getApplicationContext(), UserRegistrationActivity.class);
241
+                startActivity(intent);
242
+                });
243
+            }
244
+
245
+                @Override
246
+                public void onFailure(Exception e) {
247
+                    runOnUiThread(() -> {
248
+                        Toast.makeText(getApplicationContext(), "Couldn't send consent form to server!\nPlease retry registration...", Toast.LENGTH_LONG).show();
249
+                Log.e(TAG, "Couldn't send consent form to server!");
250
+                e.printStackTrace();
251
+
252
+                // TODO: prompt retry? although user can just register again...
253
+                //  1. when cancelling, return to getting started activity
254
+                //  2. when retrying, just do recursive call
255
+                    });
256
+            }
257
+        });
258
+
259
+        String deviceToken = GlobalValues.getInstance().getDeviceToken();
260
+        sendConsentFormTask.execute(deviceToken, consentFormBase64, signatureDate);
261
+
262
+    }
263
+
264
+    private void launchConsent() {
265
+
266
+
267
+        // Consent Section 1 - Overview (Welcome)
268
+        ConsentSection section1 = new ConsentSection(ConsentSection.Type.Overview);
269
+        section1.setTitle(getString(R.string.overviewTitleText));
270
+        section1.setSummary(getString(R.string.overviewConsentSectionSummaryText));
271
+        section1.setContent(getString(R.string.overviewConsentSectionContentText));
272
+
273
+        ConsentVisualStep visualStep1 = new ConsentVisualStep(VISUAL_CONSENT_IDENTIFIER);
274
+        visualStep1.setStepTitle(R.string.rsb_consent);
275
+        visualStep1.setSection(section1);
276
+        visualStep1.setNextButtonString("Next");
277
+
278
+
279
+        // Consent Section 2 - Time Commitment
280
+        ConsentSection section2 = new ConsentSection(ConsentSection.Type.TimeCommitment);
281
+        section2.setTitle(getString(R.string.timeCommitmentTitleText));
282
+        section2.setSummary(getString(R.string.timeCommitmentConsentSectionSummaryText));
283
+        section2.setContent(getString(R.string.timeCommitmentConsentSectionContentText));
284
+
285
+        ConsentVisualStep visualStep2 = new ConsentVisualStep("visual_step_2");
286
+        visualStep2.setStepTitle(R.string.rsb_consent);
287
+        visualStep2.setSection(section2);
288
+        visualStep2.setNextButtonString("Next");
289
+
290
+
291
+        // Consent Section 3 - Data Gathering
292
+        ConsentSection section3 = new ConsentSection(ConsentSection.Type.DataGathering);
293
+        section3.setTitle(getString(R.string.dataGatheringTitleText));
294
+        section3.setSummary(getString(R.string.dataGatheringConsentSectionSummaryText));
295
+        section3.setContent(getString(R.string.dataGatheringConsentSectionContentText));
296
+
297
+        ConsentVisualStep visualStep3 = new ConsentVisualStep("visual_step_3");
298
+        visualStep3.setStepTitle(R.string.rsb_consent);
299
+        visualStep3.setSection(section3);
300
+        visualStep3.setNextButtonString("Next");
301
+
302
+
303
+        // Consent Section 4 - Privacy
304
+        ConsentSection section4 = new ConsentSection(ConsentSection.Type.Privacy);
305
+        section4.setTitle(getString(R.string.privacyTitleText));
306
+        section4.setSummary(getString(R.string.privacyConsentSectionSummaryText));
307
+        section4.setContent(getString(R.string.privacyConsentSectionContentText));
308
+
309
+        ConsentVisualStep visualStep4 = new ConsentVisualStep("visual_step_4");
310
+        visualStep4.setStepTitle(R.string.rsb_consent);
311
+        visualStep4.setSection(section4);
312
+        visualStep4.setNextButtonString("Next");
313
+
314
+
315
+        // Consent Section 5 - Data Use
316
+        ConsentSection section5 = new ConsentSection(ConsentSection.Type.DataUse);
317
+        section5.setTitle(getString(R.string.dataUseTitleText));
318
+        section5.setSummary(getString(R.string.dataUseConsentSectionSummaryText));
319
+        section5.setContent(getString(R.string.dataUseConsentSectionContentText));
320
+
321
+        ConsentVisualStep visualStep5 = new ConsentVisualStep("visual_step_5");
322
+        visualStep5.setStepTitle(R.string.rsb_consent);
323
+        visualStep5.setSection(section5);
324
+        visualStep5.setNextButtonString("Next");
325
+
326
+
327
+        // Create consent signature object and set what info is required
328
+        ConsentSignature signature = new ConsentSignature();
329
+        signature.setRequiresName(true);
330
+        signature.setRequiresSignatureImage(true);
331
+
332
+
333
+        // Create our HTML to show the user and have them accept or decline.
334
+        String consentHtmlString =
335
+
336
+                "<br>" +
337
+
338
+                        "<div style='padding: 10px;' class='header'>" +
339
+                        "<h1 style='text-align: center; font-family:sans-serif-light;'>" + getString(R.string.rsb_consent_review_title) + "</h1>" +
340
+                        "<p style='text-align: center'>" + getString(R.string.rsb_consent_review_instruction) + "</p>" +
341
+                        "</div>" +
342
+
343
+                        "<br>" +
344
+
345
+                        "<div>" +
346
+                        "<h2>" + getString(R.string.overviewTitleText) + "</h2>" +
347
+                        "<p align='justify'>" + getString(R.string.consentOverview) + "</p>" +
348
+
349
+                        "<h2>" + getString(R.string.timeCommitmentTitleText) + "</h2>" +
350
+                        "<p align='justify'>" + getString(R.string.consentTimeCommitment) + "</p>" +
351
+
352
+                        "<h2>" + getString(R.string.dataGatheringTitleText) + "</h2>" +
353
+                        "<p align='justify'>" + getString(R.string.consentDataGathering) + "</p>" +
354
+
355
+                        "<h2>" + getString(R.string.privacyTitleText) + "</h2>" +
356
+                        "<p align='justify'>" + getString(R.string.consentPrivacy) + "</p>" +
357
+
358
+                        "<h2>" + getString(R.string.dataUseTitleText) + "</h2>" +
359
+                        "<p align='justify'>" + getString(R.string.consentDataUse) + "</p>" +
360
+
361
+                        "<h2>" + getString(R.string.generalTitleText) + "</h2>" +
362
+                        "<p align='justify'>" + getString(R.string.consentGeneral) + "</p>" +
363
+                        "<p align='justify'>" + getString(R.string.consentConclusion) + "</p>" +
364
+                        "</div>";
365
+
366
+
367
+        // Create the consent document step and pass in our HTML
368
+        ConsentDocumentStep documentStep = new ConsentDocumentStep(CONSENT_DOC);
369
+        documentStep.setConsentHTML(consentHtmlString);
370
+        documentStep.setConfirmMessage(getString(R.string.rsb_consent_review_reason));
371
+
372
+
373
+        // Create Consent form step, to get users first & last name
374
+//        FormStep formStep = new FormStep(SIGNATURE_FORM_STEP,
375
+//                "Form Title",
376
+//                "Form step description");
377
+//        formStep.setStepTitle(R.string.rsb_consent);
378
+//
379
+//        TextAnswerFormat format = new TextAnswerFormat();
380
+//        format.setIsMultipleLines(false);
381
+//
382
+//        QuestionStep fullName = new QuestionStep(NAME, "Full name", format);
383
+//        formStep.setFormSteps(Collections.singletonList(fullName));
384
+
385
+
386
+        // Create the consent signature step (so that user can sign)
387
+        ConsentSignatureStep signatureStep = new ConsentSignatureStep(SIGNATURE);
388
+        signatureStep.setStepTitle(R.string.rsb_consent);
389
+        signatureStep.setTitle(getString(R.string.rsb_consent_signature_title));
390
+        signatureStep.setText(getString(R.string.rsb_consent_signature_instruction));
391
+        signatureStep.setSignatureDateFormat(signature.getSignatureDateFormatString());
392
+        signatureStep.setOptional(false);
393
+        signatureStep.setStepLayoutClass(ConsentSignatureStepLayout.class);
394
+
395
+
396
+        // Create consent document
397
+        ConsentDocument document = new ConsentDocument();
398
+        document.setTitle(getString(R.string.appName) + " Consent");
399
+        document.setSignaturePageTitle(R.string.rsb_consent);
400
+        document.addSignature(signature);
401
+        document.getHtmlReviewContent();
402
+
403
+
404
+        // Finally, create and present a task including these steps.
405
+        Task consentTask = new OrderedTask(CONSENT,
406
+                visualStep1,
407
+                visualStep2,
408
+                visualStep3,
409
+                visualStep4,
410
+                visualStep5,
411
+                documentStep,
412
+//                formStep,
413
+                signatureStep);
414
+
415
+
416
+        // Launch using the ViewTaskActivity and make sure to listen for the activity result
417
+        Intent intent = ViewTaskActivity.newIntent(this, consentTask);
418
+        startActivityForResult(intent, REQUEST_CONSENT);
419
+
420
+    }
421
+
422
+    // TODO: IO should be moved to a background task
423
+//    private String createPdf(byte[] signatureBytes, String consentDate) {
424
+//
425
+//        try {
426
+//            Log.d(TAG,"Aqui");
427
+//
428
+//            // Open document
429
+//            Document document = new Document();
430
+//            String filePath = Environment.getExternalStorageDirectory().getPath() + getString(R.string.consentFileName);
431
+//            PdfWriter.getInstance(document, new FileOutputStream(filePath));
432
+//            document.open();
433
+//
434
+//            // Define fonts and offsets
435
+//            Font titleFont = new Font(Font.FontFamily.HELVETICA, 20, Font.BOLD, BaseColor.BLACK);
436
+//            Font subtitleFont = new Font(Font.FontFamily.HELVETICA, 14, Font.BOLD, BaseColor.BLACK);
437
+//            Font signatureSectionFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLDITALIC, BaseColor.BLACK);
438
+//            int signatureOffsetX = 145;
439
+//            int signatureOffsetY = -100; // TODO: must set absolute offset to avoid bad signatures from cluttering up the PDF (originally -24)
440
+//
441
+//
442
+//            // Create reusable newline component
443
+//            Paragraph newLine = new Paragraph("\n");
444
+//
445
+//
446
+//            // Consent Section 1 - Overview (Welcome)
447
+//            Chunk titleChunk = new Chunk("Consent form:\n" + getString(R.string.appName), titleFont);
448
+//            Paragraph title = new Paragraph(titleChunk);
449
+//            title.setAlignment(Element.ALIGN_CENTER);
450
+//            document.add(title);
451
+//            document.add(newLine);
452
+//
453
+//            String consentOverview = getString(R.string.consentOverview);
454
+//            Paragraph paragraph1 = new Paragraph(consentOverview);
455
+//            paragraph1.setAlignment(Element.ALIGN_JUSTIFIED);
456
+//            document.add(paragraph1);
457
+//            document.add(newLine);
458
+//
459
+//
460
+//            // Consent Section 2 - Time Commitment
461
+//            Chunk subtitle1 = new Chunk(getString(R.string.timeCommitmentTitleText), subtitleFont);
462
+//            Paragraph timeCommitmentTitle = new Paragraph(subtitle1);
463
+//            document.add(timeCommitmentTitle);
464
+//
465
+//            String consentTimeCommitment = getString(R.string.consentTimeCommitment);
466
+//            Paragraph paragraph2 = new Paragraph(consentTimeCommitment);
467
+//            paragraph2.setAlignment(Element.ALIGN_JUSTIFIED);
468
+//            document.add(paragraph2);
469
+//            document.add(newLine);
470
+//
471
+//
472
+//            // Consent Section 3 - Data Gathering
473
+//            Chunk subtitle2 = new Chunk(getString(R.string.dataGatheringTitleText), subtitleFont);
474
+//            Paragraph dataGatheringTitle = new Paragraph(subtitle2);
475
+//            document.add(dataGatheringTitle);
476
+//
477
+//            String consentDataGathering = getString(R.string.consentDataGathering);
478
+//            Paragraph paragraph3 = new Paragraph(consentDataGathering);
479
+//            paragraph3.setAlignment(Element.ALIGN_JUSTIFIED);
480
+//            document.add(paragraph3);
481
+//            document.add(newLine);
482
+//
483
+//
484
+//            // Consent Section 4 - Privacy
485
+//            Chunk subtitle3 = new Chunk(getString(R.string.privacyTitleText), subtitleFont);
486
+//            Paragraph privacyTitle = new Paragraph(subtitle3);
487
+//            document.add(privacyTitle);
488
+//
489
+//            String consentPrivacy = getString(R.string.consentPrivacy);
490
+//            Paragraph paragraph4 = new Paragraph(consentPrivacy);
491
+//            paragraph4.setAlignment(Element.ALIGN_JUSTIFIED);
492
+//            document.add(paragraph4);
493
+//            document.add(newLine);
494
+//
495
+//
496
+//            // Consent Section 5 - Data Use
497
+//            Chunk subtitle4 = new Chunk(getString(R.string.dataUseTitleText), subtitleFont);
498
+//            Paragraph useOfDataTitle = new Paragraph(subtitle4);
499
+//            document.add(useOfDataTitle);
500
+//
501
+//            String consentUseOfData = getString(R.string.consentDataUse);
502
+//            Paragraph paragraph5 = new Paragraph(consentUseOfData);
503
+//            paragraph5.setAlignment(Element.ALIGN_JUSTIFIED);
504
+//            document.add(paragraph5);
505
+//            document.add(newLine);
506
+//
507
+//
508
+//            // Consent Section 6 - General & Conclusion
509
+//            Chunk subtitle5 = new Chunk(getString(R.string.generalTitleText), subtitleFont);
510
+//            Paragraph generalTitle = new Paragraph(subtitle5);
511
+//            document.add(generalTitle);
512
+//
513
+//            String consentGeneral = getString(R.string.consentGeneral);
514
+//            Paragraph paragraph6 = new Paragraph(consentGeneral);
515
+//            paragraph6.setAlignment(Element.ALIGN_JUSTIFIED);
516
+//            document.add(paragraph6);
517
+//            document.add(newLine);
518
+//
519
+//            String consentConclusion = getString(R.string.consentConclusion);
520
+//            Paragraph paragraph7 = new Paragraph(consentConclusion);
521
+//            paragraph7.setAlignment(Element.ALIGN_JUSTIFIED);
522
+//            document.add(paragraph7);
523
+//            document.add(newLine);
524
+//
525
+//
526
+//            // Signature Section
527
+//            Chunk participantSignature = new Chunk("Participant's signature:", signatureSectionFont);
528
+//            Paragraph paragraph8 = new Paragraph(participantSignature);
529
+//            document.add(paragraph8);
530
+//
531
+//            Image consentSignatureImage = Image.getInstance(signatureBytes);
532
+//            Chunk signatureImage = new Chunk(consentSignatureImage, signatureOffsetX, signatureOffsetY);
533
+//            Paragraph paragraph11 = new Paragraph(signatureImage);
534
+//            document.add(paragraph11);
535
+//
536
+//            Chunk signatureDate = new Chunk("Signed on: " + consentDate, signatureSectionFont);
537
+//            Paragraph paragraph9 = new Paragraph(signatureDate);
538
+//            document.add(paragraph9);
539
+//
540
+//
541
+//            // Finish and store document
542
+//            document.close();
543
+//
544
+//
545
+//            // Read document back into consentBytes array, and encode it to base64
546
+//            File file = new File(filePath);
547
+//            int size = (int) file.length();
548
+//            byte[] consentBytes = new byte[size];
549
+//
550
+//            BufferedInputStream buffer = new BufferedInputStream(new FileInputStream(file));
551
+//            int bytesRead = buffer.read(consentBytes, 0, consentBytes.length);
552
+//            Log.d(TAG, "Read a total of " + bytesRead + " bytes, which is supposed to be " + size + " bytes!");
553
+//            buffer.close();
554
+//
555
+//            return Base64.encodeToString(consentBytes, Base64.DEFAULT);
556
+//
557
+//        } catch (FileNotFoundException e) {
558
+//            Log.e(TAG, "Couldn't find generated consent form!");
559
+//            e.printStackTrace();
560
+//            return null;
561
+//        } catch (IOException e) {
562
+//            Log.e(TAG, "Couldn't read back generated consent form!");
563
+//            e.printStackTrace();
564
+//            return null;
565
+//        } catch (BadElementException e) {
566
+//            Log.e(TAG, "Couldn't create a consent form element!");
567
+//            e.printStackTrace();
568
+//            return null;
569
+//        } catch (DocumentException e) {
570
+//            Log.e(TAG, "Something went wrong while fiddling with the consent form creation!");
571
+//            e.printStackTrace();
572
+//            return null;
573
+//        } catch (Exception e) {
574
+//            Log.wtf(TAG, "Call to launchConsent() went wrong!");
575
+//            e.printStackTrace();
576
+//            return null;
577
+//        }
578
+//
579
+//    }
580
+    private String createPdf(byte[] signatureBytes, String consentDate) {
581
+        File file = null;
582
+        FileOutputStream outputStream = null;
583
+        BufferedInputStream buffer = null;
584
+
585
+        try {
586
+            Log.d(TAG, "Starting PDF creation");
587
+
588
+            // Create file in app-specific storage
589
+            file = new File(getFilesDir(), getString(R.string.consentFileName));
590
+            outputStream = new FileOutputStream(file);
591
+
592
+            // Open document
593
+            Document document = new Document();
594
+            PdfWriter.getInstance(document, outputStream);
595
+            document.open();
596
+
597
+            // Define fonts and offsets
598
+            Font titleFont = new Font(Font.FontFamily.HELVETICA, 20, Font.BOLD, BaseColor.BLACK);
599
+            Font subtitleFont = new Font(Font.FontFamily.HELVETICA, 14, Font.BOLD, BaseColor.BLACK);
600
+            Font signatureSectionFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLDITALIC, BaseColor.BLACK);
601
+            int signatureOffsetX = 145;
602
+            int signatureOffsetY = -100;
603
+
604
+            // Create reusable newline component
605
+            Paragraph newLine = new Paragraph("\n");
606
+
607
+            // Consent Section 1 - Overview (Welcome)
608
+            Chunk titleChunk = new Chunk("Consent form:\n" + getString(R.string.appName), titleFont);
609
+            Paragraph title = new Paragraph(titleChunk);
610
+            title.setAlignment(Element.ALIGN_CENTER);
611
+            document.add(title);
612
+            document.add(newLine);
613
+
614
+            String consentOverview = getString(R.string.consentOverview);
615
+            Paragraph paragraph1 = new Paragraph(consentOverview);
616
+            paragraph1.setAlignment(Element.ALIGN_JUSTIFIED);
617
+            document.add(paragraph1);
618
+            document.add(newLine);
619
+
620
+            // ... (rest of the content sections remain the same) ...
621
+
622
+            // Signature Section
623
+            Chunk participantSignature = new Chunk("Participant's signature:", signatureSectionFont);
624
+            Paragraph paragraph8 = new Paragraph(participantSignature);
625
+            document.add(paragraph8);
626
+
627
+            Image consentSignatureImage = Image.getInstance(signatureBytes);
628
+            Chunk signatureImage = new Chunk(consentSignatureImage, signatureOffsetX, signatureOffsetY);
629
+            Paragraph paragraph11 = new Paragraph(signatureImage);
630
+            document.add(paragraph11);
631
+
632
+            Chunk signatureDate = new Chunk("Signed on: " + consentDate, signatureSectionFont);
633
+            Paragraph paragraph9 = new Paragraph(signatureDate);
634
+            document.add(paragraph9);
635
+
636
+            // Finish and store document
637
+            document.close();
638
+
639
+            // Read document back into consentBytes array, and encode it to base64
640
+            int size = (int) file.length();
641
+            byte[] consentBytes = new byte[size];
642
+
643
+            buffer = new BufferedInputStream(new FileInputStream(file));
644
+            int bytesRead = buffer.read(consentBytes, 0, consentBytes.length);
645
+            Log.d(TAG, "Read a total of " + bytesRead + " bytes, which is supposed to be " + size + " bytes!");
646
+
647
+            return Base64.encodeToString(consentBytes, Base64.DEFAULT);
648
+
649
+        } catch (Exception e) {
650
+            Log.e(TAG, "Error creating or reading PDF", e);
651
+            return null;
652
+        } finally {
653
+            try {
654
+                if (buffer != null) buffer.close();
655
+                if (outputStream != null) outputStream.close();
656
+            } catch (IOException e) {
657
+                Log.e(TAG, "Error closing streams", e);
658
+            }
659
+            if (file != null && file.exists() && !file.delete()) {
660
+                Log.w(TAG, "Failed to delete temporary PDF file");
661
+            }
662
+        }
663
+    }
664
+
665
+
666
+    private void restoreConsentForm(String response) {
667
+        FileOutputStream consentForm = null;
668
+        try {
669
+            byte[] consentBytes = Base64.decode(response, Base64.NO_WRAP);
670
+            File file = new File(getFilesDir(), getString(R.string.consentFileName));
671
+            consentForm = new FileOutputStream(file);
672
+            consentForm.write(consentBytes);
673
+            Log.d(TAG, "Consent form restored successfully");
674
+        } catch (IllegalArgumentException e) {
675
+            Log.e(TAG, "Invalid Base64 string", e);
676
+        } catch (IOException e) {
677
+            Log.e(TAG, "Error writing consent form", e);
678
+        } finally {
679
+            if (consentForm != null) {
680
+                try {
681
+                    consentForm.close();
682
+                } catch (IOException e) {
683
+                    Log.e(TAG, "Error closing FileOutputStream", e);
684
+                }
685
+            }
686
+        }
687
+    }
688
+    private void openPrivacyPolicy() {
689
+        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://tania.uprrp.edu/privacy_policy.html"));
690
+        startActivity(browserIntent);
691
+    }
692
+}

+ 97
- 0
app/src/main/java/uprrp/tania/activities/MainActivity.java View File

@@ -0,0 +1,97 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.os.Bundle;
4
+import android.util.Log;
5
+import android.view.MenuItem;
6
+
7
+import android.content.Intent;
8
+import android.net.Uri;
9
+import android.os.Bundle;
10
+import android.util.Log;
11
+import android.view.MenuItem;
12
+import android.widget.TextView;
13
+
14
+import androidx.annotation.NonNull;
15
+import androidx.appcompat.app.ActionBar;
16
+import androidx.appcompat.widget.Toolbar;
17
+import androidx.fragment.app.Fragment;
18
+
19
+import com.google.android.material.bottomnavigation.BottomNavigationView;
20
+
21
+import org.researchstack.backbone.ui.PinCodeActivity;
22
+
23
+import uprrp.tania.R;
24
+import uprrp.tania.fragments.AssessmentsFragment;
25
+import uprrp.tania.fragments.ConsentFragment;
26
+import uprrp.tania.fragments.WithdrawFragment;
27
+import uprrp.tania.fragments.NotificationPermissionHandler;
28
+
29
+
30
+public class MainActivity extends PinCodeActivity {
31
+
32
+    private static final String TAG = "MainActivity";
33
+    private NotificationPermissionHandler notificationPermissionHandler;
34
+
35
+    @Override
36
+    protected void onCreate(final Bundle savedInstanceState) {
37
+
38
+        // Constructor stuff
39
+        super.onCreate(savedInstanceState);
40
+        setContentView(R.layout.activity_main);
41
+
42
+        notificationPermissionHandler = new NotificationPermissionHandler(this);
43
+        notificationPermissionHandler.checkAndRequestPermission();
44
+
45
+        // Create toolbar
46
+        Toolbar toolbar = findViewById(R.id.toolbar);
47
+        setSupportActionBar(toolbar);
48
+        ActionBar actionBar = getSupportActionBar();
49
+        assert actionBar != null; // TODO: find out if this causes crashes
50
+        actionBar.setTitle(R.string.appName);
51
+        actionBar.setDisplayShowTitleEnabled(true);
52
+
53
+        TextView privacyPolicyLink = findViewById(R.id.privacy_policy_link);
54
+        privacyPolicyLink.setOnClickListener(v -> openPrivacyPolicy());
55
+
56
+        // Set navigation buttons listener
57
+        BottomNavigationView bottomNavigationMenu = findViewById(R.id.bottom_navigation_menu);
58
+        bottomNavigationMenu.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
59
+            @Override
60
+            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
61
+
62
+                Fragment selectedFragment;
63
+
64
+                int id = item.getItemId();
65
+                if(id == R.id.nav_assessments) {
66
+                    selectedFragment = new AssessmentsFragment();
67
+                } else if(id == R.id.nav_consent) {
68
+                    selectedFragment = new ConsentFragment();
69
+                } else if(id == R.id.nav_settings) {
70
+                    selectedFragment = new WithdrawFragment();
71
+                } else {
72
+                    Log.wtf(TAG, "Illegal fragment reach attempt with id: " + id);
73
+                    selectedFragment = new Fragment(); // use empty fragment
74
+                }
75
+
76
+                getSupportFragmentManager()
77
+                        .beginTransaction()
78
+                        .replace(R.id.mainFragment, selectedFragment)
79
+                        .commit();
80
+
81
+                return true;
82
+
83
+            }
84
+        });
85
+
86
+    }
87
+    private void openPrivacyPolicy() {
88
+        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://tania.uprrp.edu/privacy_policy.html"));
89
+        startActivity(browserIntent);
90
+    }
91
+
92
+    @Override
93
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
94
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
95
+        notificationPermissionHandler.onRequestPermissionsResult(requestCode, permissions, grantResults);
96
+    }
97
+}

+ 102
- 0
app/src/main/java/uprrp/tania/activities/ResetPasswordActivity.java View File

@@ -0,0 +1,102 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.app.ProgressDialog;
4
+import android.content.Intent;
5
+import android.net.Uri;
6
+import android.os.Bundle;
7
+import android.util.Log;
8
+import android.view.View;
9
+import android.widget.Button;
10
+import android.widget.EditText;
11
+import android.widget.Toast;
12
+
13
+import androidx.annotation.Nullable;
14
+import androidx.appcompat.app.AppCompatActivity;
15
+
16
+import uprrp.tania.GlobalValues;
17
+import uprrp.tania.R;
18
+import uprrp.tania.networking.SendResetPassword;
19
+import uprrp.tania.utils.URLEventListener;
20
+
21
+public class ResetPasswordActivity extends AppCompatActivity {
22
+
23
+    private static final String TAG = "ResetPasswordActivity";
24
+    private String FORGOT_PASSWORD_KEY;
25
+
26
+    @Override
27
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
28
+
29
+        // Constructor stuff
30
+        super.onCreate(savedInstanceState);
31
+        setContentView(R.layout.activity_reset_password);
32
+
33
+        // Fetch experience token from URL (only if we launched this Activity from Browser)
34
+        Intent intent = getIntent();
35
+        if(Intent.ACTION_VIEW.equals(intent.getAction())) {
36
+            Uri uri = intent.getData();
37
+            assert uri != null; // TODO: figure out if this causes crashes...
38
+            FORGOT_PASSWORD_KEY = uri.getQueryParameter("forgotPasswordKey");
39
+            Log.d(TAG, "ForgotPasswordKey is " + FORGOT_PASSWORD_KEY);
40
+        } else {
41
+            Log.wtf(TAG, "Activity started from somewhere other than the browser!");
42
+        }
43
+
44
+        // Change all caps text to normal capitalization and add onClick listeners
45
+        final Button resetPasswordButton = findViewById(R.id.buttonResetPassword);
46
+        resetPasswordButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
47
+        resetPasswordButton.setOnClickListener(new View.OnClickListener() {
48
+            @Override
49
+            public void onClick(View v) {
50
+                sendPasswordResetRequest();
51
+            }
52
+        });
53
+
54
+    }
55
+
56
+    private void sendPasswordResetRequest() {
57
+
58
+        final EditText newPasswordEditText = findViewById(R.id.editTextNewPassword);
59
+        final EditText confirmPasswordEditText = findViewById(R.id.editTextConfirmPassword);
60
+
61
+        String newPassword = newPasswordEditText.getText().toString();
62
+        String confirmPassword = confirmPasswordEditText.getText().toString();
63
+
64
+        if(newPassword.equals(confirmPassword) && !newPassword.equals("")) {
65
+
66
+            // Initiate progress dialog
67
+            // TODO: find substitute for this deprecated dialog box
68
+            final ProgressDialog progressDialog = ProgressDialog.show(ResetPasswordActivity.this,
69
+                    "Resetting Password",
70
+                    getString(R.string.progressDialogDescriptionText));
71
+
72
+            // Define task
73
+            SendResetPassword sendResetPasswordTask = new SendResetPassword(new URLEventListener() {
74
+                @Override
75
+                public void onSuccess() {
76
+                    progressDialog.dismiss();
77
+                    Toast.makeText(getApplicationContext(), "Success!\nYou can login with your new password now.", Toast.LENGTH_SHORT).show();
78
+                    Intent intent = new Intent(getApplicationContext(), GettingStartedActivity.class);
79
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
80
+                    startActivity(intent);
81
+                }
82
+
83
+                @Override
84
+                public void onFailure(Exception e) {
85
+                    progressDialog.dismiss();
86
+                    Toast.makeText(getApplicationContext(), "An error occurred while trying to register. Please try again.", Toast.LENGTH_LONG).show();
87
+                    Log.e(TAG, "Couldn't reset password!");
88
+                    e.printStackTrace();
89
+                }
90
+            });
91
+
92
+            // Start task
93
+            String deviceToken = GlobalValues.getInstance().getDeviceToken();
94
+            sendResetPasswordTask.execute(deviceToken, newPassword, FORGOT_PASSWORD_KEY);
95
+
96
+        } else {
97
+            Toast.makeText(ResetPasswordActivity.this, "Both fields must match and can't be blank!", Toast.LENGTH_LONG).show();
98
+        }
99
+
100
+
101
+    }
102
+}

+ 224
- 0
app/src/main/java/uprrp/tania/activities/UserRegistrationActivity.java View File

@@ -0,0 +1,224 @@
1
+package uprrp.tania.activities;
2
+
3
+import android.app.DatePickerDialog;
4
+import android.app.ProgressDialog;
5
+import android.content.Context;
6
+import android.content.Intent;
7
+import android.content.SharedPreferences;
8
+import android.graphics.Color;
9
+import android.graphics.drawable.ColorDrawable;
10
+import android.os.Bundle;
11
+import android.util.Log;
12
+import android.view.View;
13
+import android.widget.Button;
14
+import android.widget.DatePicker;
15
+import android.widget.EditText;
16
+import android.widget.RadioButton;
17
+import android.widget.TextView;
18
+import android.widget.Toast;
19
+
20
+import androidx.appcompat.app.AppCompatActivity;
21
+
22
+import java.util.Objects;
23
+
24
+import uprrp.tania.GlobalValues;
25
+import uprrp.tania.R;
26
+import uprrp.tania.networking.SendUserRegistration;
27
+import uprrp.tania.models.UserModel;
28
+import uprrp.tania.utils.URLEventListener;
29
+
30
+public class UserRegistrationActivity extends AppCompatActivity {
31
+
32
+    private static final String TAG = "RegisterActivity";
33
+
34
+    @Override
35
+    protected void onCreate(Bundle savedInstanceState) {
36
+
37
+        // Constructor stuff
38
+        super.onCreate(savedInstanceState);
39
+        setContentView(R.layout.activity_user_registration);
40
+
41
+        // Set onClick listener for the birth date text
42
+        // TODO: should change UI to use an actual datePicker instead of this garbage
43
+        final TextView birthDateText = findViewById(R.id.birthDateText);
44
+        birthDateText.setOnClickListener(view -> openDatePickerNew());
45
+
46
+        // Change all caps text to normal capitalization and add onClick listener
47
+        final Button buttonRegister = findViewById(R.id.registerButton);
48
+        buttonRegister.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
49
+        buttonRegister.setOnClickListener(v -> sendRegistrationData());
50
+
51
+    }
52
+
53
+    private void openDatePickerNew() {
54
+        final TextView birthDateText = findViewById(R.id.birthDateText);
55
+
56
+        DatePickerDialog dialog = new DatePickerDialog(UserRegistrationActivity.this, android.R.style.Theme_Holo_Light_Dialog_MinWidth);
57
+        dialog.setOnDateSetListener((datePicker, year, month, day) -> {
58
+            month = month + 1; // 0-indexed month
59
+            Log.d(TAG, "onDateSet: mm/dd/yyy: " + month + "/" + day + "/" + year);
60
+            String date = month + "/" + day + "/" + year;
61
+            birthDateText.setText(date);
62
+        });
63
+
64
+        // Remove ugly border
65
+        Objects.requireNonNull(dialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
66
+
67
+        dialog.show();
68
+    }
69
+
70
+    private boolean validInputs() {
71
+
72
+        final EditText nameEditText = findViewById(R.id.nameEditText);
73
+        final TextView birthDateText = findViewById(R.id.birthDateText);
74
+
75
+        final RadioButton maleGender = findViewById(R.id.maleRadioButton);
76
+        final RadioButton femaleGender = findViewById(R.id.femaleRadioButton);
77
+        final RadioButton notSpecifyGender = findViewById(R.id.notSayRadioButton);
78
+        final RadioButton otherGender = findViewById(R.id.otherRadioButton);
79
+
80
+        final RadioButton partTime = findViewById(R.id.partTimeRadioButton);
81
+        final RadioButton fullTime = findViewById(R.id.fullTimeRadioButton);
82
+        final RadioButton noJob = findViewById(R.id.noJobRadioButton);
83
+
84
+        final EditText emailText = findViewById(R.id.emailText);
85
+        final EditText passwordText = findViewById(R.id.passwordText);
86
+
87
+        if(nameEditText.getText().toString().equals("")) {
88
+            return false;
89
+        }
90
+
91
+        if(birthDateText.getText().toString().equals("")) {
92
+            return false;
93
+        }
94
+
95
+        if(emailText.getText().toString().equals("")) {
96
+            return false;
97
+        }
98
+
99
+        if(passwordText.getText().toString().equals("")) {
100