Browse Source

Applicacion completa atemperada a android 34

Carlos J Corrada Bravo 2 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
+            return false;
101
+        }
102
+
103
+        // Genders
104
+        if(!(maleGender.isChecked() || femaleGender.isChecked() || notSpecifyGender.isChecked() || otherGender.isChecked())) {
105
+            return false;
106
+        }
107
+
108
+        // Job Field
109
+        return partTime.isChecked() || fullTime.isChecked() || noJob.isChecked();
110
+    }
111
+
112
+    private void sendRegistrationData() {
113
+
114
+        final EditText nameEditText = findViewById(R.id.nameEditText);
115
+        final TextView birthDateText = findViewById(R.id.birthDateText);
116
+
117
+        final RadioButton maleGender = findViewById(R.id.maleRadioButton);
118
+        final RadioButton femaleGender = findViewById(R.id.femaleRadioButton);
119
+        final RadioButton notSpecifyGender = findViewById(R.id.notSayRadioButton);
120
+        final RadioButton otherGender = findViewById(R.id.otherRadioButton);
121
+
122
+        final RadioButton partTime = findViewById(R.id.partTimeRadioButton);
123
+        final RadioButton fullTime = findViewById(R.id.fullTimeRadioButton);
124
+        final RadioButton noJob = findViewById(R.id.noJobRadioButton);
125
+
126
+        final EditText emailText = findViewById(R.id.emailText);
127
+        final EditText passwordText = findViewById(R.id.passwordText);
128
+
129
+
130
+        if(this.validInputs()) {
131
+
132
+            // Extract fields
133
+            String name = nameEditText.getText().toString();
134
+            String birthDate = birthDateText.getText().toString();
135
+            String email = emailText.getText().toString();
136
+            String password = passwordText.getText().toString();
137
+            String deviceToken = GlobalValues.getInstance().getDeviceToken();
138
+
139
+            String genderText;
140
+            if(maleGender.isChecked()) {
141
+                genderText = "Male";
142
+            } else if (femaleGender.isChecked()) {
143
+                genderText = "Female";
144
+            } else if (notSpecifyGender.isChecked()) {
145
+                genderText = "Prefer not to say";
146
+            } else if (otherGender.isChecked()){
147
+                genderText = "Other";
148
+            } else {
149
+                Log.wtf(TAG, "Unknown radio button case for gender!");
150
+                genderText = "Other";
151
+            }
152
+
153
+            String jobType;
154
+            if(partTime.isChecked()) {
155
+                jobType = "Part time";
156
+            } else if (fullTime.isChecked()) {
157
+                jobType = "Full time";
158
+            } else if (noJob.isChecked()) {
159
+                jobType = "No job";
160
+            } else {
161
+                Log.wtf(TAG, "Unknown radio button case for job type!");
162
+                jobType = "No job";
163
+            }
164
+
165
+            // Save name in preferences
166
+            SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
167
+            SharedPreferences.Editor editor = prefs.edit();
168
+            editor.putString("consentName", name);
169
+            editor.apply();
170
+
171
+            // Send registration request
172
+            UserModel user = new UserModel(birthDate, email, password, genderText, jobType, deviceToken);
173
+            this.sendUserRegistrationRequest(user);
174
+
175
+        } else {
176
+            Toast.makeText(UserRegistrationActivity.this, "All fields are required!", Toast.LENGTH_LONG).show();
177
+        }
178
+
179
+    }
180
+
181
+    private void sendUserRegistrationRequest(UserModel user) {
182
+
183
+        // Initiate progress dialog
184
+        // TODO: find substitute for this deprecated dialog box
185
+        final ProgressDialog progressDialog = ProgressDialog.show(UserRegistrationActivity.this,
186
+                "Registering User",
187
+                getString(R.string.progressDialogDescriptionText));
188
+
189
+        // Send registration request to server
190
+        SendUserRegistration sendUserRegistrationTask = new SendUserRegistration(new URLEventListener() {
191
+            @Override
192
+            public void onSuccess() {
193
+
194
+                progressDialog.dismiss();
195
+                Context context = getApplicationContext();
196
+                Toast.makeText(getApplicationContext(), "Welcome to TANIA!", Toast.LENGTH_LONG).show();
197
+
198
+                // Change needsToRegister to false
199
+                SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
200
+                SharedPreferences.Editor editor = prefs.edit();
201
+                editor.putBoolean("needsToRegister", false);
202
+                editor.apply();
203
+
204
+                // Redirect to MainActivity
205
+                Intent intent = new Intent(context, MainActivity.class);
206
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
207
+                context.startActivity(intent);
208
+            }
209
+
210
+            @Override
211
+            public void onFailure(Exception e) {
212
+                progressDialog.dismiss();
213
+                Toast.makeText(getApplicationContext(), "An error occurred while trying to register. Please start again.", Toast.LENGTH_LONG).show();
214
+                Log.e(TAG, "An error occurred while trying to register user!");
215
+                e.printStackTrace();
216
+            }
217
+        });
218
+
219
+        // Start task
220
+        sendUserRegistrationTask.execute(user);
221
+
222
+    }
223
+
224
+}

+ 492
- 0
app/src/main/java/uprrp/tania/fragments/AssessmentsFragment.java View File

@@ -0,0 +1,492 @@
1
+package uprrp.tania.fragments;
2
+
3
+import android.app.Activity;
4
+import android.app.ProgressDialog;
5
+import android.content.DialogInterface;
6
+import android.content.Intent;
7
+import android.os.Bundle;
8
+import android.util.Log;
9
+import android.view.LayoutInflater;
10
+import android.view.View;
11
+import android.view.ViewGroup;
12
+import android.widget.Button;
13
+import android.widget.TextView;
14
+import android.widget.Toast;
15
+
16
+import androidx.annotation.Nullable;
17
+import androidx.appcompat.app.AlertDialog;
18
+import androidx.fragment.app.Fragment;
19
+
20
+import org.researchstack.backbone.StorageAccess;
21
+import org.researchstack.backbone.answerformat.AnswerFormat;
22
+import org.researchstack.backbone.answerformat.ChoiceAnswerFormat;
23
+import org.researchstack.backbone.answerformat.TextAnswerFormat;
24
+import org.researchstack.backbone.model.Choice;
25
+import org.researchstack.backbone.result.StepResult;
26
+import org.researchstack.backbone.result.TaskResult;
27
+import org.researchstack.backbone.step.InstructionStep;
28
+import org.researchstack.backbone.step.QuestionStep;
29
+import org.researchstack.backbone.step.Step;
30
+import org.researchstack.backbone.task.OrderedTask;
31
+import org.researchstack.backbone.ui.ViewTaskActivity;
32
+
33
+import java.util.ArrayList;
34
+import java.util.List;
35
+import java.util.Objects;
36
+import java.util.Observable;
37
+import java.util.Observer;
38
+
39
+import uprrp.tania.GlobalValues;
40
+import uprrp.tania.R;
41
+import uprrp.tania.utils.URLEventListener;
42
+import uprrp.tania.models.AnsweredAssessmentModel;
43
+import uprrp.tania.models.AnsweredQuestionModel;
44
+import uprrp.tania.models.AssessmentModel;
45
+import uprrp.tania.models.QuestionModel;
46
+import uprrp.tania.models.UserStatusModel;
47
+import uprrp.tania.networking.FetchAssessment;
48
+import uprrp.tania.networking.FetchUserStatus;
49
+import uprrp.tania.networking.SendAnswers;
50
+
51
+public class AssessmentsFragment extends Fragment implements Observer {
52
+
53
+    // These variables came from the sample app provided to explain the use of Research Stack
54
+    private static final int REQUEST_SURVEY = 1;
55
+    private static final String INSTRUCTION = "identifier";
56
+    private static final String SAMPLE_SURVEY = "sample_assessment";
57
+
58
+    // Our variables
59
+    private static final String TAG = "AssessmentsFragment";
60
+    private String ASSESSMENT_ID;
61
+    private View thisFragment;
62
+
63
+    @Nullable
64
+    @Override
65
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
66
+
67
+        // Constructor
68
+        thisFragment = inflater.inflate(R.layout.fragment_assessments, container, false);
69
+
70
+        // Listen for device token changes
71
+        this.startObservingGlobals();
72
+
73
+        // Disable interaction initially while token is being fetched
74
+        if(GlobalValues.getInstance().getDeviceToken() == null) {
75
+            disableInteractionTemporarily();
76
+        } else {
77
+            enableInteraction();
78
+        }
79
+
80
+        // Display user status if available
81
+        UserStatusModel userStatus = GlobalValues.getInstance().getUserStatus();
82
+        if(userStatus != null) {
83
+            positiveUserStatus(userStatus);
84
+        }
85
+
86
+        // Change all caps text to normal capitalization and add onClick listener
87
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
88
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
89
+        surveyButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
90
+        refreshButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
91
+        surveyButton.setOnClickListener(new View.OnClickListener() {
92
+            @Override
93
+            public void onClick(View v) {
94
+                fetchAssessment();
95
+            }
96
+        });
97
+        refreshButton.setOnClickListener(new View.OnClickListener() {
98
+            @Override
99
+            public void onClick(View v) {
100
+                fetchUserStatus();
101
+            }
102
+        });
103
+
104
+        return thisFragment;
105
+
106
+    }
107
+
108
+    private void disableInteractionTemporarily() {
109
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
110
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
111
+        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
112
+        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
113
+
114
+        surveyButton.setEnabled(false);
115
+        refreshButton.setEnabled(false);
116
+        statusTextView.setText(R.string.loadingText);
117
+        nextSurveyTextView.setVisibility(View.INVISIBLE);
118
+    }
119
+
120
+    private void disableInteraction() {
121
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
122
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
123
+        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
124
+        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
125
+
126
+        surveyButton.setEnabled(false);
127
+        refreshButton.setEnabled(false);
128
+        statusTextView.setText(R.string.tokenErrorText);
129
+        nextSurveyTextView.setVisibility(View.INVISIBLE);
130
+    }
131
+
132
+    private void enableInteraction() {
133
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
134
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
135
+
136
+        surveyButton.setEnabled(true);
137
+        refreshButton.setEnabled(true);
138
+        if(GlobalValues.getInstance().getUserStatus() == null) {
139
+            fetchUserStatus(); // refresh user status
140
+        }
141
+    }
142
+
143
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
144
+    private void startObservingGlobals() {
145
+        GlobalValues.getInstance().addObserver(this);
146
+    }
147
+
148
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
149
+    @Override
150
+    public void update(Observable observable, Object o) {
151
+        if(observable instanceof GlobalValues) {
152
+            if(o instanceof GlobalValues.ValueKey) {
153
+                switch(((GlobalValues.ValueKey) o).getKey()) {
154
+                    case DEVICE_TOKEN:
155
+                        if(GlobalValues.getInstance().getDeviceToken() == null) {
156
+                            disableInteraction();
157
+                        } else {
158
+                            enableInteraction();
159
+                        }
160
+                        break;
161
+                    case USER_STATUS:
162
+                        UserStatusModel userStatus = GlobalValues.getInstance().getUserStatus();
163
+                        if(userStatus == null) {
164
+                            negativeUserStatus();
165
+                        } else {
166
+                            positiveUserStatus(userStatus);
167
+                        }
168
+                        break;
169
+                }
170
+            }
171
+        }
172
+    }
173
+
174
+    private void negativeUserStatus() {
175
+        Toast.makeText(getContext(), "Can't verify identity!", Toast.LENGTH_LONG).show();
176
+
177
+        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
178
+        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
179
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
180
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
181
+
182
+        statusTextView.setText(R.string.userStatusErrorText);
183
+        nextSurveyTextView.setVisibility(View.INVISIBLE);
184
+        surveyButton.setVisibility(View.INVISIBLE);
185
+        surveyButton.setEnabled(false);
186
+        refreshButton.setVisibility(View.VISIBLE);
187
+    }
188
+
189
+    private void positiveUserStatus(UserStatusModel userStatus) {
190
+
191
+        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
192
+        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
193
+        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
194
+        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
195
+
196
+        statusTextView.setText(userStatus.toString());
197
+
198
+        if(userStatus.isEnrolled()) {
199
+
200
+            nextSurveyTextView.setText(userStatus.getTimeToNext());
201
+            nextSurveyTextView.setVisibility(View.VISIBLE);
202
+
203
+            if(userStatus.surveyAvailable()) {
204
+                refreshButton.setVisibility(View.INVISIBLE);
205
+                refreshButton.setEnabled(false);
206
+                surveyButton.setVisibility(View.VISIBLE);
207
+                surveyButton.setEnabled(true);
208
+            } else {
209
+                surveyButton.setVisibility(View.INVISIBLE);
210
+                surveyButton.setEnabled(false);
211
+                refreshButton.setVisibility(View.VISIBLE);
212
+                refreshButton.setEnabled(true);
213
+            }
214
+
215
+        } else {
216
+
217
+            nextSurveyTextView.setText("");
218
+            nextSurveyTextView.setVisibility(View.INVISIBLE);
219
+
220
+            surveyButton.setVisibility(View.INVISIBLE);
221
+            surveyButton.setEnabled(false);
222
+            refreshButton.setVisibility(View.VISIBLE);
223
+            refreshButton.setEnabled(true);
224
+
225
+        }
226
+
227
+    }
228
+
229
+    private void fetchUserStatus() {
230
+
231
+        disableInteractionTemporarily();
232
+
233
+        // Define task
234
+        FetchUserStatus userStatusTask = new FetchUserStatus(new URLEventListener() {
235
+            @Override
236
+            public void onSuccess(UserStatusModel userStatus) {
237
+                GlobalValues.getInstance().setUserStatus(userStatus);
238
+                enableInteraction();
239
+            }
240
+            @Override
241
+            public void onFailure(Exception e) {
242
+                GlobalValues.getInstance().setUserStatus(null);
243
+                Log.e(TAG, "Error while fetching user status: " + e.toString());
244
+            }
245
+        });
246
+
247
+        // Start task
248
+        userStatusTask.execute(GlobalValues.getInstance().getDeviceToken());
249
+
250
+    }
251
+
252
+    private void fetchAssessment() {
253
+
254
+        // Initiate progress dialog
255
+        // TODO: find substitute for this deprecated dialog box
256
+        final ProgressDialog progressDialog = ProgressDialog.show(getContext(),
257
+                "Looking for Survey",
258
+                getString(R.string.progressDialogDescriptionText));
259
+
260
+        // Fetch the assessment to be taken (if there is at least one available),
261
+        // and launch the assessment (if the button is pressed)
262
+        FetchAssessment assessmentTask = new FetchAssessment(new URLEventListener() {
263
+
264
+            @Override
265
+            public void onSuccess(AssessmentModel assessment) {
266
+                progressDialog.dismiss();
267
+                if(assessment.isEmpty()) {
268
+                    fetchUserStatus(); // update status to let user know if it already had expired by the time he clicked
269
+                    Toast.makeText(getContext(), "There are no surveys at the moment!", Toast.LENGTH_LONG).show();
270
+                } else {
271
+                    launchSurvey(assessment);
272
+                }
273
+            }
274
+
275
+            @Override
276
+            public void onFailure(Exception e) {
277
+                progressDialog.dismiss();
278
+                Toast.makeText(getContext(), "Can't reach surveys at the moment!", Toast.LENGTH_LONG).show();
279
+                Log.e("ERROR WHILE FETCHING ASSESSMENT", e.toString());
280
+            }
281
+
282
+        });
283
+
284
+        // Start task
285
+        assessmentTask.execute(GlobalValues.getInstance().getDeviceToken());
286
+
287
+    }
288
+
289
+    @Override
290
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
291
+        super.onActivityResult(requestCode, resultCode, data);
292
+        if(requestCode == REQUEST_SURVEY && resultCode == Activity.RESULT_OK) {
293
+            this.processSurveyResult((TaskResult) data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT));
294
+        }
295
+    }
296
+
297
+    private void launchSurvey(AssessmentModel assessment) {
298
+        this.ASSESSMENT_ID = assessment.getID();
299
+        List<Step> steps = this.prepareSurveySteps(assessment);
300
+        OrderedTask task = new OrderedTask(SAMPLE_SURVEY, steps);
301
+        Intent intent = ViewTaskActivity.newIntent(getContext(), task);
302
+        startActivityForResult(intent, REQUEST_SURVEY);
303
+    }
304
+
305
+    private void processSurveyResult(TaskResult result) {
306
+
307
+        // Save results to local database
308
+        StorageAccess.getInstance().getAppDatabase().saveTaskResult(result);
309
+
310
+        // Load results
311
+        TaskResult taskResult = StorageAccess.getInstance()
312
+                .getAppDatabase()
313
+                .loadLatestTaskResult(SAMPLE_SURVEY);
314
+
315
+        // Prepare and send results
316
+        AnsweredAssessmentModel answeredAssessment = this.prepareAnsweredAssessment(taskResult);
317
+        this.sendSurveyInfo(answeredAssessment);
318
+
319
+    }
320
+
321
+    private void sendSurveyInfo(final AnsweredAssessmentModel answeredAssessment) {
322
+
323
+        // Initiate progress dialog
324
+        // TODO: find substitute for this deprecated dialog box
325
+        final ProgressDialog progressDialog = ProgressDialog.show(getContext(),
326
+                "Sending Answers",
327
+                getString(R.string.progressDialogDescriptionText));
328
+
329
+        // Send answers and prompt user if he wants to retry sending them if an error occurs
330
+        SendAnswers sendAnswersTask = new SendAnswers(new URLEventListener() {
331
+
332
+           @Override public void onSuccess() {
333
+               progressDialog.dismiss();
334
+               Toast.makeText(getContext(), "Answers sent!", Toast.LENGTH_LONG).show();
335
+               fetchUserStatus();
336
+           }
337
+
338
+           @Override
339
+           public void onFailure(Exception e) {
340
+               Log.e(TAG, "Error whilte sending answers: " + e.toString());
341
+               e.printStackTrace();
342
+               progressDialog.dismiss();
343
+               promptRetrySend(answeredAssessment);
344
+           }
345
+
346
+        });
347
+
348
+
349
+        // Prepare results and start task
350
+       sendAnswersTask.execute(answeredAssessment);
351
+
352
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
353
+//        Handler handler = new Handler();
354
+//        handler.postDelayed(new Runnable() {
355
+//            public void run() {
356
+//                progressDialog.dismiss();
357
+//                if(true) { // Imitate success
358
+//                    Toast.makeText(getContext(), "Answers sent!", Toast.LENGTH_LONG).show();
359
+//                    fetchUserStatus();
360
+//                } else { // Imitate failure
361
+//                    promptRetrySend2(answeredAssessment);
362
+//                }
363
+//            }
364
+//        }, 5000);
365
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
366
+
367
+    }
368
+
369
+    private AnsweredAssessmentModel prepareAnsweredAssessment(TaskResult taskResult) {
370
+
371
+        // Gather answered questions
372
+        ArrayList<AnsweredQuestionModel> answeredQuestions = new ArrayList<>();
373
+        for (String questionID : taskResult.getResults().keySet()) {
374
+
375
+            // Extract question info
376
+            // TODO: check if instead of getting the taskResult start/end times,
377
+            //  we should be getting the stepResult start/end times...
378
+            //  IF NOT: we should think about restructuring the JSON
379
+            //  because we're being highly redundant and inefficient
380
+            StepResult stepResult = taskResult.getStepResult(questionID);
381
+            String answer = stepResult.getResult().toString();
382
+            String startDatetime = taskResult.getStartDate().toString();
383
+            String endDatetime = taskResult.getEndDate().toString();
384
+
385
+            // Append question
386
+            AnsweredQuestionModel answeredQuestion = new AnsweredQuestionModel(questionID, answer, startDatetime, endDatetime);
387
+            answeredQuestions.add(answeredQuestion);
388
+
389
+        }
390
+
391
+        // Return answered assessment
392
+        return new AnsweredAssessmentModel(this.ASSESSMENT_ID, GlobalValues.getInstance().getDeviceToken(), answeredQuestions);
393
+
394
+    }
395
+
396
+    private List<Step> prepareSurveySteps(AssessmentModel assessment) {
397
+
398
+        // Initialize survey steps
399
+        List<Step> steps = new ArrayList<>();
400
+
401
+        // Create instruction screen (add it to total steps)
402
+        InstructionStep instructionStep = new InstructionStep(INSTRUCTION,
403
+                getString(R.string.appName),
404
+                assessment.getDescription());
405
+        instructionStep.setStepTitle(R.string.surveyToolbarText);
406
+        steps.add(0, instructionStep);
407
+
408
+        // Prepare survey question by question
409
+        List<QuestionModel> questions = assessment.getQuestions();
410
+        Log.d(TAG, "AssessmentCount: " + assessment.getQuestionCount() + " QuestionCount: " + questions.size()); // just to be sure
411
+        for(int i = 0; i < assessment.getQuestionCount(); i++) {
412
+
413
+            QuestionModel question = questions.get(i);
414
+            Log.d(TAG, "QuestionType:" + question.getType());
415
+
416
+            if(question.getType().equals("SCALED")) {
417
+
418
+                // Create question choices
419
+                Choice[] choices = new Choice[question.getMaxValue()];
420
+                for(int m = 0; m < question.getMaxValue(); m++) {
421
+                    if (m == 0) {
422
+                        choices[m] = new Choice<>((m + 1) + " " + question.getMinText(), m + 1);
423
+                    } else if (m == question.getMaxValue() - 1) {
424
+                        choices[m] = new Choice<>((m + 1) + " " + question.getMaxText(), m + 1);
425
+                    } else {
426
+                        choices[m] = new Choice<>(Integer.toString(m + 1), m + 1);
427
+                    }
428
+                }
429
+
430
+                // Create question step along with its choices
431
+                QuestionStep newQuestionStep = new QuestionStep(question.getID());
432
+                AnswerFormat multiFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, choices);
433
+
434
+                // Set question step details
435
+                newQuestionStep.setStepTitle(R.string.surveyToolbarText);
436
+                newQuestionStep.setTitle(question.getPremise());
437
+                newQuestionStep.setAnswerFormat(multiFormat);
438
+                newQuestionStep.setOptional(false);
439
+
440
+                // Add step to arrays
441
+                steps.add(newQuestionStep);
442
+
443
+            } else if(question.getType().equals("OPEN")) {
444
+
445
+                // Create question step along with its choices
446
+                QuestionStep newQuestionStep = new QuestionStep(question.getID());
447
+                TextAnswerFormat format = new TextAnswerFormat(100);
448
+                format.setIsMultipleLines(true);
449
+
450
+                // Set question step details
451
+                newQuestionStep.setStepTitle(R.string.surveyToolbarText);
452
+                newQuestionStep.setTitle(question.getPremise());
453
+                newQuestionStep.setAnswerFormat(format);
454
+                newQuestionStep.setOptional(false);
455
+
456
+                // Add step to arrays
457
+                steps.add(newQuestionStep);
458
+
459
+            } else {
460
+                Log.wtf(TAG, "Neither OPEN nor SCALED if-statement has been reached");
461
+            }
462
+
463
+        }
464
+
465
+        return steps;
466
+    }
467
+
468
+    private void promptRetrySend(final AnsweredAssessmentModel answeredAssessment) {
469
+        AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getContext()));
470
+        builder.setCancelable(true);
471
+        builder.setTitle("ERROR");
472
+        builder.setMessage("Couldn't send answers!");
473
+        builder.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
474
+            @Override
475
+            public void onClick(DialogInterface dialog, int which) {
476
+                Log.d(TAG, "Retrying answer send!");
477
+                sendSurveyInfo(answeredAssessment);
478
+            }
479
+        });
480
+        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
481
+            @Override
482
+            public void onClick(DialogInterface dialog, int which) {
483
+                Toast.makeText(getContext(), "Didn't send answers!", Toast.LENGTH_LONG).show();
484
+                fetchUserStatus();
485
+            }
486
+        });
487
+
488
+        AlertDialog dialog = builder.create();
489
+        dialog.show();
490
+    }
491
+
492
+}

