diff --git a/app/build.gradle b/app/build.gradle index 2854a2e..ec3756f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,39 +40,52 @@ android { } } + dependencies { - implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'com.google.firebase:firebase-database-ktx:20.1.0' - implementation 'com.google.firebase:firebase-database:20.1.0' + implementation 'com.google.firebase:firebase-database-ktx:20.2.0' + implementation 'com.google.firebase:firebase-database:20.2.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0" + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' // Testing dependencies + implementation 'androidx.arch.core:core-testing:2.2.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.junit.jupiter:junit-jupiter' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation('androidx.test.espresso:espresso-contrib:3.5.1') { + exclude group: 'com.android.support', module: 'appcompat' + exclude group: 'com.android.support', module: 'support-v4' + exclude module: 'recyclerview-v7' + exclude module: "protobuf-lite" + } + androidTestImplementation "org.hamcrest:hamcrest:2.2" androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:core:1.5.0' androidTestImplementation 'androidx.test:core-ktx:1.5.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' + // https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy + implementation 'net.bytebuddy:byte-buddy-android:1.14.4' + // Optional -- Mockito framework - testImplementation 'org.mockito:mockito-core:5.0.0' + testImplementation 'org.mockito:mockito-core:5.3.1' + testImplementation 'org.mockito:mockito-android:5.3.1' testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0' - testImplementation 'io.mockk:mockk:1.13.4' + testImplementation 'io.mockk:mockk:1.13.5' // Import the BoM for the Firebase platform - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.5.0') // Add the dependency for the Firebase library // When using the BoM, you don't specify versions in Firebase library dependencies @@ -95,11 +108,11 @@ dependencies { implementation 'com.firebaseui:firebase-ui-storage:8.0.2' // Also add the dependency for the Google Play services library and specify its version - implementation 'com.google.android.gms:play-services-auth:20.4.1' + implementation 'com.google.android.gms:play-services-auth:20.5.0' // Add the dependency for Glide - implementation 'com.github.bumptech.glide:glide:4.15.0' - kapt 'com.github.bumptech.glide:compiler:4.15.0' + implementation 'com.github.bumptech.glide:glide:4.15.1' + kapt 'com.github.bumptech.glide:compiler:4.15.1' // Konfetti by https://github.com/DanielMartinus/Konfetti implementation 'nl.dionsegijn:konfetti-xml:2.0.2' diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/CalendarActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/CalendarActivityTest.kt new file mode 100644 index 0000000..5b87b9d --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/CalendarActivityTest.kt @@ -0,0 +1,18 @@ +package com.symphony.mrfit.ui + +import org.junit.Assert.* + +import org.junit.Test + +class CalendarActivityTest { + //behavioral + @Test + fun onCreate() { + } + @Test + fun onStart() { + } + @Test + fun onSupportNavigateUp() { + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/CoreFunctionalityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/CoreFunctionalityTest.kt new file mode 100644 index 0000000..6aa60e6 --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/CoreFunctionalityTest.kt @@ -0,0 +1,150 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class CoreFunctionalityTest { + + @get:Rule + var activityScenarioRule = activityScenarioRule() + + /** + * Test a run through of the app's core functionality. It should: + * Log in with the credentials 'demo@email.com' and 'pass123' + * Start a new workout + * Make a new workout called "Automated Testing" + * Add 3 exercises to it, whichever 3 are at the top of the list + * Start the workout, wait 5 seconds, then finish the workout + * Return to the home screen after + * Navigate to and delete this new workout + * Navigate to the user's profile and log out + */ + @Test + fun coreFunctionality() { + // Wait to automatically move past welcome screen + Thread.sleep(100) + Espresso.onView(withId(R.id.layout_loginActivity)) + .check(matches(isDisplayed())) + + // Attempt to log in + Espresso.onView(withId(R.id.loginEmail)) + .perform(typeText(TEST_EMAIL), closeSoftKeyboard()) + Thread.sleep(500) + Espresso.onView(withId(R.id.loginPassword)) + .perform(typeText(TEST_PASS), closeSoftKeyboard()) + Thread.sleep(500) + Espresso.onView(withId(R.id.loginButton)) + .perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.layout_homeActivity)) + .check(matches(isDisplayed())) + + // Navigate to the Template selection screen + Espresso.onView(withId(R.id.workoutButton)) + .perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.layout_selectionActivity)) + .check(matches(isDisplayed())) + + // Make a new workout and name it + Espresso.onView(withId(R.id.newWorkoutButton)) + .perform(click()) + Thread.sleep(500) + Espresso.onView(withId(R.id.routineNameEditText)) + .perform(clearText()) + .perform(typeText(TEST_TEMPLATE), closeSoftKeyboard()) + + // Add 3 exercises + // 1st Exercise + Thread.sleep(1000) + Espresso.onView(withId(R.id.newExerciseButton)) + .perform(click()) + Thread.sleep(500) + Espresso.onView(withId(R.id.exerciseListView)) + .perform(actionOnItemAtPosition(0, click())) + Thread.sleep(500) + Espresso.onView(withId(R.id.saveTemplateButton)) + .perform(click()) + Thread.sleep(1000) + // 2nd Exercise + Espresso.onView(withId(R.id.newExerciseButton)) + .perform(click()) + Thread.sleep(500) + Espresso.onView(withId(R.id.exerciseListView)) + .perform(actionOnItemAtPosition(1, click())) + Thread.sleep(500) + Espresso.onView(withId(R.id.saveTemplateButton)) + .perform(click()) + Thread.sleep(1000) + // 3rd Exercise + Espresso.onView(withId(R.id.newExerciseButton)) + .perform(click()) + Thread.sleep(500) + Espresso.onView(withId(R.id.exerciseListView)) + .perform(actionOnItemAtPosition(2, click())) + Thread.sleep(500) + Espresso.onView(withId(R.id.saveTemplateButton)) + .perform(click()) + Thread.sleep(1000) + + // Start the workout and finish it after 10 seconds + Espresso.onView(withId(R.id.startWorkoutButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_currentActivity)) + .check(matches(isDisplayed())) + Thread.sleep(10000) + Espresso.onView(withId(R.id.finishWorkoutButton)) + .perform(click()) + Espresso.onView(withText("Good job!")).check(matches(isDisplayed())) + Espresso.onView(withText("Return Home")).perform(click()) + + // Navigate back and delete workout + Thread.sleep(1000) + Espresso.onView(withId(R.id.workoutButton)) + .perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.layout_selectionActivity)) + .check(matches(isDisplayed())) + Espresso.onView(withText(TEST_TEMPLATE)) + .perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.deleteWorkoutButton)) + .perform(click()) + Espresso.pressBack() + Thread.sleep(1000) + + // Navigate to user profile and log out + Espresso.onView(withId(R.id.userLayout)) + .perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.layout_profileActivity)) + .check(matches(isDisplayed())) + Espresso.onView(withId(R.id.logoutButton)) + .perform(click()) + } + + companion object { + const val TEST_EMAIL = "demo@email.com" + const val TEST_PASS = "pass123" + const val TEST_TEMPLATE = "Automated Testing" + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/CurrentWorkoutActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/CurrentWorkoutActivityTest.kt new file mode 100644 index 0000000..3eff07c --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/CurrentWorkoutActivityTest.kt @@ -0,0 +1,56 @@ +package com.symphony.mrfit.ui +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.ActivityTestRule +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.Assert.assertEquals + + + + +@RunWith(AndroidJUnit4::class) +class CurrentWorkoutActivityTest { + //behavioral + @Test + fun onCreate() { + + } + //behavioral + @Test + fun onStart() { + + } + @Test + fun gotoHome() { + // Start the activity + val context = ApplicationProvider.getApplicationContext() + val intent = Intent(context, HomeActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + val expectedFlags = (Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_ACTIVITY_NEW_TASK) + + assertEquals(expectedFlags, intent.flags) + assertEquals(context.packageName, intent.component?.packageName) + assertEquals(HomeActivity::class.java.name, intent.component?.className) + } + //behavioral + @Test + fun onSupportNavigateUp() { + + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/CustomExercisesActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/CustomExercisesActivityTest.kt new file mode 100644 index 0000000..b4086ae --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/CustomExercisesActivityTest.kt @@ -0,0 +1,27 @@ +package com.symphony.mrfit.ui + +import androidx.test.core.app.ApplicationProvider +import com.symphony.mrfit.data.exercise.ExerciseViewModel +import com.symphony.mrfit.data.model.Exercise +import org.junit.Assert.* +import org.junit.Before + +import org.junit.Test + +class CustomExercisesActivityTest { + //behavioral + //TODO + @Test + fun onCreate() { + } + @Test + fun onStart() { + } + @Test + fun onSupportNavigateUp() { + } + @Test + fun deleteExercise(){ + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/ExerciseTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/ExerciseTest.kt new file mode 100644 index 0000000..bb258ad --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/ExerciseTest.kt @@ -0,0 +1,70 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.hamcrest.CoreMatchers.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class ExerciseTest { + + @Before + fun setUp() { + } + + @get:Rule + var activityScenarioRule = activityScenarioRule() + + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_exerciseSelectionActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ + @Test + fun checkViewVisibility() { + Thread.sleep(500) + Espresso.onView(withId(R.id.exerciseScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.exerciseListView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.exerciseSearchTextView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.exerciseSearchEditText)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.exerciseSearchButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.button2)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.loadingSpinner)) + .check(matches(not(isDisplayed()))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/GoalsActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/GoalsActivityTest.kt new file mode 100644 index 0000000..d422f68 --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/GoalsActivityTest.kt @@ -0,0 +1,22 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import androidx.test.ext.junit.rules.activityScenarioRule +import org.junit.Rule +import org.junit.Test + +class GoalsActivityTest { + @get:Rule + var activityScenarioRule = activityScenarioRule() + + //TODO + @Test + fun checkActivityVisibility() { + } +} + diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/HelperTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/HelperTest.kt new file mode 100644 index 0000000..7dc1366 --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/HelperTest.kt @@ -0,0 +1,29 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import android.app.Activity +import androidx.test.espresso.matcher.ViewMatchers.assertThat +import org.hamcrest.CoreMatchers.`is` +import org.junit.Test +import java.util.* + + +class HelperTest{ + private lateinit var activity: Activity + + //test functionality of calendar method + @Test + fun toCalendarconvertsDatetoCalendar() { + val date = Date() + val cal = Helper.toCalendar(date) + assertThat(cal.get(Calendar.YEAR), `is`(Calendar.getInstance().get(Calendar.YEAR))) + assertThat(cal.get(Calendar.MONTH), `is`(Calendar.getInstance().get(Calendar.MONTH))) + assertThat(cal.get(Calendar.DAY_OF_MONTH), `is`(Calendar.getInstance().get(Calendar.DAY_OF_MONTH)) + ) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/HomeActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/HomeActivityTest.kt new file mode 100644 index 0000000..118c83a --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/HomeActivityTest.kt @@ -0,0 +1,84 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.hamcrest.core.IsNot.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class HomeActivityTest { + + @Before + fun setUp() { + } + + @get:Rule + var activityScenarioRule = activityScenarioRule() + + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_homeActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ + @Test + fun checkViewVisibility() { + Thread.sleep(1000) + Espresso.onView(withId(R.id.homeScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.userLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.homeProfilePicture)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.homeWelcomeTextView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.homeNameTextView)) + .check(matches(isDisplayed())) + Espresso.onView(withId(R.id.homeScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.settingsCog)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.scheduleButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.pastWorkout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.workoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.historyList)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.loadingSpinner)) + .check(matches(not(isDisplayed()))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/LoginActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/LoginActivityTest.kt index 41094ea..eb6f532 100644 --- a/app/src/androidTest/java/com/symphony/mrfit/ui/LoginActivityTest.kt +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/LoginActivityTest.kt @@ -1,14 +1,13 @@ /* - * Created by Team Symphony on 2/24/23, 11:21 PM + * Created by Team Symphony on 4/22/23, 6:21 AM * Copyright (c) 2023 . All rights reserved. - * Last modified 2/24/23, 11:20 PM + * Last modified 4/22/23, 6:05 AM */ package com.symphony.mrfit.ui -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions.* import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId @@ -33,7 +32,8 @@ class LoginActivityTest { */ @Test fun checkActivityVisibility() { - onView(withId(R.id.layout_loginActivity)).check(matches(isDisplayed())) + Espresso.onView(withId(R.id.layout_loginActivity)) + .check(matches(isDisplayed())) } /** @@ -41,19 +41,19 @@ class LoginActivityTest { */ @Test fun checkViewVisibility() { - onView(withId(R.id.loginEmail)) + Espresso.onView(withId(R.id.loginEmail)) .check(matches(isDisplayed())) - onView(withId(R.id.loginPassword)) + Espresso.onView(withId(R.id.loginPassword)) .check(matches(isDisplayed())) - onView(withId(R.id.loginButton)) + Espresso.onView(withId(R.id.loginButton)) .check(matches(isDisplayed())) - onView(withId(R.id.googleButton)) + Espresso.onView(withId(R.id.googleButton)) .check(matches(isDisplayed())) - onView(withId(R.id.toRegisterTextView)) + Espresso.onView(withId(R.id.toRegisterTextView)) .check(matches(isDisplayed())) } @@ -62,37 +62,36 @@ class LoginActivityTest { */ @Test fun checkFailedLogin() { - onView(withId(R.id.loginEmail)) - .perform(ViewActions.typeText(TEST_EMAIL), ViewActions.closeSoftKeyboard()) - onView(withId(R.id.loginPassword)) - .perform(ViewActions.typeText(TEST_PASS), ViewActions.closeSoftKeyboard()) - onView(withId(R.id.loginButton)) + Espresso.onView(withId(R.id.loginEmail)) + .perform(typeText(TEST_EMAIL), closeSoftKeyboard()) + Espresso.onView(withId(R.id.loginPassword)) + .perform(typeText(TEST_PASS), closeSoftKeyboard()) + Espresso.onView(withId(R.id.loginButton)) .perform(click()) - onView(withId(R.id.layout_loginActivity)).check(matches(isDisplayed())) + Espresso.onView(withId(R.id.layout_loginActivity)) + .check(matches(isDisplayed())) } /** * Test if login works */ - /* @Test fun checkValidLogin() { - onView(withId(R.id.loginEmail)) - .perform(ViewActions.typeText(REAL_EMAIL), ViewActions.closeSoftKeyboard()) - onView(withId(R.id.loginPassword)) - .perform(ViewActions.typeText(REAL_PASS), ViewActions.closeSoftKeyboard()) - onView(withId(R.id.loginButton)) + Espresso.onView(withId(R.id.loginEmail)) + .perform(typeText(REAL_EMAIL), closeSoftKeyboard()) + Espresso.onView(withId(R.id.loginPassword)) + .perform(typeText(REAL_PASS), closeSoftKeyboard()) + Espresso.onView(withId(R.id.loginButton)) .perform(click()) - onView(withId(R.id.layout_loginActivity)).check(matches(isDisplayed())) + Espresso.onView(withId(R.id.layout_loginActivity)) + .check(matches(isDisplayed())) } - */ - companion object { - const val REAL_EMAIL = "test@symphony.com" - const val REAL_PASS = "abcd1234" + const val REAL_EMAIL = "demo@symphony.com" + const val REAL_PASS = "pass123" const val TEST_EMAIL = "fake_email@symphony.com" const val TEST_PASS = "1234abcd" } diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/TemplateSelectionActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/TemplateSelectionActivityTest.kt new file mode 100644 index 0000000..76aee11 --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/TemplateSelectionActivityTest.kt @@ -0,0 +1,61 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.hamcrest.CoreMatchers.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class TemplateSelectionActivityTest { + + @Before + fun setUp() { + } + + @get:Rule + var activityScenarioRule = activityScenarioRule() + + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_selectionActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ + @Test + fun checkViewVisibility() { + Thread.sleep(500) + Espresso.onView(withId(R.id.selectionScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.routineListView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.newWorkoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.loadingSpinner)) + .check(matches(not(isDisplayed()))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/UserProfileActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/UserProfileActivityTest.kt index e15a655..ed0b9c1 100644 --- a/app/src/androidTest/java/com/symphony/mrfit/ui/UserProfileActivityTest.kt +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/UserProfileActivityTest.kt @@ -1,38 +1,164 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + package com.symphony.mrfit.ui -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import com.symphony.mrfit.R -import org.junit.Assert.* +import org.hamcrest.CoreMatchers.not +import org.junit.Before import org.junit.Rule - import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest class UserProfileActivityTest { + @Before + fun setUp() { + } + @get:Rule var activityScenarioRule = activityScenarioRule() - /*tests if you can logout from user profile*/ + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_profileActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ @Test fun checkViewVisibility() { - onView(withId(R.id.profileScreenView)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Thread.sleep(500) + Espresso.onView(withId(R.id.profileScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.profilePicture)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.profileNameTextView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.editProfileButton)) + .check(matches(not(isDisplayed()))) + + Espresso.onView(withId(R.id.heightLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.weightLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.ageLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.stuffLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.goalsButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.notificationsButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.historyButton)) + .check(matches(isDisplayed())) - onView(withId(R.id.profileNameTextView)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Espresso.onView(withId(R.id.customExercisesButton)) + .check(matches(isDisplayed())) - onView(withId(R.id.weightLayout)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Espresso.onView(withId(R.id.settingLayout)) + .check(matches(isDisplayed())) - onView(withId(R.id.ageLayout)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Espresso.onView(withId(R.id.notificationToggle)) + .check(matches(not(isDisplayed()))) - onView(withId(R.id.heightLayout)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Espresso.onView(withId(R.id.logoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.deleteButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.loadingSpinner)) + .check(matches(not(isDisplayed()))) + } + + /** + * Test if the Notifications button navigates properly + */ + @Test + fun notificationsButtonTest() { + Espresso.onView(withId(R.id.notificationsButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_notificationLogActivity)) + .check(matches(isDisplayed())) + Espresso.pressBack() + Thread.sleep(500) + } + + /** + * Test if the Goals button navigates properly + */ + @Test + fun goalsButtonTest() { + Espresso.onView(withId(R.id.goalsButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_goalsActivity)) + .check(matches(isDisplayed())) + Espresso.pressBack() + Thread.sleep(500) + } + + /** + * Test if the History button navigates properly + */ + @Test + fun historyButtonTest() { + Espresso.onView(withId(R.id.historyButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_workoutHistoryActivity)) + .check(matches(isDisplayed())) + Espresso.pressBack() + Thread.sleep(500) + } + + /** + * Test if the Exercises button navigates properly + */ + @Test + fun exerciseButtonTest() { + Espresso.onView(withId(R.id.customExercisesButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_customExerciseActivity)) + .check(matches(isDisplayed())) + Espresso.pressBack() + Thread.sleep(500) + } + + /** + * Test if the Logout button navigates properly + */ + @Test + fun logoutButtonTest() { + Espresso.onView(withId(R.id.logoutButton)) + .perform(click()) + Espresso.onView(withId(R.id.layout_loginActivity)) + .check(matches(isDisplayed())) } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutRoutineActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutRoutineActivityTest.kt new file mode 100644 index 0000000..3057e4e --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutRoutineActivityTest.kt @@ -0,0 +1,93 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import android.content.Intent +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.hamcrest.CoreMatchers.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class WorkoutRoutineActivityTest { + + @Before + fun setUp() { + } + + @get:Rule + var activityScenarioRule = activityScenarioRule( + intent = Intent( + ApplicationProvider.getApplicationContext(), + WorkoutRoutineActivity::class.java + ) + .putExtra(WorkoutTemplateActivity.EXTRA_IDENTITY, "test") + ) + + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_workoutBuilderActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ + @Test + fun checkViewVisibility() { + Thread.sleep(1000) + Espresso.onView(withId(R.id.routineScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.headerLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.routineNameEditText)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.workoutPlaylistEditText)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.openPlaylistButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.workoutListView)) + .check(matches(not(isDisplayed()))) + + Espresso.onView(withId(R.id.routineScreenButtons)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.startWorkoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.newExerciseButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.saveWorkoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.deleteWorkoutButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.loadingSpinner)) + .check(matches(not(isDisplayed()))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutTemplateActivityTest.kt b/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutTemplateActivityTest.kt new file mode 100644 index 0000000..ac1eb55 --- /dev/null +++ b/app/src/androidTest/java/com/symphony/mrfit/ui/WorkoutTemplateActivityTest.kt @@ -0,0 +1,86 @@ +/* + * Created by Team Symphony on 4/22/23, 6:21 AM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/22/23, 6:05 AM + */ + +package com.symphony.mrfit.ui + +import android.content.Intent +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.symphony.mrfit.R +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@LargeTest +class WorkoutTemplateActivityTest { + + @Before + fun setUp() { + } + + @get:Rule + var activityScenarioRule = activityScenarioRule( + intent = Intent( + ApplicationProvider.getApplicationContext(), + WorkoutTemplateActivity::class.java + ) + .putExtra(WorkoutRoutineActivity.EXTRA_ROUTINE, "test") + .putExtra(WorkoutTemplateActivity.EXTRA_IDENTITY, "test") + .putExtra(WorkoutTemplateActivity.EXTRA_EXERCISE, "test") + .putExtra(WorkoutTemplateActivity.EXTRA_LIST, ArrayList()) + ) + + /** + * Test if the activity is displayed and visible to user + */ + @Test + fun checkActivityVisibility() { + Espresso.onView(withId(R.id.layout_exerciseBuilderActivity)) + .check(matches(isDisplayed())) + } + + /** + * Test if all the appropriate components are visible + */ + @Test + fun checkViewVisibility() { + Thread.sleep(500) + Espresso.onView(withId(R.id.templateScreenView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.exerciseCardView)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.editDuration)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.editDistance)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.repsAndSetsLayout)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.editReps)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.editSets)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.deleteTemplateButton)) + .check(matches(isDisplayed())) + + Espresso.onView(withId(R.id.saveTemplateButton)) + .check(matches(isDisplayed())) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e6be84..1bf2511 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + + + + if (result.resultCode == Activity.RESULT_OK) { + // User picked a time, sanity check it is before the current end time + val newStart = Calendar.getInstance() + newStart.timeInMillis = result.data!!.getLongExtra(EXTRA_TIME, 0) + val t = Calendar.getInstance() + t.timeInMillis = endTime + if (newStart.after(t)) { + // Times are off, set the end time to at least 1 minute after the new start time + val diff = intent.getLongExtra(TIME_DIFF, ONE_MINUTE.toLong()) + t.timeInMillis = newStart.timeInMillis + diff + } + startTime = newStart.timeInMillis + endTime = t.timeInMillis + + // Set the text views + setText() + } else if (result.resultCode == Activity.RESULT_CANCELED) { + /* Intentionally left blank */ + } + } + + private val launchEndTimePicker = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + + if (result.resultCode == Activity.RESULT_OK) { + // User picked a time, sanity check it is before the current end time + val newEnd = Calendar.getInstance() + newEnd.timeInMillis = result.data!!.getLongExtra(EXTRA_TIME, 0) + val t = Calendar.getInstance() + t.timeInMillis = startTime + if (newEnd.before(t)) { + // Times are off, set the start time to at least 1 minute before the new end time + val diff = intent.getLongExtra(TIME_DIFF, ONE_MINUTE.toLong()) + t.timeInMillis = newEnd.timeInMillis - diff + } + endTime = newEnd.timeInMillis + startTime = t.timeInMillis + + // Set the text views + setText() + } else if (result.resultCode == Activity.RESULT_CANCELED) { + /* Intentionally left blank */ + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityManualWorkoutBinding.inflate(layoutInflater) + setContentView(binding.root) + exerciseViewModel = ViewModelProvider( + this, ExerciseViewModelFactory() + )[ExerciseViewModel::class.java] + profileViewModel = ViewModelProvider( + this, ProfileViewModelFactory() + )[ProfileViewModel::class.java] + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onStart() { + super.onStart() + + val routineList = binding.templateRecyclerList + val startTimePicker = binding.startTimeRow + val endTimePicker = binding.endTimeRow + startTimeSelection = binding.startTimeSelection + endTimeSelection = binding.endTimeSelection + val save = binding.saveManualWorkoutButton + + var selectedThing = intent.getIntExtra(SAVED_SELECTION, -999) + var routineName = "" + var routineID = "" + var selection = false + + fun update(name: String, id: String, num: Int) { + routineName = name + routineID = id + selection = true + selectedThing = num + } + + // Set the layout of the grid of routines presented to the user + layoutManager = GridLayoutManager(this, 2) + routineList.layoutManager = layoutManager + + // Initialize the current date time, will be overwritten later if needed + startTime = intent.getLongExtra(SAVED_START, Date().time) + endTime = intent.getLongExtra(SAVED_END, Date().time + ONE_MINUTE) + setText() + + // Initialize the routine list, then listen to it to update the UI + exerciseViewModel.getUserRoutines() + exerciseViewModel.workoutRoutineList.observe(this, Observer { + Log.d(ContentValues.TAG, "Updating routine list") + val workoutRoutineList = it ?: return@Observer + + routineList.adapter = RoutineAdapter2(this, workoutRoutineList, ::update, selectedThing) + + }) + + /** + * Navigate to a screen for the user to select a start time, await its result + * If selected time is after end time, adjust end time accordingly + */ + startTimePicker.setOnClickListener { + intent.putExtra(SAVED_START, startTime) + intent.putExtra(SAVED_END, endTime) + intent.putExtra(SAVED_SELECTION, selectedThing) + intent.putExtra(TIME_DIFF, endTime - startTime) + val picker = Intent(this, DateTimeActivity::class.java) + picker.putExtra(EXTRA_TIME, startTime) + launchStartTimePicker.launch(picker) + } + + /** + * Navigate to a screen for the user to select an end time, await its result + * If selected time is before start time, adjust start time accordingly + */ + endTimePicker.setOnClickListener { + intent.putExtra(SAVED_START, startTime) + intent.putExtra(SAVED_END, endTime) + intent.putExtra(SAVED_SELECTION, selectedThing) + intent.putExtra(TIME_DIFF, endTime - startTime) + val picker = Intent(this, DateTimeActivity::class.java) + picker.putExtra(EXTRA_TIME, endTime) + launchEndTimePicker.launch(picker) + } + + /** + * Create a history from the given information and add it to the database + * A template must be selected + * The end time must not be before the start time + */ + save.setOnClickListener { + if (selection) { + if (startTime < endTime) { + profileViewModel.addWorkoutToHistory( + History( + routineName, + Timestamp(Date(startTime)), + endTime - startTime, + routineID + ) + ) + finish() + } else { + Toast.makeText( + this, + "Times are out of order", + Toast.LENGTH_SHORT + ).show() + } + } else { + Toast.makeText( + this, + "Must select a template", + Toast.LENGTH_SHORT + ).show() + } + } + } + + private fun setText() { + startTimeSelection.text = dateFormat.format(startTime) + endTimeSelection.text = dateFormat.format(endTime) + } + + override fun onSupportNavigateUp(): Boolean { + onBackPressedDispatcher.onBackPressed() + return true + } + + companion object { + const val EXTRA_TIME = "passed time" + const val SAVED_START = "saved start time" + const val SAVED_END = "saved end time" + const val SAVED_SELECTION = "saved selection" + const val TIME_DIFF = "difference between times" + const val ONE_MINUTE = 60000 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/symphony/mrfit/data/exercise/RoutineAdapter2.kt b/app/src/main/java/com/symphony/mrfit/data/exercise/RoutineAdapter2.kt new file mode 100644 index 0000000..42af742 --- /dev/null +++ b/app/src/main/java/com/symphony/mrfit/data/exercise/RoutineAdapter2.kt @@ -0,0 +1,79 @@ +/* + * Created by Team Symphony on 4/21/23, 3:22 PM + * Copyright (c) 2023 . All rights reserved. + * Last modified 4/21/23, 3:22 PM + */ + +package com.symphony.mrfit.data.exercise + +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.recyclerview.widget.RecyclerView +import com.symphony.mrfit.R +import com.symphony.mrfit.data.model.WorkoutRoutine + +/** + * Adapter for dynamically populating card_routine with information read from a list of Routines + */ + +class RoutineAdapter2( + val context: Context, + val data: ArrayList, + val update: (String, String, Int) -> Unit, + private var selectedPos: Int +) : RecyclerView.Adapter() { + + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder { + val v = LayoutInflater.from(viewGroup.context) + .inflate(R.layout.card_routine, viewGroup, false) + if (selectedPos == -999) + selectedPos = RecyclerView.NO_POSITION + return ViewHolder(v) + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onBindViewHolder(holder: ViewHolder, i: Int) { + val colour: Int = if (selectedPos == i) { + R.color.off_garnet + } else { + R.color.garnet + } + holder.cardBackground.background = ColorDrawable(context.getColor(colour)) + holder.routineTitle.text = data[i].name + + /** + * Change the selected item + */ + holder.itemView.setOnClickListener { + notifyItemChanged(selectedPos) + selectedPos = holder.absoluteAdapterPosition + notifyItemChanged(selectedPos) + update(data[selectedPos].name, data[selectedPos].routineID, selectedPos) + } + } + + override fun getItemCount(): Int { + return data.size + } + + inner class ViewHolder(routineView: View) : RecyclerView.ViewHolder(routineView) { + + var routineTitle: TextView + var routineDetail: TextView + var cardBackground: RelativeLayout + + init { + routineTitle = routineView.findViewById(R.id.workoutNameTextView) + routineDetail = routineView.findViewById(R.id.workoutDescriptionTextView) + cardBackground = routineView.findViewById(R.id.cardBackground) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/symphony/mrfit/data/exercise/WorkoutAdapter.kt b/app/src/main/java/com/symphony/mrfit/data/exercise/WorkoutAdapter.kt index e485500..c1d6d02 100644 --- a/app/src/main/java/com/symphony/mrfit/data/exercise/WorkoutAdapter.kt +++ b/app/src/main/java/com/symphony/mrfit/data/exercise/WorkoutAdapter.kt @@ -1,7 +1,7 @@ /* - * Created by Team Symphony on 4/2/23, 10:27 PM + * Created by Team Symphony on 4/22/23, 6:21 AM * Copyright (c) 2023 . All rights reserved. - * Last modified 4/2/23, 10:22 PM + * Last modified 4/22/23, 6:05 AM */ package com.symphony.mrfit.data.exercise @@ -31,7 +31,12 @@ import com.symphony.mrfit.ui.WorkoutTemplateActivity.Companion.EXTRA_LIST * a passed list of Workouts, as well as the parent Routine's ID and workoutList */ -class WorkoutAdapter (val context: Context, val data: ArrayList, private val rID: String, private val rList: ArrayList): RecyclerView.Adapter() { +class WorkoutAdapter( + val context: Context, + val data: ArrayList, + private val rID: String, + private val rList: ArrayList +) : RecyclerView.Adapter() { private var storage: FirebaseStorage = Firebase.storage diff --git a/app/src/main/java/com/symphony/mrfit/data/profile/UserRepository.kt b/app/src/main/java/com/symphony/mrfit/data/profile/UserRepository.kt index 5bdac84..4ab6e3c 100644 --- a/app/src/main/java/com/symphony/mrfit/data/profile/UserRepository.kt +++ b/app/src/main/java/com/symphony/mrfit/data/profile/UserRepository.kt @@ -1,7 +1,7 @@ /* - * Created by Team Symphony on 4/19/23, 7:07 PM + * Created by Team Symphony on 4/22/23, 6:21 AM * Copyright (c) 2023 . All rights reserved. - * Last modified 4/19/23, 7:07 PM + * Last modified 4/22/23, 6:05 AM */ package com.symphony.mrfit.data.profile @@ -50,21 +50,26 @@ class UserRepository { * Pull the currently logged in user's profile from Firestore */ suspend fun getCurrentUser() : User? { - Log.d(TAG, "Retrieving User ${auth.currentUser!!.uid} from Firestore") - val doc = auth.currentUser!!.uid - val docRef = database.collection(USER_COLLECTION).document(doc) - return try { - val snapshot = docRef.get().await() - // Something wrong happened when deleting this user, fix it - if (snapshot.get("userID") == null) { - LoginRepository().delete() - LoginRepository().logout() - User("delete") + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Retrieving User ${user.uid} from Firestore") + val doc = user.uid + val docRef = database.collection(USER_COLLECTION).document(doc) + return try { + val snapshot = docRef.get().await() + // Something wrong happened when deleting this user, fix it + if (snapshot.get("userID") == null) { + LoginRepository().delete() + LoginRepository().logout() + User("delete") + } + snapshot.toObject() + } catch (e: java.lang.Exception) { + Log.w(TAG, "Error getting document", e) + null } - snapshot.toObject() - } catch (e: java.lang.Exception) { - Log.w(TAG, "Error getting document", e) - null + } else { + return user } } @@ -97,15 +102,17 @@ class UserRepository { * Only called when a user is being deleted, remove their document from Firestore */ suspend fun removeUser() { - val user = auth.currentUser!! - Log.d(TAG, "Removing User ${user.uid} from Firestore") - try { - database.collection(USER_COLLECTION).document(user.uid) - .delete() - .await() - Log.d(TAG, "DocumentSnapshot successfully deleted!") - } catch (e: java.lang.Exception) { - Log.w(TAG, "Error deleting document", e) + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Removing User ${user.uid} from Firestore") + try { + database.collection(USER_COLLECTION).document(user.uid) + .delete() + .await() + Log.d(TAG, "DocumentSnapshot successfully deleted!") + } catch (e: java.lang.Exception) { + Log.w(TAG, "Error deleting document", e) + } } } @@ -113,14 +120,16 @@ class UserRepository { * Add a new Workout History to the user's subcollection */ suspend fun addWorkoutHistory(history: History) { - val user = auth.currentUser!! - Log.d(TAG, "Adding to the history of ${user.uid}") - try { - val docRef = database.collection(USER_COLLECTION).document(user.uid) - .collection(HISTORY_COLLECTION).add(history).await() - docRef.update("historyID", docRef.id).await() - } catch (e: java.lang.Exception) { - Log.d(TAG, "Error writing documents: ", e) + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Adding to the history of ${user.uid}") + try { + val docRef = database.collection(USER_COLLECTION).document(user.uid) + .collection(HISTORY_COLLECTION).add(history).await() + docRef.update("historyID", docRef.id).await() + } catch (e: java.lang.Exception) { + Log.d(TAG, "Error writing documents: ", e) + } } } @@ -128,37 +137,43 @@ class UserRepository { * Delete a given history via its ID */ suspend fun deleteWorkoutHistory(historyID: String) { - val user = auth.currentUser!! - Log.d(TAG, "Deleting history $historyID belonging to ${user.uid}") - database.collection(USER_COLLECTION) - .document(user.uid) - .collection(HISTORY_COLLECTION) - .document(historyID) - .delete() - .await() + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Deleting history $historyID belonging to ${user.uid}") + database.collection(USER_COLLECTION) + .document(user.uid) + .collection(HISTORY_COLLECTION) + .document(historyID) + .delete() + .await() + } } /** * Get a user's complete Workout History */ suspend fun getWorkoutHistory(): ArrayList { - val user = auth.currentUser!! + val user = auth.currentUser val historyList = arrayListOf() - Log.d(TAG, "Getting the history of ${user.uid}") - try { - val result = database.collection(USER_COLLECTION) - .document(user.uid) - .collection(HISTORY_COLLECTION) - .get() - .await() + if (user != null) { + Log.d(TAG, "Getting the history of ${user.uid}") + try { + val result = database.collection(USER_COLLECTION) + .document(user.uid) + .collection(HISTORY_COLLECTION) + .get() + .await() - for (document in result) { - historyList.add(document.toObject()) + for (document in result) { + historyList.add(document.toObject()) + } + } catch (e: java.lang.Exception) { + Log.d(TAG, "Error getting documents: ", e) } - } catch (e: java.lang.Exception) { - Log.d(TAG, "Error getting documents: ", e) + return historyList + } else { + return historyList } - return historyList } /** @@ -191,69 +206,78 @@ class UserRepository { * Add a new Notification to the user's notification subcollection */ suspend fun addNotification(notification: Notification) { - val user = auth.currentUser!! - Log.d(TAG, "Adding to the history of ${user.uid}") - database.collection(USER_COLLECTION) - .document(user.uid) - .collection(NOTIFICATION_COLLECTION) - .document(notification.date!!.toDate().time.toString()) - .set(notification) - .await() + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Adding to the history of ${user.uid}") + database.collection(USER_COLLECTION) + .document(user.uid) + .collection(NOTIFICATION_COLLECTION) + .document(notification.date!!.toDate().time.toString()) + .set(notification) + .await() + } } /** * Get a user's scheduled and past notifications */ suspend fun getNotifications(): ArrayList { - val user = auth.currentUser!! + val user = auth.currentUser val notificationList = arrayListOf() - Log.d(TAG, "Getting the notifications of ${user.uid}") - try { - val result = database.collection(USER_COLLECTION) - .document(user.uid) - .collection(NOTIFICATION_COLLECTION) - .get() - .await() + if (user == null) { + return notificationList + } else { + Log.d(TAG, "Getting the notifications of ${user.uid}") + try { + val result = database.collection(USER_COLLECTION) + .document(user.uid) + .collection(NOTIFICATION_COLLECTION) + .get() + .await() - for (document in result) { - notificationList.add(document.toObject()) + for (document in result) { + notificationList.add(document.toObject()) + } + } catch (e: java.lang.Exception) { + Log.d(TAG, "Error getting documents: ", e) } - } catch (e: java.lang.Exception) { - Log.d(TAG, "Error getting documents: ", e) + return notificationList } - return notificationList } /** * Remove a notification from the user's subcollection */ suspend fun deleteNotification(date: String) { - val user = auth.currentUser!! - Log.d(TAG, "Deleting notification with timestamp: $date") - database.collection(USER_COLLECTION) - .document(user.uid) - .collection(NOTIFICATION_COLLECTION) - .document(date) - .delete() - .await() - + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Deleting notification with timestamp: $date") + database.collection(USER_COLLECTION) + .document(user.uid) + .collection(NOTIFICATION_COLLECTION) + .document(date) + .delete() + .await() + } } /** * Add a new Goal to the user's goal subcollection */ suspend fun addGoal(goal: Goal) { - val user = auth.currentUser!! - Log.d(TAG, "Adding to the goals of ${user.uid}") - try { - val docRef = database.collection(USER_COLLECTION) - .document(user.uid) - .collection(GOAL_COLLECTION) - .add(goal) - .await() - docRef.update("goalID", docRef.id).await() - } catch (e: java.lang.Exception) { - Log.w(TAG, "Error writing document", e) + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Adding to the goals of ${user.uid}") + try { + val docRef = database.collection(USER_COLLECTION) + .document(user.uid) + .collection(GOAL_COLLECTION) + .add(goal) + .await() + docRef.update("goalID", docRef.id).await() + } catch (e: java.lang.Exception) { + Log.w(TAG, "Error writing document", e) + } } } @@ -261,16 +285,18 @@ class UserRepository { * Update an existing goal with its id */ suspend fun updateGoal(goal: Goal) { - val user = auth.currentUser!! - Log.d(TAG, "Updating goal ${goal.goalID} belonging to ${user.uid}") - try { - database.collection(USER_COLLECTION) - .document(user.uid) - .collection(GOAL_COLLECTION) - .document(goal.goalID!!) - .set(goal) - } catch (e: java.lang.Exception) { - Log.w(TAG, "Error writing document", e) + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Updating goal ${goal.goalID} belonging to ${user.uid}") + try { + database.collection(USER_COLLECTION) + .document(user.uid) + .collection(GOAL_COLLECTION) + .document(goal.goalID!!) + .set(goal) + } catch (e: java.lang.Exception) { + Log.w(TAG, "Error writing document", e) + } } } @@ -278,38 +304,43 @@ class UserRepository { * Get a user's scheduled and past goals */ suspend fun getGoals(): ArrayList { - val user = auth.currentUser!! + val user = auth.currentUser val goalList = arrayListOf() - Log.d(TAG, "Getting the goals of ${user.uid}") - try { - val result = database.collection(USER_COLLECTION) - .document(user.uid) - .collection(GOAL_COLLECTION) - .get() - .await() + if (user == null) { + return goalList + } else { + Log.d(TAG, "Getting the goals of ${user.uid}") + try { + val result = database.collection(USER_COLLECTION) + .document(user.uid) + .collection(GOAL_COLLECTION) + .get() + .await() - for (document in result) { - goalList.add(document.toObject()) + for (document in result) { + goalList.add(document.toObject()) + } + } catch (e: java.lang.Exception) { + Log.d(TAG, "Error getting documents: ", e) } - } catch (e: java.lang.Exception) { - Log.d(TAG, "Error getting documents: ", e) + return goalList } - return goalList } /** * Remove a goal from the user's subcollection */ suspend fun deleteGoal(goalID: String) { - val user = auth.currentUser!! - Log.d(TAG, "Deleting goal with ID: $goalID") - database.collection(USER_COLLECTION) - .document(user.uid) - .collection(GOAL_COLLECTION) - .document(goalID) - .delete() - .await() - + val user = auth.currentUser + if (user != null) { + Log.d(TAG, "Deleting goal with ID: $goalID") + database.collection(USER_COLLECTION) + .document(user.uid) + .collection(GOAL_COLLECTION) + .document(goalID) + .delete() + .await() + } } companion object { diff --git a/app/src/main/java/com/symphony/mrfit/ui/CalendarActivity.kt b/app/src/main/java/com/symphony/mrfit/ui/CalendarActivity.kt index 93d369e..377d797 100644 --- a/app/src/main/java/com/symphony/mrfit/ui/CalendarActivity.kt +++ b/app/src/main/java/com/symphony/mrfit/ui/CalendarActivity.kt @@ -1,7 +1,7 @@ /* - * Created by Team Symphony on 4/2/23, 6:07 PM + * Created by Team Symphony on 4/20/23, 2:11 AM * Copyright (c) 2023 . All rights reserved. - * Last modified 4/2/23, 6:06 PM + * Last modified 4/20/23, 2:08 AM */ package com.symphony.mrfit.ui @@ -36,6 +36,10 @@ import com.symphony.mrfit.ui.Helper.toCalendar import java.text.SimpleDateFormat import java.util.* +/** + * Screen featuring a Material UI-based Calendar widget for easy date selection + */ + class CalendarActivity : AppCompatActivity() { private var layoutManager: RecyclerView.LayoutManager? = null @@ -77,6 +81,10 @@ class CalendarActivity : AppCompatActivity() { profileViewModel.getNotifications() } + /** + * Cancel a pending notification + * PendingIntent must match the notificaiton to be cancelled + */ @RequiresApi(Build.VERSION_CODES.M) fun cancelNotification(time: String) { val notificationIntent = Intent(applicationContext, Notifications::class.java) @@ -98,6 +106,7 @@ class CalendarActivity : AppCompatActivity() { layoutManager = LinearLayoutManager(this) alarmList.layoutManager = layoutManager + // Populate the calendar with current notifications profileViewModel.getNotifications() profileViewModel.notifications.observe(this, Observer { Log.d(ContentValues.TAG, "Filling in username") @@ -106,6 +115,7 @@ class CalendarActivity : AppCompatActivity() { val todayAlerts = ArrayList() notifDays.clear() + // Iterate over all notifications, note any on the currently selected date for (n in notifications) { notifDays.add(n) val notificationDate = n.date!!.toDate() @@ -126,6 +136,7 @@ class CalendarActivity : AppCompatActivity() { calendar.setEvents(alertDays) }) + // When a new day is selected, check for notifications on that date calendar.setOnDayClickListener(object : OnDayClickListener { override fun onDayClick(eventDay: EventDay) { val clickedDay = eventDay.calendar @@ -146,10 +157,6 @@ class CalendarActivity : AppCompatActivity() { } }) - /** - * TODO: Pass the currently selected date to the new intent - * then receive selected date to position selected day on restart - */ newAlarm.setOnClickListener { val intent = Intent(this, NotificationActivity::class.java) intent.putExtra(EXTRA_DATE, calendar.firstSelectedDate.timeInMillis) diff --git a/app/src/main/java/com/symphony/mrfit/ui/CurrentWorkoutActivity.kt b/app/src/main/java/com/symphony/mrfit/ui/CurrentWorkoutActivity.kt index 2b60a48..3a8bb0d 100644 --- a/app/src/main/java/com/symphony/mrfit/ui/CurrentWorkoutActivity.kt +++ b/app/src/main/java/com/symphony/mrfit/ui/CurrentWorkoutActivity.kt @@ -1,18 +1,17 @@ /* - * Created by Team Symphony on 4/19/23, 4:23 PM + * Created by Team Symphony on 4/21/23, 10:36 PM * Copyright (c) 2023 . All rights reserved. - * Last modified 4/19/23, 4:15 PM + * Last modified 4/21/23, 10:36 PM */ package com.symphony.mrfit.ui -import android.app.Dialog import android.content.ContentValues import android.content.Intent import android.os.Bundle import android.os.SystemClock import android.util.Log -import android.widget.Button +import android.view.LayoutInflater import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity @@ -20,6 +19,7 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.firebase.Timestamp import com.symphony.mrfit.R import com.symphony.mrfit.data.exercise.ExerciseViewModel @@ -78,6 +78,8 @@ class CurrentWorkoutActivity : AppCompatActivity() { /** * Retrieve the extras passed to this intent + * passedRoutineName = The name of the parent routine + * passedRoutineID = The ID of the parent routine * passedList = The workoutList from the parent Routine */ val passedRoutineName = intent.getStringExtra(EXTRA_STRING) @@ -104,6 +106,10 @@ class CurrentWorkoutActivity : AppCompatActivity() { cancelButton.setOnClickListener { onSupportNavigateUp() } + /** + * When finishing a workout, save it to the User's history and display + * a congratulation dialog + */ finishButton.setOnClickListener { timer.stop() val timeSpent = SystemClock.elapsedRealtime() - timer.base @@ -122,20 +128,21 @@ class CurrentWorkoutActivity : AppCompatActivity() { ).show() - val dialog = Dialog(this) - dialog.setContentView(R.layout.activity_post_workout) - dialog.setTitle("Post Workout") + // Create the dialog and inflate its view like an activity + val materialDialog = MaterialAlertDialogBuilder(this) + val dialogView = LayoutInflater.from(this) + .inflate(R.layout.activity_post_workout, null, false) + + materialDialog.setView(dialogView) - val goals = dialog.findViewById