From 886a3068ef04e53b97eaf8ba6fdeb7fc074e595d Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:33:32 +0100 Subject: [PATCH 01/24] [MBL-17188][Student][Parent] Late penalty display for assignments refs: MBL-17188 affects: Student, Parent release note: Improved display of the late penalty. * Student late penalty changes * Parent late penalty changes * Fixed string * Fixed strings * Test fix * test fix --- .../lib/l10n/app_localizations.dart | 13 +++- .../lib/models/grade_cell_data.dart | 12 +++- .../lib/models/grade_cell_data.g.dart | 14 ++++ .../lib/screens/assignments/grade_cell.dart | 71 +++++++++++-------- .../assignments/grade_cell_data_test.dart | 7 +- .../assignments/grade_cell_widget_test.dart | 7 +- .../renderTests/views/GradeCellRenderTest.kt | 6 +- .../gradecellview/GradeCellViewData.kt | 10 ++- .../ui/gradeCell/GradeCellView.kt | 2 +- .../ui/gradeCell/GradeCellViewState.kt | 13 ++-- .../view_student_enhanced_grade_cell.xml | 24 +++++-- .../res/layout/view_student_grade_cell.xml | 24 +++++-- .../assignment/details/GradeCellStateTest.kt | 10 +-- libs/pandares/src/main/res/values/strings.xml | 2 + 14 files changed, 150 insertions(+), 65 deletions(-) diff --git a/apps/flutter_parent/lib/l10n/app_localizations.dart b/apps/flutter_parent/lib/l10n/app_localizations.dart index 53eda6d918..86768767b3 100644 --- a/apps/flutter_parent/lib/l10n/app_localizations.dart +++ b/apps/flutter_parent/lib/l10n/app_localizations.dart @@ -989,11 +989,18 @@ class AppLocalizations { desc: 'Screen reader-friendly replacement for the "-" character in letter grades like "A-"', ); - String latePenalty(String pointsLost) => Intl.message( - 'Late penalty (-$pointsLost)', + String yourGrade(String pointsAchieved) => Intl.message( + 'Your grade: $pointsAchieved', + desc: 'Text displayed when a late penalty has been applied to the assignment, this is the achieved score without the penalty', + args: [pointsAchieved], + name: 'yourGrade', + ); + + String latePenaltyUpdated(String pointsLost) => Intl.message( + 'Late Penalty: -$pointsLost pts', desc: 'Text displayed when a late penalty has been applied to the assignment', args: [pointsLost], - name: 'latePenalty', + name: 'latePenaltyUpdated', ); String finalGrade(String grade) => Intl.message( diff --git a/apps/flutter_parent/lib/models/grade_cell_data.dart b/apps/flutter_parent/lib/models/grade_cell_data.dart index 642c29700e..f30b60241f 100644 --- a/apps/flutter_parent/lib/models/grade_cell_data.dart +++ b/apps/flutter_parent/lib/models/grade_cell_data.dart @@ -40,6 +40,7 @@ abstract class GradeCellData implements Built 0.0) { grade = ''; // Grade will be shown in the 'final grade' text var pointsDeducted = NumberFormat.decimalPattern().format(submission.pointsDeducted ?? 0.0); - latePenalty = l10n.latePenalty(pointsDeducted); + var pointsAchieved = NumberFormat.decimalPattern().format(submission.enteredScore); + yourGrade = l10n.yourGrade(pointsAchieved); + latePenalty = l10n.latePenaltyUpdated(pointsDeducted); finalGrade = l10n.finalGrade(submission.grade ?? grade); } @@ -166,6 +171,7 @@ abstract class GradeCellData implements Built _$this._outOf; set outOf(String? outOf) => _$this._outOf = outOf; + String? _yourGrade; + String? get yourGrade => _$this._yourGrade; + set yourGrade(String? yourGrade) => _$this._yourGrade = yourGrade; + String? _latePenalty; String? get latePenalty => _$this._latePenalty; set latePenalty(String? latePenalty) => _$this._latePenalty = latePenalty; @@ -221,6 +233,7 @@ class GradeCellDataBuilder _grade = $v.grade; _gradeContentDescription = $v.gradeContentDescription; _outOf = $v.outOf; + _yourGrade = $v.yourGrade; _latePenalty = $v.latePenalty; _finalGrade = $v.finalGrade; _$v = null; @@ -264,6 +277,7 @@ class GradeCellDataBuilder grade: BuiltValueNullFieldError.checkNotNull(grade, r'GradeCellData', 'grade'), gradeContentDescription: BuiltValueNullFieldError.checkNotNull(gradeContentDescription, r'GradeCellData', 'gradeContentDescription'), outOf: BuiltValueNullFieldError.checkNotNull(outOf, r'GradeCellData', 'outOf'), + yourGrade: BuiltValueNullFieldError.checkNotNull(yourGrade, r'GradeCellData', 'yourGrade'), latePenalty: BuiltValueNullFieldError.checkNotNull(latePenalty, r'GradeCellData', 'latePenalty'), finalGrade: BuiltValueNullFieldError.checkNotNull(finalGrade, r'GradeCellData', 'finalGrade')); replace(_$result); diff --git a/apps/flutter_parent/lib/screens/assignments/grade_cell.dart b/apps/flutter_parent/lib/screens/assignments/grade_cell.dart index 9d8bb559bf..d8bac77e11 100644 --- a/apps/flutter_parent/lib/screens/assignments/grade_cell.dart +++ b/apps/flutter_parent/lib/screens/assignments/grade_cell.dart @@ -18,6 +18,7 @@ import 'package:flutter_parent/models/assignment.dart'; import 'package:flutter_parent/models/course.dart'; import 'package:flutter_parent/models/grade_cell_data.dart'; import 'package:flutter_parent/models/submission.dart'; +import 'package:flutter_parent/utils/design/parent_colors.dart'; import 'package:flutter_parent/utils/design/parent_theme.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; @@ -134,37 +135,49 @@ class GradeCell extends StatelessWidget { ], ), if (!_isGradedRestrictQuantitativeData) SizedBox(width: 16), - if (!_isGradedRestrictQuantitativeData) Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (data.grade.isNotEmpty) - Text( - data.grade, - key: Key('grade-cell-grade'), - style: Theme.of(context).textTheme.headlineMedium, - semanticsLabel: data.gradeContentDescription, - ), - if (data.outOf.isNotEmpty) Text(data.outOf, key: Key('grade-cell-out-of')), - if (data.latePenalty.isNotEmpty) - Text( - data.latePenalty, - style: TextStyle(color: data.accentColor), - key: Key('grade-cell-late-penalty'), - ), - if (data.finalGrade.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - data.finalGrade, - key: Key('grade-cell-final-grade'), - style: Theme.of(context).textTheme.titleMedium, + if (!_isGradedRestrictQuantitativeData) + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (data.grade.isNotEmpty) + Text( + data.grade, + key: Key('grade-cell-grade'), + style: Theme.of(context).textTheme.headlineMedium, + semanticsLabel: data.gradeContentDescription, + ), + if (data.outOf.isNotEmpty) Text(data.outOf, key: Key('grade-cell-out-of')), + if (data.yourGrade.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.yourGrade, + key: Key('grade-cell-your-grade'), + ), + ), + if (data.latePenalty.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.latePenalty, + style: TextStyle(color: ParentColors.failure), + key: Key('grade-cell-late-penalty'), + ), + ), + if (data.finalGrade.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.finalGrade, + key: Key('grade-cell-final-grade'), + style: Theme.of(context).textTheme.titleMedium, + ), ), - ), - ], + ], + ), ), - ), ], ); } diff --git a/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart b/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart index 134975c7df..2fe2782299 100644 --- a/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart +++ b/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart @@ -250,10 +250,11 @@ void main() { ..grade = '79' ..score = 79.0); var expected = baseGradedState.rebuild((b) => b - ..graphPercent = 0.85 - ..score = '85' + ..graphPercent = 0.79 + ..score = '79' ..showPointsLabel = true - ..latePenalty = 'Late penalty (-6)' + ..yourGrade = 'Your grade: 85' + ..latePenalty = 'Late Penalty: -6 pts' ..finalGrade = 'Final Grade: 79'); var actual = GradeCellData.forSubmission(baseCourse, baseAssignment, submission, theme, l10n); expect(actual, expected); diff --git a/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart b/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart index 37bd688ec3..3cc7c10885 100644 --- a/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart +++ b/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_parent/l10n/app_localizations.dart'; import 'package:flutter_parent/models/grade_cell_data.dart'; import 'package:flutter_parent/screens/assignments/grade_cell.dart'; +import 'package:flutter_parent/utils/design/parent_colors.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; @@ -158,11 +159,13 @@ void main() { GradeCellData data = GradeCellData((b) => b ..state = GradeCellState.graded ..accentColor = Colors.pinkAccent - ..latePenalty = 'Late penalty: (-25)'); + ..yourGrade = 'Your grade: 85' + ..latePenalty = 'Late penalty: -25'); await setupWithData(tester, data); expect(find.byKey(Key('grade-cell-late-penalty')).evaluate(), find.text(data.latePenalty).evaluate()); - expect(tester.widget(find.byKey(Key('grade-cell-late-penalty'))).style!.color, data.accentColor); + expect(find.byKey(Key('grade-cell-your-grade')).evaluate(), find.text(data.yourGrade).evaluate()); + expect(tester.widget(find.byKey(Key('grade-cell-late-penalty'))).style!.color, ParentColors.failure); }); testWidgetsWithAccessibilityChecks('Displays final grade text', (tester) async { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt index 432f6a7c6c..ecb161ed32 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt @@ -37,6 +37,7 @@ class GradeCellRenderTest : StudentRenderTest() { val submittedTitle by OnViewWithId(R.id.submittedTitle) val submittedSubtitle by OnViewWithId(R.id.submittedSubtitle) val pointsLabel by OnViewWithId(R.id.pointsLabel) + val yourGrade by OnViewWithId(R.id.yourGrade) val latePenalty by OnViewWithId(R.id.latePenalty) val finalGrade by OnViewWithId(R.id.finalGrade) val grade by OnViewWithId(R.id.grade) @@ -122,7 +123,8 @@ class GradeCellRenderTest : StudentRenderTest() { score = "91", showPointsLabel = true, outOf = "Out of 100 pts", - latePenalty = "Late Penalty (-2 pts)", + yourGrade = "Your Grade: 91 pts", + latePenalty = "Late Penalty: -2 pts", finalGrade = "Final Grade: 89 pts" ) setupViewWithState(state) @@ -132,6 +134,7 @@ class GradeCellRenderTest : StudentRenderTest() { score.assertDisplayed() pointsLabel.assertDisplayed() outOf.assertDisplayed() + yourGrade.assertDisplayed() latePenalty.assertDisplayed() finalGrade.assertDisplayed() } @@ -140,6 +143,7 @@ class GradeCellRenderTest : StudentRenderTest() { with(gradeCell) { score.assertHasText(state.score) outOf.assertHasText(state.outOf) + yourGrade.assertHasText(state.yourGrade) latePenalty.assertHasText(state.latePenalty) finalGrade.assertHasText(state.finalGrade) } diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt index 7fb3b6eae8..fe8c7ca580 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt @@ -24,6 +24,7 @@ data class GradeCellViewData( val grade: String = "", val gradeCellContentDescription: String = "", val outOf: String = "", + val yourGrade: String = "", val latePenalty: String = "", val finalGrade: String = "", val stats: GradeCellViewState.GradeStats? = null @@ -39,7 +40,6 @@ data class GradeCellViewData( } companion object { - @Suppress("DEPRECATION") fun fromSubmission( resources: Resources, courseColor: ThemedColor, @@ -145,7 +145,7 @@ data class GradeCellViewData( gradeCellContentDescription = contentDescription, ) } else { - val score = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + val score = NumberHelper.formatDecimal(submission.score, 2, true) val chartPercent = (submission.enteredScore / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() // If grading type is Points, don't show the grade since we're already showing it as the score var grade = if (assignment.gradingType != Assignment.POINTS_TYPE) submission.grade.orEmpty() else "" @@ -170,12 +170,15 @@ data class GradeCellViewData( var latePenalty = "" var finalGrade = "" + var yourGrade = "" // Adjust for late penalty, if any if (submission.pointsDeducted.orDefault() > 0.0) { grade = "" // Grade will be shown in the 'final grade' text val pointsDeducted = NumberHelper.formatDecimal(submission.pointsDeducted.orDefault(), 2, true) - latePenalty = resources.getString(R.string.latePenalty, pointsDeducted) + val achievedScore = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + yourGrade = resources.getString(R.string.yourGrade, achievedScore) + latePenalty = resources.getString(R.string.latePenaltyUpdated, pointsDeducted) finalGrade = resources.getString(R.string.finalGradeFormatted, submission.grade) } @@ -210,6 +213,7 @@ data class GradeCellViewData( grade = grade, gradeCellContentDescription = gradeCellContentDescription, outOf = outOfText, + yourGrade = yourGrade, latePenalty = latePenalty, finalGrade = finalGrade, stats = stats diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt index 49b796f7ad..b5a9df7835 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt @@ -67,6 +67,7 @@ class GradeCellView @JvmOverloads constructor( pointsLabel.setVisible(state.showPointsLabel) completeIcon.setVisible(state.showCompleteIcon) incompleteIcon.setVisible(state.showIncompleteIcon) + yourGrade.setTextForVisibility(state.yourGrade) latePenalty.setTextForVisibility(state.latePenalty) finalGrade.setTextForVisibility(state.finalGrade) grade.setTextForVisibility(state.grade) @@ -75,7 +76,6 @@ class GradeCellView @JvmOverloads constructor( outOf.contentDescription = state.outOfContentDescription // Accent color - latePenalty.setTextColor(state.accentColor) chart.setColor(state.accentColor) completeIcon.imageTintList = ColorStateList.valueOf(state.accentColor) incompleteIcon.imageTintList = ColorStateList.valueOf(state.accentColor) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt index 42cf48e75c..f5f368b06a 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt @@ -43,6 +43,7 @@ sealed class GradeCellViewState { val gradeCellContentDescription: String = "", val outOf: String = "", val outOfContentDescription: String = "", + val yourGrade: String = "", val latePenalty: String = "", val finalGrade: String = "", val stats: GradeStats? = null @@ -132,8 +133,8 @@ sealed class GradeCellViewState { ) } - val score = NumberHelper.formatDecimal(submission.enteredScore, 2, true) - val graphPercent = (submission.enteredScore / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() + val score = NumberHelper.formatDecimal(submission.score, 2, true) + val graphPercent = (submission.score / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() // If grading type is Points, don't show the grade since we're already showing it as the score var grade = if (assignment.gradingType != Assignment.POINTS_TYPE) submission.grade.orEmpty() else "" @@ -148,12 +149,15 @@ sealed class GradeCellViewState { var latePenalty = "" var finalGrade = "" + var yourGrade = "" // Adjust for late penalty, if any - if (submission.pointsDeducted ?: 0.0 > 0.0) { + if ((submission.pointsDeducted ?: 0.0) > 0.0) { grade = "" // Grade will be shown in the 'final grade' text val pointsDeducted = NumberHelper.formatDecimal(submission.pointsDeducted!!, 2, true) - latePenalty = context.getString(R.string.latePenalty, pointsDeducted) + val achievedScore = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + yourGrade = context.getString(R.string.yourGrade, achievedScore) + latePenalty = context.getString(R.string.latePenaltyUpdated, pointsDeducted) finalGrade = context.getString(R.string.finalGradeFormatted, submission.grade) } @@ -190,6 +194,7 @@ sealed class GradeCellViewState { grade = grade, gradeContentDescription = accessibleGradeString, gradeCellContentDescription = gradeCellContentDescription, + yourGrade = yourGrade, latePenalty = latePenalty, finalGrade = finalGrade, stats = stats diff --git a/apps/student/src/main/res/layout/view_student_enhanced_grade_cell.xml b/apps/student/src/main/res/layout/view_student_enhanced_grade_cell.xml index fb89c22521..7eeb284f87 100644 --- a/apps/student/src/main/res/layout/view_student_enhanced_grade_cell.xml +++ b/apps/student/src/main/res/layout/view_student_enhanced_grade_cell.xml @@ -273,12 +273,27 @@ android:text="@{viewData.outOf}" android:textColor="@color/textDarkest" android:visibility="@{viewData.outOf.empty ? View.GONE : View.VISIBLE}" - app:layout_constraintBottom_toTopOf="@id/latePenalty" + app:layout_constraintBottom_toTopOf="@id/yourGrade" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/chart" app:layout_constraintTop_toBottomOf="@id/grade" tools:text="Out of 100 pts" /> + + + tools:text="89%" /> + tools:text="Out of 100 pts" /> + + + android:textColor="@color/textWarning" + tools:text="Late Penalty: -46 pts" /> + tools:text="Final Grade: 89 pts" /> diff --git a/apps/student/src/test/java/com/instructure/student/test/assignment/details/GradeCellStateTest.kt b/apps/student/src/test/java/com/instructure/student/test/assignment/details/GradeCellStateTest.kt index 461a326251..0f60757041 100644 --- a/apps/student/src/test/java/com/instructure/student/test/assignment/details/GradeCellStateTest.kt +++ b/apps/student/src/test/java/com/instructure/student/test/assignment/details/GradeCellStateTest.kt @@ -314,11 +314,13 @@ class GradeCellStateTest : Assert() { score = 79.0 ) val expected = baseGradedState.copy( - graphPercent = 0.85f, - score = "85", + graphPercent = 0.79f, + score = "79", showPointsLabel = true, - latePenalty = "Late penalty (-6)", - finalGrade = "Final Grade: 79" + yourGrade = "Your Grade: 85 pts", + latePenalty = "Late Penalty: -6 pts", + finalGrade = "Final Grade: 79", + gradeCellContentDescription = "79 Out of 100 points" ) val actual = GradeCellViewState.fromSubmission(context, baseAssignment, submission) assertEquals(expected, actual) diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index c301e10fc8..e10ab2c072 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -957,6 +957,8 @@ Invite accepted! Invite declined. Late penalty (-%s) + Late Penalty: -%s pts + Your Grade: %s pts Final Grade 99+ From dd1682a39951b92c0b0d5d2fff7730c2aa17bbf9 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:21:41 +0100 Subject: [PATCH 02/24] [MBL-17115][Student] - Offline LeftSideMenu Unavailable features E2E test (#2275) --- .../e2e/offline/OfflineLeftSideMenuE2ETest.kt | 101 ++++++++++++++++++ .../student/ui/pages/DashboardPage.kt | 4 + .../ui/pages/LeftSideNavigationDrawerPage.kt | 89 ++++++++++++++- .../espresso/CustomViewAssertions.kt | 1 - .../panda_annotations/TestMetaData.kt | 2 +- 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt new file mode 100644 index 0000000000..b3dedd84bb --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.panda_annotations.FeatureCategory +import com.instructure.panda_annotations.Priority +import com.instructure.panda_annotations.TestCategory +import com.instructure.panda_annotations.TestMetaData +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.assertNoInternetConnectionDialog +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.assertOfflineIndicator +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.dismissNoInternetConnectionDialog +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test + +@HiltAndroidTest +class OfflineLeftSideMenuE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.LEFT_SIDE_MENU, TestCategory.E2E) + fun testOfflineLeftSideMenuUnavailableFunctionsE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 2, announcements = 1) + val student = data.studentsList[0] + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") + assertOfflineIndicator() + + Log.d(STEP_TAG, "Open Left Side Menu by clicking on the 'hamburger icon' on the Dashboard Page.") + dashboardPage.openLeftSideMenu() + + Log.d(STEP_TAG, "Assert that the offline indicator is displayed below the user info within the header.") + leftSideNavigationDrawerPage.assertOfflineIndicatorDisplayed() + + Log.d(STEP_TAG, "Assert that the 'Files, Bookmarks, Studio, Color Overlay, Help' menus are disabled in offline mode.") + leftSideNavigationDrawerPage.assertOfflineDisabledMenus(0.5f) + + Log.d(STEP_TAG, "Assert that the 'Settings, Show Grades, Change User, Log Out' menus are enabled in offline mode because they are supported.") + leftSideNavigationDrawerPage.assertOfflineEnabledMenus(1.0f) + + Log.d(STEP_TAG, "Click on 'Files' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickFilesMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Bookmarks' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickBookmarksMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Studio' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickStudioMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Help' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickHelpMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device via ADB, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt index a8b51842eb..18971f74a5 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt @@ -147,6 +147,10 @@ class DashboardPage : BasePage(R.id.dashboardPage) { onView(hamburgerButtonMatcher).waitForCheck(matches(isDisplayed())) } + fun openLeftSideMenu() { + onView(hamburgerButtonMatcher).click() + } + private fun scrollAndAssertDisplayed(matcher: Matcher) { // Arggghhh... This scrolling logic on the recycler view is really unreliable and seems // to fail for nonsensical reasons. For now, "scrollAndAssertDisplayed"" is just going to diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt index da39b4cefe..0fa4f1c98e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt @@ -12,8 +12,22 @@ import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.waitForMatcherWithSleeps import com.instructure.canvasapi2.models.User import com.instructure.dataseeding.model.CanvasUserApiModel -import com.instructure.espresso.* -import com.instructure.espresso.page.* +import com.instructure.espresso.OnViewWithContentDescription +import com.instructure.espresso.OnViewWithId +import com.instructure.espresso.ViewAlphaAssertion +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.assertNotDisplayed +import com.instructure.espresso.click +import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.onViewWithId +import com.instructure.espresso.page.onViewWithText +import com.instructure.espresso.page.waitForView +import com.instructure.espresso.page.waitForViewWithId +import com.instructure.espresso.page.withId +import com.instructure.espresso.scrollTo +import com.instructure.espresso.swipeDown +import com.instructure.espresso.swipeUp import com.instructure.student.R import org.hamcrest.CoreMatchers import org.hamcrest.Matcher @@ -86,6 +100,10 @@ class LeftSideNavigationDrawerPage : BasePage() { clickMenu(R.id.navigationDrawerItem_bookmarks) } + fun clickStudioMenu() { + clickMenu(R.id.navigationDrawerItem_studio) + } + fun clickSettingsMenu() { clickMenu(R.id.navigationDrawerSettings) } @@ -181,6 +199,73 @@ class LeftSideNavigationDrawerPage : BasePage() { logoutButton.assertDisplayed() } + //OfflineMethod + private fun assertViewAlpha(matcher: Matcher, expectedAlphaValue: Float) { + onView(matcher).check(ViewAlphaAssertion(expectedAlphaValue)) + } + + //OfflineMethod + private fun assertFilesMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_files), expectedAlphaValue) + } + + //OfflineMethod + private fun assertBookmarksMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_bookmarks), expectedAlphaValue) + } + + //OfflineMethod + private fun assertStudioMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_studio), expectedAlphaValue) + } + + //OfflineMethod + private fun assertSettingsMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerSettings), expectedAlphaValue) + } + + //OfflineMethod + private fun assertShowGradesMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_showGrades), expectedAlphaValue) + } + + //OfflineMethod + private fun assertColorOverlayMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_colorOverlay), expectedAlphaValue) + } + + //OfflineMethod + private fun assertHelpMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_help), expectedAlphaValue) + } + + //OfflineMethod + private fun assertChangeUserMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_changeUser), expectedAlphaValue) + } + + //OfflineMethod + private fun assertLogoutMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_logout), expectedAlphaValue) + } + + //OfflineMethod + fun assertOfflineDisabledMenus(expectedAlphaValue: Float) { + assertFilesMenuAlphaValue(expectedAlphaValue) + assertBookmarksMenuAlphaValue(expectedAlphaValue) + assertStudioMenuAlphaValue(expectedAlphaValue) + assertColorOverlayMenuAlphaValue(expectedAlphaValue) + assertHelpMenuAlphaValue(expectedAlphaValue) + } + + //OfflineMethod + fun assertOfflineEnabledMenus(expectedAlphaValue: Float) { + assertSettingsMenuAlphaValue(expectedAlphaValue) + assertShowGradesMenuAlphaValue(expectedAlphaValue) + assertChangeUserMenuAlphaValue(expectedAlphaValue) + assertLogoutMenuAlphaValue(expectedAlphaValue) + } + /** * Custom ViewAction to set a SwitchCompat to the desired on/off position * [position]: true -> "on", false -> "off" diff --git a/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt b/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt index 77a1c2d6ec..64eee4f838 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt @@ -128,4 +128,3 @@ class ViewAlphaAssertion(private val expectedAlpha: Float): ViewAssertion { assertThat("View alpha should be $expectedAlpha", view.alpha, `is`(expectedAlpha)) } } - diff --git a/libs/panda_annotations/src/main/java/com/instructure/panda_annotations/TestMetaData.kt b/libs/panda_annotations/src/main/java/com/instructure/panda_annotations/TestMetaData.kt index 4400d2346b..4dfec47796 100644 --- a/libs/panda_annotations/src/main/java/com/instructure/panda_annotations/TestMetaData.kt +++ b/libs/panda_annotations/src/main/java/com/instructure/panda_annotations/TestMetaData.kt @@ -34,7 +34,7 @@ enum class Priority { enum class FeatureCategory { ASSIGNMENTS, SUBMISSIONS, LOGIN, COURSE, DASHBOARD, GROUPS, SETTINGS, PAGES, DISCUSSIONS, MODULES, INBOX, GRADES, FILES, EVENTS, PEOPLE, CONFERENCES, COLLABORATIONS, SYLLABUS, TODOS, QUIZZES, NOTIFICATIONS, - ANNOTATIONS, ANNOUNCEMENTS, COMMENTS, BOOKMARKS, NONE, K5_DASHBOARD, SPEED_GRADER, SYNC_SETTINGS, SYNC_PROGRESS, OFFLINE_CONTENT + ANNOTATIONS, ANNOUNCEMENTS, COMMENTS, BOOKMARKS, NONE, K5_DASHBOARD, SPEED_GRADER, SYNC_SETTINGS, SYNC_PROGRESS, OFFLINE_CONTENT, LEFT_SIDE_MENU } enum class SecondaryFeatureCategory { From eae402259694d1e0cf829937adc07c0645342304 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:26:48 +0100 Subject: [PATCH 03/24] Stabilize Offline ManageOfflineContentE2ETest. (#2278) --- .../student/ui/e2e/offline/ManageOfflineContentE2ETest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt index 3d300a2e1a..431b8d1509 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt @@ -73,6 +73,9 @@ class ManageOfflineContentE2ETest : StudentTest() { Log.d(STEP_TAG, "Assert that the tool bar texts are displayed properly, so the subtitle is '${course1.name}', because we are on the Manage Offline Content page of '${course1.name}' course.") manageOfflineContentPage.assertToolbarTexts(course1.name) + Log.d(STEP_TAG, "Assert that the '${course1.name}' course's checkbox state became 'Checked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course1.name, MaterialCheckBox.STATE_CHECKED) + Log.d(STEP_TAG, "Deselect the 'Announcements' and 'Discussions' of the '${course1.name}' course.") manageOfflineContentPage.changeItemSelectionState("Announcements") manageOfflineContentPage.changeItemSelectionState("Discussions") From b2527335642cf573dda3771515a7e6d8e9aeaf88 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:19:33 +0100 Subject: [PATCH 04/24] [MBL-17174][Student][Teacher] Dependency update (#2272) Test plan: Smoke test the apps. refs: MBL-17174 affects: Student, Teacher release note: none --- .../student/espresso/TestAppManager.kt | 5 +- .../student/fragment/ViewImageFragment.kt | 12 +++-- .../drawer/files/ui/SubmissionFilesAdapter.kt | 13 +++-- .../student/util/BaseAppManager.kt | 10 ++-- .../teacher/fragments/ViewImageFragment.kt | 12 +++-- .../instructure/teacher/utils/AppManager.kt | 7 +-- .../teacher/utils/BaseAppManager.kt | 10 ++-- buildSrc/src/main/java/GlobalDependencies.kt | 48 +++++++++---------- libs/DocumentScanner/build.gradle | 14 +++--- .../canvasapi2/utils/DataResultTest.kt | 21 +++----- libs/pandautils/build.gradle | 4 +- .../pandautils/utils/AvatarCropUtils.kt | 4 +- .../instructure/pandautils/utils/SvgUtils.kt | 15 +++++- .../pandautils/utils/ViewExtensions.kt | 8 ++-- .../pandautils/views/RecipientChipsInput.kt | 8 ++-- .../DashboardNotificationsViewModelTest.kt | 16 +++---- .../offline/sync/OfflineSyncHelperTest.kt | 10 ++-- .../ShareExtensionProgressViewModelTest.kt | 20 ++++---- 18 files changed, 124 insertions(+), 113 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt index c99b8cf92d..fc00cc637e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt @@ -16,7 +16,6 @@ */ package com.instructure.student.espresso -import androidx.work.Configuration import androidx.work.WorkerFactory import com.instructure.student.util.BaseAppManager @@ -25,8 +24,6 @@ open class TestAppManager : BaseAppManager() { var workerFactory: WorkerFactory? = null override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory?.let { - it - } ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt index e5c586603f..bce47bc595 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt @@ -91,7 +91,7 @@ class ViewImageFragment : Fragment(), ShareableFile { private val requestListener = object : RequestListener { - override fun onLoadFailed(p0: GlideException?, p1: Any?, p2: Target?, p3: Boolean): Boolean { + override fun onLoadFailed(p0: GlideException?, p1: Any?, target: Target, p3: Boolean): Boolean { binding.photoView.setGone() binding.progressBar.setGone() binding.errorContainer.setVisible() @@ -100,11 +100,17 @@ class ViewImageFragment : Fragment(), ShareableFile { return false } - override fun onResourceReady(drawable: Drawable?, p1: Any?, p2: Target?, p3: DataSource?, p4: Boolean): Boolean { + override fun onResourceReady( + resource: Drawable, + model: Any, + p2: Target?, + dataSource: DataSource, + p4: Boolean + ): Boolean { binding.progressBar.setGone() // Try to set the background color using palette if we can - (drawable as? BitmapDrawable)?.bitmap?.let { colorBackground(it) } + (resource as? BitmapDrawable)?.bitmap?.let { colorBackground(it) } return false } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt index f9018ce7f9..0c1c06eb89 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt @@ -69,17 +69,22 @@ internal class SubmissionFilesHolder(view: View) : RecyclerView.ViewHolder(view) thumbnail.setVisible(data.thumbnailUrl.isValid()) data.thumbnailUrl.validOrNull()?.let { Glide.with(root.context).load(it).listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { fileIcon.setVisible(true) thumbnail.setVisible(false) return false } override fun onResourceReady( - resource: Drawable?, - model: Any?, + resource: Drawable, + model: Any, target: Target?, - dataSource: DataSource?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { return false diff --git a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt index 51b573aaa4..09f8856ab6 100644 --- a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt +++ b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt @@ -49,6 +49,11 @@ import io.flutter.embedding.engine.dart.DartExecutor abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling, Configuration.Provider { + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(getWorkManagerFactory()) + .build() + override fun onCreate() { super.onCreate() @@ -153,11 +158,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti override fun performLogoutOnAuthError() = Unit - override fun getWorkManagerConfiguration(): Configuration = - Configuration.Builder() - .setWorkerFactory(getWorkManagerFactory()) - .build() - abstract fun getWorkManagerFactory(): WorkerFactory companion object { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewImageFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewImageFragment.kt index 1cd5cc518a..26488116a0 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewImageFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewImageFragment.kt @@ -109,7 +109,7 @@ class ViewImageFragment : Fragment(), ShareableFile { private val requestListener = object : RequestListener { - override fun onLoadFailed(p0: GlideException?, p1: Any?, p2: Target?, p3: Boolean): Boolean = with(binding) { + override fun onLoadFailed(p0: GlideException?, p1: Any?, target: Target, p3: Boolean): Boolean = with(binding) { photoView.setGone() progressBar.setGone() errorContainer.setVisible() @@ -118,11 +118,17 @@ class ViewImageFragment : Fragment(), ShareableFile { return false } - override fun onResourceReady(bitmap: Bitmap?, p1: Any?, p2: Target?, p3: DataSource?, p4: Boolean): Boolean { + override fun onResourceReady( + resource: Bitmap, + model: Any, + p2: Target?, + dataSource: DataSource, + p4: Boolean + ): Boolean { binding.progressBar.setGone() // Try to set the background color using palette if we can - bitmap?.let { colorBackground(it) } + colorBackground(resource) return false } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/AppManager.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/AppManager.kt index 34cb5dcc21..d87159491f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/AppManager.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/AppManager.kt @@ -23,15 +23,10 @@ import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject @HiltAndroidApp -class AppManager : BaseAppManager(), Configuration.Provider { +class AppManager : BaseAppManager() { @Inject lateinit var workerFactory: HiltWorkerFactory - override fun getWorkManagerConfiguration(): Configuration = - Configuration.Builder() - .setWorkerFactory(workerFactory) - .build() - override fun getWorkManagerFactory(): WorkerFactory = workerFactory } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt index f79b57adcc..5acca986bb 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt @@ -43,6 +43,11 @@ import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Configuration.Provider { + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(getWorkManagerFactory()) + .build() + override fun onCreate() { super.onCreate() @@ -98,11 +103,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Configu TeacherLogoutTask(LogoutTask.Type.LOGOUT).execute() } - override fun getWorkManagerConfiguration(): Configuration = - Configuration.Builder() - .setWorkerFactory(getWorkManagerFactory()) - .build() - abstract fun getWorkManagerFactory(): WorkerFactory companion object { diff --git a/buildSrc/src/main/java/GlobalDependencies.kt b/buildSrc/src/main/java/GlobalDependencies.kt index 8ad33f36dd..287dfe6e02 100644 --- a/buildSrc/src/main/java/GlobalDependencies.kt +++ b/buildSrc/src/main/java/GlobalDependencies.kt @@ -27,17 +27,17 @@ object Versions { const val PSPDFKIT = "8.9.1" const val PHOTO_VIEW = "2.3.0" const val MOBIUS = "1.2.1" - const val SQLDELIGHT = "1.5.4" - const val HILT = "2.48" - const val HILT_ANDROIDX = "1.0.0" - const val LIFECYCLE = "2.6.0" - const val FRAGMENT = "1.5.5" - const val WORK_MANAGER = "2.8.1" - const val GLIDE_VERSION = "4.15.1" + const val SQLDELIGHT = "1.5.4" // 2.0 is out but may break stuff. We should look into migrating to Room. + const val HILT = "2.49" + const val HILT_ANDROIDX = "1.1.0" + const val LIFECYCLE = "2.6.2" + const val FRAGMENT = "1.6.2" + const val WORK_MANAGER = "2.9.0" + const val GLIDE_VERSION = "4.16.0" const val RETROFIT = "2.9.0" - const val OKHTTP = "4.10.0" - const val HEAP = "1.10.5" - const val ROOM = "2.6.0" + const val OKHTTP = "4.12.0" + const val HEAP = "1.10.6" + const val ROOM = "2.6.1" const val HAMCREST = "2.2" } @@ -54,35 +54,35 @@ object Libs { const val APOLLO_HTTP_CACHE = "com.apollographql.apollo:apollo-http-cache:${Versions.APOLLO}" /* Support Libs */ - const val ANDROIDX_ANNOTATION = "androidx.annotation:annotation:1.6.0" + const val ANDROIDX_ANNOTATION = "androidx.annotation:annotation:1.7.0" const val ANDROIDX_APPCOMPAT = "androidx.appcompat:appcompat:1.6.1" - const val ANDROIDX_BROWSER = "androidx.browser:browser:1.5.0" + const val ANDROIDX_BROWSER = "androidx.browser:browser:1.7.0" const val ANDROIDX_CARDVIEW = "androidx.cardview:cardview:1.0.0" const val ANDROIDX_CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:2.1.4" - const val ANDROIDX_DESIGN = "com.google.android.material:material:1.8.0" + const val ANDROIDX_DESIGN = "com.google.android.material:material:1.10.0" const val ANDROIDX_EXIF = "androidx.exifinterface:exifinterface:1.3.6" const val ANDROIDX_FRAGMENT = "androidx.fragment:fragment:${Versions.FRAGMENT}" const val FRAGMENT_KTX = "androidx.fragment:fragment-ktx:${Versions.FRAGMENT}" const val ANDROIDX_PALETTE = "androidx.palette:palette:1.0.0" const val ANDROIDX_PERCENT = "androidx.percentlayout:percentlayout:1.0.0" - const val ANDROIDX_RECYCLERVIEW = "androidx.recyclerview:recyclerview:1.3.0" + const val ANDROIDX_RECYCLERVIEW = "androidx.recyclerview:recyclerview:1.3.2" const val ANDROIDX_VECTOR = "androidx.vectordrawable:vectordrawable:1.1.0" const val ANDROIDX_SWIPE_REFRESH_LAYOUT = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" const val ANDROIDX_CORE_TESTING = "androidx.arch.core:core-testing:2.2.0" const val ANDROIDX_WORK_MANAGER = "androidx.work:work-runtime:${Versions.WORK_MANAGER}" const val ANDROIDX_WORK_MANAGER_KTX = "androidx.work:work-runtime-ktx:${Versions.WORK_MANAGER}" - const val ANDROIDX_WEBKIT = "androidx.webkit:webkit:1.6.0" + const val ANDROIDX_WEBKIT = "androidx.webkit:webkit:1.9.0" const val ANDROIDX_DATABINDING_COMPILER = "androidx.databinding:databinding-compiler:${Versions.ANDROID_GRADLE_TOOLS}" // This is bundled with the gradle plugin so we use the same version /* Firebase */ - const val FIREBASE_BOM = "com.google.firebase:firebase-bom:31.2.3" + const val FIREBASE_BOM = "com.google.firebase:firebase-bom:32.6.0" const val FIREBASE_CRASHLYTICS = "com.google.firebase:firebase-crashlytics" const val FIREBASE_MESSAGING = "com.google.firebase:firebase-messaging" const val FIREBASE_CONFIG = "com.google.firebase:firebase-config" const val FIREBASE_CRASHLYTICS_NDK = "com.google.firebase:firebase-crashlytics-ndk" /* Play Services */ - const val PLAY_IN_APP_UPDATES = "com.google.android.play:app-update:2.0.1" + const val PLAY_IN_APP_UPDATES = "com.google.android.play:app-update:2.1.0" const val FLEXBOX_LAYOUT = "com.google.android.flexbox:flexbox:3.0.0" /* Mobius */ @@ -95,8 +95,8 @@ object Libs { const val JUNIT = "junit:junit:${Versions.JUNIT}" const val ROBOLECTRIC = "org.robolectric:robolectric:${Versions.ROBOLECTRIC}" const val ANDROIDX_TEST_JUNIT = "androidx.test.ext:junit:1.1.5" - const val MOCKK = "io.mockk:mockk:1.12.3" - const val THREETEN_BP = "org.threeten:threetenbp:1.6.5" + const val MOCKK = "io.mockk:mockk:1.12.8" + const val THREETEN_BP = "org.threeten:threetenbp:1.6.8" const val UI_AUTOMATOR = "com.android.support.test.uiautomator:uiautomator-v18:2.1.3" const val TEST_ORCHESTRATOR = "androidx.test:orchestrator:1.4.2" @@ -120,14 +120,14 @@ object Libs { /* Media and content handling */ const val PSPDFKIT = "com.pspdfkit:pspdfkit:${Versions.PSPDFKIT}" - const val EXOPLAYER = "com.google.android.exoplayer:exoplayer:2.18.5" + const val EXOPLAYER = "com.google.android.exoplayer:exoplayer:2.18.5" // This is deprecated, we should migrate to https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide const val PHOTO_VIEW = "com.github.chrisbanes:PhotoView:${Versions.PHOTO_VIEW}" const val ANDROID_SVG = "com.caverock:androidsvg:1.4" const val RICH_EDITOR = "jp.wasabeef:richeditor-android:2.0.0" const val GLIDE = "com.github.bumptech.glide:glide:${Versions.GLIDE_VERSION}" const val GLIDE_OKHTTP = "com.github.bumptech.glide:okhttp3-integration:${Versions.GLIDE_VERSION}" const val GLIDE_COMPILER = "com.github.bumptech.glide:compiler:${Versions.GLIDE_VERSION}" - const val SCALE_IMAGE_VIEW = "com.davemorrissey.labs:subsampling-scale-image-view:3.9.0" + const val SCALE_IMAGE_VIEW = "com.davemorrissey.labs:subsampling-scale-image-view:3.10.0" /* Network */ const val RETROFIT = "com.squareup.retrofit2:retrofit:${Versions.RETROFIT}" @@ -138,15 +138,15 @@ object Libs { const val OKHTTP = "com.squareup.okhttp3:okhttp:${Versions.OKHTTP}" const val OKHTTP_LOGGING = "com.squareup.okhttp3:logging-interceptor:${Versions.OKHTTP}" const val OKHTTP_URL_CONNECTION = "com.squareup.okhttp3:okhttp-urlconnection:${Versions.OKHTTP}" - const val OKIO = "com.squareup.okio:okio:3.3.0" + const val OKIO = "com.squareup.okio:okio:3.6.0" /* Other */ - const val LOTTIE = "com.airbnb.android:lottie:6.0.0" + const val LOTTIE = "com.airbnb.android:lottie:6.2.0" const val SLIDING_UP_PANEL = "com.sothree.slidinguppanel:library:3.3.1" const val SQLDELIGHT = "com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT}" const val DISK_LRU_CACHE = "com.jakewharton:disklrucache:2.0.2" const val EVENTBUS = "org.greenrobot:eventbus:3.3.1" - const val JW_THREETEN_BP = "com.jakewharton.threetenabp:threetenabp:1.4.4" + const val JW_THREETEN_BP = "com.jakewharton.threetenabp:threetenabp:1.4.6" const val PROCESS_PHOENIX = "com.jakewharton:process-phoenix:2.1.2" const val PAPERDB = "io.github.pilgr:paperdb:2.7.2" const val KEYBOARD_VISIBILITY_LISTENER = "net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.2.1" diff --git a/libs/DocumentScanner/build.gradle b/libs/DocumentScanner/build.gradle index 47145c3f4d..7c7bf810a8 100644 --- a/libs/DocumentScanner/build.gradle +++ b/libs/DocumentScanner/build.gradle @@ -63,20 +63,20 @@ dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation Libs.KOTLIN_STD_LIB - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' implementation 'com.github.zynkware:Tiny-OpenCV:4.4.0-4' - implementation "androidx.camera:camera-camera2:1.0.0" - implementation "androidx.camera:camera-lifecycle:1.0.0" - implementation "androidx.camera:camera-view:1.0.0-alpha24" + implementation "androidx.camera:camera-camera2:1.3.0" + implementation "androidx.camera:camera-lifecycle:1.3.0" + implementation "androidx.camera:camera-view:1.4.0-alpha02" implementation 'com.github.tbruyelle:rxpermissions:0.12' - implementation 'androidx.exifinterface:exifinterface:1.3.2' + implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation Libs.KOTLIN_COROUTINES_ANDROID implementation 'id.zelory:compressor:3.0.1' } diff --git a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/utils/DataResultTest.kt b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/utils/DataResultTest.kt index df82ca9630..3eae2c1028 100644 --- a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/utils/DataResultTest.kt +++ b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/utils/DataResultTest.kt @@ -87,8 +87,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Success(expectedValue) val block = mockk<(String) -> Unit>(relaxed = true) result.onSuccess(block) - verify { block.invoke(expectedValue) } - confirmVerified() + verify(exactly = 1) { block.invoke(expectedValue) } } @Test @@ -97,7 +96,6 @@ class DataResultTest : Assert() { val block = mockk<(String) -> Unit>(relaxed = true) result.onSuccess(block) verify(exactly = 0) { block.invoke(capture(slot())) } - confirmVerified() } @Test @@ -106,8 +104,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Fail(expectedFailure) val block = mockk<(Failure) -> Unit>(relaxed = true) result.onFail(block) - verify { block.invoke(expectedFailure) } - confirmVerified() + verify(exactly = 1) { block.invoke(expectedFailure) } } @Test @@ -116,8 +113,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Fail(expectedFailure) val block = mockk<(Failure) -> Unit>(relaxed = true) result.onFail(block) - verify { block.invoke(expectedFailure) } - confirmVerified() + verify(exactly = 1) { block.invoke(expectedFailure) } } @Test @@ -126,8 +122,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Fail(expectedFailure) val block = mockk<(Failure) -> Unit>(relaxed = true) result.onFail(block) - verify { block.invoke(expectedFailure) } - confirmVerified() + verify(exactly = 1) { block.invoke(expectedFailure) } } @Test @@ -135,8 +130,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Fail() val block = mockk<(Failure?) -> Unit>(relaxed = true) result.onFailure(block) - verify { block.invoke(null) } - confirmVerified() + verify(exactly = 1) { block.invoke(null) } } @Test @@ -145,8 +139,7 @@ class DataResultTest : Assert() { val result: DataResult = DataResult.Fail(expectedFailure) val block = mockk<(Failure?) -> Unit>(relaxed = true) result.onFailure(block) - verify { block.invoke(expectedFailure) } - confirmVerified() + verify(exactly = 1) { block.invoke(expectedFailure) } } @Test @@ -155,7 +148,6 @@ class DataResultTest : Assert() { val block = mockk<(Failure?) -> Unit>(relaxed = true) result.onFailure(block) verify(exactly = 0) { block.invoke(capture(slot())) } - confirmVerified() } @Test @@ -164,6 +156,5 @@ class DataResultTest : Assert() { val block = mockk<(Failure) -> Unit>(relaxed = true) result.onFail(block) verify(exactly = 0) { block.invoke(capture(slot())) } - confirmVerified() } } diff --git a/libs/pandautils/build.gradle b/libs/pandautils/build.gradle index e0adde4efa..2353288f5c 100644 --- a/libs/pandautils/build.gradle +++ b/libs/pandautils/build.gradle @@ -125,8 +125,8 @@ dependencies { implementation Libs.KOTLIN_STD_LIB implementation Libs.ANDROIDX_BROWSER implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' + implementation Libs.LIVE_DATA + implementation Libs.VIEW_MODEL /* Test Dependencies */ testImplementation Libs.JUNIT diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/AvatarCropUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/AvatarCropUtils.kt index 2932710efc..2c9a8a6c93 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/AvatarCropUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/AvatarCropUtils.kt @@ -199,8 +199,8 @@ class AvatarCropActivity : AppCompatActivity() { * crop boundary as a percentage (0f to 1f) of the source image dimensions. */ private fun getCropInfo(): RectF = with(binding) { - val origin = imageView.viewToSourceCoord(0f, 0f) - val dimen = imageView.viewToSourceCoord(imageView.width.toFloat(), imageView.height.toFloat()) + val origin = imageView.viewToSourceCoord(0f, 0f) ?: return RectF(0f, 0f, 1f, 1f) + val dimen = imageView.viewToSourceCoord(imageView.width.toFloat(), imageView.height.toFloat()) ?: return RectF(0f, 0f, 1f, 1f) val (appliedWidth, appliedHeight) = when (imageView.appliedOrientation) { 90, 270 -> imageView.sHeight to imageView.sWidth else -> imageView.sWidth to imageView.sHeight diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/SvgUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/SvgUtils.kt index 99bb9cf272..e4eb8050de 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/SvgUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/SvgUtils.kt @@ -91,12 +91,23 @@ internal class SvgDrawableTranscoder : ResourceTranscoder * can't render on a hardware backed [Canvas][android.graphics.Canvas]. */ internal class SvgSoftwareLayerSetter : RequestListener { - override fun onResourceReady(resource: PictureDrawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + override fun onResourceReady( + resource: PictureDrawable, + model: Any, + target: Target?, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { (target as? ImageViewTarget)?.view?.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null) return false } - override fun onLoadFailed(p0: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { (target as? ImageViewTarget)?.view?.setLayerType(ImageView.LAYER_TYPE_NONE, null) return false } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt index 1980d35c8b..1189f32411 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt @@ -781,7 +781,7 @@ fun ImageView.loadCircularImage( override fun onLoadFailed( e: GlideException?, model: Any?, - target: Target?, + target: Target, isFirstResource: Boolean ): Boolean { onFailure?.invoke() @@ -789,10 +789,10 @@ fun ImageView.loadCircularImage( } override fun onResourceReady( - resource: Bitmap?, - model: Any?, + resource: Bitmap, + model: Any, target: Target?, - dataSource: DataSource?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { return false diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/views/RecipientChipsInput.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/views/RecipientChipsInput.kt index 5a91c11bcd..a09bed935f 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/views/RecipientChipsInput.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/views/RecipientChipsInput.kt @@ -196,7 +196,7 @@ class RecipientChip(context: Context, val recipient: Recipient, onRemoved: (Stri override fun onLoadFailed( e: GlideException?, model: Any?, - target: Target?, + target: Target, isFirstResource: Boolean ): Boolean { chipIcon = placeholder @@ -204,10 +204,10 @@ class RecipientChip(context: Context, val recipient: Recipient, onRemoved: (Stri } override fun onResourceReady( - resource: Drawable?, - model: Any?, + resource: Drawable, + model: Any, target: Target?, - source: DataSource?, + source: DataSource, isFirstResource: Boolean ): Boolean { chipIcon = resource diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt index 7a917177c8..5343a8829a 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt @@ -505,8 +505,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), Data.Builder() .putString(FileUploadWorker.PROGRESS_DATA_TITLE, title) .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, subTitle) @@ -522,8 +522,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId2, WorkInfo.State.SUCCEEDED, + emptySet(), Data.EMPTY, - emptyList(), Data.Builder() .putString(FileUploadWorker.PROGRESS_DATA_TITLE, title2) .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, subTitle2) @@ -539,8 +539,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId3, WorkInfo.State.FAILED, + emptySet(), Data.EMPTY, - emptyList(), Data.Builder() .putString(FileUploadWorker.PROGRESS_DATA_TITLE, title3) .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, subTitle3) @@ -586,8 +586,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), Data.EMPTY, 1, 1 @@ -603,8 +603,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.SUCCEEDED, + emptySet(), Data.EMPTY, - emptyList(), Data.EMPTY, 1, 1 @@ -631,8 +631,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), Data.EMPTY, 1, 1 @@ -666,8 +666,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.SUCCEEDED, + emptySet(), Data.EMPTY, - emptyList(), Data.EMPTY, 1, 1 @@ -707,8 +707,8 @@ class DashboardNotificationsViewModelTest { WorkInfo( workerId, WorkInfo.State.SUCCEEDED, + emptySet(), Data.EMPTY, - emptyList(), Data.EMPTY, 1, 1 diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt index d84a44897d..f4bd92e64e 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt @@ -262,7 +262,7 @@ class OfflineSyncHelperTest { every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( listOf( - WorkInfo(originalRequest.id, WorkInfo.State.ENQUEUED, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1) + WorkInfo(originalRequest.id, WorkInfo.State.ENQUEUED, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) @@ -281,7 +281,7 @@ class OfflineSyncHelperTest { every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( listOf( - WorkInfo(uuid, WorkInfo.State.RUNNING, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1) + WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) @@ -297,7 +297,7 @@ class OfflineSyncHelperTest { val uuid = UUID.randomUUID() every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( listOf( - WorkInfo(uuid, WorkInfo.State.RUNNING, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1) + WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( @@ -350,7 +350,7 @@ class OfflineSyncHelperTest { val uuid = UUID.randomUUID() every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( listOf( - WorkInfo(uuid, WorkInfo.State.RUNNING, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1) + WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( @@ -377,7 +377,7 @@ class OfflineSyncHelperTest { ) every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( listOf( - WorkInfo(uuid, WorkInfo.State.RUNNING, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1) + WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt index 6b1eb762ff..77a3757aca 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt @@ -71,7 +71,7 @@ class ShareExtensionProgressViewModelTest { @Test fun `Show success dialog after uploading`() { viewModel.setUUID(uuid) - mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.SUCCEEDED, Data.EMPTY, emptyList(), Data.EMPTY, 1, 1)) + mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.SUCCEEDED, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1)) viewModel.events.observe(lifecycleOwner) {} assertEquals(ShareExtensionProgressAction.ShowSuccessDialog(FileUploadType.USER), viewModel.events.value?.getContentIfNotHandled()) @@ -86,7 +86,7 @@ class ShareExtensionProgressViewModelTest { .putStringArray(FileUploadWorker.PROGRESS_DATA_FILES_TO_UPLOAD, emptyArray()) .build() - mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.FAILED, outputData, emptyList(), Data.EMPTY, 1, 1)) + mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.FAILED, emptySet(), outputData, Data.EMPTY, 1, 1)) assertEquals("Error", viewModel.data.value?.subtitle) } @@ -124,8 +124,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), progressData, 1, 1 @@ -156,8 +156,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), progressData, 1, 1 @@ -213,8 +213,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), progressData.build(), 1, 1 @@ -248,8 +248,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), progressData.build(), 1, 1 @@ -290,8 +290,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.FAILED, + emptySet(), progressData, - emptyList(), Data.EMPTY, 1, 1 @@ -342,8 +342,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.FAILED, + emptySet(), failedOutputData, - emptyList(), Data.EMPTY, 1, 1 @@ -379,8 +379,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.RUNNING, + emptySet(), Data.EMPTY, - emptyList(), successProgressData, 1, 1 @@ -395,8 +395,8 @@ class ShareExtensionProgressViewModelTest { WorkInfo( uuid, WorkInfo.State.SUCCEEDED, + emptySet(), Data.EMPTY, - emptyList(), successProgressData, 1, 1 From 6c1fce5feca02f97ee2791a30d999709285c5202 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:31:02 +0100 Subject: [PATCH 05/24] [MBL-17237][Teacher] SpeedGraderCommentHolder crash refs: MBL-17237 affects: Teacher, Student release note: none --- .../mobius/common/ui/MobiusFragment.kt | 27 ++++++++++++------- .../holders/SpeedGraderCommentHolder.kt | 18 +++++++++---- .../mobius/common/ui/MobiusFragment.kt | 26 +++++++++++------- .../pandautils/utils/ActivityExtensions.kt | 10 ++++++- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt index c1e5000be0..bd4b8b96b5 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt @@ -18,7 +18,6 @@ package com.instructure.student.mobius.common.ui import android.app.Activity import android.content.Context -import android.content.ContextWrapper import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -29,10 +28,24 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.instructure.canvasapi2.utils.Analytics import com.instructure.interactions.FragmentInteractions import com.instructure.interactions.Navigation +import com.instructure.pandautils.utils.getFragmentActivity import com.instructure.student.BuildConfig import com.instructure.student.fragment.ParentFragment -import com.instructure.student.mobius.common.* -import com.spotify.mobius.* +import com.instructure.student.mobius.common.ConsumerQueueWrapper +import com.instructure.student.mobius.common.CoroutineConnection +import com.instructure.student.mobius.common.GlobalEventMapper +import com.instructure.student.mobius.common.GlobalEventSource +import com.instructure.student.mobius.common.LateInit +import com.instructure.student.mobius.common.MobiusExceptionLogger +import com.instructure.student.mobius.common.contraMap +import com.spotify.mobius.Connectable +import com.spotify.mobius.Connection +import com.spotify.mobius.EventSource +import com.spotify.mobius.First +import com.spotify.mobius.Init +import com.spotify.mobius.Mobius +import com.spotify.mobius.MobiusLoop +import com.spotify.mobius.Update import com.spotify.mobius.android.MobiusAndroid import com.spotify.mobius.android.runners.MainThreadWorkRunner import com.spotify.mobius.functions.Consumer @@ -176,13 +189,7 @@ abstract class MobiusView(inflater: Lay get() = parent.context protected val activity: Activity - get() = getActivity(context) - - private fun getActivity(context: Context): Activity { - if (context is Activity) return context - if (context is ContextWrapper) return getActivity(context.baseContext) - else throw IllegalStateException("Not activity context") - } + get() = context.getFragmentActivity() abstract fun onConnect(output: Consumer) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/holders/SpeedGraderCommentHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/holders/SpeedGraderCommentHolder.kt index f42843bd3a..46bff66066 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/holders/SpeedGraderCommentHolder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/holders/SpeedGraderCommentHolder.kt @@ -17,13 +17,17 @@ package com.instructure.teacher.holders import android.widget.ImageView -import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.RecyclerView -import com.instructure.canvasapi2.models.* +import com.instructure.canvasapi2.models.Assignee +import com.instructure.canvasapi2.models.Attachment +import com.instructure.canvasapi2.models.GroupAssignee +import com.instructure.canvasapi2.models.StudentAssignee +import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.models.postmodels.CommentSendStatus import com.instructure.canvasapi2.utils.Pronouns import com.instructure.canvasapi2.utils.isValid import com.instructure.interactions.router.Route +import com.instructure.pandautils.utils.getFragmentActivity import com.instructure.pandautils.utils.onClick import com.instructure.pandautils.utils.setGone import com.instructure.pandautils.utils.setVisible @@ -41,7 +45,11 @@ import com.instructure.teacher.utils.getColorCompat import com.instructure.teacher.utils.getSubmissionFormattedDate import com.instructure.teacher.utils.iconRes import com.instructure.teacher.utils.setAnonymousAvatar -import com.instructure.teacher.view.* +import com.instructure.teacher.view.CommentAttachmentsView +import com.instructure.teacher.view.CommentDirection +import com.instructure.teacher.view.CommentMediaAttachmentView +import com.instructure.teacher.view.CommentSubmissionView +import com.instructure.teacher.view.CommentView class SpeedGraderCommentHolder(private val binding: AdapterSubmissionCommentBinding) : RecyclerView.ViewHolder(binding.root) { fun bind( @@ -83,7 +91,7 @@ class SpeedGraderCommentHolder(private val binding: AdapterSubmissionCommentBind avatarView.setupAvatarA11y(comment.authorName) avatarView.onClick { val bundle = StudentContextFragment.makeBundle(comment.authorId, courseId) - RouteMatcher.route(context as FragmentActivity, Route(StudentContextFragment::class.java, null, bundle)) + RouteMatcher.route(context.getFragmentActivity(), Route(StudentContextFragment::class.java, null, bundle)) } } Triple( @@ -143,7 +151,7 @@ class SpeedGraderCommentHolder(private val binding: AdapterSubmissionCommentBind avatarView.setupAvatarA11y(assignee.name) avatarView.onClick { val bundle = StudentContextFragment.makeBundle(assignee.id, courseId) - RouteMatcher.route(context as FragmentActivity, Route(StudentContextFragment::class.java, null, bundle)) + RouteMatcher.route(context.getFragmentActivity(), Route(StudentContextFragment::class.java, null, bundle)) } Triple(null, Pronouns.span(assignee.name, assignee.pronouns), assignee.student.avatarUrl) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/mobius/common/ui/MobiusFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/mobius/common/ui/MobiusFragment.kt index d6267030a5..6149b5b7ee 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/mobius/common/ui/MobiusFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/mobius/common/ui/MobiusFragment.kt @@ -18,15 +18,27 @@ package com.instructure.teacher.mobius.common.ui import android.app.Activity import android.content.Context -import android.content.ContextWrapper import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding -import com.instructure.teacher.mobius.common.* -import com.spotify.mobius.* +import com.instructure.pandautils.utils.getFragmentActivity +import com.instructure.teacher.mobius.common.ConsumerQueueWrapper +import com.instructure.teacher.mobius.common.CoroutineConnection +import com.instructure.teacher.mobius.common.GlobalEventMapper +import com.instructure.teacher.mobius.common.GlobalEventSource +import com.instructure.teacher.mobius.common.LateInit +import com.instructure.teacher.mobius.common.contraMap +import com.spotify.mobius.Connectable +import com.spotify.mobius.Connection +import com.spotify.mobius.EventSource +import com.spotify.mobius.First +import com.spotify.mobius.Init +import com.spotify.mobius.Mobius +import com.spotify.mobius.MobiusLoop +import com.spotify.mobius.Update import com.spotify.mobius.android.MobiusAndroid import com.spotify.mobius.android.runners.MainThreadWorkRunner import com.spotify.mobius.functions.Consumer @@ -154,13 +166,7 @@ abstract class MobiusView( get() = parent.context protected val activity: Activity - get() = getActivity(context) - - private fun getActivity(context: Context): Activity { - if (context is Activity) return context - if (context is ContextWrapper) return getActivity(context.baseContext) - else throw IllegalStateException("Not activity context") - } + get() = context.getFragmentActivity() abstract fun onConnect(output: Consumer) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ActivityExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ActivityExtensions.kt index 37f7385d6f..31e9928ca0 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ActivityExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ActivityExtensions.kt @@ -16,6 +16,8 @@ */ package com.instructure.pandautils.utils +import android.content.Context +import android.content.ContextWrapper import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentActivity import com.instructure.canvasapi2.utils.APIHelper @@ -32,4 +34,10 @@ fun FragmentActivity.withRequireNetwork(block: () -> Unit) { .setPositiveButton(android.R.string.ok, { dialog, _ -> dialog.dismiss() }) .showThemed() } -} \ No newline at end of file +} + +fun Context.getFragmentActivity(): FragmentActivity { + if (this is FragmentActivity) return this + if (this is ContextWrapper) return this.baseContext.getFragmentActivity() + else throw IllegalStateException("Not FragmentActivity context") +} From cb4fceac92ab532db8a1ee400af2b86b8fbf7ff2 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:57:34 +0100 Subject: [PATCH 06/24] [MBL-14485][Teacher] Module items have no previous or next buttons refs: MBL-14485 affects: Teacher release note: Added previous and next buttons to module items. * teacher module progression * Fixed routing * Added file details fragment * Edit file details update fix * fixed caching * Tablet toolbar changes * Chevron changes * fixed tests * fixed some comments * PR findings * Test fix * Test fix * Test fix * Binding issue fix * Test fix * Test fix * Module item remove issue fix * Test fix --- .../CourseModuleProgressionFragment.kt | 4 +- .../res/layout/course_module_progression.xml | 18 +- .../teacher/di/FileDetailsModule.kt | 44 +++ .../teacher/di/ModuleProgressionModule.kt | 34 +++ .../discussion/DiscussionsDetailsFragment.kt | 9 +- .../files/details/FileDetailsFragment.kt | 130 ++++++++ .../files/details/FileDetailsRepository.kt | 44 +++ .../files/details/FileDetailsViewData.kt | 60 ++++ .../files/details/FileDetailsViewModel.kt | 144 +++++++++ .../modules/list/ModuleListEffectHandler.kt | 46 +-- .../features/modules/list/ModuleListModels.kt | 4 - .../features/modules/list/ModuleListUpdate.kt | 9 +- .../modules/list/ui/ModuleListView.kt | 73 +---- .../modules/progression/ModuleItemAsset.kt | 37 +++ .../progression/ModuleProgressionAdapter.kt | 31 ++ .../progression/ModuleProgressionFragment.kt | 194 ++++++++++++ .../ModuleProgressionRepository.kt | 59 ++++ .../progression/ModuleProgressionViewData.kt | 40 +++ .../progression/ModuleProgressionViewModel.kt | 117 ++++++++ .../commentlibrary/CommentLibraryViewModel.kt | 3 +- .../teacher/fragments/AddMessageFragment.kt | 40 ++- .../fragments/AssignmentDetailsFragment.kt | 34 ++- .../fragments/CourseBrowserEmptyFragment.kt | 14 +- .../fragments/CourseSettingsFragment.kt | 19 +- .../fragments/CreateDiscussionFragment.kt | 50 +++- .../CreateOrEditAnnouncementFragment.kt | 37 ++- .../CreateOrEditPageDetailsFragment.kt | 34 ++- .../fragments/DiscussionsReplyFragment.kt | 20 +- .../fragments/DiscussionsUpdateFragment.kt | 23 +- .../fragments/EditFileFolderFragment.kt | 39 ++- .../fragments/EditQuizDetailsFragment.kt | 36 ++- .../fragments/InternalWebViewFragment.kt | 66 ++++- .../teacher/fragments/PageDetailsFragment.kt | 27 +- .../teacher/fragments/ProfileEditFragment.kt | 27 +- .../teacher/fragments/QuizDetailsFragment.kt | 42 ++- .../teacher/fragments/SettingsFragment.kt | 22 +- .../fragments/SpeedGraderGradeFragment.kt | 30 +- .../teacher/fragments/ViewHtmlFragment.kt | 25 +- .../teacher/fragments/ViewImageFragment.kt | 31 +- .../teacher/fragments/ViewMediaFragment.kt | 78 ++++- .../teacher/fragments/ViewPdfFragment.kt | 32 +- .../fragments/ViewUnsupportedFileFragment.kt | 36 ++- .../teacher/router/RouteMatcher.kt | 62 +++- .../teacher/router/RouteResolver.kt | 3 + .../main/res/layout/fragment_file_details.xml | 30 ++ .../res/layout/fragment_internal_webview.xml | 1 + .../layout/fragment_module_progression.xml | 89 ++++++ .../layout/fragment_speed_grader_media.xml | 16 +- .../details/FileDetailsRepositoryTest.kt | 90 ++++++ .../files/details/FileDetailsViewModelTest.kt | 277 ++++++++++++++++++ .../{ => list}/ModuleListEffectHandlerTest.kt | 62 ++-- .../{ => list}/ModuleListPresenterTest.kt | 2 +- .../{ => list}/ModuleListUpdateTest.kt | 36 +-- .../ModuleProgressionRepositoryTest.kt | 119 ++++++++ .../ModuleProgressionViewModelTest.kt | 225 ++++++++++++++ .../canvasapi2/apis/FileFolderAPI.kt | 3 + .../instructure/canvasapi2/apis/ModuleAPI.kt | 3 + .../src/main/res/drawable/ic_chevron_left.xml | 11 +- .../main/res/drawable/ic_chevron_right.xml | 11 +- .../pandautils/binding/BindingAdapters.kt | 6 + .../fragments/BasePresenterFragment.kt | 12 +- .../instructure/pandautils/mvvm/ViewState.kt | 4 + 62 files changed, 2539 insertions(+), 385 deletions(-) create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/di/FileDetailsModule.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/di/ModuleProgressionModule.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/files/details/FileDetailsFragment.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/files/details/FileDetailsRepository.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/files/details/FileDetailsViewData.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/files/details/FileDetailsViewModel.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleItemAsset.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionAdapter.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionFragment.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionRepository.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewData.kt create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewModel.kt create mode 100644 apps/teacher/src/main/res/layout/fragment_file_details.xml create mode 100644 apps/teacher/src/main/res/layout/fragment_module_progression.xml create mode 100644 apps/teacher/src/test/java/com/instructure/teacher/features/files/details/FileDetailsRepositoryTest.kt create mode 100644 apps/teacher/src/test/java/com/instructure/teacher/features/files/details/FileDetailsViewModelTest.kt rename apps/teacher/src/test/java/com/instructure/teacher/unit/modules/{ => list}/ModuleListEffectHandlerTest.kt (86%) rename apps/teacher/src/test/java/com/instructure/teacher/unit/modules/{ => list}/ModuleListPresenterTest.kt (99%) rename apps/teacher/src/test/java/com/instructure/teacher/unit/modules/{ => list}/ModuleListUpdateTest.kt (95%) create mode 100644 apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionRepositoryTest.kt create mode 100644 apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionViewModelTest.kt diff --git a/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt b/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt index 9bf146ebba..7338f5d9a9 100644 --- a/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt @@ -127,8 +127,8 @@ class CourseModuleProgressionFragment : ParentFragment(), Bookmarkable { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.prevItem.background = ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_left, canvasContext.textAndIconColor) - binding.nextItem.background = ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_right, canvasContext.textAndIconColor) + binding.prevItem.setImageDrawable(ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_left, canvasContext.textAndIconColor)) + binding.nextItem.setImageDrawable(ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_right, canvasContext.textAndIconColor)) } override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/apps/student/src/main/res/layout/course_module_progression.xml b/apps/student/src/main/res/layout/course_module_progression.xml index 4918489d1c..59ac4aa00f 100644 --- a/apps/student/src/main/res/layout/course_module_progression.xml +++ b/apps/student/src/main/res/layout/course_module_progression.xml @@ -147,23 +147,25 @@ -