+ 95
- 0
app/src/main/java/uprrp/tania/fragments/ConsentFragment.java View File

@@ -0,0 +1,95 @@
1
+package uprrp.tania.fragments;
2
+
3
+import android.content.Context;
4
+import android.graphics.Canvas;
5
+import android.os.Bundle;
6
+import android.view.LayoutInflater;
7
+import android.view.MotionEvent;
8
+import android.view.View;
9
+import android.view.ViewGroup;
10
+import android.widget.Toast;
11
+
12
+import androidx.annotation.NonNull;
13
+import androidx.annotation.Nullable;
14
+import androidx.fragment.app.Fragment;
15
+
16
+import com.github.barteksc.pdfviewer.PDFView;
17
+import com.github.barteksc.pdfviewer.listener.OnDrawListener;
18
+import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
19
+import com.github.barteksc.pdfviewer.listener.OnPageChangeListener;
20
+import com.github.barteksc.pdfviewer.listener.OnPageErrorListener;
21
+import com.github.barteksc.pdfviewer.listener.OnTapListener;
22
+import com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle;
23
+import com.github.barteksc.pdfviewer.util.FitPolicy;
24
+
25
+import java.io.File;
26
+
27
+import uprrp.tania.R;
28
+
29
+public class ConsentFragment extends Fragment {
30
+
31
+    private static final String TAG = "ConsentFragment";
32
+    private PDFView pdfView;
33
+
34
+    @Nullable
35
+    @Override
36
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
37
+        View view = inflater.inflate(R.layout.fragment_consent, container, false);
38
+
39
+        this.pdfView = view.findViewById(R.id.pdf_viewer);
40
+
41
+        // Get the file from app-specific storage
42
+        File consentFile = new File(requireContext().getFilesDir(), getString(R.string.consentFileName));
43
+
44
+        if (!consentFile.exists()) {
45
+            Toast.makeText(getContext(), "Consent file not found", Toast.LENGTH_LONG).show();
46
+            return view;
47
+        }
48
+
49
+        this.pdfView.fromFile(consentFile)
50
+                .defaultPage(0)
51
+                .enableSwipe(true)
52
+                .swipeHorizontal(false)
53
+                .enableDoubletap(true)
54
+                .onDraw(new OnDrawListener() {
55
+                    @Override
56
+                    public void onLayerDrawn(Canvas canvas, float pageWidth, float pageHeight, int displayedPage) {
57
+                        // Code here if you want to do something
58
+                    }
59
+                })
60
+                .onPageError(new OnPageErrorListener() {
61
+                    @Override
62
+                    public void onPageError(int page, Throwable t) {
63
+                        Toast.makeText(getContext(), "Error opening page " + page, Toast.LENGTH_LONG).show();
64
+                    }
65
+                })
66
+                .onPageChange(new OnPageChangeListener() {
67
+                    @Override
68
+                    public void onPageChanged(int page, int pageCount) {
69
+                        // Code here
70
+                    }
71
+                })
72
+                .onTap(new OnTapListener() {
73
+                    @Override
74
+                    public boolean onTap(MotionEvent e) {
75
+                        return true;
76
+                    }
77
+                })
78
+                .onLoad(new OnLoadCompleteListener() {
79
+                    @Override
80
+                    public void loadComplete(int nbPages) {
81
+                        pdfView.fitToWidth(0);
82
+                    }
83
+                })
84
+                .enableAnnotationRendering(true)
85
+                .scrollHandle(new DefaultScrollHandle(getContext()))
86
+                .spacing(10) // in dp
87
+                .pageFitPolicy(FitPolicy.WIDTH)
88
+                .pageSnap(true)
89
+                .pageFling(true)
90
+                .nightMode(false)
91
+                .load();
92
+
93
+        return view;
94
+    }
95
+}

+ 42
- 0
app/src/main/java/uprrp/tania/fragments/NotificationPermissionHandler.java View File

@@ -0,0 +1,42 @@
1
+package uprrp.tania.fragments;
2
+
3
+import android.Manifest;
4
+import android.app.Activity;
5
+import android.content.pm.PackageManager;
6
+import android.os.Build;
7
+import androidx.core.app.ActivityCompat;
8
+import androidx.core.content.ContextCompat;
9
+
10
+public class NotificationPermissionHandler {
11
+    private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 123;
12
+    private Activity activity;
13
+
14
+    public NotificationPermissionHandler(Activity activity) {
15
+        this.activity = activity;
16
+    }
17
+
18
+    public void checkAndRequestPermission() {
19
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
20
+            if (ContextCompat.checkSelfPermission(
21
+                    activity,
22
+                    Manifest.permission.POST_NOTIFICATIONS
23
+            ) != PackageManager.PERMISSION_GRANTED) {
24
+                ActivityCompat.requestPermissions(
25
+                        activity,
26
+                        new String[]{Manifest.permission.POST_NOTIFICATIONS},
27
+                        NOTIFICATION_PERMISSION_REQUEST_CODE
28
+                );
29
+            }
30
+        }
31
+    }
32
+
33
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
34
+        if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
35
+            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
36
+                // Permission granted, you can now send notifications
37
+            } else {
38
+                // Permission denied, handle accordingly (e.g., show a message to the user)
39
+            }
40
+        }
41
+    }
42
+}

+ 199
- 0
app/src/main/java/uprrp/tania/fragments/WithdrawFragment.java View File

@@ -0,0 +1,199 @@
1
+package uprrp.tania.fragments;
2
+
3
+import android.app.ProgressDialog;
4
+import android.content.Context;
5
+import android.content.DialogInterface;
6
+import android.content.Intent;
7
+import android.content.SharedPreferences;
8
+import android.os.Bundle;
9
+import android.util.Log;
10
+import android.view.LayoutInflater;
11
+import android.view.View;
12
+import android.view.ViewGroup;
13
+import android.widget.Button;
14
+import android.widget.TextView;
15
+import android.widget.Toast;
16
+
17
+import androidx.annotation.NonNull;
18
+import androidx.annotation.Nullable;
19
+import androidx.appcompat.app.AlertDialog;
20
+import androidx.fragment.app.Fragment;
21
+
22
+import java.util.Objects;
23
+import java.util.Observable;
24
+import java.util.Observer;
25
+
26
+import uprrp.tania.GlobalValues;
27
+import uprrp.tania.R;
28
+import uprrp.tania.utils.URLEventListener;
29
+import uprrp.tania.activities.GettingStartedActivity;
30
+import uprrp.tania.networking.SendWithdrawal;
31
+
32
+public class WithdrawFragment extends Fragment implements Observer {
33
+
34
+    private static final String TAG = "WithdrawFragment";
35
+    private View thisFragment;
36
+
37
+    @Nullable
38
+    @Override
39
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
40
+
41
+        // Constructor
42
+        thisFragment = inflater.inflate(R.layout.fragment_withdrawal, container, false);
43
+
44
+        // Listen for device token changes
45
+        this.startObservingGlobals();
46
+
47
+        // Disable interaction initially while token is being fetched
48
+        if(GlobalValues.getInstance().getDeviceToken() == null) {
49
+            disableInteractionTemporarily();
50
+        } else {
51
+            enableInteraction();
52
+        }
53
+
54
+        // Change all caps text to normal capitalization and add onClick listener
55
+        final Button withdrawButton = thisFragment.findViewById(R.id.withdrawButton);
56
+        withdrawButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
57
+        withdrawButton.setOnClickListener(new View.OnClickListener() {
58
+            @Override
59
+            public void onClick(View v) {
60
+                promptConfirmation();
61
+            }
62
+        });
63
+
64
+        return thisFragment;
65
+
66
+    }
67
+
68
+    private void disableInteractionTemporarily() {
69
+        final Button withdrawButton = thisFragment.findViewById(R.id.withdrawButton);
70
+        final TextView withdrawTextView = thisFragment.findViewById(R.id.withdrawText);
71
+
72
+        withdrawButton.setEnabled(false);
73
+        withdrawTextView.setText(R.string.loadingText);
74
+    }
75
+
76
+    private void disableInteraction() {
77
+        final Button withdrawButton = thisFragment.findViewById(R.id.withdrawButton);
78
+        final TextView withdrawTextView = thisFragment.findViewById(R.id.withdrawText);
79
+
80
+        withdrawButton.setEnabled(false);
81
+        withdrawTextView.setText(R.string.tokenErrorText);
82
+    }
83
+
84
+    private void enableInteraction() {
85
+        final Button withdrawButton = thisFragment.findViewById(R.id.withdrawButton);
86
+        final TextView withdrawTextView = thisFragment.findViewById(R.id.withdrawText);
87
+
88
+        withdrawButton.setEnabled(true);
89
+        withdrawTextView.setText(R.string.withdrawDescriptionText);
90
+    }
91
+
92
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
93
+    private void startObservingGlobals() {
94
+        GlobalValues.getInstance().addObserver(this);
95
+    }
96
+
97
+    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
98
+    @Override
99
+    public void update(Observable observable, Object o) {
100
+        if(observable instanceof GlobalValues) {
101
+            if(o instanceof GlobalValues.ValueKey) {
102
+                if(((GlobalValues.ValueKey) o).getKey() == GlobalValues.ValueName.DEVICE_TOKEN) {
103
+                    if(GlobalValues.getInstance().getDeviceToken() == null) {
104
+                        disableInteraction();
105
+                    } else {
106
+                        enableInteraction();
107
+                    }
108
+                }
109
+            }
110
+        }
111
+    }
112
+
113
+    private void promptConfirmation() {
114
+        AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getContext()));
115
+        builder.setCancelable(true);
116
+        builder.setTitle("WITHDRAWING");
117
+        builder.setMessage("Are you absolutely sure you want to withdraw from the project?");
118
+        builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
119
+            @Override
120
+            public void onClick(DialogInterface dialog, int which) {
121
+                sendWithdrawalRequest();
122
+            }
123
+        });
124
+        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
125
+            @Override
126
+            public void onClick(DialogInterface dialog, int which) {
127
+                Log.d(TAG, "Cancelled withdrawal!");
128
+            }
129
+        });
130
+
131
+        AlertDialog dialog = builder.create();
132
+        dialog.show();
133
+    }
134
+
135
+    private void sendWithdrawalRequest() {
136
+
137
+        // Initiate progress dialog
138
+        // TODO: find substitute for this deprecated dialog box
139
+        final ProgressDialog progressDialog = ProgressDialog.show(getContext(),
140
+                "Withdrawing",
141
+                getString(R.string.progressDialogDescriptionText));
142
+
143
+        // Send withdrawal request to server
144
+        SendWithdrawal sendWithdrawalTask = new SendWithdrawal(new URLEventListener() {
145
+            @Override
146
+            public void onSuccess() {
147
+
148
+                progressDialog.dismiss();
149
+                Context context = getContext();
150
+
151
+                // Change system preferences to begin in GettingStartedActivity by default
152
+                SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
153
+                SharedPreferences.Editor editor = prefs.edit();
154
+                editor.putBoolean("needsToRegister", true);
155
+                editor.apply();
156
+
157
+                // Start GettingStartedActivity with a success message
158
+                Intent intent = new Intent(context, GettingStartedActivity.class);
159
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
160
+                context.startActivity(intent);
161
+                Toast.makeText(context.getApplicationContext(), "Successfully withdrawn!", Toast.LENGTH_LONG).show();
162
+
163
+            }
164
+
165
+            @Override
166
+            public void onFailure(Exception e) {
167
+                progressDialog.dismiss();
168
+                Toast.makeText(getContext(), "Oops! Something went wrong...", Toast.LENGTH_LONG).show();
169
+                Log.e(TAG, "Error occurred while sending withdrawal request to server...");
170
+                e.printStackTrace();
171
+            }
172
+        });
173
+
174
+        // Start task
175
+        sendWithdrawalTask.execute(GlobalValues.getInstance().getDeviceToken());
176
+
177
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
178
+        /*
179
+        Handler handler = new Handler();
180
+        handler.postDelayed(new Runnable() {
181
+            public void run() {
182
+                progressDialog.dismiss();
183
+                if(true) { // Imitate success
184
+                    Context context = getContext();
185
+                    Intent intent = new Intent(context, GettingStartedActivity.class);
186
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
187
+                    context.startActivity(intent);
188
+                    Toast.makeText(context.getApplicationContext(), "Successfully withdrawn!", Toast.LENGTH_LONG).show();
189
+                } else { // Imitate failure
190
+                    Toast.makeText(getContext(), "Oops! Something went wrong...", Toast.LENGTH_LONG).show();
191
+                }
192
+            }
193
+        }, 5000);
194
+        */
195
+        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
196
+
197
+    }
198
+
199
+}

+ 16
- 0
app/src/main/java/uprrp/tania/models/AnsweredAssessmentModel.java View File

@@ -0,0 +1,16 @@
1
+package uprrp.tania.models;
2
+
3
+import java.util.ArrayList;
4
+
5
+public class AnsweredAssessmentModel {
6
+    // NOTE these attribute must have the same names as the JSON fields we'll be sending to the server
7
+    private final String id_subquestionnair;
8
+    private final String token;
9
+    private final ArrayList<AnsweredQuestionModel> preguntas;
10
+
11
+    public AnsweredAssessmentModel(String id, String token, ArrayList<AnsweredQuestionModel> answeredQuestions) {
12
+        this.id_subquestionnair = id;
13
+        this.token = token;
14
+        this.preguntas = answeredQuestions;
15
+    }
16
+}

+ 16
- 0
app/src/main/java/uprrp/tania/models/AnsweredQuestionModel.java View File

@@ -0,0 +1,16 @@
1
+package uprrp.tania.models;
2
+
3
+public class AnsweredQuestionModel {
4
+    // NOTE these attribute must have the same names as the JSON fields we'll be sending to the server
5
+    private final String id_question;
6
+    private final String question_answer;
7
+    private final String start_datetime;
8
+    private final String end_datetime;
9
+
10
+    public AnsweredQuestionModel(String id, String answer, String startDatetime, String endDatetime) {
11
+        this.id_question = id;
12
+        this.question_answer = answer;
13
+        this.start_datetime = startDatetime;
14
+        this.end_datetime = endDatetime;
15
+    }
16
+}

+ 34
- 0
app/src/main/java/uprrp/tania/models/AssessmentModel.java View File

@@ -0,0 +1,34 @@
1
+package uprrp.tania.models;
2
+
3
+import java.util.ArrayList;
4
+
5
+public class AssessmentModel {
6
+
7
+    // NOTE: these attributes must have the same name
8
+    // as in the JSON and need not be manually initialized
9
+    private String id_subquestionnair;
10
+    private String description;
11
+    private int cantidad_preguntas;
12
+    private ArrayList<QuestionModel> preguntas;
13
+
14
+    public boolean isEmpty() {
15
+        return false;
16
+    }
17
+
18
+    public String getID() {
19
+        return this.id_subquestionnair;
20
+    }
21
+
22
+    public int getQuestionCount() {
23
+        return this.cantidad_preguntas;
24
+    }
25
+
26
+    public String getDescription() {
27
+        return this.description;
28
+    }
29
+
30
+    public ArrayList<QuestionModel> getQuestions() {
31
+        return this.preguntas;
32
+    }
33
+
34
+}

+ 9
- 0
app/src/main/java/uprrp/tania/models/EmptyAssessmentModel.java View File

@@ -0,0 +1,9 @@
1
+package uprrp.tania.models;
2
+
3
+public class EmptyAssessmentModel extends AssessmentModel {
4
+    private static final String TAG = "EmptyUserStatusModel";
5
+    @Override
6
+    public boolean isEmpty() {
7
+        return true;
8
+    }
9
+}

+ 8
- 0
app/src/main/java/uprrp/tania/models/EmptyUserStatusModel.java View File

@@ -0,0 +1,8 @@
1
+package uprrp.tania.models;
2
+
3
+public class EmptyUserStatusModel extends UserStatusModel {
4
+    @Override
5
+    public boolean isEnrolled() {
6
+        return false;
7
+    }
8
+}

+ 50
- 0
app/src/main/java/uprrp/tania/models/QuestionModel.java View File

@@ -0,0 +1,50 @@
1
+package uprrp.tania.models;
2
+
3
+import android.util.Log;
4
+
5
+public class QuestionModel {
6
+
7
+    // NOTE: these attributes must have the same name as in the JSON
8
+    private String id_question;
9
+    private String id_type;
10
+    private String premise;
11
+    private String max_text;
12
+    private String min_text;
13
+    private int max_val;
14
+
15
+    public String getID() {
16
+        return this.id_question;
17
+    }
18
+
19
+    public String getType() {
20
+        if(this.id_type.equals("1")) {
21
+            return "SCALED";
22
+        } else if(this.id_type.equals("2")) {
23
+            return "OPEN";
24
+        } else {
25
+            Log.wtf("QUESTION MODEL DESERIALIZATION", "Encountered id_type of " + this.id_type);
26
+            return "OPEN";
27
+        }
28
+    }
29
+
30
+    public String getPremise() {
31
+        return this.premise;
32
+    }
33
+
34
+    public String getMaxText() {
35
+        return this.max_text;
36
+    }
37
+
38
+    public String getMinText() {
39
+        return this.min_text;
40
+    }
41
+
42
+    public int getMaxValue() {
43
+        return this.max_val;
44
+    }
45
+
46
+    public int getMinValue() {
47
+        return 1;
48
+    }
49
+
50
+}

+ 23
- 0
app/src/main/java/uprrp/tania/models/UserModel.java View File

@@ -0,0 +1,23 @@
1
+package uprrp.tania.models;
2
+
3
+public class UserModel {
4
+
5
+    // NOTE: these attributes must have the same name
6
+    // as in the JSON and need not be manually initialized
7
+    String birthDate;
8
+    String email;
9
+    String password;
10
+    String gender;
11
+    String hasJob;
12
+    String token;
13
+
14
+    public UserModel(String birthDate, String email, String password, String gender, String jobType, String token) {
15
+        this.birthDate = birthDate;
16
+        this.email = email;
17
+        this.password = password;
18
+        this.gender = gender;
19
+        this.hasJob = jobType;
20
+        this.token = token;
21
+    }
22
+
23
+}

+ 115
- 0
app/src/main/java/uprrp/tania/models/UserStatusModel.java View File

