Skip to content

Commit 74a5ba7

Browse files
authored
Release Parent 4.1.0 (57) (#2916)
Release Parent 4.1.0 (57)
2 parents 6b2f24e + ea4d80e commit 74a5ba7

File tree

277 files changed

+7289
-4108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

277 files changed

+7289
-4108
lines changed

android-vault

apps/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ buildscript {
3434
classpath Plugins.FIREBASE_CRASHLYTICS
3535
if (project.coverageEnabled) { classpath Plugins.JACOCO_ANDROID }
3636
classpath Plugins.HILT
37-
classpath Plugins.HEAP
3837
}
3938
}
4039

4140
allprojects {
4241
repositories {
4342
google()
43+
maven {
44+
url = uri("https://software.mobile.pendo.io/artifactory/androidx-release")
45+
}
4446
mavenCentral()
4547
maven { url 'https://jitpack.io' }
4648
maven {

apps/buildSrc/src/main/java/GlobalDependencies.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object Versions {
2323
const val GOOGLE_SERVICES = "4.4.2"
2424

2525
/* Others */
26-
const val APOLLO = "2.5.14" // There is already a brand new version, Apollo 3, that requires lots of migration
26+
const val APOLLO = "4.1.1"
2727
const val PSPDFKIT = "2024.3.1"
2828
const val PHOTO_VIEW = "2.3.0"
2929
const val MOBIUS = "1.2.1"
@@ -35,7 +35,6 @@ object Versions {
3535
const val GLIDE_VERSION = "4.16.0"
3636
const val RETROFIT = "2.11.0"
3737
const val OKHTTP = "4.12.0"
38-
const val HEAP = "1.10.6"
3938
const val ROOM = "2.6.1"
4039
const val HAMCREST = "2.2"
4140
const val NAVIGATION = "2.8.3"
@@ -54,7 +53,7 @@ object Libs {
5453

5554
/* Apollo/GraphQL */
5655
const val APOLLO_RUNTIME = "com.apollographql.apollo:apollo-runtime:${Versions.APOLLO}"
57-
const val APOLLO_ANDROID_SUPPORT = "com.apollographql.apollo:apollo-android-support:${Versions.APOLLO}"
56+
const val APOLLO_API = "com.apollographql.apollo:apollo-api:${Versions.APOLLO}"
5857
const val APOLLO_HTTP_CACHE = "com.apollographql.apollo:apollo-http-cache:${Versions.APOLLO}"
5958

6059
/* Androidx libraries */
@@ -165,7 +164,7 @@ object Libs {
165164
const val APACHE_COMMONS_TEXT = "org.apache.commons:commons-text:1.12.0"
166165
const val CAMERA_VIEW = "com.otaliastudios:cameraview:2.7.2"
167166

168-
const val HEAP_CORE = "com.contentsquare.android:sdk:0.3.0"
167+
const val PENDO = "sdk.pendo.io:pendoIO:3.6+"
169168

170169
const val ROOM = "androidx.room:room-runtime:${Versions.ROOM}"
171170
const val ROOM_COMPILER = "androidx.room:room-compiler:${Versions.ROOM}"
@@ -185,6 +184,7 @@ object Libs {
185184
const val COMPOSE_UI = "androidx.compose.ui:ui-android"
186185
const val COMPOSE_UI_TEST = "androidx.compose.ui:ui-test-junit4"
187186
const val COMPOSE_UI_TEST_MANIFEST = "androidx.compose.ui:ui-test-manifest"
187+
const val COMPOSE_MATERIAL_3 = "androidx.compose.material3:material3"
188188

189189
// Navigation
190190
const val NAVIGATION_FRAGMENT = "androidx.navigation:navigation-fragment-ktx:${Versions.NAVIGATION}"
@@ -200,5 +200,4 @@ object Plugins {
200200
const val GOOGLE_SERVICES = "com.google.gms:google-services:${Versions.GOOGLE_SERVICES}"
201201
const val JACOCO_ANDROID = "com.dicedmelon.gradle:jacoco-android:${Versions.JACOCO_ANDROID}"
202202
const val HILT = "com.google.dagger:hilt-android-gradle-plugin:${Versions.HILT}"
203-
const val HEAP = "io.heap.gradle:io.heap.gradle.gradle.plugin:0.5.0"
204203
}

apps/parent/build.gradle

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ plugins {
2323
id 'com.google.firebase.crashlytics'
2424
id 'dagger.hilt.android.plugin'
2525
id 'org.jetbrains.kotlin.plugin.compose'
26-
id 'io.heap.gradle'
2726
}
2827

2928
configurations {
@@ -41,8 +40,8 @@ android {
4140
applicationId "com.instructure.parentapp"
4241
minSdkVersion Versions.MIN_SDK
4342
targetSdkVersion Versions.TARGET_SDK
44-
versionCode 56
45-
versionName "4.0.0"
43+
versionCode 57
44+
versionName "4.1.0"
4645

4746
buildConfigField "boolean", "IS_TESTING", "false"
4847
testInstrumentationRunner 'com.instructure.parentapp.ui.espresso.ParentHiltTestRunner'
@@ -99,7 +98,7 @@ android {
9998
mappingFileUploadEnabled false
10099
}
101100

102-
buildConfigField "String", "HEAP_APP_ID", "\"$heapStagingId\""
101+
buildConfigField "String", "PENDO_TOKEN", "\"$pendoAccessToken\""
103102
}
104103

105104
debugMinify {
@@ -115,7 +114,7 @@ android {
115114
shrinkResources true
116115
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
117116

118-
buildConfigField "String", "HEAP_APP_ID", "\"$heapProductionId\""
117+
buildConfigField "String", "PENDO_TOKEN", "\"$pendoAccessToken\""
119118
}
120119
}
121120

@@ -227,5 +226,5 @@ dependencies {
227226

228227
implementation Libs.ENCRYPTED_SHARED_PREFERENCES
229228

230-
implementation Libs.HEAP_CORE
229+
implementation Libs.PENDO
231230
}

apps/parent/proguard-rules.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,7 @@
251251

252252
# AGP 8 update
253253
-dontwarn java.beans.MethodDescriptor
254-
-dontwarn java.beans.SimpleBeanInfo
254+
-dontwarn java.beans.SimpleBeanInfo
255+
256+
-keep class androidx.navigation.** { *; }
257+
-keep interface androidx.navigation.** { *; }

apps/parent/src/androidTest/java/com/instructure/parentapp/ui/compose/managestudents/ManageStudentsScreenTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class ManageStudentsScreenTest {
6060
composeTestRule.onNodeWithText("You are not observing any students.")
6161
.assertIsDisplayed()
6262
composeTestRule.onNodeWithTag("EmptyContent")
63-
.performScrollToNode(hasText("Retry"))
64-
composeTestRule.onNodeWithText("Retry")
63+
.performScrollToNode(hasText("Refresh"))
64+
composeTestRule.onNodeWithText("Refresh")
6565
.assertIsDisplayed()
6666
.assertHasClickAction()
6767
composeTestRule.onNodeWithTag(R.drawable.panda_manage_students.toString())
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Copyright (C) 2025 - present Instructure, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.instructure.parentapp.ui.e2e
18+
19+
import android.util.Log
20+
import androidx.test.espresso.Espresso
21+
import com.instructure.canvas.espresso.E2E
22+
import com.instructure.canvas.espresso.FeatureCategory
23+
import com.instructure.canvas.espresso.Priority
24+
import com.instructure.canvas.espresso.SecondaryFeatureCategory
25+
import com.instructure.canvas.espresso.TestCategory
26+
import com.instructure.canvas.espresso.TestMetaData
27+
import com.instructure.canvas.espresso.checkToastText
28+
import com.instructure.dataseeding.api.AssignmentsApi
29+
import com.instructure.dataseeding.model.GradingType
30+
import com.instructure.dataseeding.model.SubmissionType
31+
import com.instructure.dataseeding.util.ago
32+
import com.instructure.dataseeding.util.days
33+
import com.instructure.dataseeding.util.fromNow
34+
import com.instructure.dataseeding.util.iso8601
35+
import com.instructure.pandautils.utils.toFormattedString
36+
import com.instructure.parentapp.R
37+
import com.instructure.parentapp.utils.ParentComposeTest
38+
import com.instructure.parentapp.utils.seedData
39+
import com.instructure.parentapp.utils.tokenLogin
40+
import dagger.hilt.android.testing.HiltAndroidTest
41+
import org.junit.Test
42+
import java.util.Calendar
43+
44+
@HiltAndroidTest
45+
class AssignmentReminderE2ETest: ParentComposeTest() {
46+
47+
override fun displaysPageObjects() = Unit
48+
49+
override fun enableAndConfigureAccessibilityChecks() = Unit
50+
51+
@E2E
52+
@Test
53+
@TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.E2E, SecondaryFeatureCategory.ASSIGNMENT_REMINDER)
54+
fun testAssignmentCustomReminderE2E() {
55+
56+
Log.d(PREPARATION_TAG, "Seeding data.")
57+
val data = seedData(students = 2, courses = 1, teachers = 1, parents = 1)
58+
val course = data.coursesList[0]
59+
val parent = data.parentsList[0]
60+
val teacher = data.teachersList[0]
61+
val futureDueDate = 2.days.fromNow
62+
val pastDueDate = 2.days.ago
63+
64+
Log.d(PREPARATION_TAG,"Seeding assignment for '${course.name}' course.")
65+
val testAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, gradingType = GradingType.POINTS, pointsPossible = 15.0, dueAt = 1.days.fromNow.iso8601, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY))
66+
67+
Log.d(PREPARATION_TAG,"Seeding 'Text Entry' assignment for '${course.name}' course with 2 days past due date.")
68+
val alreadyPastAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, dueAt = pastDueDate.iso8601, pointsPossible = 15.0, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY))
69+
70+
Log.d(STEP_TAG, "Login with user: '${parent.name}', login id: '${parent.loginId}'.")
71+
tokenLogin(parent)
72+
73+
Log.d(STEP_TAG, "Assert that the Dashboard Page is the landing page and it is loaded successfully.")
74+
dashboardPage.waitForRender()
75+
dashboardPage.assertPageObjects()
76+
77+
coursesPage.clickCourseItem(course.name)
78+
courseDetailsPage.assertCourseNameDisplayed(course)
79+
80+
Log.d(STEP_TAG,"Click on assignment '${testAssignment.name}'.")
81+
courseDetailsPage.clickAssignment(testAssignment.name)
82+
83+
Log.d(ASSERTION_TAG, "Assert that the toolbar title is 'Assignment Details' as the user is on the assignment details page and the subtitle is the '${course.name}' course's name.")
84+
assignmentDetailsPage.assertDisplayToolbarTitle()
85+
assignmentDetailsPage.assertDisplayToolbarSubtitle(course.name)
86+
assignmentDetailsPage.assertPageObjects()
87+
88+
Log.d(STEP_TAG, "Assert that the reminder section is displayed as well.")
89+
reminderPage.assertReminderSectionDisplayed()
90+
91+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
92+
reminderPage.clickAddReminder()
93+
94+
Log.d(STEP_TAG, "Select '1 Hour Before' and assert that the reminder has been picked up and displayed on the Assignment Details Page.")
95+
val reminderDateOneHour = futureDueDate.apply { add(Calendar.HOUR, -1) }
96+
reminderPage.clickCustomReminderOption()
97+
reminderPage.selectDate(reminderDateOneHour)
98+
reminderPage.selectTime(reminderDateOneHour)
99+
reminderPage.assertReminderDisplayedWithText(reminderDateOneHour.time.toFormattedString())
100+
101+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
102+
reminderPage.clickAddReminder()
103+
104+
Log.d(STEP_TAG, "Select '1 Hour Before' again, and assert that a toast message is occurring which warns that we cannot pick up the same time reminder twice.")
105+
reminderPage.clickCustomReminderOption()
106+
reminderPage.selectDate(reminderDateOneHour)
107+
reminderPage.selectTime(reminderDateOneHour)
108+
checkToastText(R.string.reminderAlreadySet, activityRule.activity)
109+
110+
Log.d(STEP_TAG, "Remove the '1 Hour Before' reminder, confirm the deletion dialog and assert that the '1 Hour Before' reminder is not displayed any more.")
111+
reminderPage.removeReminderWithText(reminderDateOneHour.time.toFormattedString())
112+
reminderPage.assertReminderNotDisplayedWithText(reminderDateOneHour.time.toFormattedString())
113+
futureDueDate.apply { add(Calendar.HOUR, 1) }
114+
115+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
116+
reminderPage.clickAddReminder()
117+
118+
Log.d(STEP_TAG, "Select '1 Week Before' and assert that a toast message is occurring which warns that we cannot pick up a reminder which has already passed (for example cannot pick '1 Week Before' reminder for an assignment which ends tomorrow).")
119+
val reminderDateOneWeek = futureDueDate.apply { add(Calendar.WEEK_OF_YEAR, -1) }
120+
reminderPage.clickCustomReminderOption()
121+
reminderPage.selectDate(reminderDateOneWeek)
122+
reminderPage.selectTime(reminderDateOneWeek)
123+
reminderPage.assertReminderNotDisplayedWithText(reminderDateOneWeek.time.toFormattedString())
124+
checkToastText(R.string.reminderInPast, activityRule.activity)
125+
futureDueDate.apply { add(Calendar.WEEK_OF_YEAR, 1) }
126+
127+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
128+
reminderPage.clickAddReminder()
129+
130+
Log.d(STEP_TAG, "Select '1 Day Before' and assert that the reminder has been picked up and displayed on the Assignment Details Page.")
131+
val reminderDateOneDay = futureDueDate.apply { add(Calendar.DAY_OF_MONTH, -1) }
132+
reminderPage.clickCustomReminderOption()
133+
reminderPage.selectDate(reminderDateOneDay)
134+
reminderPage.selectTime(reminderDateOneDay)
135+
reminderPage.assertReminderDisplayedWithText(reminderDateOneDay.time.toFormattedString())
136+
137+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
138+
reminderPage.clickAddReminder()
139+
140+
reminderPage.clickCustomReminderOption()
141+
reminderPage.selectDate(reminderDateOneDay)
142+
reminderPage.selectTime(reminderDateOneDay)
143+
144+
Log.d(STEP_TAG, "Assert that a toast message is occurring which warns that we cannot pick up the same time reminder twice. (Because 1 days and 24 hours is the same)")
145+
checkToastText(R.string.reminderAlreadySet, activityRule.activity)
146+
147+
futureDueDate.apply { add(Calendar.DAY_OF_MONTH, 1) }
148+
149+
Log.d(STEP_TAG, "Navigate back to Assignment List Page.")
150+
Espresso.pressBack()
151+
152+
Log.d(STEP_TAG,"Click on assignment '${alreadyPastAssignment.name}'.")
153+
courseDetailsPage.clickAssignment(alreadyPastAssignment.name)
154+
155+
Log.d(STEP_TAG, "Assert that the reminder section is NOT displayed, because the '${alreadyPastAssignment.name}' assignment has already passed..")
156+
reminderPage.assertReminderSectionDisplayed()
157+
}
158+
159+
@E2E
160+
@Test
161+
@TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.E2E, SecondaryFeatureCategory.ASSIGNMENT_REMINDER)
162+
fun testAssignmentBeforeReminderE2E() {
163+
164+
Log.d(PREPARATION_TAG, "Seeding data.")
165+
val data = seedData(students = 2, courses = 1, teachers = 1, parents = 1)
166+
val course = data.coursesList[0]
167+
val parent = data.parentsList[0]
168+
val teacher = data.teachersList[0]
169+
val futureDueDate = 2.days.fromNow
170+
val pastDueDate = 2.days.ago
171+
172+
Log.d(PREPARATION_TAG,"Seeding 'Text Entry' assignment for '${course.name}' course with 2 days ahead due date.")
173+
val testAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, dueAt = futureDueDate.iso8601, pointsPossible = 15.0, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY))
174+
175+
Log.d(PREPARATION_TAG,"Seeding 'Text Entry' assignment for '${course.name}' course with 2 days past due date.")
176+
val alreadyPastAssignment = AssignmentsApi.createAssignment(course.id, teacher.token, dueAt = pastDueDate.iso8601, pointsPossible = 15.0, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY))
177+
178+
Log.d(STEP_TAG, "Login with user: '${parent.name}', login id: '${parent.loginId}'.")
179+
tokenLogin(parent)
180+
dashboardPage.waitForRender()
181+
182+
Log.d(STEP_TAG, "Assert that the Dashboard Page is the landing page and it is loaded successfully.")
183+
dashboardPage.waitForRender()
184+
dashboardPage.assertPageObjects()
185+
186+
coursesPage.clickCourseItem(course.name)
187+
courseDetailsPage.assertCourseNameDisplayed(course)
188+
189+
Log.d(STEP_TAG,"Click on assignment '${testAssignment.name}'.")
190+
courseDetailsPage.clickAssignment(testAssignment.name)
191+
192+
Log.d(ASSERTION_TAG, "Assert that the toolbar title is 'Assignment Details' as the user is on the assignment details page and the subtitle is the '${course.name}' course's name.")
193+
assignmentDetailsPage.assertDisplayToolbarTitle()
194+
assignmentDetailsPage.assertDisplayToolbarSubtitle(course.name)
195+
assignmentDetailsPage.assertPageObjects()
196+
197+
Log.d(STEP_TAG, "Assert that the reminder section is displayed as well.")
198+
reminderPage.assertReminderSectionDisplayed()
199+
200+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
201+
reminderPage.clickAddReminder()
202+
203+
Log.d(STEP_TAG, "Select '1 Hour Before' and assert that the reminder has been picked up and displayed on the Assignment Details Page.")
204+
val reminderDateOneHour = futureDueDate.apply { add(Calendar.HOUR, -1) }
205+
reminderPage.clickBeforeReminderOption("1 Hour Before")
206+
reminderPage.assertReminderDisplayedWithText(reminderDateOneHour.time.toFormattedString())
207+
208+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
209+
reminderPage.clickAddReminder()
210+
211+
Log.d(STEP_TAG, "Select '1 Hour Before' again, and assert that a toast message is occurring which warns that we cannot pick up the same time reminder twice.")
212+
reminderPage.clickBeforeReminderOption("1 Hour Before")
213+
checkToastText(R.string.reminderAlreadySet, activityRule.activity)
214+
215+
Log.d(STEP_TAG, "Remove the '1 Hour Before' reminder, confirm the deletion dialog and assert that the '1 Hour Before' reminder is not displayed any more.")
216+
reminderPage.removeReminderWithText(reminderDateOneHour.time.toFormattedString())
217+
reminderPage.assertReminderNotDisplayedWithText(reminderDateOneHour.time.toFormattedString())
218+
futureDueDate.apply { add(Calendar.HOUR, 1) }
219+
220+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
221+
reminderPage.clickAddReminder()
222+
223+
Log.d(STEP_TAG, "Select '1 Week Before' and assert that a toast message is occurring which warns that we cannot pick up a reminder which has already passed (for example cannot pick '1 Week Before' reminder for an assignment which ends tomorrow).")
224+
val reminderDateOneWeek = futureDueDate.apply { add(Calendar.WEEK_OF_YEAR, -1) }
225+
reminderPage.clickBeforeReminderOption("1 Week Before")
226+
reminderPage.assertReminderNotDisplayedWithText(reminderDateOneWeek.time.toFormattedString())
227+
composeTestRule.waitForIdle()
228+
checkToastText(R.string.reminderInPast, activityRule.activity)
229+
composeTestRule.waitForIdle()
230+
futureDueDate.apply { add(Calendar.WEEK_OF_YEAR, 1) }
231+
232+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
233+
reminderPage.clickAddReminder()
234+
235+
Log.d(STEP_TAG, "Select '1 Day Before' and assert that the reminder has been picked up and displayed on the Assignment Details Page.")
236+
val reminderDateOneDay = futureDueDate.apply { add(Calendar.DAY_OF_MONTH, -1) }
237+
reminderPage.clickBeforeReminderOption("1 Day Before")
238+
reminderPage.assertReminderDisplayedWithText(reminderDateOneDay.time.toFormattedString())
239+
240+
Log.d(STEP_TAG, "Click on the '+' button (Add reminder) to pick up a new reminder.")
241+
reminderPage.clickAddReminder()
242+
243+
reminderPage.clickBeforeReminderOption("1 Day Before")
244+
245+
Log.d(STEP_TAG, "Assert that a toast message is occurring which warns that we cannot pick up the same time reminder twice. (Because 1 days and 24 hours is the same)")
246+
composeTestRule.waitForIdle()
247+
checkToastText(R.string.reminderAlreadySet, activityRule.activity)
248+
composeTestRule.waitForIdle()
249+
250+
futureDueDate.apply { add(Calendar.DAY_OF_MONTH, 1) }
251+
252+
Log.d(STEP_TAG, "Navigate back to Assignment List Page.")
253+
Espresso.pressBack()
254+
255+
Log.d(STEP_TAG,"Click on assignment '${alreadyPastAssignment.name}'.")
256+
courseDetailsPage.clickAssignment(alreadyPastAssignment.name)
257+
258+
Log.d(STEP_TAG, "Assert that the reminder section is NOT displayed, because the '${alreadyPastAssignment.name}' assignment has already passed..")
259+
reminderPage.assertReminderSectionDisplayed()
260+
}
261+
}

0 commit comments

Comments
 (0)