@@ -0,0 +1,115 @@
1
+package uprrp.tania.models;
2
+
3
+import android.util.Log;
4
+
5
+public class UserStatusModel {
6
+
7
+    private static final String TAG = "UserStatusModel";
8
+    // NOTE: these attributes must have the same name
9
+    // as in the JSON and need not be manually initialized
10
+    private String institucion;
11
+    private String experiencia;
12
+    private int surveys_completed;
13
+    private int surveys_sent;
14
+    private String time_to_next;
15
+    private boolean available_now;
16
+
17
+    public boolean isEnrolled() {
18
+        return true;
19
+    }
20
+
21
+    public boolean surveyAvailable() {
22
+        return this.available_now;
23
+    }
24
+
25
+    public boolean ranOutOfMoments() {
26
+        // NOTE: null signifies experience has ran out of moments
27
+        return this.time_to_next == null;
28
+    }
29
+
30
+    public String getTimeToNext() {
31
+        if(this.ranOutOfMoments()) {
32
+            return "No more surveys for now.\nCome back later!";
33
+        } else if(this.surveyAvailable()) {
34
+            return "Survey available!";
35
+        } else {
36
+            return "Next survey in " + this.formatTimeToNext() + ".";
37
+        }
38
+    }
39
+
40
+    private boolean isInteger(String s) {
41
+        return s.matches("-?\\d+");
42
+    }
43
+
44
+    private boolean validTimeToNext() {
45
+        // Format is DD:HH:MM (can be negative if survey can be answered)
46
+        String[] parts = this.time_to_next.split(":");
47
+
48
+        if(parts.length != 3) {
49
+            return false;
50
+        } else if(!isInteger(parts[0]) || !isInteger(parts[1]) || !isInteger(parts[2])) {
51
+            return false;
52
+        } else {
53
+            return true;
54
+        }
55
+    }
56
+
57
+    private String formatTimeToNext() {
58
+
59
+        // Validation
60
+        if(!this.validTimeToNext()) {
61
+            Log.wtf(TAG, "Backend gave an invalid time_to_next: " + this.time_to_next);
62
+            return ".."; // forms ellipsis
63
+        }
64
+
65
+        // Format is DD:HH:MM (can be negative if survey can be answered)
66
+        String[] parts = this.time_to_next.split(":");
67
+        int days = Integer.parseInt(parts[0]);
68
+        int hours = Integer.parseInt(parts[1]);
69
+        int minutes = Integer.parseInt(parts[2]);
70
+        String formattedTimeToNext = "";
71
+
72
+        if(days != 0) {
73
+            formattedTimeToNext += days + " days";
74
+            if(hours != 0) {
75
+                formattedTimeToNext += ", " + hours + " hours";
76
+            } else {
77
+                formattedTimeToNext += ", " + minutes + " minutes";
78
+            }
79
+        } else {
80
+            if(hours != 0) {
81
+                formattedTimeToNext += hours + " hours";
82
+                formattedTimeToNext += ", " + minutes + " minutes";
83
+            } else {
84
+                if(minutes != 0) {
85
+                    formattedTimeToNext += minutes + " minutes";
86
+                } else {
87
+                    Log.wtf(TAG, "Encountered a 0:0:0 in UserStatusModel.formatTimeToNext()!\nShould've been handled by UserStatusModel.surveyAvailable()");
88
+                }
89
+            }
90
+        }
91
+
92
+        return formattedTimeToNext;
93
+
94
+    }
95
+
96
+    public String toString() {
97
+
98
+        // If user is not enrolled in an experience,
99
+        // return early with this string
100
+        if(!this.isEnrolled()) {
101
+            return "You are not enrolled in any experience.\nIf you think this is a mistake, contact your supervisors.";
102
+        }
103
+
104
+        // Else, return proper string
105
+        String status = "You're enrolled in " + this.experiencia + ", by " + this.institucion + ".";
106
+        if(this.surveys_completed == this.surveys_sent) {
107
+            status += " You've answered all surveys. Come back later in case a new one pops up!";
108
+        } else {
109
+            status += " So far, you have completed " + this.surveys_completed + " out of " + this.surveys_sent + " delivered surveys.";
110
+        }
111
+        return status;
112
+
113
+    }
114
+
115
+}

+ 145
- 0
app/src/main/java/uprrp/tania/networking/FetchAssessment.java View File

@@ -0,0 +1,145 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import com.google.gson.GsonBuilder;
7
+import com.google.gson.JsonSyntaxException;
8
+
9
+import org.json.JSONException;
10
+import org.json.JSONObject;
11
+
12
+import java.io.BufferedReader;
13
+import java.io.IOException;
14
+import java.io.InputStream;
15
+import java.io.InputStreamReader;
16
+import java.net.MalformedURLException;
17
+import java.net.URL;
18
+
19
+import javax.net.ssl.HttpsURLConnection;
20
+
21
+import uprrp.tania.utils.URLEventListener;
22
+import uprrp.tania.models.AssessmentModel;
23
+import uprrp.tania.models.EmptyAssessmentModel;
24
+
25
+public class FetchAssessment extends AsyncTask<String, Void, JSONObject> {
26
+
27
+    private static final String TAG = "FetchAssessment";
28
+    private static final String assessmentBaseURL = "https://tania.uprrp.edu/getSubQ2.php?tk=";
29
+    private final URLEventListener myCallBack;
30
+
31
+    public FetchAssessment(URLEventListener callback) {
32
+        this.myCallBack = callback;
33
+    }
34
+
35
+    private boolean validInputs(String... strings) {
36
+
37
+        if(strings.length != 1) {
38
+            Log.e(TAG, "Invalid string array length!");
39
+            return false;
40
+        }
41
+
42
+        for(int i = 0; i < strings.length; i++) {
43
+            if(strings[i] == null) {
44
+                Log.e(TAG, "Encountered null parameter on index " + i);
45
+                return false;
46
+            }
47
+        }
48
+
49
+        return true;
50
+
51
+    }
52
+
53
+    @Override
54
+    protected JSONObject doInBackground(String... strings) {
55
+
56
+        // Validation
57
+        if(!validInputs(strings)) {
58
+            Log.e(TAG, "Invalid inputs given!");
59
+            return null;
60
+        }
61
+
62
+        // Extract variables
63
+        String deviceToken = strings[0];
64
+
65
+        try {
66
+
67
+            // Send GET data request
68
+            URL url = new URL(assessmentBaseURL + deviceToken);
69
+            Log.d(TAG, "token:" + deviceToken); // log
70
+            Log.d(TAG, "url:" + url.toString()); // log
71
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
72
+            InputStream inputStream = conn.getInputStream();
73
+
74
+            // Get the server response
75
+            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
76
+            StringBuilder serverResponse =  new StringBuilder();
77
+
78
+            String line = "";
79
+            while (line != null) {
80
+                serverResponse.append(line);
81
+                line = bufferedReader.readLine();
82
+            }
83
+
84
+            String response = serverResponse.toString();
85
+            Log.d(TAG, response.substring(0, 10)); // log
86
+            if(response.startsWith("Error:NoHa")) {
87
+                return new JSONObject("{}");
88
+            } else {
89
+                return new JSONObject(response);
90
+            }
91
+
92
+        } catch (MalformedURLException e) {
93
+            Log.e(TAG, "Wrong URL used!");
94
+            e.printStackTrace();
95
+            return null;
96
+        } catch (IOException e) {
97
+            Log.e(TAG, "IOStream error occurred!");
98
+            e.printStackTrace();
99
+            return null;
100
+        } catch (JSONException e) {
101
+            Log.e(TAG, "Couldn't construct return JSON!");
102
+            e.printStackTrace();
103
+            return null;
104
+        }
105
+
106
+    }
107
+
108
+    @Override
109
+    protected void onPostExecute(JSONObject obj) {
110
+
111
+        if(this.myCallBack == null) {
112
+            Log.e(TAG, "Callback wasn't initialized first!");
113
+            return;
114
+        }
115
+
116
+        if(obj == null) {
117
+            this.myCallBack.onFailure(new Exception("An error occurred during FetchAssessment!"));
118
+            return;
119
+        }
120
+
121
+        // If there's no survey available, return empty AssessmentModel
122
+        if(obj.length() == 0) {
123
+            Log.d(TAG, "SENT EMPTY ASSESSMENT MODEL");
124
+            AssessmentModel assessment = new EmptyAssessmentModel();
125
+            this.myCallBack.onSuccess(assessment);
126
+            return;
127
+        }
128
+
129
+        // Decode into AssessmentModel and send
130
+        try {
131
+            Log.d(TAG, "rawString:" + obj.toString());
132
+            GsonBuilder builder = new GsonBuilder();
133
+            builder.setPrettyPrinting();
134
+            AssessmentModel assessment = builder.create().fromJson(obj.toString(), AssessmentModel.class);
135
+            Log.d(TAG,"reconstructedJSON:\n" + builder.create().toJson(assessment));
136
+            this.myCallBack.onSuccess(assessment);
137
+        } catch (JsonSyntaxException e) {
138
+            this.myCallBack.onFailure(new Exception("Something went wrong during data preparation step in FetchAssessment.onPostExecute()!"));
139
+        }
140
+
141
+    }
142
+
143
+}
144
+
145
+

+ 153
- 0
app/src/main/java/uprrp/tania/networking/FetchUserStatus.java View File

@@ -0,0 +1,153 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import com.google.gson.GsonBuilder;
7
+import com.google.gson.JsonSyntaxException;
8
+
9
+import org.json.JSONArray;
10
+import org.json.JSONException;
11
+import org.json.JSONObject;
12
+
13
+import java.io.BufferedReader;
14
+import java.io.IOException;
15
+import java.io.InputStream;
16
+import java.io.InputStreamReader;
17
+import java.net.MalformedURLException;
18
+import java.net.URL;
19
+
20
+import javax.net.ssl.HttpsURLConnection;
21
+
22
+import uprrp.tania.utils.URLEventListener;
23
+import uprrp.tania.models.EmptyUserStatusModel;
24
+import uprrp.tania.models.UserStatusModel;
25
+
26
+public class FetchUserStatus extends AsyncTask<String, Void, JSONArray> {
27
+
28
+    private static final String TAG = "FetchUserStatus";
29
+    private static final String userStatusBaseURL = "https://tania.uprrp.edu/status.php?tk=";
30
+    private final URLEventListener myCallBack;
31
+
32
+    public FetchUserStatus(URLEventListener callback) {
33
+        this.myCallBack = callback;
34
+    }
35
+
36
+    private boolean validInputs(String... strings) {
37
+
38
+        if(strings.length != 1) {
39
+            Log.e(TAG, "Invalid string array length!");
40
+            return false;
41
+        }
42
+
43
+        for(int i = 0; i < strings.length; i++) {
44
+            if(strings[i] == null) {
45
+                Log.e(TAG, "Encountered null parameter on index " + i);
46
+                return false;
47
+            }
48
+        }
49
+
50
+        return true;
51
+
52
+    }
53
+
54
+    @Override
55
+    protected JSONArray doInBackground(String... strings) {
56
+
57
+        // Validation
58
+        if(!validInputs(strings)) {
59
+            Log.e(TAG, "Invalid inputs given!");
60
+            return null;
61
+        }
62
+
63
+        // Extract variables
64
+        String deviceToken = strings[0];
65
+
66
+        try {
67
+
68
+            // Send GET data request
69
+            URL url = new URL(userStatusBaseURL + deviceToken);
70
+            Log.d(TAG, "token:" + deviceToken); // log
71
+            Log.d(TAG, "url:" + url.toString()); // log
72
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
73
+            InputStream inputStream = conn.getInputStream();
74
+
75
+            // Get the server response
76
+            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
77
+            StringBuilder serverResponse =  new StringBuilder();
78
+
79
+            String line = "";
80
+            while(line != null) {
81
+                serverResponse.append(line);
82
+                line = bufferedReader.readLine();
83
+            }
84
+
85
+            String response = serverResponse.toString();
86
+            Log.d(TAG, "rawString:" + response); // log
87
+            if(response.contains("No hay nada")) { // no esta inscrito
88
+                return new JSONArray("[]");
89
+            } else {
90
+                return new JSONArray(response);
91
+            }
92
+
93
+        } catch (MalformedURLException e) {
94
+            Log.e(TAG, "Wrong URL used!");
95
+            e.printStackTrace();
96
+            return null;
97
+        } catch (IOException e) {
98
+            Log.e(TAG, "IOStream error occurred!");
99
+            e.printStackTrace();
100
+            return null;
101
+        } catch (JSONException e) {
102
+            Log.e(TAG, "Couldn't construct return JSON!");
103
+            e.printStackTrace();
104
+            return null;
105
+        }
106
+
107
+    }
108
+
109
+    @Override
110
+    protected void onPostExecute(JSONArray arr) {
111
+        super.onPostExecute(arr);
112
+
113
+        if(this.myCallBack == null) {
114
+            Log.e(TAG, "Callback wasn't initialized first!");
115
+            return;
116
+        }
117
+
118
+        if(arr == null) {
119
+            this.myCallBack.onFailure(new Exception("Something went wrong with FetchUserStatus.doInBackground()!"));
120
+            return;
121
+        }
122
+
123
+        // If json array is empty, return empty user status
124
+        if(arr.length() == 0) {
125
+            Log.d(TAG, "SENT EMPTY USER STATUS");
126
+            UserStatusModel userData = new EmptyUserStatusModel();
127
+            this.myCallBack.onSuccess(userData);
128
+            return;
129
+        }
130
+
131
+        // Decode into UserStatusModel and send
132
+        try {
133
+
134
+            // TODO: Should find moment that is nearest to current date,
135
+            //  instead of assuming it will always be sorted (just in case)
136
+            JSONObject obj = arr.getJSONObject(0);
137
+
138
+            // Actual decoding
139
+            Log.d(TAG, "rawString:" + obj.toString());
140
+            GsonBuilder builder = new GsonBuilder();
141
+            builder.setPrettyPrinting();
142
+            UserStatusModel userStatus = builder.create().fromJson(obj.toString(), UserStatusModel.class);
143
+            Log.d(TAG,"reconstructedJSON:\n" + builder.create().toJson(userStatus));
144
+            this.myCallBack.onSuccess(userStatus);
145
+
146
+        } catch(JsonSyntaxException e) {
147
+            this.myCallBack.onFailure(new Exception("Something went wrong during Gson decoding step in FetchUserStatus.onPostExecute()!"));
148
+        } catch (JSONException e) {
149
+            this.myCallBack.onFailure(new Exception("Something went wrong during JSONObject extraction step in FetchUserStatus.onPostExecute()!"));
150
+        }
151
+    }
152
+
153
+}

+ 187
- 0
app/src/main/java/uprrp/tania/networking/SendAnswers.java View File

@@ -0,0 +1,187 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import com.google.gson.GsonBuilder;
7
+import com.google.gson.JsonSyntaxException;
8
+
9
+import org.json.JSONException;
10
+import org.json.JSONObject;
11
+
12
+import java.io.BufferedReader;
13
+import java.io.InputStreamReader;
14
+import java.io.OutputStreamWriter;
15
+import java.io.UnsupportedEncodingException;
16
+import java.net.URL;
17
+import java.net.URLEncoder;
18
+
19
+import javax.net.ssl.HttpsURLConnection;
20
+
21
+import uprrp.tania.utils.URLEventListener;
22
+import uprrp.tania.models.AnsweredAssessmentModel;
23
+
24
+public class SendAnswers extends AsyncTask<AnsweredAssessmentModel, Void, String> {
25
+
26
+    private static final String TAG = "SendAnswers";
27
+    private static final String sendAnswersURL = "https://tania.uprrp.edu/parseAnswers.php";
28
+    private final URLEventListener myCallback;
29
+
30
+    public SendAnswers(URLEventListener callback) {
31
+        this.myCallback = callback;
32
+    }
33
+
34
+    private boolean validInputs(AnsweredAssessmentModel... assessments) {
35
+
36
+        if(assessments.length != 1) {
37
+            Log.e(TAG, "Invalid array length!");
38
+            return false;
39
+        }
40
+
41
+        for(int i = 0; i < assessments.length; i++) {
42
+            if(assessments[i] == null) {
43
+                Log.e(TAG, "Encountered null parameter on index " + i);
44
+                return false;
45
+            }
46
+        }
47
+
48
+        return true;
49
+
50
+    }
51
+
52
+    // TODO: instead of making a normal POST request with
53
+    //  the body data=<uglyJSON>, we should just send the JSON itself
54
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
55
+    /*
56
+        uglyJSON Format:
57
+        {
58
+            "os": "Android",
59
+            "data": String (containing escaped serializedAssessmentJSON)
60
+        }
61
+
62
+        serializedAssessmentJSON Format:
63
+        {
64
+            "id_subquestionnair": String,
65
+            "token": String,
66
+            "preguntas": [
67
+                {
68
+                    "id_question": String,
69
+                    "start_datetime": String,
70
+                    "question_answer": String,
71
+                    "end_datetime": String
72
+                },
73
+                ...
74
+            ]
75
+        }
76
+
77
+        WE SHOULD BE INSTEAD SENDING SOMETHING LIKE:
78
+        {
79
+            "os": "Android",
80
+            "token" String,
81
+            "data": {
82
+                "id_subquestionnair": String,
83
+                "preguntas": [
84
+                    {
85
+                        "id_question": String,
86
+                        "start_datetime": String,
87
+                        "question_answer": String,
88
+                        "end_datetime": String
89
+                    },
90
+                    ...
91
+                ]
92
+            }
93
+        }
94
+     */
95
+    @Override
96
+    protected String doInBackground(AnsweredAssessmentModel... assessments) {
97
+
98
+        // Validation
99
+        if(!validInputs(assessments)) {
100
+            Log.e(TAG, "Invalid inputs given!");
101
+            return null;
102
+        }
103
+
104
+        // Extract variables
105
+        AnsweredAssessmentModel answeredAssessment = assessments[0]; // array will only ever contain a single element
106
+
107
+        try {
108
+
109
+            // Serialize answered assessment
110
+            GsonBuilder builder = new GsonBuilder();
111
+//            builder.setPrettyPrinting(); // the server doesn't like pretty printing...
112
+            String serializedAssessment = builder.create().toJson(answeredAssessment);
113
+            Log.d(TAG,"reconstructedJSON:\n" + serializedAssessment); // log
114
+
115
+            // Create wrapper json (a.k.a. uglyJSON)
116
+            JSONObject surveyResults = new JSONObject();
117
+            surveyResults.put("os", "Android");
118
+            surveyResults.put("data", serializedAssessment);
119
+
120
+            // Encode data
121
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(surveyResults.toString(), "UTF-8");
122
+
123
+            // Send POST data request
124
+            URL url = new URL(sendAnswersURL);
125
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
126
+            conn.setDoOutput(true);
127
+            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
128
+            wr.write(encodedRequestBody);
129
+            wr.flush();
130
+
131
+            // Get the server response
132
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
133
+            StringBuilder serverResponse = new StringBuilder();
134
+
135
+            String line = "";
136
+            while(line != null) {
137
+                serverResponse.append(line);
138
+                line = serverReader.readLine();
139
+            }
140
+
141
+            String response = serverResponse.toString();
142
+            Log.d(TAG, "The server's response is: " + response);
143
+            if(response.startsWith("Success")) {
144
+                return response;
145
+            } else {
146
+                return null;
147
+            }
148
+
149
+
150
+        } catch (JsonSyntaxException e) {
151
+            Log.e(TAG, "Failed to JSONify AnsweredAssessment!");
152
+            e.printStackTrace();
153
+            return null;
154
+        } catch (JSONException e) {
155
+            Log.e(TAG, "Failed to put AnsweredAssessment to surveyResults!");
156
+            e.printStackTrace();
157
+            return null;
158
+        } catch (UnsupportedEncodingException e) {
159
+            Log.e(TAG, "Couldn't encode surveyResultsJSON!");
160
+            e.printStackTrace();
161
+            return null;
162
+        } catch(Exception e) {
163
+            Log.e(TAG,"Couldn't communicate with server while sending answers!" + e.getMessage());
164
+            e.printStackTrace();
165
+            return null;
166
+        }
167
+
168
+    }
169
+
170
+    @Override
171
+    protected void onPostExecute(String response) {
172
+
173
+        if(this.myCallback == null) {
174
+            Log.e(TAG, "Callback wasn't initialized first!");
175
+            return;
176
+        }
177
+
178
+        if(response == null) {
179
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
180
+            return;
181
+        }
182
+
183
+        this.myCallback.onSuccess();
184
+
185
+    }
186
+
187
+}

+ 219
- 0
app/src/main/java/uprrp/tania/networking/SendConsentForm.java View File

@@ -0,0 +1,219 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.util.Log;
4
+
5
+import org.json.JSONException;
6
+import org.json.JSONObject;
7
+
8
+import java.io.BufferedReader;
9
+import java.io.BufferedWriter;
10
+import java.io.InputStreamReader;
11
+import java.io.OutputStreamWriter;
12
+import java.io.UnsupportedEncodingException;
13
+import java.io.Writer;
14
+import java.net.URL;
15
+import java.net.URLEncoder;
16
+import java.nio.charset.StandardCharsets;
17
+import java.util.concurrent.Executor;
18
+import java.util.concurrent.Executors;
19
+
20
+import javax.net.ssl.HttpsURLConnection;
21
+
22
+import uprrp.tania.utils.URLEventListener;
23
+
24
+public class SendConsentForm {
25
+
26
+    private static final String TAG = "SendConsentForm";
27
+    private static final String consentFormBaseURL = "https://tania.uprrp.edu/getSignatureIOS.php";
28
+    private final URLEventListener myCallback;
29
+    private final Executor executor = Executors.newSingleThreadExecutor();
30
+
31
+    public SendConsentForm(URLEventListener callback) {
32
+        this.myCallback = callback;
33
+    }
34
+
35
+    private boolean validInputs(String... strings) {
36
+        if (strings == null || strings.length != 3) {
37
+            Log.e(TAG, "Invalid string array length!");
38
+            return false;
39
+        }
40
+
41
+        for (int i = 0; i < strings.length; i++) {
42
+            if (strings[i] == null || strings[i].isEmpty()) {
43
+                Log.e(TAG, "Encountered null or empty parameter on index " + i);
44
+                return false;
45
+            }
46
+        }
47
+
48
+        return true;
49
+    }
50
+    public void execute(String... strings) {
51
+        executor.execute(() -> {
52
+            String result = doInBackground(strings);
53
+            onPostExecute(result);
54
+        });
55
+    }
56
+
57
+//    private String doInBackground(String... strings) {
58
+//        // Validation
59
+//        if(!validInputs(strings)) {
60
+//            Log.e(TAG, "Invalid inputs given!");
61
+//            return null;
62
+//        }
63
+//
64
+//        // Extract variables
65
+//        String deviceToken = strings[0];
66
+//        String consentFormBase64 = strings[1];
67
+//        String signatureDate = strings[2];
68
+//
69
+//        try {
70
+//            // Create JSON
71
+//            JSONObject consentFormJSON = new JSONObject();
72
+//            consentFormJSON.put("token", deviceToken);
73
+//            consentFormJSON.put("signatureDate", signatureDate);
74
+//            consentFormJSON.put("signaturePDFBase64", consentFormBase64);
75
+//            Log.d(TAG, "ConsentFormJSON is " + consentFormJSON.toString()); // log
76
+//
77
+//            // Encode data
78
+//            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(consentFormJSON.toString(), "UTF-8");
79
+//
80
+//            // Send POST data request
81
+//            URL url = new URL(consentFormBaseURL);
82
+//            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
83
+//            conn.setRequestMethod("POST");
84
+//            conn.setDoOutput(true);
85
+//            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
86
+//
87
+//            try (Writer writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8))) {
88
+//                writer.write(encodedRequestBody);
89
+//            }
90
+//
91
+//            // Get the server response
92
+//            StringBuilder serverResponse = new StringBuilder();
93
+//            try (BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
94
+//                String line;
95
+//                while ((line = serverReader.readLine()) != null) {
96
+//                    serverResponse.append(line);
97
+//                }
98
+//            }
99
+//
100
+//            String response = serverResponse.toString();
101
+//            Log.d(TAG, "The server's response is " + response);
102
+//            if(response.toLowerCase().contains("error")) {
103
+//                return null;
104
+//            } else {
105
+//                return response;
106
+//            }
107
+//
108
+//        } catch (JSONException e) {
109
+//            Log.e(TAG, "Couldn't prepare consentFormJSON!", e);
110
+//            return null;
111
+//        } catch (UnsupportedEncodingException e) {
112
+//            Log.e(TAG, "Couldn't encode consentFormJSON!", e);
113
+//            return null;
114
+//        } catch(Exception e) {
115
+//            Log.e(TAG, "Couldn't communicate with server while recovering account!", e);
116
+//            return null;
117
+//        }
118
+//    }
119
+private String doInBackground(String... strings) {
120
+    Log.d(TAG, "doInBackground: Starting method execution");
121
+
122
+    // Validation
123
+    if(!validInputs(strings)) {
124
+        Log.e(TAG, "doInBackground: Invalid inputs given!");
125
+        return null;
126
+    }
127
+
128
+    // Extract variables
129
+    String deviceToken = strings[0];
130
+    String consentFormBase64 = strings[1];
131
+    String signatureDate = strings[2];
132
+
133
+    Log.d(TAG, "doInBackground: Extracted variables - deviceToken length: " + deviceToken.length() +
134
+            ", consentFormBase64 length: " + consentFormBase64.length() +
135
+            ", signatureDate: " + signatureDate);
136
+
137
+    HttpsURLConnection conn = null;
138
+    try {
139
+        // Create JSON
140
+        JSONObject consentFormJSON = new JSONObject();
141
+        consentFormJSON.put("token", deviceToken);
142
+        consentFormJSON.put("signatureDate", signatureDate);
143
+        consentFormJSON.put("signaturePDFBase64", consentFormBase64);
144
+        Log.d(TAG, "doInBackground: ConsentFormJSON created successfully");
145
+
146
+        // Encode data
147
+        String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(consentFormJSON.toString(), "UTF-8");
148
+        Log.d(TAG, "doInBackground: Request body encoded, length: " + encodedRequestBody.length());
149
+
150
+        // Send POST data request
151
+        URL url = new URL(consentFormBaseURL);
152
+        Log.d(TAG, "doInBackground: Attempting to connect to URL: " + consentFormBaseURL);
153
+
154
+        conn = (HttpsURLConnection) url.openConnection();
155
+        conn.setRequestMethod("POST");
156
+        conn.setDoOutput(true);
157
+        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
158
+        conn.setConnectTimeout(15000); // 15 seconds timeout
159
+        conn.setReadTimeout(15000); // 15 seconds timeout
160
+
161
+        Log.d(TAG, "doInBackground: Connection opened, writing request body");
162
+        try (Writer writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8))) {
163
+            writer.write(encodedRequestBody);
164
+        }
165
+        Log.d(TAG, "doInBackground: Request body written successfully");
166
+
167
+        // Get the server response
168
+        int responseCode = conn.getResponseCode();
169
+        Log.d(TAG, "doInBackground: Received response code: " + responseCode);
170
+
171
+        StringBuilder serverResponse = new StringBuilder();
172
+        try (BufferedReader serverReader = new BufferedReader(new InputStreamReader(
173
+                responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream()))) {
174
+            String line;
175
+            while ((line = serverReader.readLine()) != null) {
176
+                serverResponse.append(line);
177
+            }
178
+        }
179
+
180
+        String response = serverResponse.toString();
181
+        Log.d(TAG, "doInBackground: Server response: " + response);
182
+
183
+        if(responseCode >= 400 || response.toLowerCase().contains("error")) {
184
+            Log.e(TAG, "doInBackground: Error response received");
185
+            return null;
186
+        } else {
187
+            Log.d(TAG, "doInBackground: Successful response received");
188
+            return response;
189
+        }
190
+
191
+    } catch (JSONException e) {
192
+        Log.e(TAG, "doInBackground: Couldn't prepare consentFormJSON", e);
193
+        return null;
194
+    } catch (UnsupportedEncodingException e) {
195
+        Log.e(TAG, "doInBackground: Couldn't encode consentFormJSON", e);
196
+        return null;
197
+    } catch(Exception e) {
198
+        Log.e(TAG, "doInBackground: Couldn't communicate with server", e);
199
+        return null;
200
+    } finally {
201
+        if (conn != null) {
202
+            conn.disconnect();
203
+            Log.d(TAG, "doInBackground: Connection closed");
204
+        }
205
+    }
206
+}
207
+    private void onPostExecute(String response) {
208
+        if(this.myCallback == null) {
209
+            Log.e(TAG, "Callback wasn't initialized first!");
210
+            return;
211
+        }
212
+
213
+        if(response == null) {
214
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
215
+        } else {
216
+            this.myCallback.onSuccess();
217
+        }
218
+    }
219
+}

+ 142
- 0
app/src/main/java/uprrp/tania/networking/SendExperienceRegistration.java View File

@@ -0,0 +1,142 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import org.json.JSONException;
7
+import org.json.JSONObject;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.InputStreamReader;
11
+import java.io.OutputStreamWriter;
12
+import java.io.UnsupportedEncodingException;
13
+import java.net.URL;
14
+import java.net.URLEncoder;
15
+
16
+import javax.net.ssl.HttpsURLConnection;
17
+
18
+import uprrp.tania.utils.URLEventListener;
19
+
20
+public class SendExperienceRegistration extends AsyncTask<String, Void, String> {
21
+
22
+    private static final String TAG = "SendActivateExperience";
23
+    private static final String activateExperienceURL = "https://tania.uprrp.edu/inscripcionExperiencia.php";
24
+    private final URLEventListener myCallback;
25
+
26
+    public SendExperienceRegistration(URLEventListener callback) {
27
+        this.myCallback = callback;
28
+    }
29
+
30
+    private boolean validInputs(String... strings) {
31
+
32
+        if(strings.length != 2) {
33
+            Log.e(TAG, "Invalid string array length!");
34
+            return false;
35
+        }
36
+
37
+        for(int i = 0; i < strings.length; i++) {
38
+            if(strings[i] == null) {
39
+                Log.e(TAG, "Encountered null parameter on index " + i);
40
+                return false;
41
+            }
42
+        }
43
+
44
+        return true;
45
+
46
+    }
47
+
48
+    // TODO: instead of making a normal POST request with
49
+    //  the body data=<JSON>, we should just send the JSON itself
50
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
51
+    /*
52
+        JSON format:
53
+        {
54
+            "token": String,
55
+            "id_experiencia": String
56
+        }
57
+    */
58
+    @Override
59
+    protected String doInBackground(String... strings) {
60
+
61
+        // Validation
62
+        if(!validInputs(strings)) {
63
+            Log.e(TAG, "Invalid inputs given!");
64
+            return null;
65
+        }
66
+
67
+        // Extract variables
68
+        String deviceToken = strings[0];
69
+        String experienceID = strings[1];
70
+
71
+        try {
72
+
73
+            // Create JSON
74
+            JSONObject experienceRegistrationJSON = new JSONObject();
75
+            experienceRegistrationJSON.put("token", deviceToken);
76
+            experienceRegistrationJSON.put("id_experiencia", experienceID);
77
+            Log.d(TAG, "Prepared JSON: " + experienceRegistrationJSON.toString());
78
+
79
+            // Encode data
80
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(experienceRegistrationJSON.toString(), "UTF-8");
81
+
82
+            // Send POST data request
83
+            URL url = new URL(activateExperienceURL);
84
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
85
+            conn.setDoOutput(true);
86
+            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
87
+            wr.write(encodedRequestBody);
88
+            wr.flush();
89
+
90
+            // Get the server response
91
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
92
+            StringBuilder serverResponse = new StringBuilder();
93
+
94
+            String line = "";
95
+            while(line != null) {
96
+                serverResponse.append(line);
97
+                line = serverReader.readLine();
98
+            }
99
+
100
+            // TODO: restrict double registration (maybe in backend?)
101
+            String response = serverResponse.toString();
102
+            Log.d(TAG, "The server's response is: " + response);
103
+            if(response.startsWith("Success")) {
104
+                return response;
105
+            } else {
106
+                return null;
107
+            }
108
+
109
+        } catch (JSONException e) {
110
+            Log.e(TAG, "Couldn't prepare experienceRegistrationJSON!");
111
+            e.printStackTrace();
112
+            return null;
113
+        } catch (UnsupportedEncodingException e) {
114
+            Log.e(TAG, "Couldn't encode experienceRegistrationJSON!");
115
+            e.printStackTrace();
116
+            return null;
117
+        } catch(Exception e) {
118
+            Log.e(TAG, "Couldn't communicate with server while activating experience!");
119
+            e.printStackTrace();
120
+            return null;
121
+        }
122
+
123
+    }
124
+
125
+    @Override
126
+    protected void onPostExecute(String response) {
127
+
128
+        if(this.myCallback == null) {
129
+            Log.e(TAG, "Callback wasn't initialized first!");
130
+            return;
131
+        }
132
+
133
+        if(response == null) {
134
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
135
+            return;
136
+        }
137
+
138
+        this.myCallback.onSuccess();
139
+
140
+    }
141
+
142
+}

+ 144
- 0
app/src/main/java/uprrp/tania/networking/SendLoginCredentials.java View File

@@ -0,0 +1,144 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import org.json.JSONException;
7
+import org.json.JSONObject;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.InputStreamReader;
11
+import java.io.OutputStreamWriter;
12
+import java.io.UnsupportedEncodingException;
13
+import java.net.URL;
14
+import java.net.URLEncoder;
15
+
16
+import javax.net.ssl.HttpsURLConnection;
17
+
18
+import uprrp.tania.utils.URLEventListener;
19
+
20
+public class SendLoginCredentials extends AsyncTask<String, Void, String> {
21
+
22
+    private static final String TAG = "SendAccountRecovery";
23
+    private static final String loginBaseURL = "https://tania.uprrp.edu/recoverAccount.php";
24
+    private final URLEventListener myCallback;
25
+
26
+    public SendLoginCredentials(URLEventListener callback) {
27
+        this.myCallback = callback;
28
+    }
29
+
30
+    private boolean validInputs(String... strings) {
31
+
32
+        if(strings.length != 3) {
33
+            Log.e(TAG, "Invalid string array length!");
34
+            return false;
35
+        }
36
+
37
+        for(int i = 0; i < strings.length; i++) {
38
+            if(strings[i] == null) {
39
+                Log.e(TAG, "Encountered null parameter on index " + i);
40
+                return false;
41
+            }
42
+        }
43
+
44
+        return true;
45
+
46
+    }
47
+
48
+    // TODO: instead of making a normal POST request with
49
+    //  the body data=<JSON>, we should just send the JSON itself
50
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
51
+    /*
52
+        JSON format:
53
+        {
54
+            "email": String,
55
+            "password": String,
56
+            "token": String,
57
+        }
58
+    */
59
+    @Override
60
+    protected String doInBackground(String... strings) {
61
+
62
+        // Validation
63
+        if(!validInputs(strings)) {
64
+            Log.e(TAG, "Invalid inputs given!");
65
+            return null;
66
+        }
67
+
68
+        // Extract variables
69
+        String deviceToken = strings[0];
70
+        String email = strings[1];
71
+        String password = strings[2];
72
+
73
+        try {
74
+
75
+            // Create JSON
76
+            JSONObject accountRecoveryJSON = new JSONObject();
77
+            accountRecoveryJSON.put("email", email);
78
+            accountRecoveryJSON.put("password", password);
79
+            accountRecoveryJSON.put("token", deviceToken);
80
+            Log.d(TAG, "RecoveryJSON: " + accountRecoveryJSON.toString());
81
+
82
+            // Encode data
83
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(accountRecoveryJSON.toString(), "UTF-8");
84
+
85
+            // Send POST data request
86
+            URL url = new URL(loginBaseURL);
87
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
88
+            conn.setDoOutput(true);
89
+            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
90
+            wr.write(encodedRequestBody);
91
+            wr.flush();
92
+
93
+            // Get the server response
94
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
95
+            StringBuilder serverResponse = new StringBuilder();
96
+
97
+            String line = "";
98
+            while(line != null) {
99
+                serverResponse.append(line);
100
+                line = serverReader.readLine();
101
+            }
102
+
103
+            String response = serverResponse.toString();
104
+            Log.d(TAG, "The server's response is: " + response);
105
+            if(response.contains("Error")) {
106
+                return null;
107
+            } else {
108
+                return response;
109
+            }
110
+
111
+        } catch (JSONException e) {
112
+            Log.e(TAG, "Couldn't prepare accountRecoveryJSON!");
113
+            e.printStackTrace();
114
+            return null;
115
+        } catch (UnsupportedEncodingException e) {
116
+            Log.e(TAG, "Couldn't encode accountRecoveryJSON!");
117
+            e.printStackTrace();
118
+            return null;
119
+        } catch(Exception e) {
120
+            Log.e(TAG, "Couldn't communicate with server while recovering account!");
121
+            e.printStackTrace();
122
+            return null;
123
+        }
124
+
125
+    }
126
+
127
+    @Override
128
+    protected void onPostExecute(String response) {
129
+
130
+        if(this.myCallback == null) {
131
+            Log.e(TAG, "Callback wasn't initialized first!");
132
+            return;
133
+        }
134
+
135
+        if(response == null) {
136
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
137
+            return;
138
+        }
139
+
140
+        this.myCallback.onSuccess(response);
141
+
142
+    }
143
+
144
+}

+ 138
- 0
app/src/main/java/uprrp/tania/networking/SendRecoveryEmail.java View File

@@ -0,0 +1,138 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import org.json.JSONException;
7
+import org.json.JSONObject;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.InputStreamReader;
11
+import java.io.OutputStreamWriter;
12
+import java.io.UnsupportedEncodingException;
13
+import java.net.URL;
14
+import java.net.URLEncoder;
15
+
16
+import javax.net.ssl.HttpsURLConnection;
17
+
18
+import uprrp.tania.utils.URLEventListener;
19
+
20
+public class SendRecoveryEmail extends AsyncTask<String, String, String> {
21
+
22
+    private static final String TAG = "SendRecoveryEmail";
23
+    private static final String emailRecoveryBaseURL = "https://tania.uprrp.edu/askForEmail.php";
24
+    private final URLEventListener myCallback;
25
+
26
+    public SendRecoveryEmail(URLEventListener callback){
27
+        this.myCallback = callback;
28
+    }
29
+
30
+    private boolean validInputs(String... strings) {
31
+
32
+        if(strings.length != 1) {
33
+            Log.e(TAG, "Invalid string array length!");
34
+            return false;
35
+        }
36
+
37
+        for(int i = 0; i < strings.length; i++) {
38
+            if(strings[i] == null) {
39
+                Log.e(TAG, "Encountered null parameter on index " + i);
40
+                return false;
41
+            }
42
+        }
43
+
44
+        return true;
45
+
46
+    }
47
+
48
+    // TODO: instead of making a normal POST request with
49
+    //  the body data=<JSON>, we should just send the JSON itself
50
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
51
+    /*
52
+        JSON format:
53
+        {
54
+            "email": String,
55
+        }
56
+    */
57
+    @Override
58
+    protected String doInBackground(String... strings) {
59
+
60
+        // Validation
61
+        if(!validInputs(strings)) {
62
+            Log.e(TAG, "Invalid inputs given!");
63
+            return null;
64
+        }
65
+
66
+        // Extract variables
67
+        String email = strings[0];
68
+
69
+        try {
70
+
71
+            // Create JSON
72
+            JSONObject recoveryEmailJSON = new JSONObject();
73
+            recoveryEmailJSON.put("email", email);
74
+            Log.d(TAG, "recoveryEmailJSON is " + recoveryEmailJSON.toString());
75
+
76
+            // Encode data
77
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(recoveryEmailJSON.toString(), "UTF-8");
78
+
79
+            // Send POST data request
80
+            URL url = new URL(emailRecoveryBaseURL);
81
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
82
+            conn.setDoOutput(true);
83
+            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
84
+            wr.write(encodedRequestBody);
85
+            wr.flush();
86
+
87
+            // Get the server response
88
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
89
+            StringBuilder serverResponse = new StringBuilder();
90
+
91
+            String line = "";
92
+            while(line != null) {
93
+                serverResponse.append(line);
94
+                line = serverReader.readLine();
95
+            }
96
+
97
+            String response = serverResponse.toString();
98
+            Log.d(TAG, "The server's response is " + response);
99
+            if(response.contains("Success")) {
100
+                return response;
101
+            } else {
102
+                return null;
103
+            }
104
+
105
+        } catch(JSONException e) {
106
+            Log.e(TAG, "Couldn't prepare recoveryEmailJSON!");
107
+            e.printStackTrace();
108
+            return null;
109
+        } catch (UnsupportedEncodingException e) {
110
+            Log.e(TAG, "Couldn't encode recoveryEmailJSON!");
111
+            e.printStackTrace();
112
+            return null;
113
+        } catch(Exception e) {
114
+            Log.e(TAG, "Couldn't communicate with server while sending recovery email!");
115
+            e.printStackTrace();
116
+            return null;
117
+        }
118
+
119
+    }
120
+
121
+    @Override
122
+    protected void onPostExecute(String response) {
123
+
124
+        if(this.myCallback == null) {
125
+            Log.e(TAG, "Callback wasn't initialized first!");
126
+            return;
127
+        }
128
+
129
+        if(response == null) {
130
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
131
+            return;
132
+        }
133
+
134
+        this.myCallback.onSuccess();
135
+
136
+    }
137
+
138
+}

+ 146
- 0
app/src/main/java/uprrp/tania/networking/SendResetPassword.java View File

@@ -0,0 +1,146 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import org.json.JSONException;
7
+import org.json.JSONObject;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.BufferedWriter;
11
+import java.io.InputStreamReader;
12
+import java.io.OutputStreamWriter;
13
+import java.io.UnsupportedEncodingException;
14
+import java.io.Writer;
15
+import java.net.URL;
16
+import java.net.URLEncoder;
17
+import java.nio.charset.StandardCharsets;
18
+
19
+import javax.net.ssl.HttpsURLConnection;
20
+
21
+import uprrp.tania.utils.URLEventListener;
22
+
23
+public class SendResetPassword extends AsyncTask <String, Void, String> {
24
+
25
+    private static final String TAG = "SendResetPassword";
26
+    private static final String resetPasswordBaseURL = "https://tania.uprrp.edu/resetPassword.php";
27
+    private final URLEventListener myCallback;
28
+
29
+    public SendResetPassword(URLEventListener callback){
30
+        this.myCallback = callback;
31
+    }
32
+
33
+    private boolean validInputs(String... strings) {
34
+
35
+        if(strings.length != 3) {
36
+            Log.e(TAG, "Invalid string array length!");
37
+            return false;
38
+        }
39
+
40
+        for(int i = 0; i < strings.length; i++) {
41
+            if(strings[i] == null) {
42
+                Log.e(TAG, "Encountered null parameter on index " + i);
43
+                return false;
44
+            }
45
+        }
46
+
47
+        return true;
48
+
49
+    }
50
+
51
+    // TODO: instead of making a normal POST request with
52
+    //  the body data=<JSON>, we should just send the JSON itself
53
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
54
+    /*
55
+        JSON format:
56
+        {
57
+            "forgotPasswordKey": String,
58
+            "password": String,
59
+            "token": String,
60
+        }
61
+    */
62
+    @Override
63
+    protected String doInBackground(String... strings) {
64
+
65
+        // Validation
66
+        if(!validInputs(strings)) {
67
+            Log.e(TAG, "Invalid inputs given!");
68
+            return null;
69
+        }
70
+
71
+        // Extract variables
72
+        String deviceToken = strings[0];
73
+        String newPassword = strings[1];
74
+        String forgotPasswordKey = strings[2];
75
+
76
+        try {
77
+
78
+            // Create JSON
79
+            JSONObject resetPasswordJSON = new JSONObject();
80
+            resetPasswordJSON.put("token", deviceToken);
81
+            resetPasswordJSON.put("password", newPassword);
82
+            resetPasswordJSON.put("forgotPasswordKey", forgotPasswordKey);
83
+            Log.d(TAG, "resetPasswordJSON is " + resetPasswordJSON.toString());
84
+
85
+            // Encode data
86
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(resetPasswordJSON.toString(), "UTF-8");
87
+
88
+            // Send POST data request
89
+            URL url = new URL(resetPasswordBaseURL);
90
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
91
+            conn.setDoOutput(true);
92
+            Writer writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8));
93
+            writer.write(encodedRequestBody);
94
+            writer.close();
95
+
96
+            // Get the server response
97
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
98
+            StringBuilder serverResponse = new StringBuilder();
99
+
100
+            String line = "";
101
+            while(line != null) {
102
+                serverResponse.append(line);
103
+                line = serverReader.readLine();
104
+            }
105
+
106
+            String response = serverResponse.toString();
107
+            Log.d(TAG, "The server's response is " + response);
108
+            if(response.contains("Success")) {
109
+                return response;
110
+            } else {
111
+                return null;
112
+            }
113
+
114
+        } catch (JSONException e) {
115
+            Log.e(TAG, "Couldn't prepare resetPasswordJSON!");
116
+            e.printStackTrace();
117
+            return null;
118
+        } catch (UnsupportedEncodingException e) {
119
+            Log.e(TAG, "Couldn't encode resetPasswordJSON!");
120
+            e.printStackTrace();
121
+            return null;
122
+        } catch(Exception e) {
123
+            Log.e(TAG, "Couldn't communicate with server while resetting password!");
124
+            e.printStackTrace();
125
+            return null;
126
+        }
127
+
128
+    }
129
+
130
+    @Override
131
+    protected void onPostExecute(String response) {
132
+
133
+        if(this.myCallback == null) {
134
+            Log.e(TAG, "Callback wasn't initialized first!");
135
+            return;
136
+        }
137
+
138
+        if(response == null) {
139
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
140
+            return;
141
+        }
142
+
143
+        this.myCallback.onSuccess();
144
+
145
+    }
146
+}

+ 146
- 0
app/src/main/java/uprrp/tania/networking/SendUserRegistration.java View File

@@ -0,0 +1,146 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import com.google.gson.GsonBuilder;
7
+import com.google.gson.JsonSyntaxException;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.InputStreamReader;
11
+import java.io.OutputStreamWriter;
12
+import java.io.UnsupportedEncodingException;
13
+import java.net.URL;
14
+import java.net.URLEncoder;
15
+
16
+import javax.net.ssl.HttpsURLConnection;
17
+
18
+import uprrp.tania.models.UserModel;
19
+import uprrp.tania.utils.URLEventListener;
20
+
21
+public class SendUserRegistration extends AsyncTask <UserModel, Void, String> {
22
+
23
+    private static final String TAG = "SendUserRegistration";
24
+    private static final String sendUserRegistrationURL = "https://tania.uprrp.edu/registrationAnd.php";
25
+    private final URLEventListener myCallback;
26
+
27
+    public SendUserRegistration(URLEventListener callback) {
28
+        this.myCallback = callback;
29
+    }
30
+
31
+    private boolean validInputs(UserModel... users) {
32
+
33
+        if(users.length != 1) {
34
+            Log.e(TAG, "Invalid array length!");
35
+            return false;
36
+        }
37
+
38
+        for(int i = 0; i < users.length; i++) {
39
+            if(users[i] == null) {
40
+                Log.e(TAG, "Encountered null parameter on index " + i);
41
+                return false;
42
+            }
43
+        }
44
+
45
+        return true;
46
+
47
+    }
48
+
49
+    // TODO: instead of making a normal POST request with
50
+    //  the body data=<JSON>, we should just send the JSON itself
51
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
52
+    /*
53
+        JSON format:
54
+        {
55
+            "email": String,
56
+            "birthDate": String,
57
+            "password": String,
58
+            "gender": String,
59
+            "hasJob": String,
60
+            "token": String
61
+        }
62
+    */
63
+    @Override
64
+    protected String doInBackground(UserModel... users) {
65
+
66
+        // Validation
67
+        if(!validInputs(users)) {
68
+            Log.e(TAG, "Invalid inputs given!");
69
+            return null;
70
+        }
71
+
72
+        // Extract variables
73
+        UserModel user = users[0];
74
+
75
+        try {
76
+
77
+            // Serialize user
78
+            GsonBuilder builder = new GsonBuilder();
79
+//            builder.setPrettyPrinting(); // the server doesn't like pretty printing...
80
+            String serializedUser = builder.create().toJson(user);
81
+            Log.d(TAG,"RegistrationJSON is \n" + serializedUser);
82
+
83
+            // Encode data
84
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(serializedUser, "UTF-8");
85
+
86
+            // Send POST data request
87
+            URL url = new URL(sendUserRegistrationURL);
88
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
89
+            conn.setDoOutput(true);
90
+            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
91
+            wr.write(encodedRequestBody);
92
+            wr.flush();
93
+
94
+            // Get the server response
95
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
96
+            StringBuilder serverResponse = new StringBuilder();
97
+
98
+            String line = "";
99
+            while(line != null) {
100
+                serverResponse.append(line);
101
+                line = serverReader.readLine();
102
+            }
103
+
104
+            String response = serverResponse.toString();
105
+//            String response = "lkajhkljsdhlakjsdhaf";
106
+            Log.d(TAG, "The server's response is: " + response);
107
+            if(response.contains("Success")) {
108
+                return response;
109
+            } else {
110
+                return null;
111
+            }
112
+
113
+        } catch (JsonSyntaxException e) {
114
+            Log.e(TAG, "Failed to JSONify User!");
115
+            e.printStackTrace();
116
+            return null;
117
+        } catch (UnsupportedEncodingException e) {
118
+            Log.e(TAG, "Couldn't encode registrationJSON!");
119
+            e.printStackTrace();
120
+            return null;
121
+        } catch(Exception e) {
122
+            Log.e(TAG,"Couldn't communicate with server while sending user registration!" + e.getMessage());
123
+            e.printStackTrace();
124
+            return null;
125
+        }
126
+
127
+    }
128
+
129
+    @Override
130
+    protected void onPostExecute(String response) {
131
+
132
+        if(this.myCallback == null) {
133
+            Log.e(TAG, "Callback wasn't initialized first!");
134
+            return;
135
+        }
136
+
137
+        if(response == null) {
138
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
139
+            return;
140
+        }
141
+
142
+        this.myCallback.onSuccess();
143
+
144
+    }
145
+
146
+}

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

@@ -0,0 +1,140 @@
1
+package uprrp.tania.networking;
2
+
3
+import android.os.AsyncTask;
4
+import android.util.Log;
5
+
6
+import org.json.JSONException;
7
+import org.json.JSONObject;
8
+
9
+import java.io.BufferedReader;
10
+import java.io.BufferedWriter;
11
+import java.io.InputStreamReader;
12
+import java.io.OutputStreamWriter;
13
+import java.io.UnsupportedEncodingException;
14
+import java.io.Writer;
15
+import java.net.URL;
16
+import java.net.URLEncoder;
17
+import java.nio.charset.StandardCharsets;
18
+
19
+import javax.net.ssl.HttpsURLConnection;
20
+
21
+import uprrp.tania.utils.URLEventListener;
22
+
23
+public class SendWithdrawal extends AsyncTask<String, Void, String> {
24
+
25
+    private static final String TAG = "SendWithdrawal";
26
+    private static final String withdrawalURL = "https://tania.uprrp.edu/withdrawal.php";
27
+    private final URLEventListener myCallback;
28
+
29
+    public SendWithdrawal(URLEventListener callback) {
30
+        this.myCallback = callback;
31
+    }
32
+
33
+    private boolean validInputs(String... strings) {
34
+
35
+        if(strings.length != 1) {
36
+            Log.e(TAG, "Invalid string array length!");
37
+            return false;
38
+        }
39
+
40
+        for(int i = 0; i < strings.length; i++) {
41
+            if(strings[i] == null) {
42
+                Log.e(TAG, "Encountered null parameter on index " + i);
43
+                return false;
44
+            }
45
+        }
46
+
47
+        return true;
48
+
49
+    }
50
+
51
+    // TODO: instead of making a normal POST request with
52
+    //  the body data=<JSON>, we should just send the JSON itself
53
+    //  This is adding unnecessary layers of complexity when decoding AND encoding
54
+    /*
55
+        JSON format:
56
+        {
57
+            "token": String
58
+        }
59
+    */
60
+    @Override
61
+    protected String doInBackground(String... strings) {
62
+
63
+        // Validation
64
+        if(!validInputs(strings)) {
65
+            Log.e(TAG, "Invalid inputs given!");
66
+            return null;
67
+        }
68
+
69
+        // Extract variables
70
+        String deviceToken = strings[0];
71
+
72
+        try {
73
+
74
+            // Create JSON
75
+            JSONObject withdrawJSON = new JSONObject();
76
+            withdrawJSON.put("token", deviceToken);
77
+            Log.d(TAG, "Prepared JSON: " + withdrawJSON.toString());
78
+
79
+            // Encode data
80
+            String encodedRequestBody = URLEncoder.encode("data", "UTF-8") + "=" + URLEncoder.encode(withdrawJSON.toString(), "UTF-8");
81
+
82
+            // Send POST data request
83
+            URL url = new URL(withdrawalURL);
84
+            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
85
+            conn.setDoOutput(true);
86
+            Writer writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8));
87
+            writer.write(encodedRequestBody);
88
+            writer.close();
89
+
90
+            // Get the server response
91
+            BufferedReader serverReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
92
+            StringBuilder serverResponse = new StringBuilder();
93
+
94
+            String line = "";
95
+            while(line != null) {
96
+                serverResponse.append(line);
97
+                line = serverReader.readLine();
98
+            }
99
+
100
+            String response = serverResponse.toString();
101
+            Log.d(TAG, "The server's response is: " + response);
102
+            if(response.startsWith("Success")) {
103
+                return response;
104
+            } else {
105
+                return null;
106
+            }
107
+
108
+        } catch (JSONException e){
109
+            Log.e(TAG, "Couldn't construct withdrawJSON!");
110
+            e.printStackTrace();
111
+            return null;
112
+        } catch (UnsupportedEncodingException e) {
113
+            Log.e(TAG, "Couldn't encode withdrawJSON!");
114
+            e.printStackTrace();
115
+            return null;
116
+        } catch (Exception e) {
117
+            Log.e(TAG, "Couldn't communicate with server during withdrawal!");
118
+            e.printStackTrace();
119
+            return null;
120
+        }
121
+
122
+    }
123
+
124
+    @Override
125
+    protected void onPostExecute(String response) {
126
+
127
+        if(this.myCallback == null) {
128
+            Log.e(TAG, "Callback wasn't initialized first!");
129
+            return;
130
+        }
131
+
132
+        if(response == null) {
133
+            this.myCallback.onFailure(new Exception("Error occurred during transaction!"));
134
+            return;
135
+        }
136
+
137
+        this.myCallback.onSuccess();
138
+
139
+    }
140
+}

+ 201
- 0
app/src/main/java/uprrp/tania/services/MyFirebaseMessagingService.java View File

@@ -0,0 +1,201 @@
1
+package uprrp.tania.services;
2
+
3
+import com.google.firebase.messaging.FirebaseMessagingService;
4
+import com.google.firebase.messaging.RemoteMessage;
5
+
6
+import android.app.NotificationChannel;
7
+import android.app.NotificationManager;
8
+import android.app.PendingIntent;
9
+import android.app.TaskStackBuilder;
10
+import android.content.Context;
11
+import android.content.Intent;
12
+import android.media.RingtoneManager;
13
+import android.net.Uri;
14
+import android.os.Build;
15
+import android.util.Log;
16
+
17
+import androidx.core.app.NotificationCompat;
18
+import androidx.core.app.NotificationManagerCompat;
19
+
20
+import uprrp.tania.R;
21
+import uprrp.tania.activities.MainActivity;
22
+
23
+public class MyFirebaseMessagingService extends FirebaseMessagingService {
24
+
25
+    private static final String TAG = "MyFirebaseMsgService";
26
+
27
+    /**
28
+     * Called when message is received.
29
+     *
30
+     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
31
+     */
32
+    @Override
33
+    public void onMessageReceived(RemoteMessage remoteMessage) {
34
+        // [START_EXCLUDE]
35
+        // There are two types of messages data messages and notification messages. Data messages
36
+        // are handled
37
+        // here in onMessageReceived whether the app is in the foreground or background. Data
38
+        // messages are the type
39
+        // traditionally used with GCM. Notification messages are only received here in
40
+        // onMessageReceived when the app
41
+        // is in the foreground. When the app is in the background an automatically generated
42
+        // notification is displayed.
43
+        // When the user taps on the notification they are returned to the app. Messages
44
+        // containing both notification
45
+        // and data payloads are treated as notification messages. The Firebase console always
46
+        // sends notification
47
+        // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
48
+        // [END_EXCLUDE]
49
+
50
+        Log.d(TAG, "From: " + remoteMessage.getFrom());
51
+
52
+        // Check if message contains a data payload.
53
+        if (remoteMessage.getData().size() > 0) {
54
+            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
55
+
56
+          sendNotification(remoteMessage.getData().toString());
57
+
58
+
59
+            if (/* Check if data needs to be processed by long running job */ true) {
60
+                // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
61
+                //scheduleJob();
62
+            } else {
63
+                // Handle message within 10 seconds
64
+                handleNow();
65
+            }
66
+
67
+        }
68
+
69
+        // Check if message contains a notification payload.
70
+        if (remoteMessage.getNotification() != null) {
71
+            Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
72
+
73
+            Intent resultIntent = new Intent(this, MainActivity.class);
74
+            TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
75
+            stackBuilder.addNextIntentWithParentStack(resultIntent);
76
+            PendingIntent resultPendingIntent =
77
+                    stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
78
+
79
+            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.default_notification_channel_id));
80
+            builder.setContentIntent(resultPendingIntent);
81
+
82
+            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
83
+            notificationManager.notify(0, builder.build());
84
+
85
+//            String channelId = getString(R.string.default_notification_channel_id);
86
+//
87
+//            Intent intent = new Intent(this, MainActivity.class);
88
+//            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
89
+//            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
90
+//                            PendingIntent.FLAG_ONE_SHOT);
91
+//
92
+//
93
+//            Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
94
+//            NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
95
+//                            .setSmallIcon(R.mipmap.ic_launcher)
96
+//                            .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher))
97
+//                            .setContentTitle("Title")
98
+//                            .setVibrate(new long[]{5000})
99
+////                            .setLights(Color.GREEN, 3000, 3000) //for notification led light with color
100
+//                            .setContentText(remoteMessage.getNotification().getBody())
101
+//                            .setAutoCancel(true)
102
+//                            .setSound(soundUri)
103
+//                            .setContentIntent(pendingIntent);
104
+//
105
+//    NotificationManager notificationManager =
106
+//                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
107
+//    notificationManager.notify(0, notificationBuilder.build());
108
+        }
109
+
110
+        // Also if you intend on generating your own notifications as a result of a received FCM
111
+        // message, here is where that should be initiated. See sendNotification method below.
112
+    }
113
+    // [END receive_message]
114
+
115
+
116
+    // [START on_new_token]
117
+
118
+    /**
119
+     * Called if InstanceID token is updated. This may occur if the security of
120
+     * the previous token had been compromised. Note that this is called when the InstanceID token
121
+     * is initially generated so this is where you would retrieve the token.
122
+     */
123
+    @Override
124
+    public void onNewToken(String token) {
125
+        Log.d(TAG, "Refreshed token: " + token);
126
+
127
+        // If you want to send messages to this application instance or
128
+        // manage this apps subscriptions on the server side, send the
129
+        // Instance ID token to your app server.
130
+        sendRegistrationToServer(token);
131
+    }
132
+    // [END on_new_token]
133
+
134
+
135
+
136
+    /**
137
+     * Handle time allotted to BroadcastReceivers.
138
+     */
139
+    private void handleNow() {
140
+        Log.d(TAG, "Short lived task is done.");
141
+    }
142
+
143
+    /**
144
+     * Persist token to third-party servers.
145
+     *
146
+     * Modify this method to associate the user's FCM InstanceID token with any server-side account
147
+     * maintained by your application.
148
+     *
149
+     * @param token The new token.
150
+     */
151
+    private void sendRegistrationToServer(String token) {
152
+        // TODO: Implement this method to send token to your app server.
153
+    }
154
+
155
+    /**
156
+     * Create and show a simple notification containing the received FCM message.
157
+     *
158
+     * @param messageBody FCM message body received.
159
+     */
160
+    private void sendNotification(String messageBody) {
161
+
162
+        // Create an Intent for the activity you want to start
163
+        Intent resultIntent = new Intent(this, MainActivity.class);
164
+
165
+        // Create the TaskStackBuilder and add the intent, which inflates the back stack
166
+        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
167
+        stackBuilder.addNextIntentWithParentStack(resultIntent);
168
+
169
+        // Get the PendingIntent containing the entire back stack
170
+        PendingIntent resultPendingIntent =  stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
171
+
172
+        //Intent intent = new Intent(this, MainActivity.class);
173
+        //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
174
+
175
+        //PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, 0);
176
+
177
+        String channelId = getString(R.string.default_notification_channel_id);
178
+        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
179
+        NotificationCompat.Builder notificationBuilder =
180
+                new NotificationCompat.Builder(this, channelId)
181
+                        .setSmallIcon(R.drawable.ic_stat_ic_notification)
182
+                        .setContentTitle("New Notification!")
183
+                        .setContentText(messageBody)
184
+                        .setAutoCancel(true)
185
+                        .setSound(defaultSoundUri)
186
+                        .setContentIntent(resultPendingIntent);
187
+
188
+        NotificationManager notificationManager =
189
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
190
+
191
+        // Since android Oreo notification channel is needed.
192
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
193
+            NotificationChannel channel = new NotificationChannel(channelId,
194
+                    "Channel human readable title",
195
+                    NotificationManager.IMPORTANCE_DEFAULT);
196
+            notificationManager.createNotificationChannel(channel);
197
+        }
198
+
199
+        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
200
+    }
201
+}

+ 52
- 0
app/src/main/java/uprrp/tania/unused/AppPrefs.java View File

@@ -0,0 +1,52 @@
1
+package uprrp.tania.unused;
2
+
3
+import android.content.Context;
4
+import android.content.SharedPreferences;
5
+import android.preference.PreferenceManager;
6
+
7
+public class AppPrefs {
8
+    public static final String HAS_CONSENTED     = "HAS_CONSENTED";
9
+    public static final String CONSENT_NAME      = "CONSENT_NAME";
10
+    public static final String CONSENT_SIGNATURE = "CONSENT_SIGNATURE";
11
+    public static final String HAS_SURVEYED      = "HAS_SURVEYED";
12
+    public static final String SURVEY_RESULT     = "SURVEY_RESULT";
13
+
14
+    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
15
+    // Statics
16
+    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
17
+    private static AppPrefs instance;
18
+
19
+    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
20
+    // Field Vars
21
+    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
22
+    private final SharedPreferences prefs;
23
+
24
+    AppPrefs(Context context) {
25
+        this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
26
+    }
27
+
28
+    public static synchronized AppPrefs getInstance(Context context) {
29
+        if(instance == null) {
30
+            instance = new AppPrefs(context);
31
+        }
32
+        return instance;
33
+    }
34
+
35
+    public boolean hasConsented()
36
+    {
37
+        return prefs.getBoolean(HAS_CONSENTED, false);
38
+    }
39
+
40
+    public void setHasConsented(boolean consented) {
41
+        this.prefs.edit().putBoolean(HAS_CONSENTED, consented).apply();
42
+    }
43
+
44
+    public boolean hasSurveyed()
45
+    {
46
+        return prefs.getBoolean(HAS_SURVEYED, false);
47
+    }
48
+
49
+    public void setHasSurveyed(boolean surveyed) {
50
+        this.prefs.edit().putBoolean(HAS_SURVEYED, surveyed).apply();
51
+    }
52
+}

+ 40
- 0
app/src/main/java/uprrp/tania/unused/CognitoSettings.java View File

@@ -0,0 +1,40 @@
1
+package uprrp.tania.unused;
2
+
3
+import android.content.Context;
4
+
5
+import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool;
6
+import com.amazonaws.regions.Regions;
7
+
8
+public class CognitoSettings {
9
+
10
+    private final String userPoolId = "us-east-1_68wCpnnq5";
11
+    private final String clientId = "27f4e2d01l3pva5vsstil9tmg1";
12
+    private final String clientSecret = null;
13
+    private final Regions cognitoRegion = Regions.US_EAST_1;
14
+    public Context context;
15
+
16
+    public CognitoSettings(Context context){
17
+        this.context = context;
18
+    }
19
+
20
+    public String getUserPoolId() {
21
+        return userPoolId;
22
+    }
23
+
24
+    public String getClientId() {
25
+        return clientId;
26
+    }
27
+
28
+    public String getClientSecret() {
29
+        return clientSecret;
30
+    }
31
+
32
+    public Regions getCognitoRegion() {
33
+        return cognitoRegion;
34
+    }
35
+
36
+    public CognitoUserPool getUserPool(){
37
+        return new CognitoUserPool(context, userPoolId, clientId, clientSecret, cognitoRegion);
38
+    }
39
+
40
+}

+ 12
- 0
app/src/main/java/uprrp/tania/utils/URLEventListener.java View File

@@ -0,0 +1,12 @@
1
+package uprrp.tania.utils;
2
+
3
+import uprrp.tania.models.AssessmentModel;
4
+import uprrp.tania.models.UserStatusModel;
5
+
6
+public class URLEventListener {
7
+    public void onSuccess() {}
8
+    public void onSuccess(String response) {}
9
+    public void onSuccess(AssessmentModel assessment) {}
10
+    public void onSuccess(UserStatusModel userStatus) {}
11
+    public void onFailure(Exception e) {}
12
+}

+ 7
- 0
app/src/main/res/color/bottom_nav_selector.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+    <item android:state_checked="true" android:color="@android:color/white" />
5
+    <item android:color="#80FFFFFF" />
6
+
7
+</selector>

BIN
app/src/main/res/drawable-v24/baseline_assessment_white_24dp.png View File


BIN
app/src/main/res/drawable-v24/baseline_file_copy_white_24dp.png View File


+ 34
- 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml View File

@@ -0,0 +1,34 @@
1
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+    xmlns:aapt="http://schemas.android.com/aapt"
3
+    android:width="108dp"
4
+    android:height="108dp"
5
+    android:viewportHeight="108"
6
+    android:viewportWidth="108">
7
+    <path
8
+        android:fillType="evenOdd"
9
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
10
+        android:strokeColor="#00000000"
11
+        android:strokeWidth="1">
12
+        <aapt:attr name="android:fillColor">
13
+            <gradient
14
+                android:endX="78.5885"
15
+                android:endY="90.9159"
16
+                android:startX="48.7653"
17
+                android:startY="61.0927"
18
+                android:type="linear">
19
+                <item
20
+                    android:color="#44000000"
21
+                    android:offset="0.0" />
22
+                <item
23
+                    android:color="#00000000"
24
+                    android:offset="1.0" />
25
+            </gradient>
26
+        </aapt:attr>
27
+    </path>
28
+    <path
29
+        android:fillColor="#FFFFFF"
30
+        android:fillType="nonZero"
31
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
32
+        android:strokeColor="#00000000"
33
+        android:strokeWidth="1" />
34
+</vector>

BIN
app/src/main/res/drawable/baseline_clear_white_24dp.png View File


BIN
app/src/main/res/drawable/baseline_home_white_24dp.png View File


BIN
app/src/main/res/drawable/baseline_info_white_24dp.png View File


BIN
app/src/main/res/drawable/baseline_settings_white_24dp.png View File


+ 14
- 0
app/src/main/res/drawable/button_background.xml View File

@@ -0,0 +1,14 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
3
+    <!--  Disabled  -->
4
+    <item android:state_enabled="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/button_disabled" />
5
+    <item android:state_enabled="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/button_disabled" />
6
+    <!--  Enabled  -->
7
+    <item android:state_enabled="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/button_unfocused" />
8
+    <item android:state_enabled="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/button_unfocused" />
9
+    <!--  Focused  -->
10
+<!--    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/button_focus" />-->
11
+<!--    <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/button_focus" />-->
12
+    <!--  Pressed  -->
13
+    <item android:state_pressed="true" android:drawable="@drawable/button_press" />
14
+</selector>

+ 12
- 0
app/src/main/res/drawable/button_disabled.xml View File

@@ -0,0 +1,12 @@
1
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
2
+<!--  Border  -->
3
+<!--    <stroke android:width="1dp" android:color="@color/colorPrimary" />-->
4
+<!--  Radius  -->
5
+<corners android:radius="@dimen/cornerRadius" />
6
+<!--  Gradient  -->
7
+<!--    FF6800 -> FF8000 -> FF9700    -->
8
+<gradient android:startColor="@color/colorPrimaryDisabled"
9
+    android:centerColor="@color/colorPrimaryDisabled"
10
+    android:endColor="@color/colorPrimaryDisabled"
11
+    android:angle="90" />
12
+</shape>

+ 12
- 0
app/src/main/res/drawable/button_press.xml View File

@@ -0,0 +1,12 @@
1
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
2
+    <!--  Border  -->
3
+<!--    <stroke android:width="1dp" android:color="@color/colorAccent" />-->
4
+    <!--  Radius  -->
5
+    <corners android:radius="@dimen/cornerRadius" />
6
+    <!--  Gradient  -->
7
+    <!--    FF6800 -> FF8000 -> FF9700    -->
8
+    <gradient android:startColor="@color/colorAccent"
9
+        android:centerColor="@color/colorAccent"
10
+        android:endColor="@color/colorAccent"
11
+        android:angle="90" />
12
+</shape>

+ 12
- 0
app/src/main/res/drawable/button_unfocused.xml View File

@@ -0,0 +1,12 @@
1
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
2
+    <!--  Border  -->
3
+<!--    <stroke android:width="1dp" android:color="@color/colorPrimary" />-->
4
+    <!--  Radius  -->
5
+    <corners android:radius="@dimen/cornerRadius" />
6
+    <!--  Gradient  -->
7
+    <!--    FF6800 -> FF8000 -> FF9700    -->
8
+    <gradient android:startColor="@color/colorPrimary"
9
+        android:centerColor="@color/colorPrimary"
10
+        android:endColor="@color/colorPrimary"
11
+        android:angle="90" />
12
+</shape>

+ 12
- 0
app/src/main/res/drawable/circle.xml View File

@@ -0,0 +1,12 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+    <shape
3
+        xmlns:android="http://schemas.android.com/apk/res/android"
4
+        android:shape="oval">
5
+
6
+        <solid
7
+            android:color="#397af3"/>
8
+
9
+        <size
10
+            android:width="120dp"
11
+            android:height="120dp"/>
12
+    </shape>

+ 9
- 0
app/src/main/res/drawable/ic_home_black_24dp.xml View File

@@ -0,0 +1,9 @@
1
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+    android:width="24dp"
3
+    android:height="24dp"
4
+    android:viewportWidth="24.0"
5
+    android:viewportHeight="24.0">
6
+    <path
7
+        android:fillColor="#FF000000"
8
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
9
+</vector>

+ 170
- 0
app/src/main/res/drawable/ic_launcher_background.xml View File

@@ -0,0 +1,170 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+    android:width="108dp"
4
+    android:height="108dp"
5
+    android:viewportHeight="108"
6
+    android:viewportWidth="108">
7
+    <path
8
+        android:fillColor="#26A69A"
9
+        android:pathData="M0,0h108v108h-108z" />
10
+    <path
11
+        android:fillColor="#00000000"
12
+        android:pathData="M9,0L9,108"
13
+        android:strokeColor="#33FFFFFF"
14
+        android:strokeWidth="0.8" />
15
+    <path
16
+        android:fillColor="#00000000"
17
+        android:pathData="M19,0L19,108"
18
+        android:strokeColor="#33FFFFFF"
19
+        android:strokeWidth="0.8" />
20
+    <path
21
+        android:fillColor="#00000000"
22
+        android:pathData="M29,0L29,108"
23
+        android:strokeColor="#33FFFFFF"
24
+        android:strokeWidth="0.8" />
25
+    <path
26
+        android:fillColor="#00000000"
27
+        android:pathData="M39,0L39,108"
28
+        android:strokeColor="#33FFFFFF"
29
+        android:strokeWidth="0.8" />
30
+    <path
31
+        android:fillColor="#00000000"
32
+        android:pathData="M49,0L49,108"
33
+        android:strokeColor="#33FFFFFF"
34
+        android:strokeWidth="0.8" />
35
+    <path
36
+        android:fillColor="#00000000"
37
+        android:pathData="M59,0L59,108"
38
+        android:strokeColor="#33FFFFFF"
39
+        android:strokeWidth="0.8" />
40
+    <path
41
+        android:fillColor="#00000000"
42
+        android:pathData="M69,0L69,108"
43
+        android:strokeColor="#33FFFFFF"
44
+        android:strokeWidth="0.8" />
45
+    <path
46
+        android:fillColor="#00000000"
47
+        android:pathData="M79,0L79,108"
48
+        android:strokeColor="#33FFFFFF"
49
+        android:strokeWidth="0.8" />
50
+    <path
51
+        android:fillColor="#00000000"
52
+        android:pathData="M89,0L89,108"
53
+        android:strokeColor="#33FFFFFF"
54
+        android:strokeWidth="0.8" />
55
+    <path
56
+        android:fillColor="#00000000"
57
+        android:pathData="M99,0L99,108"
58
+        android:strokeColor="#33FFFFFF"
59
+        android:strokeWidth="0.8" />
60
+    <path
61
+        android:fillColor="#00000000"
62
+        android:pathData="M0,9L108,9"
63
+        android:strokeColor="#33FFFFFF"
64
+        android:strokeWidth="0.8" />
65
+    <path
66
+        android:fillColor="#00000000"
67
+        android:pathData="M0,19L108,19"
68
+        android:strokeColor="#33FFFFFF"
69
+        android:strokeWidth="0.8" />
70
+    <path
71
+        android:fillColor="#00000000"
72
+        android:pathData="M0,29L108,29"
73
+        android:strokeColor="#33FFFFFF"
74
+        android:strokeWidth="0.8" />
75
+    <path
76
+        android:fillColor="#00000000"
77
+        android:pathData="M0,39L108,39"
78
+        android:strokeColor="#33FFFFFF"
79
+        android:strokeWidth="0.8" />
80
+    <path
81
+        android:fillColor="#00000000"
82
+        android:pathData="M0,49L108,49"
83
+        android:strokeColor="#33FFFFFF"
84
+        android:strokeWidth="0.8" />
85
+    <path
86
+        android:fillColor="#00000000"
87
+        android:pathData="M0,59L108,59"
88
+        android:strokeColor="#33FFFFFF"
89
+        android:strokeWidth="0.8" />
90
+    <path
91
+        android:fillColor="#00000000"
92
+        android:pathData="M0,69L108,69"
93
+        android:strokeColor="#33FFFFFF"
94
+        android:strokeWidth="0.8" />
95
+    <path
96
+        android:fillColor="#00000000"
97
+        android:pathData="M0,79L108,79"
98
+        android:strokeColor="#33FFFFFF"
99
+        android:strokeWidth="0.8" />
100
+    <path
101
+        android:fillColor="#00000000"
102
+        android:pathData="M0,89L108,89"
103
+        android:strokeColor="#33FFFFFF"
104
+        android:strokeWidth="0.8" />
105
+    <path
106
+        android:fillColor="#00000000"
107
+        android:pathData="M0,99L108,99"
108
+        android:strokeColor="#33FFFFFF"
109
+        android:strokeWidth="0.8" />
110
+    <path
111
+        android:fillColor="#00000000"
112
+        android:pathData="M19,29L89,29"
113
+        android:strokeColor="#33FFFFFF"
114
+        android:strokeWidth="0.8" />
115
+    <path
116
+        android:fillColor="#00000000"
117
+        android:pathData="M19,39L89,39"
118
+        android:strokeColor="#33FFFFFF"
119
+        android:strokeWidth="0.8" />
120
+    <path
121
+        android:fillColor="#00000000"
122
+        android:pathData="M19,49L89,49"
123
+        android:strokeColor="#33FFFFFF"
124
+        android:strokeWidth="0.8" />
125
+    <path
126
+        android:fillColor="#00000000"
127
+        android:pathData="M19,59L89,59"
128
+        android:strokeColor="#33FFFFFF"
129
+        android:strokeWidth="0.8" />
130
+    <path
131
+        android:fillColor="#00000000"
132
+        android:pathData="M19,69L89,69"
133
+        android:strokeColor="#33FFFFFF"
134
+        android:strokeWidth="0.8" />
135
+    <path
136
+        android:fillColor="#00000000"
137
+        android:pathData="M19,79L89,79"
138
+        android:strokeColor="#33FFFFFF"
139
+        android:strokeWidth="0.8" />
140
+    <path
141
+        android:fillColor="#00000000"
142
+        android:pathData="M29,19L29,89"
143
+        android:strokeColor="#33FFFFFF"
144
+        android:strokeWidth="0.8" />
145
+    <path
146
+        android:fillColor="#00000000"
147
+        android:pathData="M39,19L39,89"
148
+        android:strokeColor="#33FFFFFF"
149
+        android:strokeWidth="0.8" />
150
+    <path
151
+        android:fillColor="#00000000"
152
+        android:pathData="M49,19L49,89"
153
+        android:strokeColor="#33FFFFFF"
154
+        android:strokeWidth="0.8" />
155
+    <path
156
+        android:fillColor="#00000000"
157
+        android:pathData="M59,19L59,89"
158
+        android:strokeColor="#33FFFFFF"
159
+        android:strokeWidth="0.8" />
160
+    <path
161
+        android:fillColor="#00000000"
162
+        android:pathData="M69,19L69,89"
163
+        android:strokeColor="#33FFFFFF"
164
+        android:strokeWidth="0.8" />
165
+    <path
166
+        android:fillColor="#00000000"
167
+        android:pathData="M79,19L79,89"
168
+        android:strokeColor="#33FFFFFF"
169
+        android:strokeWidth="0.8" />
170
+</vector>

+ 9
- 0
app/src/main/res/drawable/ic_notifications_black_24dp.xml View File

@@ -0,0 +1,9 @@
1
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+    android:width="24dp"
3
+    android:height="24dp"
4
+    android:viewportWidth="24.0"
5
+    android:viewportHeight="24.0">
6
+    <path
7
+        android:fillColor="#FF000000"
8
+        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
9
+</vector>

BIN
app/src/main/res/drawable/ic_stat_ic_notification.png View File


+ 12
- 0
app/src/main/res/drawable/login_rectangle.xml View File

@@ -0,0 +1,12 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<shape
3
+    xmlns:android="http://schemas.android.com/apk/res/android"
4
+    android:shape="rectangle">
5
+
6
+    <solid
7
+        android:color="@android:color/white"/>
8
+
9
+    <size
10
+        android:width="339dp"
11
+        android:height="150dp"/>
12
+</shape>

+ 7
- 0
app/src/main/res/font/abril_fatface.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Abril Fatface"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 7
- 0
app/src/main/res/font/acme.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Acme"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 7
- 0
app/src/main/res/font/amaranth.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Amaranth"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 7
- 0
app/src/main/res/font/andada.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Andada"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 7
- 0
app/src/main/res/font/arbutus_slab.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Arbutus Slab"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 7
- 0
app/src/main/res/font/cabin.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
3
+        app:fontProviderAuthority="com.google.android.gms.fonts"
4
+        app:fontProviderPackage="com.google.android.gms"
5
+        app:fontProviderQuery="Cabin"
6
+        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
7
+</font-family>

+ 118
- 0
app/src/main/res/layout/activity_account_recovery.xml View File

@@ -0,0 +1,118 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent"
7
+    android:background="@android:color/white">
8
+
9
+    <TextView
10
+        android:id="@+id/accountRecoveryTitle"
11
+        android:layout_width="wrap_content"
12
+        android:layout_height="wrap_content"
13
+        android:layout_marginStart="@dimen/horizontalMargin"
14
+        android:layout_marginEnd="@dimen/horizontalMargin"
15
+        android:fontFamily="@font/cabin"
16
+        android:text="@string/accountRecoveryTitleText"
17
+        android:textAlignment="center"
18
+        android:textColor="@color/colorPrimary"
19
+        android:textSize="24sp"
20
+        android:textStyle="bold"
21
+        app:layout_constraintBottom_toTopOf="@+id/recoverButton"
22
+        app:layout_constraintEnd_toEndOf="parent"
23
+        app:layout_constraintStart_toStartOf="parent"
24
+        app:layout_constraintTop_toTopOf="parent" />
25
+
26
+    <Button
27
+        android:id="@+id/recoverButton"
28
+        android:layout_width="0dp"
29
+        android:layout_height="wrap_content"
30
+        android:layout_marginStart="@dimen/horizontalMargin"
31
+        android:layout_marginEnd="@dimen/horizontalMargin"
32
+        android:layout_marginBottom="@dimen/verticalMargin"
33
+        android:background="@drawable/button_background"
34
+        android:fontFamily="sans-serif-black"
35
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
36
+        android:paddingVertical="@dimen/buttonVerticalPadding"
37
+        android:text="@string/recoverAccountButtonText"
38
+        android:textColor="@android:color/white"
39
+        android:textSize="@dimen/buttonTextSize"
40
+        android:textStyle="bold"
41
+        app:layout_constraintBottom_toBottomOf="parent"
42
+        app:layout_constraintEnd_toEndOf="parent"
43
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
44
+        app:layout_constraintStart_toStartOf="parent"
45
+        app:layout_constraintTop_toBottomOf="@+id/accountRecoveryTitle" />
46
+
47
+    <TextView
48
+        android:id="@+id/emailLabelRecover"
49
+        android:layout_width="wrap_content"
50
+        android:layout_height="@dimen/accountRecoveryTextFieldHeight"
51
+        android:layout_marginStart="@dimen/horizontalMargin"
52
+        android:layout_marginTop="32dp"
53
+        android:layout_marginEnd="16dp"
54
+        android:fontFamily="@font/cabin"
55
+        android:gravity="center"
56
+        android:text="@string/emailInputLabelText"
57
+        android:textSize="@dimen/accountRecoveryLabelTextSize"
58
+        app:layout_constraintEnd_toStartOf="@+id/emailTextRecover"
59
+        app:layout_constraintStart_toStartOf="parent"
60
+        app:layout_constraintTop_toBottomOf="@id/accountRecoveryTitle" />
61
+
62
+    <TextView
63
+        android:id="@+id/passwordLabelRecover"
64
+        android:layout_width="wrap_content"
65
+        android:layout_height="@dimen/accountRecoveryTextFieldHeight"
66
+        android:layout_marginTop="16dp"
67
+        android:fontFamily="@font/cabin"
68
+        android:gravity="center"
69
+        android:text="@string/passwordInputLabelText"
70
+        android:textSize="@dimen/accountRecoveryLabelTextSize"
71
+        app:layout_constraintStart_toStartOf="@id/emailLabelRecover"
72
+        app:layout_constraintTop_toBottomOf="@id/emailTextRecover" />
73
+
74
+    <EditText
75
+        android:id="@+id/passwordTextRecover"
76
+        android:layout_width="@dimen/accountRecoveryTextFieldWidth"
77
+        android:layout_height="@dimen/accountRecoveryTextFieldHeight"
78
+        android:autofillHints="AUTOFILL_HINT_PASSWORD"
79
+        android:backgroundTint="@color/colorPrimary"
80
+        android:hint="@string/password"
81
+        android:inputType="textPassword"
82
+        android:textColor="@color/textColor2"
83
+        app:layout_constraintStart_toStartOf="@id/emailTextRecover"
84
+        app:layout_constraintTop_toTopOf="@id/passwordLabelRecover" />
85
+
86
+    <EditText
87
+        android:id="@+id/emailTextRecover"
88
+        android:layout_width="@dimen/accountRecoveryTextFieldWidth"
89
+        android:layout_height="@dimen/accountRecoveryTextFieldHeight"
90
+        android:layout_marginEnd="@dimen/horizontalMargin"
91
+        android:autofillHints="AUTOFILL_HINT_EMAIL_ADDRESS"
92
+        android:backgroundTint="@color/colorPrimary"
93
+        android:hint="@string/email"
94
+        android:inputType="textEmailAddress"
95
+        android:textColor="@color/textColor2"
96
+        app:layout_constraintEnd_toEndOf="parent"
97
+        app:layout_constraintStart_toEndOf="@+id/emailLabelRecover"
98
+        app:layout_constraintTop_toTopOf="@id/emailLabelRecover" />
99
+
100
+    <Button
101
+        android:id="@+id/forgotPasswordButton"
102
+        android:layout_width="0dp"
103
+        android:layout_height="wrap_content"
104
+        android:layout_marginStart="@dimen/horizontalMargin"
105
+        android:layout_marginTop="16dp"
106
+        android:layout_marginEnd="@dimen/horizontalMargin"
107
+        android:background="@drawable/button_background"
108
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
109
+        android:paddingVertical="@dimen/buttonVerticalPadding"
110
+        android:text="@string/forgotPasswordButtonText"
111
+        android:textColor="@android:color/white"
112
+        android:textSize="18sp"
113
+        android:textStyle="bold"
114
+        app:layout_constraintEnd_toEndOf="parent"
115
+        app:layout_constraintStart_toStartOf="parent"
116
+        app:layout_constraintTop_toBottomOf="@+id/recoverButton" />
117
+
118
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 65
- 0
app/src/main/res/layout/activity_experience_registration.xml View File

@@ -0,0 +1,65 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent">
7
+
8
+    <TextView
9
+        android:id="@+id/textViewActivateExperience"
10
+        android:layout_width="0dp"
11
+        android:layout_height="wrap_content"
12
+        android:layout_marginStart="@dimen/horizontalMargin"
13
+        android:layout_marginTop="@dimen/verticalMargin"
14
+        android:layout_marginEnd="@dimen/horizontalMargin"
15
+        android:layout_marginBottom="16dp"
16
+        android:fontFamily="@font/cabin"
17
+        android:text="@string/experienceRegistrationTitleText"
18
+        android:textAlignment="center"
19
+        android:textColor="@color/textColor2"
20
+        android:textSize="@dimen/sectionTitleTextSize"
21
+        android:textStyle="bold"
22
+        app:layout_constraintBottom_toTopOf="@id/buttonEnterExperience"
23
+        app:layout_constraintEnd_toEndOf="parent"
24
+        app:layout_constraintStart_toStartOf="parent"
25
+        app:layout_constraintTop_toTopOf="parent" />
26
+
27
+    <TextView
28
+        android:id="@+id/experienceRegistrationText"
29
+        android:layout_width="0dp"
30
+        android:layout_height="wrap_content"
31
+        android:layout_marginStart="@dimen/horizontalMargin"
32
+        android:layout_marginTop="16dp"
33
+        android:layout_marginEnd="@dimen/horizontalMargin"
34
+        android:fontFamily="sans-serif"
35
+        android:gravity="center"
36
+        android:text="@string/loadingText"
37
+        android:textAlignment="center"
38
+        android:textSize="18sp"
39
+        app:layout_constraintEnd_toEndOf="parent"
40
+        app:layout_constraintStart_toStartOf="parent"
41
+        app:layout_constraintTop_toBottomOf="@+id/textViewActivateExperience"
42
+        tools:text="@string/experienceRegistrationDescriptionText" />
43
+
44
+    <Button
45
+        android:id="@+id/buttonEnterExperience"
46
+        android:layout_width="0dp"
47
+        android:layout_height="wrap_content"
48
+        android:layout_marginStart="@dimen/horizontalMargin"
49
+        android:layout_marginEnd="@dimen/horizontalMargin"
50
+        android:layout_marginBottom="@dimen/verticalMargin"
51
+        android:background="@drawable/button_background"
52
+        android:fontFamily="sans-serif-black"
53
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
54
+        android:paddingVertical="@dimen/buttonVerticalPadding"
55
+        android:text="@string/experienceRegistrationButtonText"
56
+        android:textColor="@android:color/white"
57
+        android:textSize="@dimen/buttonTextSize"
58
+        android:textStyle="bold"
59
+        app:layout_constraintBottom_toBottomOf="parent"
60
+        app:layout_constraintEnd_toEndOf="parent"
61
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
62
+        app:layout_constraintStart_toStartOf="parent"
63
+        app:layout_constraintTop_toBottomOf="@id/textViewActivateExperience" />
64
+
65
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 57
- 0
app/src/main/res/layout/activity_forgot_password.xml View File

@@ -0,0 +1,57 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent">
7
+
8
+    <TextView
9
+        android:id="@+id/textViewEmailResetPassword"
10
+        android:layout_width="0dp"
11
+        android:layout_height="wrap_content"
12
+        android:layout_marginStart="@dimen/horizontalMargin"
13
+        android:layout_marginEnd="@dimen/horizontalMargin"
14
+        android:fontFamily="@font/cabin"
15
+        android:text="@string/forgotPasswordDescriptionText"
16
+        android:textAlignment="center"
17
+        android:textSize="18sp"
18
+        app:layout_constraintBottom_toTopOf="@+id/buttonSendEmail"
19
+        app:layout_constraintEnd_toEndOf="parent"
20
+        app:layout_constraintStart_toStartOf="parent"
21
+        app:layout_constraintTop_toTopOf="parent" />
22
+
23
+    <EditText
24
+        android:id="@+id/editTextEmailRecovery"
25
+        android:layout_width="0dp"
26
+        android:layout_height="wrap_content"
27
+        android:layout_marginStart="@dimen/horizontalMargin"
28
+        android:layout_marginTop="24dp"
29
+        android:layout_marginEnd="@dimen/horizontalMargin"
30
+        android:autofillHints="AUTOFILL_HINT_EMAIL_ADDRESS"
31
+        android:backgroundTint="@color/colorPrimary"
32
+        android:hint="@string/email"
33
+        android:inputType="textEmailAddress"
34
+        android:textAlignment="center"
35
+        android:textColor="@color/textColor2"
36
+        app:layout_constraintEnd_toEndOf="parent"
37
+        app:layout_constraintStart_toStartOf="parent"
38
+        app:layout_constraintTop_toBottomOf="@+id/textViewEmailResetPassword" />
39
+
40
+    <Button
41
+        android:id="@+id/buttonSendEmail"
42
+        android:layout_width="0dp"
43
+        android:layout_height="wrap_content"
44
+        android:layout_marginStart="@dimen/horizontalMargin"
45
+        android:layout_marginEnd="@dimen/horizontalMargin"
46
+        android:layout_marginBottom="@dimen/verticalMargin"
47
+        android:background="@drawable/button_background"
48
+        android:fontFamily="sans-serif-black"
49
+        android:text="@string/sendEmailButtonText"
50
+        android:textColor="@android:color/white"
51
+        android:textSize="18sp"
52
+        android:textStyle="bold"
53
+        app:layout_constraintBottom_toBottomOf="parent"
54
+        app:layout_constraintEnd_toEndOf="parent"
55
+        app:layout_constraintStart_toStartOf="parent"
56
+        app:layout_constraintTop_toBottomOf="@+id/textViewEmailResetPassword" />
57
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 141
- 0
app/src/main/res/layout/activity_getting_started.xml View File

@@ -0,0 +1,141 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:id="@+id/gettingStartedLayout"
6
+    android:layout_width="match_parent"
7
+    android:layout_height="match_parent"
8
+    android:background="@color/white_60">
9
+
10
+<!-- 
11
+    <ImageView
12
+        android:id="@+id/bubble1"
13
+        android:layout_width="@dimen/bubbleSize"
14
+        android:layout_height="@dimen/bubbleSize"
15
+        android:layout_marginStart="@dimen/horizontalMargin"
16
+        android:layout_marginTop="52dp"
17
+        android:layout_marginEnd="@dimen/horizontalMargin"
18
+        android:contentDescription="@string/bubbleDescription"
19
+        app:layout_constraintBottom_toTopOf="@+id/buttonCreateAccount"
20
+        app:layout_constraintEnd_toEndOf="parent"
21
+        app:layout_constraintStart_toStartOf="parent"
22
+        app:layout_constraintTop_toBottomOf="@+id/applicationNameLong"
23
+        app:srcCompat="@drawable/circle" />
24
+ -->
25
+
26
+<!--    <ImageView-->
27
+<!--        android:id="@+id/bubble2"-->
28
+<!--        android:layout_width="@dimen/bubbleSize"-->
29
+<!--        android:layout_height="@dimen/bubbleSize"-->
30
+<!--        android:layout_marginStart="@dimen/horizontalMargin"-->
31
+<!--        android:layout_marginTop="28dp"-->
32
+<!--        android:layout_marginEnd="@dimen/horizontalMargin"-->
33
+<!--        android:contentDescription="@string/bubbleDescription"-->
34
+<!--        app:layout_constraintBottom_toTopOf="@+id/bubble3"-->
35
+<!--        app:layout_constraintEnd_toEndOf="parent"-->
36
+<!--        app:layout_constraintStart_toStartOf="parent"-->
37
+<!--        app:layout_constraintTop_toBottomOf="@id/bubble1"-->
38
+<!--        app:srcCompat="@drawable/circle" />-->
39
+
40
+<!--    <ImageView-->
41
+<!--        android:id="@+id/bubble3"-->
42
+<!--        android:layout_width="@dimen/bubbleSize"-->
43
+<!--        android:layout_height="@dimen/bubbleSize"-->
44
+<!--        android:layout_marginStart="@dimen/horizontalMargin"-->
45
+<!--        android:layout_marginTop="28dp"-->
46
+<!--        android:layout_marginEnd="@dimen/horizontalMargin"-->
47
+<!--        android:contentDescription="@string/bubbleDescription"-->
48
+<!--        app:layout_constraintBottom_toTopOf="@id/buttonCreateAccount"-->
49
+<!--        app:layout_constraintEnd_toEndOf="parent"-->
50
+<!--        app:layout_constraintStart_toStartOf="parent"-->
51
+<!--        app:layout_constraintTop_toBottomOf="@id/bubble2"-->
52
+<!--        app:srcCompat="@drawable/circle" />-->
53
+
54
+    <TextView
55
+        android:id="@+id/ApplicationName"
56
+        android:layout_width="0dp"
57
+        android:layout_height="wrap_content"
58
+        android:layout_marginStart="@dimen/horizontalMargin"
59
+        android:layout_marginTop="128dp"
60
+        android:layout_marginEnd="@dimen/horizontalMargin"
61
+        android:fontFamily="@font/cabin"
62
+        android:text="@string/appName"
63
+        android:textAlignment="center"
64
+        android:textColor="@color/colorPrimary"
65
+        android:textSize="80sp"
66
+        android:textStyle="bold"
67
+        app:layout_constraintEnd_toEndOf="parent"
68
+        app:layout_constraintStart_toStartOf="parent"
69
+        app:layout_constraintTop_toTopOf="parent" />
70
+
71
+    <TextView
72
+        android:id="@+id/applicationNameLong"
73
+        android:layout_width="0dp"
74
+        android:layout_height="wrap_content"
75
+        android:layout_marginStart="@dimen/horizontalMargin"
76
+        android:layout_marginEnd="@dimen/horizontalMargin"
77
+        android:fontFamily="@font/cabin"
78
+        android:text="@string/appNameLong"
79
+        android:textAlignment="center"
80
+        android:textSize="18sp"
81
+        app:layout_constraintEnd_toEndOf="parent"
82
+        app:layout_constraintStart_toStartOf="parent"
83
+        app:layout_constraintTop_toBottomOf="@+id/ApplicationName" />
84
+
85
+    <Button
86
+        android:id="@+id/buttonCreateAccount"
87
+        android:layout_width="0dp"
88
+        android:layout_height="wrap_content"
89
+        android:layout_marginStart="@dimen/horizontalMargin"
90
+        android:layout_marginTop="52dp"
91
+        android:layout_marginEnd="@dimen/horizontalMargin"
92
+        android:background="@drawable/button_background"
93
+        android:fontFamily="sans-serif-black"
94
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
95
+        android:paddingVertical="@dimen/buttonVerticalPadding"
96
+        android:text="@string/createAccountButtonText"
97
+        android:textColor="@android:color/white"
98
+        android:textSize="@dimen/buttonTextSize"
99
+        android:textStyle="bold"
100
+        app:layout_constraintEnd_toEndOf="parent"
101
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
102
+        app:layout_constraintStart_toStartOf="parent"
103
+        app:layout_constraintTop_toBottomOf="@id/privacy_policy_link" />
104
+
105
+    <Button
106
+        android:id="@+id/buttonRecoverAccount"
107
+        android:layout_width="0dp"
108
+        android:layout_height="wrap_content"
109
+        android:layout_marginStart="@dimen/horizontalMargin"
110
+        android:layout_marginTop="16dp"
111
+        android:layout_marginEnd="@dimen/horizontalMargin"
112
+        android:background="@drawable/button_background"
113
+        android:fontFamily="sans-serif-black"
114
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
115
+        android:paddingVertical="@dimen/buttonVerticalPadding"
116
+        android:text="@string/recoverAccountButtonText"
117
+        android:textColor="@android:color/white"
118
+        android:textSize="@dimen/buttonTextSize"
119
+        android:textStyle="bold"
120
+        app:layout_constraintEnd_toEndOf="parent"
121
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
122
+        app:layout_constraintStart_toStartOf="parent"
123
+        app:layout_constraintTop_toBottomOf="@id/buttonCreateAccount" />
124
+
125
+    <TextView
126
+        android:id="@+id/privacy_policy_link"
127
+        android:layout_width="wrap_content"
128
+        android:layout_height="wrap_content"
129
+        android:background="?attr/selectableItemBackground"
130
+        android:clickable="true"
131
+        android:focusable="true"
132
+        android:textSize="24sp"
133
+        android:padding="8dp"
134
+        android:text="@string/privacy_policy"
135
+        android:textColor="#000000"
136
+        app:layout_constraintBottom_toTopOf="@+id/buttonCreateAccount"
137
+        app:layout_constraintEnd_toEndOf="parent"
138
+        app:layout_constraintStart_toStartOf="parent"
139
+        app:layout_constraintTop_toBottomOf="@+id/applicationNameLong" />
140
+
141
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 81
- 0
app/src/main/res/layout/activity_main.xml View File

@@ -0,0 +1,81 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent"
7
+    android:backgroundTint="@color/textColor1"
8
+    android:orientation="vertical"
9
+    tools:context="uprrp.tania.activities.MainActivity">
10
+
11
+    <androidx.appcompat.widget.Toolbar
12
+        android:id="@+id/toolbar"
13
+        android:layout_width="match_parent"
14
+        android:layout_height="@dimen/mainActivityToolbarHeight"
15
+        android:background="@color/colorPrimary"
16
+        android:minHeight="?attr/actionBarSize"
17
+        android:theme="?attr/actionBarTheme"
18
+        app:layout_constraintEnd_toEndOf="parent"
19
+        app:layout_constraintStart_toStartOf="parent"
20
+        app:layout_constraintTop_toTopOf="parent"
21
+        app:titleTextColor="@color/textColor1" />
22
+
23
+    <View
24
+        android:id="@+id/horizontalGrayLine"
25
+        android:layout_width="match_parent"
26
+        android:layout_height="1dp"
27
+        android:background="@color/dark_gray"
28
+        app:layout_constraintEnd_toEndOf="parent"
29
+        app:layout_constraintStart_toStartOf="parent"
30
+        app:layout_constraintTop_toBottomOf="@+id/toolbar" />
31
+
32
+    <TextView
33
+        android:id="@+id/survey_results"
34
+        android:layout_width="wrap_content"
35
+        android:layout_height="wrap_content"
36
+        app:layout_constraintEnd_toEndOf="parent"
37
+        app:layout_constraintStart_toStartOf="parent"
38
+        app:layout_constraintTop_toBottomOf="@id/horizontalGrayLine" />
39
+
40
+    <TextView
41
+        android:id="@+id/privacy_policy_link"
42
+        android:layout_width="wrap_content"
43
+        android:layout_height="wrap_content"
44
+        android:background="?attr/selectableItemBackground"
45
+        android:clickable="true"
46
+        android:focusable="true"
47
+        android:minHeight="56dp"
48
+        android:textSize="24sp"
49
+        android:padding="8dp"
50
+        android:text="@string/privacy_policy"
51
+        android:textColor="#1976D2"
52
+        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_menu"
53
+        app:layout_constraintEnd_toEndOf="parent"
54
+        app:layout_constraintStart_toStartOf="parent" />
55
+
56
+    <com.google.android.material.bottomnavigation.BottomNavigationView
57
+        android:id="@+id/bottom_navigation_menu"
58
+        android:layout_width="0dp"
59
+        android:layout_height="wrap_content"
60
+        android:background="@color/colorPrimaryDark"
61
+        app:itemBackground="@color/colorPrimary"
62
+        app:itemIconTint="@color/bottom_nav_selector"
63
+        app:itemTextColor="@color/bottom_nav_selector"
64
+        app:layout_constraintBottom_toBottomOf="parent"
65
+        app:layout_constraintLeft_toLeftOf="parent"
66
+        app:layout_constraintRight_toRightOf="parent"
67
+        app:menu="@menu/bottom_navigation" />
68
+
69
+    <androidx.fragment.app.FragmentContainerView
70
+        android:id="@+id/mainFragment"
71
+        android:name="androidx.navigation.fragment.NavHostFragment"
72
+        android:layout_width="match_parent"
73
+        android:layout_height="0dp"
74
+        app:defaultNavHost="true"
75
+        app:layout_constraintBottom_toTopOf="@+id/privacy_policy_link"
76
+        app:layout_constraintEnd_toEndOf="parent"
77
+        app:layout_constraintStart_toStartOf="parent"
78
+        app:layout_constraintTop_toTopOf="@+id/survey_results"
79
+        app:navGraph="@navigation/mobile_navigation" />
80
+
81
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 75
- 0
app/src/main/res/layout/activity_reset_password.xml View File

@@ -0,0 +1,75 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent"
7
+    android:orientation="vertical">
8
+
9
+    <TextView
10
+        android:id="@+id/textViewResetPassword"
11
+        android:layout_width="0dp"
12
+        android:layout_height="wrap_content"
13
+        android:layout_marginStart="@dimen/horizontalMargin"
14
+        android:layout_marginTop="@dimen/verticalMargin"
15
+        android:layout_marginEnd="@dimen/horizontalMargin"
16
+        android:fontFamily="@font/cabin"
17
+        android:text="@string/resetPasswordDescriptionText"
18
+        android:textAlignment="center"
19
+        android:textColor="@color/textColor2"
20
+        android:textSize="24sp"
21
+        android:textStyle="bold"
22
+        app:layout_constraintBottom_toTopOf="@+id/buttonResetPassword"
23
+        app:layout_constraintEnd_toEndOf="parent"
24
+        app:layout_constraintStart_toStartOf="parent"
25
+        app:layout_constraintTop_toTopOf="parent" />
26
+
27
+    <EditText
28
+        android:id="@+id/editTextNewPassword"
29
+        android:layout_width="0dp"
30
+        android:layout_height="wrap_content"
31
+        android:layout_marginStart="@dimen/horizontalMargin"
32
+        android:layout_marginTop="24dp"
33
+        android:layout_marginEnd="@dimen/horizontalMargin"
34
+        android:autofillHints="AUTOFILL_HINT_PASSWORD"
35
+        android:backgroundTint="@color/colorPrimary"
36
+        android:ems="10"
37
+        android:hint="@string/newPasswordButtonHint"
38
+        android:inputType="textPassword"
39
+        android:textColor="@color/textColor2"
40
+        app:layout_constraintEnd_toEndOf="parent"
41
+        app:layout_constraintStart_toStartOf="parent"
42
+        app:layout_constraintTop_toBottomOf="@+id/textViewResetPassword" />
43
+
44
+    <EditText
45
+        android:id="@+id/editTextConfirmPassword"
46
+        android:layout_width="0dp"
47
+        android:layout_height="wrap_content"
48
+        android:layout_marginTop="16dp"
49
+        android:autofillHints="AUTOFILL_HINT_PASSWORD"
50
+        android:backgroundTint="@color/colorPrimary"
51
+        android:ems="10"
52
+        android:hint="@string/confirmPasswordButtonHint"
53
+        android:inputType="textPassword"
54
+        android:textColor="@color/textColor2"
55
+        app:layout_constraintEnd_toEndOf="@+id/editTextNewPassword"
56
+        app:layout_constraintStart_toStartOf="@+id/editTextNewPassword"
57
+        app:layout_constraintTop_toBottomOf="@+id/editTextNewPassword" />
58
+
59
+    <Button
60
+        android:id="@+id/buttonResetPassword"
61
+        android:layout_width="0dp"
62
+        android:layout_height="wrap_content"
63
+        android:layout_marginStart="@dimen/horizontalMargin"
64
+        android:layout_marginEnd="@dimen/horizontalMargin"
65
+        android:layout_marginBottom="@dimen/verticalMargin"
66
+        android:background="@drawable/button_background"
67
+        android:fontFamily="sans-serif-black"
68
+        android:text="@string/resetPasswordButtonText"
69
+        android:textColor="@android:color/white"
70
+        android:textSize="18sp"
71
+        app:layout_constraintBottom_toBottomOf="parent"
72
+        app:layout_constraintEnd_toEndOf="parent"
73
+        app:layout_constraintStart_toStartOf="parent"
74
+        app:layout_constraintTop_toBottomOf="@+id/textViewResetPassword" />
75
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 308
- 0
app/src/main/res/layout/activity_user_registration.xml View File

@@ -0,0 +1,308 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent">
7
+
8
+    <TextView
9
+        android:id="@+id/titleBackground"
10
+        android:layout_width="411dp"
11
+        android:layout_height="135dp"
12
+        android:background="@color/colorPrimary"
13
+        app:layout_constraintBottom_toBottomOf="parent"
14
+        app:layout_constraintEnd_toEndOf="parent"
15
+        app:layout_constraintHorizontal_bias="0.0"
16
+        app:layout_constraintStart_toStartOf="parent"
17
+        app:layout_constraintTop_toTopOf="parent"
18
+        app:layout_constraintVertical_bias="0.0" />
19
+
20
+    <TextView
21
+        android:id="@+id/registrationTitle"
22
+        android:layout_width="wrap_content"
23
+        android:layout_height="wrap_content"
24
+        android:fontFamily="@font/acme"
25
+        android:text="REGISTRATION"
26
+        android:textColor="@color/white_60"
27
+        android:textSize="30sp"
28
+        android:textStyle="bold"
29
+        app:layout_constraintBottom_toBottomOf="parent"
30
+        app:layout_constraintEnd_toEndOf="parent"
31
+        app:layout_constraintStart_toStartOf="parent"
32
+        app:layout_constraintTop_toTopOf="@+id/titleBackground"
33
+        app:layout_constraintVertical_bias="0.117" />
34
+
35
+    <TextView
36
+        android:id="@+id/birthDateLabel"
37
+        android:layout_width="wrap_content"
38
+        android:layout_height="wrap_content"
39
+        android:layout_marginTop="8dp"
40
+        android:fontFamily="@font/cabin"
41
+        android:text="Date of birth:"
42
+        android:textColor="@color/textColor2"
43
+        android:textSize="18sp"
44
+        android:textStyle="bold"
45
+        app:layout_constraintBottom_toBottomOf="parent"
46
+        app:layout_constraintEnd_toEndOf="parent"
47
+        app:layout_constraintHorizontal_bias="0.03"
48
+        app:layout_constraintStart_toStartOf="@+id/guideline6"
49
+        app:layout_constraintTop_toBottomOf="@+id/titleBackground"
50
+        app:layout_constraintVertical_bias="0.13" />
51
+
52
+    <TextView
53
+        android:id="@+id/birthDateText"
54
+        android:layout_width="167dp"
55
+        android:layout_height="35dp"
56
+        android:fontFamily="@font/cabin"
57
+        android:textAlignment="center"
58
+        android:textColor="@color/textColor2"
59
+        android:textSize="18sp"
60
+        app:layout_constraintBottom_toBottomOf="parent"
61
+        app:layout_constraintEnd_toStartOf="@+id/guideline7"
62
+        app:layout_constraintHorizontal_bias="0.428"
63
+        app:layout_constraintStart_toEndOf="@+id/birthDateLabel"
64
+        app:layout_constraintTop_toBottomOf="@+id/titleBackground"
65
+        app:layout_constraintVertical_bias="0.145"
66
+        tools:text="Select a date" />
67
+
68
+    <TextView
69
+        android:id="@+id/genderLabel"
70
+        android:layout_width="wrap_content"
71
+        android:layout_height="wrap_content"
72
+        android:fontFamily="@font/cabin"
73
+        android:text="Gender:"
74
+        android:textColor="@color/textColor2"
75
+        android:textSize="18sp"
76
+        android:textStyle="bold"
77
+        app:layout_constraintBottom_toBottomOf="parent"
78
+        app:layout_constraintEnd_toEndOf="parent"
79
+        app:layout_constraintHorizontal_bias="0.025"
80
+        app:layout_constraintStart_toStartOf="@+id/guideline6"
81
+        app:layout_constraintTop_toBottomOf="@+id/birthDateLabel"
82
+        app:layout_constraintVertical_bias="0.039" />
83
+
84
+    <TextView
85
+        android:id="@+id/hasJobLabel"
86
+        android:layout_width="wrap_content"
87
+        android:layout_height="wrap_content"
88
+        android:fontFamily="@font/cabin"
89
+        android:text="Do you have a job?"
90
+        android:textColor="@color/textColor2"
91
+        android:textSize="18sp"
92
+        android:textStyle="bold"
93
+        app:layout_constraintBottom_toBottomOf="parent"
94
+        app:layout_constraintEnd_toEndOf="parent"
95
+        app:layout_constraintHorizontal_bias="0.131"
96
+        app:layout_constraintStart_toStartOf="parent"
97
+        app:layout_constraintTop_toBottomOf="@+id/radioGroup"
98
+        app:layout_constraintVertical_bias="0.033" />
99
+
100
+    <TextView
101
+        android:id="@+id/emailLabel"
102
+        android:layout_width="wrap_content"
103
+        android:layout_height="wrap_content"
104
+        android:fontFamily="@font/cabin"
105
+        android:text="Email:"
106
+        android:textColor="@color/textColor2"
107
+        android:textSize="18sp"
108
+        android:textStyle="bold"
109
+        app:layout_constraintBottom_toBottomOf="parent"
110
+        app:layout_constraintEnd_toEndOf="parent"
111
+        app:layout_constraintHorizontal_bias="0.031"
112
+        app:layout_constraintStart_toStartOf="@+id/guideline6"
113
+        app:layout_constraintTop_toBottomOf="@+id/radioGroup3"
114
+        app:layout_constraintVertical_bias="0.088" />
115
+
116
+    <EditText
117
+        android:id="@+id/emailText"
118
+        android:layout_width="253dp"
119
+        android:layout_height="40dp"
120
+        android:ems="10"
121
+        android:inputType="textEmailAddress"
122
+        app:layout_constraintBottom_toBottomOf="parent"
123
+        app:layout_constraintEnd_toStartOf="@+id/guideline7"
124
+        app:layout_constraintHorizontal_bias="0.272"
125
+        app:layout_constraintStart_toEndOf="@+id/emailLabel"
126
+        app:layout_constraintTop_toBottomOf="@+id/radioGroup3"
127
+        app:layout_constraintVertical_bias="0.091" />
128
+
129
+    <TextView
130
+        android:id="@+id/passwordLabel"
131
+        android:layout_width="wrap_content"
132
+        android:layout_height="wrap_content"
133
+        android:text="Password:"
134
+        android:textColor="@color/textColor2"
135
+        android:textSize="18sp"
136
+        android:textStyle="bold"
137
+        app:layout_constraintBottom_toBottomOf="parent"
138
+        app:layout_constraintStart_toStartOf="@+id/genderLabel"
139
+        app:layout_constraintTop_toBottomOf="@+id/emailLabel"
140
+        app:layout_constraintVertical_bias="0.144" />
141
+
142
+    <EditText
143
+        android:id="@+id/passwordText"
144
+        android:layout_width="218dp"
145
+        android:layout_height="39dp"
146
+        android:ems="10"
147
+        android:inputType="textPassword"
148
+        app:layout_constraintBottom_toBottomOf="parent"
149
+        app:layout_constraintEnd_toStartOf="@+id/guideline7"
150
+        app:layout_constraintHorizontal_bias="0.466"
151
+        app:layout_constraintStart_toEndOf="@+id/passwordLabel"
152
+        app:layout_constraintTop_toBottomOf="@+id/emailText"
153
+        app:layout_constraintVertical_bias="0.096" />
154
+
155
+    <Button
156
+        android:id="@+id/registerButton"
157
+        android:layout_width="270dp"
158
+        android:layout_height="43dp"
159
+        android:background="@color/colorAccent"
160
+        android:fontFamily="@font/cabin"
161
+        android:text="REGISTER"
162
+        android:textColor="@color/textColor1"
163
+        android:textSize="18sp"
164
+        android:textStyle="bold"
165
+        app:layout_constraintBottom_toBottomOf="parent"
166
+        app:layout_constraintEnd_toEndOf="parent"
167
+        app:layout_constraintHorizontal_bias="0.546"
168
+        app:layout_constraintStart_toStartOf="parent"
169
+        app:layout_constraintTop_toBottomOf="@+id/passwordText"
170
+        app:layout_constraintVertical_bias="0.253" />
171
+
172
+    <RadioGroup
173
+        android:id="@+id/radioGroup"
174
+        android:layout_width="309dp"
175
+        android:layout_height="38dp"
176
+        android:orientation="horizontal"
177
+        app:layout_constraintBottom_toBottomOf="parent"
178
+        app:layout_constraintEnd_toStartOf="@+id/guideline7"
179
+        app:layout_constraintHorizontal_bias="0.764"
180
+        app:layout_constraintStart_toStartOf="parent"
181
+        app:layout_constraintTop_toBottomOf="@+id/birthDateText"
182
+        app:layout_constraintVertical_bias="0.103">
183
+
184
+        <RadioButton
185
+            android:id="@+id/maleRadioButton"
186
+            android:layout_width="wrap_content"
187
+            android:layout_height="wrap_content"
188
+            android:layout_weight="1"
189
+            android:fontFamily="@font/cabin"
190
+            android:text="Male"
191
+            android:textColor="@color/textColor2"
192
+            android:textSize="14sp" />
193
+
194
+        <RadioButton
195
+            android:id="@+id/femaleRadioButton"
196
+            android:layout_width="wrap_content"
197
+            android:layout_height="wrap_content"
198
+            android:layout_weight="1"
199
+            android:fontFamily="@font/cabin"
200
+            android:text="Female"
201
+            android:textColor="@color/textColor2"
202
+            android:textSize="14sp" />
203
+
204
+        <RadioButton
205
+            android:id="@+id/otherRadioButton"
206
+            android:layout_width="wrap_content"
207
+            android:layout_height="wrap_content"
208
+            android:layout_weight="1"
209
+            android:fontFamily="@font/cabin"
210
+            android:text="Other"
211
+            android:textColor="@color/textColor2"
212
+            android:textSize="14sp" />
213
+
214
+        <RadioButton
215
+            android:id="@+id/notSayRadioButton"
216
+            android:layout_width="95dp"
217
+            android:layout_height="match_parent"
218
+            android:layout_weight="1"
219
+            android:fontFamily="@font/cabin"
220
+            android:text="Prefer not to say"
221
+            android:textColor="@color/textColor2"
222
+            android:textSize="14sp" />
223
+
224
+    </RadioGroup>
225
+
226
+    <RadioGroup
227
+        android:id="@+id/radioGroup3"
228
+        android:layout_width="302dp"
229
+        android:layout_height="32dp"
230
+        android:orientation="horizontal"
231
+        app:layout_constraintBottom_toBottomOf="parent"
232
+        app:layout_constraintEnd_toEndOf="parent"
233
+        app:layout_constraintStart_toStartOf="parent"
234
+        app:layout_constraintTop_toBottomOf="@+id/hasJobLabel"
235
+        app:layout_constraintVertical_bias="0.033">
236
+
237
+        <RadioButton
238
+            android:id="@+id/partTimeRadioButton"
239
+            android:layout_width="114dp"
240
+            android:layout_height="match_parent"
241
+            android:layout_weight="1"
242
+            android:fontFamily="@font/cabin"
243
+            android:text="Part time"
244
+            android:textColor="@color/textColor2" />
245
+
246
+        <RadioButton
247
+            android:id="@+id/fullTimeRadioButton"
248
+            android:layout_width="wrap_content"
249
+            android:layout_height="wrap_content"
250
+            android:layout_weight="1"
251
+            android:fontFamily="@font/cabin"
252
+            android:text="Full time"
253
+            android:textColor="@color/textColor2" />
254
+
255
+        <RadioButton
256
+            android:id="@+id/noJobRadioButton"
257
+            android:layout_width="wrap_content"
258
+            android:layout_height="wrap_content"
259
+            android:layout_weight="1"
260
+            android:text="None" />
261
+    </RadioGroup>
262
+
263
+    <TextView
264
+        android:id="@+id/textViewParticipantName"
265
+        android:layout_width="wrap_content"
266
+        android:layout_height="wrap_content"
267
+        android:fontFamily="@font/cabin"
268
+        android:text="Name:"
269
+        android:textColor="@color/textColor2"
270
+        android:textSize="18sp"
271
+        android:textStyle="bold"
272
+        app:layout_constraintBottom_toTopOf="@+id/birthDateLabel"
273
+        app:layout_constraintEnd_toEndOf="parent"
274
+        app:layout_constraintHorizontal_bias="0.024"
275
+        app:layout_constraintStart_toStartOf="@+id/guideline6"
276
+        app:layout_constraintTop_toBottomOf="@+id/registrationTitle"
277
+        app:layout_constraintVertical_bias="0.582" />
278
+
279
+    <EditText
280
+        android:id="@+id/nameEditText"
281
+        android:layout_width="wrap_content"
282
+        android:layout_height="wrap_content"
283
+        android:ems="10"
284
+        android:inputType="textPersonName"
285
+        app:layout_constraintBottom_toTopOf="@+id/birthDateText"
286
+        app:layout_constraintEnd_toStartOf="@+id/guideline7"
287
+        app:layout_constraintHorizontal_bias="0.244"
288
+        app:layout_constraintStart_toEndOf="@+id/textViewParticipantName"
289
+        app:layout_constraintTop_toBottomOf="@+id/registrationTitle"
290
+        app:layout_constraintVertical_bias="0.673" />
291
+
292
+    <androidx.constraintlayout.widget.Guideline
293
+        android:id="@+id/guideline6"
294
+        android:layout_width="wrap_content"
295
+        android:layout_height="wrap_content"
296
+        android:orientation="vertical"
297
+        app:layout_constraintGuide_begin="20dp" />
298
+
299
+    <androidx.constraintlayout.widget.Guideline
300
+        android:id="@+id/guideline7"
301
+        android:layout_width="wrap_content"
302
+        android:layout_height="wrap_content"
303
+        android:orientation="vertical"
304
+        app:layout_constraintGuide_begin="343dp" />
305
+
306
+
307
+</androidx.constraintlayout.widget.ConstraintLayout>
308
+

+ 102
- 0
app/src/main/res/layout/fragment_assessments.xml View File

@@ -0,0 +1,102 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent">
7
+
8
+    <androidx.appcompat.widget.AppCompatButton
9
+        android:id="@+id/refreshButton"
10
+        android:layout_width="0dp"
11
+        android:layout_height="wrap_content"
12
+        android:layout_gravity="center"
13
+        android:layout_marginStart="@dimen/horizontalMargin"
14
+        android:layout_marginEnd="@dimen/horizontalMargin"
15
+        android:layout_marginBottom="@dimen/verticalMargin"
16
+        android:background="@drawable/button_background"
17
+        android:fontFamily="sans-serif-black"
18
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
19
+        android:paddingVertical="@dimen/buttonVerticalPadding"
20
+        android:text="@string/refreshButtonText"
21
+        android:textColor="@android:color/white"
22
+        android:textSize="@dimen/buttonTextSize"
23
+        android:textStyle="bold"
24
+        android:visibility="visible"
25
+        app:layout_constraintBottom_toBottomOf="parent"
26
+        app:layout_constraintEnd_toEndOf="parent"
27
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
28
+        app:layout_constraintStart_toStartOf="parent"
29
+        app:layout_constraintTop_toBottomOf="@id/statusTextView"
30
+        tools:visibility="invisible" />
31
+
32
+    <androidx.appcompat.widget.AppCompatButton
33
+        android:id="@+id/surveyButton"
34
+        android:layout_width="0dp"
35
+        android:layout_height="wrap_content"
36
+        android:layout_gravity="center"
37
+        android:layout_marginStart="@dimen/horizontalMargin"
38
+        android:layout_marginEnd="@dimen/horizontalMargin"
39
+        android:layout_marginBottom="@dimen/verticalMargin"
40
+        android:background="@drawable/button_background"
41
+        android:fontFamily="sans-serif-black"
42
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
43
+        android:paddingVertical="@dimen/buttonVerticalPadding"
44
+        android:text="@string/surveyButtonText"
45
+        android:textColor="@android:color/white"
46
+        android:textSize="@dimen/buttonTextSize"
47
+        android:textStyle="bold"
48
+        android:visibility="invisible"
49
+        app:layout_constraintBottom_toBottomOf="parent"
50
+        app:layout_constraintEnd_toEndOf="parent"
51
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
52
+        app:layout_constraintStart_toStartOf="parent"
53
+        app:layout_constraintTop_toBottomOf="@id/statusTextView"
54
+        tools:visibility="visible" />
55
+
56
+    <TextView
57
+        android:id="@+id/statusTextView"
58
+        android:layout_width="0dp"
59
+        android:layout_height="wrap_content"
60
+        android:layout_marginStart="@dimen/horizontalMargin"
61
+        android:layout_marginTop="16dp"
62
+        android:layout_marginEnd="@dimen/horizontalMargin"
63
+        android:text="@string/loadingText"
64
+        android:textSize="@dimen/sectionDescriptionTextSize"
65
+        app:layout_constraintEnd_toEndOf="parent"
66
+        app:layout_constraintStart_toStartOf="parent"
67
+        app:layout_constraintTop_toBottomOf="@+id/assessmentTitle"
68
+        tools:text="You're enrolled in C01 PR-LSAMP UPR-Rio Piedras, by UPR Río Piedras. So far, you have completed 0 out of 16 delivered surveys." />
69
+
70
+    <TextView
71
+        android:id="@+id/nextSurveyTextView"
72
+        android:layout_width="0dp"
73
+        android:layout_height="wrap_content"
74
+        android:layout_marginStart="@dimen/horizontalMargin"
75
+        android:layout_marginEnd="@dimen/horizontalMargin"
76
+        android:layout_marginBottom="24dp"
77
+        android:textAlignment="center"
78
+        android:textSize="18sp"
79
+        android:textStyle="bold"
80
+        app:layout_constraintBottom_toTopOf="@+id/surveyButton"
81
+        app:layout_constraintEnd_toEndOf="parent"
82
+        app:layout_constraintHorizontal_bias="0.5"
83
+        app:layout_constraintStart_toStartOf="parent"
84
+        tools:text="Next survey in 2 days, 6 hours." />
85
+
86
+    <TextView
87
+        android:id="@+id/assessmentTitle"
88
+        android:layout_width="0dp"
89
+        android:layout_height="wrap_content"
90
+        android:layout_marginStart="@dimen/horizontalMargin"
91
+        android:layout_marginTop="@dimen/verticalMargin"
92
+        android:layout_marginEnd="@dimen/horizontalMargin"
93
+        android:fontFamily="@font/cabin"
94
+        android:text="@string/mainGreetingText"
95
+        android:textColor="@color/textColor2"
96
+        android:textSize="@dimen/sectionTitleTextSize"
97
+        android:textStyle="bold"
98
+        app:layout_constraintEnd_toEndOf="parent"
99
+        app:layout_constraintStart_toStartOf="parent"
100
+        app:layout_constraintTop_toTopOf="parent" />
101
+
102
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 21
- 0
app/src/main/res/layout/fragment_consent.xml View File

@@ -0,0 +1,21 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent"
7
+    android:background="#FFFFFF">
8
+
9
+
10
+    <com.github.barteksc.pdfviewer.PDFView
11
+        android:id="@+id/pdf_viewer"
12
+        android:layout_width="match_parent"
13
+        android:layout_height="match_parent"
14
+        android:visibility="visible"
15
+        app:layout_constraintEnd_toEndOf="parent"
16
+        app:layout_constraintStart_toStartOf="parent"
17
+        app:layout_constraintTop_toTopOf="parent" />
18
+
19
+</androidx.constraintlayout.widget.ConstraintLayout>
20
+
21
+

+ 63
- 0
app/src/main/res/layout/fragment_withdrawal.xml View File

@@ -0,0 +1,63 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:layout_width="match_parent"
6
+    android:layout_height="match_parent"
7
+    android:background="@android:color/white">
8
+
9
+    <TextView
10
+        android:id="@+id/withdrawTitle"
11
+        android:layout_width="0dp"
12
+        android:layout_height="wrap_content"
13
+        android:layout_marginStart="@dimen/horizontalMargin"
14
+        android:layout_marginTop="@dimen/verticalMargin"
15
+        android:layout_marginEnd="@dimen/horizontalMargin"
16
+        android:fontFamily="@font/cabin"
17
+        android:text="@string/withdrawTitleText"
18
+        android:textAlignment="textStart"
19
+        android:textColor="@color/textColor2"
20
+        android:textSize="@dimen/sectionTitleTextSize"
21
+        android:textStyle="bold"
22
+        app:layout_constraintEnd_toEndOf="parent"
23
+        app:layout_constraintStart_toStartOf="parent"
24
+        app:layout_constraintTop_toTopOf="parent" />
25
+
26
+    <TextView
27
+        android:id="@+id/withdrawText"
28
+        android:layout_width="0dp"
29
+        android:layout_height="wrap_content"
30
+        android:layout_marginStart="@dimen/horizontalMargin"
31
+        android:layout_marginTop="16dp"
32
+        android:layout_marginEnd="@dimen/horizontalMargin"
33
+        android:text="@string/loadingText"
34
+        android:textAlignment="textStart"
35
+        android:textSize="@dimen/sectionDescriptionTextSize"
36
+        app:layout_constraintEnd_toEndOf="parent"
37
+        app:layout_constraintStart_toStartOf="parent"
38
+        app:layout_constraintTop_toBottomOf="@+id/withdrawTitle"
39
+        tools:text="@string/withdrawDescriptionText" />
40
+
41
+    <Button
42
+        android:id="@+id/withdrawButton"
43
+        android:layout_width="0dp"
44
+        android:layout_height="wrap_content"
45
+        android:layout_marginStart="@dimen/horizontalMargin"
46
+        android:layout_marginEnd="@dimen/horizontalMargin"
47
+        android:layout_marginBottom="@dimen/verticalMargin"
48
+        android:background="@drawable/button_background"
49
+        android:fontFamily="sans-serif-black"
50
+        android:paddingHorizontal="@dimen/buttonHorizontalPadding"
51
+        android:paddingVertical="@dimen/buttonVerticalPadding"
52
+        android:text="@string/withdrawButtonText"
53
+        android:textColor="@android:color/white"
54
+        android:textSize="@dimen/buttonTextSize"
55
+        android:textStyle="bold"
56
+        app:layout_constraintBottom_toBottomOf="parent"
57
+        app:layout_constraintEnd_toEndOf="parent"
58
+        app:layout_constraintHeight_max="@dimen/buttonMaxHeight"
59
+        app:layout_constraintStart_toStartOf="parent"
60
+        app:layout_constraintTop_toBottomOf="@+id/withdrawText" />
61
+
62
+
63
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6
- 0
app/src/main/res/menu/activity_main.xml View File

@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
3
+      xmlns:app="http://schemas.android.com/apk/res-auto">
4
+
5
+
6
+</menu>

+ 19
- 0
app/src/main/res/menu/bottom_navigation.xml View File

@@ -0,0 +1,19 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+    <item
5
+        android:id="@+id/nav_assessments"
6
+        android:icon="@drawable/baseline_assessment_white_24dp"
7
+        android:title="Assessments"/>
8
+
9
+    <item
10
+        android:id="@+id/nav_consent"
11
+        android:icon="@drawable/baseline_file_copy_white_24dp"
12
+        android:title="Consent"/>
13
+
14
+    <item
15
+        android:id="@+id/nav_settings"
16
+        android:icon="@drawable/baseline_clear_white_24dp"
17
+        android:title="Withdraw"/>
18
+
19
+</menu>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png View File


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png View File


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png View File


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png View File


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png View File


+ 25
- 0
app/src/main/res/navigation/mobile_navigation.xml View File

@@ -0,0 +1,25 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    xmlns:tools="http://schemas.android.com/tools"
5
+    android:id="@+id/mobile_navigation"
6
+    app:startDestination="@+id/navigation_home">
7
+
8
+    <fragment
9
+        android:id="@+id/navigation_home"
10
+        android:name="uprrp.tania.fragments.AssessmentsFragment"
11
+        android:label="@string/title_home"
12
+        tools:layout="@layout/fragment_assessments"/>
13
+
14
+    <fragment
15
+        android:id="@+id/navigation_dashboard"
16
+        android:name="uprrp.tania.fragments.ConsentFragment"
17
+        android:label="@string/title_dashboard"
18
+        tools:layout="@layout/fragment_consent" />
19
+
20
+    <fragment
21
+        android:id="@+id/navigation_notifications"
22
+        android:name="uprrp.tania.fragments.AssessmentsFragment"
23
+        android:label="@string/title_notifications"
24
+        tools:layout="@layout/fragment_assessments" />
25
+</navigation>

+ 14
- 0
app/src/main/res/values/colors.xml View File

@@ -0,0 +1,14 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+    <color name="colorPrimary">#397af3</color>
4
+    <color name="colorPrimaryDark">#174396</color>
5
+    <color name="colorPrimaryDisabled">#7ea5ed</color>
6
+    <color name="colorAccent">#174396</color>
7
+    <color name="dark_gray">#cccccc</color>
8
+    <color name="textColor1">#FFFCEFD3</color>
9
+    <color name="textColor2">#423E3E</color>
10
+    <color name="white_60">#99FFFFFF</color>
11
+    <color name="link_color">#0000EE</color>
12
+</resources>
13
+
14
+

+ 0
- 0
app/src/main/res/values/dimens.xml View File


Some files were not shown because too many files changed in this diff