From 109586e6eeb6be737e2de465ff22dc9488ea7d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81kos=20Hermann?= Date: Fri, 16 Sep 2022 11:54:57 +0200 Subject: [PATCH 01/73] version bump --- apps/student/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 3fc93ed845..10be6c70f8 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -54,8 +54,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 242 - versionName = '6.19.0' + versionCode = 243 + versionName = '6.20.0' vectorDrawables.useSupportLibrary = true multiDexEnabled = true From 9979c2f2060ae9ada53d49318bac61ed4b5283bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81kos=20Hermann?= Date: Mon, 19 Sep 2022 13:48:00 +0200 Subject: [PATCH 02/73] fix color --- .../features/shareextension/ShareExtensionActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt index d1c073a9ce..eddd862fa4 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt @@ -226,7 +226,7 @@ abstract class ShareExtensionActivity : AppCompatActivity() { ViewStyler.setStatusBarDark(this, color) color } else { - val color = ContextCompat.getColor(this, R.color.login_studentAppTheme) + val color = ContextCompat.getColor(this, R.color.studentDocumentSharingColor) ViewStyler.setStatusBarDark(this, color) color } From e6caf6b2f4b071d4e55ee4782dd1913ac481b020 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Thu, 22 Sep 2022 14:08:26 +0200 Subject: [PATCH 03/73] [MBL-14427][Student] - Investigate collaborations e2e failures on FTL (#1719) * dummy commit to push branch to remote to try out on FTL. * Investigate collaborations e2e test. Picked up a ticket for VICE team (VICE-3157) because this test catches a screen size specific bug. (This will be breaking until they fix it) * Put KnownBug annotation to Collaborations E2E test until it has been fixed. --- .../student/ui/e2e/CollaborationsE2ETest.kt | 26 +++++++++---------- .../student/ui/pages/CollaborationsPage.kt | 13 +++------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt index 323fee40d5..6676a8a364 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt @@ -2,6 +2,7 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.KnownBug import com.instructure.panda_annotations.FeatureCategory import com.instructure.panda_annotations.Priority import com.instructure.panda_annotations.TestCategory @@ -13,6 +14,7 @@ import com.instructure.student.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test + /** * Very basic test to verify that the collaborations web page shows up correctly. * We make no attempt to actually start a collaboration. @@ -20,20 +22,16 @@ import org.junit.Test */ @HiltAndroidTest class CollaborationsE2ETest: StudentTest() { - override fun displaysPageObjects() { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } + override fun displaysPageObjects() = Unit - override fun enableAndConfigureAccessibilityChecks() { - //We don't want to see accessibility errors on E2E tests - } + override fun enableAndConfigureAccessibilityChecks() = Unit @E2E @Test + @KnownBug("https://instructure.atlassian.net/browse/VICE-3157") @TestMetaData(Priority.MANDATORY, FeatureCategory.COLLABORATIONS, TestCategory.E2E) fun testCollaborationsE2E() { - Log.d(PREPARATION_TAG,"Seeding data.") val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] @@ -50,11 +48,13 @@ class CollaborationsE2ETest: StudentTest() { Log.d(STEP_TAG,"Verify that various elements of the web page are present.") CollaborationsPage.assertCurrentCollaborationsHeaderPresent() - // For some reason, these aren't showing up when run in FTL, though they do - // show up when run locally (same server environment in each). I'll comment - // them out for now, with MBL-14427 being created to pursue the issue. -// CollaborationsPage.assertStartANewCollaborationPresent() -// CollaborationsPage.assertGoogleDocsChoicePresent() -// CollaborationsPage.assertGoogleDocsExplanationPresent() + //On some screen size, this spinner does not displayed at all, instead of it, + //there is a button on the top-right corner with the 'Start a new Collaboration' text + //and clicking on it will 'expand' and display this spinner. + //However, there is a bug (see link in this @KnownBug annotation) which is about the button not displayed on some screen size + //So this test will breaks until it this ticket will be fixed. + CollaborationsPage.assertStartANewCollaborationPresent() + CollaborationsPage.assertGoogleDocsChoicePresent() + CollaborationsPage.assertGoogleDocsExplanationPresent() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CollaborationsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CollaborationsPage.kt index 6f7528af03..ae3252b91b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CollaborationsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CollaborationsPage.kt @@ -16,17 +16,14 @@ */ package com.instructure.student.ui.pages -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.swipeUp -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.sugar.Web import androidx.test.espresso.web.webdriver.DriverAtoms import androidx.test.espresso.web.webdriver.DriverAtoms.getText import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.checkRepeat -import com.instructure.canvas.espresso.withElementRepeat import com.instructure.student.R import org.hamcrest.Matchers import org.hamcrest.Matchers.containsString @@ -44,12 +41,8 @@ object CollaborationsPage { } fun assertStartANewCollaborationPresent() { - - // Debug maneuver to help see what was being displayed - //onView(withId(R.id.canvasWebView)).perform(swipeUp()) - Web.onWebView(Matchers.allOf(withId(R.id.canvasWebView), isDisplayed())) - .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "h2")) // lucky there is only one of these! + .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "h2")) .perform(DriverAtoms.webScrollIntoView()) .checkRepeat(webMatches(getText(), containsString("Start a New Collaboration") ), 30) } From 915d5f1112d26441101e564e692e6e9740b1320b Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Thu, 22 Sep 2022 14:12:02 +0200 Subject: [PATCH 04/73] [MBL-16237][Parent] Excused does not appear in the app refs: MBL-16237 affects: Parent release note: Fixed a bug where Excused label was missing. --- .../lib/screens/courses/details/course_grades_screen.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/flutter_parent/lib/screens/courses/details/course_grades_screen.dart b/apps/flutter_parent/lib/screens/courses/details/course_grades_screen.dart index 62d53352ee..bbe10a671c 100644 --- a/apps/flutter_parent/lib/screens/courses/details/course_grades_screen.dart +++ b/apps/flutter_parent/lib/screens/courses/details/course_grades_screen.dart @@ -371,7 +371,10 @@ class _AssignmentRow extends StatelessWidget { final localizations = L10n(context); final submission = assignment.submission(studentId); - if (submission?.grade != null) { + if (submission?.excused ?? false) { + text = localizations.gradeFormatScoreOutOfPointsPossible(localizations.excused, points); + semantics = localizations.contentDescriptionScoreOutOfPointsPossible('', points); + } else if (submission?.grade != null) { text = localizations.gradeFormatScoreOutOfPointsPossible(submission.grade, points); semantics = localizations.contentDescriptionScoreOutOfPointsPossible(submission.grade, points); } else { From 658bf82da875e161d20bc902dac2e87f07245d03 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Thu, 22 Sep 2022 14:39:07 +0200 Subject: [PATCH 05/73] [MBL-16257][Student][Teacher] Calendar events for specific sections show for everyone in the app on the Syllabus refs: MBL-16257 affects: Student release note: Fixed a bug where events for specific sections show for everyone. * Fixed calendar events for specific sections * Fixed calendar events for specific sections in teacher too * Added test cases to cover isHidden flag --- .../student/mobius/syllabus/SyllabusPresenter.kt | 4 ++-- .../student/test/syllabus/SyllabusPresenterTest.kt | 11 +++++++++-- .../teacher/features/syllabus/SyllabusPresenter.kt | 2 +- .../features/syllabus/SyllabusPresenterTest.kt | 8 ++++++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/syllabus/SyllabusPresenter.kt b/apps/student/src/main/java/com/instructure/student/mobius/syllabus/SyllabusPresenter.kt index 7f97c7209e..ec8f72acb8 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/syllabus/SyllabusPresenter.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/syllabus/SyllabusPresenter.kt @@ -29,7 +29,7 @@ import com.instructure.student.mobius.syllabus.ui.EventsViewState import com.instructure.student.mobius.syllabus.ui.ScheduleItemViewState import com.instructure.student.mobius.syllabus.ui.SyllabusViewState import com.instructure.student.util.toDueAtString -import java.util.Date +import java.util.* object SyllabusPresenter : Presenter { override fun present(model: SyllabusModel, context: Context): SyllabusViewState { @@ -56,7 +56,7 @@ object SyllabusPresenter : Presenter { eventsResult.isFail -> EventsViewState.Error eventsResult.dataOrNull.isNullOrEmpty() -> EventsViewState.Empty else -> { - EventsViewState.Loaded(eventsResult.dataOrThrow.map { + EventsViewState.Loaded(eventsResult.dataOrThrow.filter { it.isHidden.not() }.map { ScheduleItemViewState( it.itemId, it.title ?: "", diff --git a/apps/student/src/test/java/com/instructure/student/test/syllabus/SyllabusPresenterTest.kt b/apps/student/src/test/java/com/instructure/student/test/syllabus/SyllabusPresenterTest.kt index fa77647763..81cbf3bf85 100644 --- a/apps/student/src/test/java/com/instructure/student/test/syllabus/SyllabusPresenterTest.kt +++ b/apps/student/src/test/java/com/instructure/student/test/syllabus/SyllabusPresenterTest.kt @@ -39,8 +39,7 @@ import org.threeten.bp.DateTimeUtils import org.threeten.bp.OffsetDateTime import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter -import java.util.Calendar -import java.util.Date +import java.util.* @RunWith(AndroidJUnit4::class) class SyllabusPresenterTest : Assert() { @@ -79,6 +78,14 @@ class SyllabusPresenterTest : Assert() { assignment = Assignment(id = 125L, submissionTypesRaw = listOf("discussion_topic")), startAt = null, itemType = ScheduleItem.Type.TYPE_ASSIGNMENT + ), + ScheduleItem( + itemId = "4", + title = "discussion", + assignment = Assignment(id = 126L, submissionTypesRaw = listOf("discussion_topic")), + startAt = null, + itemType = ScheduleItem.Type.TYPE_ASSIGNMENT, + isHidden = true ) ) private lateinit var baseEventsViewState: List diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/SyllabusPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/SyllabusPresenter.kt index 8c58ced7d7..19f3711028 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/SyllabusPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/SyllabusPresenter.kt @@ -67,7 +67,7 @@ class SyllabusPresenter : Presenter { } private fun createLoadedEvents(eventsResult: DataResult>, context: Context, color: Int): EventsViewState.Loaded { - return EventsViewState.Loaded(eventsResult.dataOrThrow.map { + return EventsViewState.Loaded(eventsResult.dataOrThrow.filter { it.isHidden.not() }.map { ScheduleItemViewState( it.itemId, it.title ?: "", diff --git a/apps/teacher/src/test/java/com/instructure/teacher/features/syllabus/SyllabusPresenterTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/features/syllabus/SyllabusPresenterTest.kt index 91da53c104..9c71e19974 100644 --- a/apps/teacher/src/test/java/com/instructure/teacher/features/syllabus/SyllabusPresenterTest.kt +++ b/apps/teacher/src/test/java/com/instructure/teacher/features/syllabus/SyllabusPresenterTest.kt @@ -79,6 +79,14 @@ class SyllabusPresenterTest { assignment = Assignment(id = 125L, submissionTypesRaw = listOf("discussion_topic")), startAt = null, itemType = ScheduleItem.Type.TYPE_ASSIGNMENT + ), + ScheduleItem( + itemId = "4", + title = "discussion", + assignment = Assignment(id = 126L, submissionTypesRaw = listOf("discussion_topic")), + startAt = null, + itemType = ScheduleItem.Type.TYPE_ASSIGNMENT, + isHidden = true ) ) From e32bb48c3f1d58f4a3a75a65431e007ed167d17b Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Thu, 22 Sep 2022 14:46:05 +0200 Subject: [PATCH 06/73] [MBL-16247][Parent] App repetitively crashes on attachment download refs: MBL-16247 affects: Parent release note: Fixed a crash when downloading inbox attachments. --- .../android/app/src/main/AndroidManifest.xml | 10 ++++++++++ apps/flutter_parent/lib/main.dart | 11 +++++++++++ .../lib/utils/veneers/flutter_downloader_veneer.dart | 5 +++-- apps/flutter_parent/pubspec.lock | 2 +- apps/flutter_parent/pubspec.yaml | 2 +- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/flutter_parent/android/app/src/main/AndroidManifest.xml b/apps/flutter_parent/android/app/src/main/AndroidManifest.xml index 88c5a4cae2..f4245b5921 100644 --- a/apps/flutter_parent/android/app/src/main/AndroidManifest.xml +++ b/apps/flutter_parent/android/app/src/main/AndroidManifest.xml @@ -89,5 +89,15 @@ + + + + diff --git a/apps/flutter_parent/lib/main.dart b/apps/flutter_parent/lib/main.dart index 1140648ed2..102b50fc81 100644 --- a/apps/flutter_parent/lib/main.dart +++ b/apps/flutter_parent/lib/main.dart @@ -14,6 +14,8 @@ import 'dart:async'; import 'dart:io'; +import 'dart:isolate'; +import 'dart:ui'; import 'package:device_info/device_info.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -49,6 +51,8 @@ void main() async { ]); PandaRouter.init(); + await FlutterDownloader.registerCallback(downloadCallback); + // This completer waits for the app to be built before allowing the notificationUtil to handle notifications final Completer _appCompleter = Completer(); NotificationUtil.init(_appCompleter); @@ -68,3 +72,10 @@ void main() async { runApp(ParentApp(_appCompleter)); }, FirebaseCrashlytics.instance.recordError); } + +@pragma('vm:entry-point') +void downloadCallback(String id, DownloadTaskStatus status, int progress) { + final SendPort send = + IsolateNameServer.lookupPortByName('downloader_send_port'); + send.send([id, status, progress]); +} diff --git a/apps/flutter_parent/lib/utils/veneers/flutter_downloader_veneer.dart b/apps/flutter_parent/lib/utils/veneers/flutter_downloader_veneer.dart index 7933ed64db..1fdd0c3ced 100644 --- a/apps/flutter_parent/lib/utils/veneers/flutter_downloader_veneer.dart +++ b/apps/flutter_parent/lib/utils/veneers/flutter_downloader_veneer.dart @@ -24,7 +24,7 @@ class FlutterDownloaderVeneer { bool showNotification = true, bool openFileFromNotification = true, bool requiresStorageNotLow = true, - }) => + bool saveInPublicStorage = true}) => FlutterDownloader.enqueue( url: url, savedDir: savedDir, @@ -32,7 +32,8 @@ class FlutterDownloaderVeneer { headers: headers, showNotification: showNotification, openFileFromNotification: openFileFromNotification, - requiresStorageNotLow: requiresStorageNotLow); + requiresStorageNotLow: requiresStorageNotLow, + saveInPublicStorage: saveInPublicStorage); Future> loadTasks() => FlutterDownloader.loadTasks(); diff --git a/apps/flutter_parent/pubspec.lock b/apps/flutter_parent/pubspec.lock index fc0fa4e97d..5f436e3e84 100644 --- a/apps/flutter_parent/pubspec.lock +++ b/apps/flutter_parent/pubspec.lock @@ -425,7 +425,7 @@ packages: name: flutter_downloader url: "https://pub.dartlang.org" source: hosted - version: "1.7.1" + version: "1.7.4" flutter_driver: dependency: "direct dev" description: flutter diff --git a/apps/flutter_parent/pubspec.yaml b/apps/flutter_parent/pubspec.yaml index 0b2485a37e..3e630126ce 100644 --- a/apps/flutter_parent/pubspec.yaml +++ b/apps/flutter_parent/pubspec.yaml @@ -58,7 +58,7 @@ dependencies: # File handling path_provider: ^2.0.6 - flutter_downloader: ^1.7.1 + flutter_downloader: 1.7.4 mime: ^1.0.1 file_picker: ^4.2.0 From c4e3538d610bf1db0e66f6b6c66db92f1fd3662c Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:58:53 +0200 Subject: [PATCH 07/73] [MBL-15647][Student][Teacher] Image loading library dependency update refs: MBL-15647 affects: Student, Teacher release note: none * WIP: Remove unnecessary image loading libraries * WIP: image library dependecy update * WIP: image library dependecy update * WIP: image library dependency update * Fixed comments * minor changes * Fixed border issue * Fixed border issue --- .../student/activity/NavigationActivity.kt | 18 +--- .../student/fragment/PeopleDetailsFragment.kt | 2 +- .../student/holders/InboxMessageHolder.kt | 4 +- .../student/holders/PeopleViewHolder.kt | 7 +- .../student/holders/RecipientViewHolder.kt | 7 +- .../drawer/comments/ui/views/CommentView.kt | 2 +- .../student/view/AttachmentView.kt | 37 +++------ .../main/res/layout/dialog_file_upload.xml | 5 +- .../layout/fragment_assignment_details.xml | 2 +- .../layout/fragment_discussions_details.xml | 2 +- .../res/layout/fragment_people_details.xml | 7 +- .../src/main/res/layout/navigation_drawer.xml | 5 +- .../src/main/res/layout/view_comment.xml | 2 +- .../src/main/res/layout/viewholder_inbox.xml | 2 +- .../main/res/layout/viewholder_message.xml | 2 +- .../src/main/res/layout/viewholder_people.xml | 5 +- .../main/res/layout/viewholder_recipient.xml | 6 +- .../teacher/activities/InitActivity.kt | 18 +--- .../teacher/binders/MessageBinder.kt | 2 +- .../instructure/teacher/binders/UserBinder.kt | 2 +- .../teacher/fragments/ProfileEditFragment.kt | 28 +------ .../teacher/fragments/ProfileFragment.kt | 26 +----- .../teacher/holders/AssigneeViewHolder.kt | 31 ++----- .../teacher/holders/AttendanceViewHolder.kt | 2 +- .../teacher/holders/RecipientViewHolder.kt | 7 +- .../teacher/utils/ViewExtensions.kt | 6 +- .../instructure/teacher/view/CommentView.kt | 2 +- .../view/FileFolderPublishedStatusIconView.kt | 8 +- .../view_submission_content.xml | 2 +- .../src/main/res/layout/adapter_assignee.xml | 10 ++- .../main/res/layout/adapter_attendance.xml | 2 +- .../adapter_gradeable_student_submission.xml | 2 +- .../src/main/res/layout/adapter_inbox.xml | 2 +- .../src/main/res/layout/adapter_message.xml | 2 +- .../adapter_speed_grader_group_member.xml | 2 +- .../src/main/res/layout/adapter_users.xml | 2 +- .../layout/fragment_discussions_details.xml | 2 +- .../src/main/res/layout/fragment_profile.xml | 9 +- .../main/res/layout/fragment_profile_edit.xml | 9 +- .../res/layout/fragment_student_context.xml | 2 +- .../src/main/res/layout/navigation_drawer.xml | 5 +- .../src/main/res/layout/view_comment.xml | 2 +- .../res/layout/view_submission_content.xml | 2 +- .../main/res/layout/viewholder_recipient.xml | 6 +- .../login/adapter/PreviousUsersAdapter.kt | 5 +- .../res/layout/adapter_previous_users.xml | 8 +- .../drawable/avatar_circular_border_thick.xml | 6 ++ .../drawable/avatar_circular_border_thin.xml | 6 ++ libs/pandares/src/main/res/values/dimens.xml | 2 + libs/pandautils/build.gradle | 4 - .../pandautils/utils/ProfileUtils.kt | 83 ++++--------------- .../pandautils/utils/ViewExtensions.kt | 69 ++++++++++----- .../pandautils/views/AttachmentView.kt | 35 +++----- .../adapter_recipient_search_result.xml | 2 +- 54 files changed, 198 insertions(+), 330 deletions(-) create mode 100644 libs/pandares/src/main/res/drawable/avatar_circular_border_thick.xml create mode 100644 libs/pandares/src/main/res/drawable/avatar_circular_border_thin.xml diff --git a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt index ed14703185..c5311a36c8 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt @@ -419,23 +419,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. if (user != null) { navigationDrawerUserName.text = Pronouns.span(user.shortName, user.pronouns) navigationDrawerUserEmail.text = user.primaryEmail - - if(ProfileUtils.shouldLoadAltAvatarImage(user.avatarUrl)) { - val initials = ProfileUtils.getUserInitials(user.shortName ?: "") - val color = ContextCompat.getColor(context, R.color.textDark) - val drawable = TextDrawable.builder() - .beginConfig() - .height(context.resources.getDimensionPixelSize(R.dimen.profileAvatarSize)) - .width(context.resources.getDimensionPixelSize(R.dimen.profileAvatarSize)) - .toUpperCase() - .useFont(Typeface.DEFAULT_BOLD) - .textColor(color) - .endConfig() - .buildRound(initials, Color.WHITE) - navigationDrawerProfileImage.setImageDrawable(drawable) - } else { - Glide.with(context).load(user.avatarUrl).into(navigationDrawerProfileImage) - } + ProfileUtils.loadAvatarForUser(navigationDrawerProfileImage, user.shortName, user.avatarUrl) } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/PeopleDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/PeopleDetailsFragment.kt index 80d28715af..27921afa79 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/PeopleDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/PeopleDetailsFragment.kt @@ -116,7 +116,7 @@ class PeopleDetailsFragment : ParentFragment(), Bookmarkable { private fun setupUserViews() { user?.let { u -> - ProfileUtils.loadAvatarForUser(avatar, u) + ProfileUtils.loadAvatarForUser(avatar, u.name, u.avatarUrl) userName.text = Pronouns.span(u.name, u.pronouns) userRole.text = u.enrollments.distinctBy { it.displayType }.joinToString { it.displayType } userBackground.setBackgroundColor(ColorKeeper.getOrGenerateColor(canvasContext)) diff --git a/apps/student/src/main/java/com/instructure/student/holders/InboxMessageHolder.kt b/apps/student/src/main/java/com/instructure/student/holders/InboxMessageHolder.kt index 0ea1ea38e7..a4fac3b5c3 100644 --- a/apps/student/src/main/java/com/instructure/student/holders/InboxMessageHolder.kt +++ b/apps/student/src/main/java/com/instructure/student/holders/InboxMessageHolder.kt @@ -37,7 +37,7 @@ import com.instructure.student.interfaces.MessageAdapterCallback import com.instructure.student.view.ViewUtils import kotlinx.android.synthetic.main.viewholder_message.view.* import java.text.SimpleDateFormat -import java.util.Locale +import java.util.* class InboxMessageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -52,7 +52,7 @@ class InboxMessageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { // Set author info if (author != null) { authorName.text = getAuthorTitle(author.id, conversation, message) - ProfileUtils.loadAvatarForUser(authorAvatar, author) + ProfileUtils.loadAvatarForUser(authorAvatar, author.name, author.avatarUrl) authorAvatar.setupAvatarA11y(author.name) authorAvatar.onClick { callback.onAvatarClicked(author) } } else { diff --git a/apps/student/src/main/java/com/instructure/student/holders/PeopleViewHolder.kt b/apps/student/src/main/java/com/instructure/student/holders/PeopleViewHolder.kt index d9ccbff790..0d6c00c201 100644 --- a/apps/student/src/main/java/com/instructure/student/holders/PeopleViewHolder.kt +++ b/apps/student/src/main/java/com/instructure/student/holders/PeopleViewHolder.kt @@ -16,6 +16,7 @@ */ package com.instructure.student.holders +import android.content.res.ColorStateList import android.view.View import androidx.recyclerview.widget.RecyclerView import com.instructure.canvasapi2.models.User @@ -25,8 +26,8 @@ import com.instructure.pandautils.utils.ProfileUtils import com.instructure.pandautils.utils.setGone import com.instructure.pandautils.utils.setVisible import com.instructure.student.R -import com.instructure.student.util.BinderUtils import com.instructure.student.interfaces.AdapterToFragmentCallback +import com.instructure.student.util.BinderUtils import kotlinx.android.synthetic.main.viewholder_people.view.* class PeopleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -37,11 +38,11 @@ class PeopleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { isFirstItem: Boolean, isLastItem: Boolean ) = with(itemView) { - ProfileUtils.loadAvatarForUser(icon, item) + ProfileUtils.loadAvatarForUser(icon, item.name, item.avatarUrl, 0) + icon.backgroundTintList = ColorStateList.valueOf(courseColor) itemView.setOnClickListener { adapterToFragmentCallback.onRowClicked(item, adapterPosition, true) } - icon.borderColor = courseColor title.text = Pronouns.span(item.name, item.pronouns) val enrollmentIndex = item.enrollmentIndex diff --git a/apps/student/src/main/java/com/instructure/student/holders/RecipientViewHolder.kt b/apps/student/src/main/java/com/instructure/student/holders/RecipientViewHolder.kt index d7f66f2f65..f40440525b 100644 --- a/apps/student/src/main/java/com/instructure/student/holders/RecipientViewHolder.kt +++ b/apps/student/src/main/java/com/instructure/student/holders/RecipientViewHolder.kt @@ -16,9 +16,10 @@ package com.instructure.student.holders import android.content.Context +import android.content.res.ColorStateList import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.view.View +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.instructure.canvasapi2.models.Recipient import com.instructure.canvasapi2.utils.Pronouns @@ -47,7 +48,9 @@ class RecipientViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun setChecked(isChecked: Boolean = true) { if (isChecked) { setBackgroundColor(selectionColor and selectionTransparencyMask) - avatar.setImageDrawable(ColorDrawable(selectionColor)) + avatar.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle)?.apply { + mutate().setTintList(ColorStateList.valueOf(selectionColor)) + }) checkMarkImageView.setVisible() ColorUtils.colorIt(Color.WHITE, checkMarkImageView) } else { diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/comments/ui/views/CommentView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/comments/ui/views/CommentView.kt index d6808caac4..bb56a568c3 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/comments/ui/views/CommentView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/comments/ui/views/CommentView.kt @@ -57,7 +57,7 @@ class CommentView @JvmOverloads constructor( fun setCommentBubbleColor(@ColorInt color: Int) = commentTextView.setBubbleColor(color) fun setAvatar(avatarUrl: String?, userName: String) { - ProfileUtils.loadAvatarForUser(avatarView, userName, avatarUrl ?: "") + ProfileUtils.loadAvatarForUser(avatarView, userName, avatarUrl.orEmpty()) } var commentTextColor: Int diff --git a/apps/student/src/main/java/com/instructure/student/view/AttachmentView.kt b/apps/student/src/main/java/com/instructure/student/view/AttachmentView.kt index c6f0cc0063..987660b3bd 100644 --- a/apps/student/src/main/java/com/instructure/student/view/AttachmentView.kt +++ b/apps/student/src/main/java/com/instructure/student/view/AttachmentView.kt @@ -16,19 +16,18 @@ package com.instructure.student.view import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import androidx.core.content.ContextCompat +import android.graphics.PorterDuff import android.view.LayoutInflater import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView -import com.instructure.student.R +import androidx.core.content.ContextCompat +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions import com.instructure.canvasapi2.models.Attachment import com.instructure.canvasapi2.models.RemoteFile import com.instructure.pandautils.utils.onClick -import com.squareup.picasso.Picasso -import com.squareup.picasso.Transformation +import com.instructure.student.R import kotlinx.android.synthetic.main.view_attachment.view.* import java.io.File @@ -93,27 +92,17 @@ class AttachmentView(context: Context) : FrameLayout(context) { private fun setThumbnail(path: String?) { if (path.isNullOrBlank()) return val file = File(path) - val picasso = Picasso.with(context) - val creator = if (file.exists() && file.isFile) picasso.load(file) else picasso.load(path) - creator.fit().centerCrop().transform(ATTACHMENT_PREVIEW_TRANSFORMER).into(previewImage) + Glide.with(context) + .load(if (file.exists() && file.isFile) file else path) + .apply(RequestOptions.centerCropTransform()) + .into(previewImage) + previewImage.setColorFilter( + 0xBB9B9B9B.toInt(), + PorterDuff.Mode.SRC_OVER + ) } companion object { - /** Picasso transformation to apply gray overlay on thumbnail */ - @JvmField - val ATTACHMENT_PREVIEW_TRANSFORMER: Transformation = object : Transformation { - override fun transform(source: Bitmap?): Bitmap? { - if (source == null) return null - val mutableSource = source.copy(source.config, true) - source.recycle() - val canvas = Canvas(mutableSource) - canvas.drawColor(0xBB9B9B9B.toInt()) - return mutableSource - } - - override fun key(): String = "gray-overlay" - } - fun setColorAndIcon( context: Context, contentType: String?, diff --git a/apps/student/src/main/res/layout/dialog_file_upload.xml b/apps/student/src/main/res/layout/dialog_file_upload.xml index ad2de6981d..cadcbd4893 100644 --- a/apps/student/src/main/res/layout/dialog_file_upload.xml +++ b/apps/student/src/main/res/layout/dialog_file_upload.xml @@ -37,12 +37,13 @@ android:gravity="center_vertical" android:paddingBottom="16dp"> - + android:background="@drawable/ic_circle" + android:visibility="invisible" /> - - - + android:layout_marginTop="16dp" + android:background="@android:color/transparent" /> - + android:importantForAccessibility="no" /> diff --git a/apps/student/src/main/res/layout/view_comment.xml b/apps/student/src/main/res/layout/view_comment.xml index cb38fedc87..04657f3dd4 100644 --- a/apps/student/src/main/res/layout/view_comment.xml +++ b/apps/student/src/main/res/layout/view_comment.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - - + android:layout_height="40dp" /> , grantResults: IntArray) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ProfileFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ProfileFragment.kt index 1ced17ab4f..dedf681f8f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ProfileFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ProfileFragment.kt @@ -16,13 +16,9 @@ */ package com.instructure.teacher.fragments -import android.graphics.Color -import android.graphics.Typeface import android.os.Bundle -import android.util.TypedValue import android.view.MenuItem import android.view.View -import com.bumptech.glide.Glide import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.Pronouns @@ -35,7 +31,6 @@ import com.instructure.teacher.R import com.instructure.teacher.dialog.NoInternetConnectionDialog import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.adoptToolbarStyle -import com.instructure.teacher.utils.getColorCompat import com.instructure.teacher.utils.setupBackButtonAsBackPressedOnly import com.instructure.teacher.utils.setupMenu import kotlinx.android.synthetic.main.fragment_profile.* @@ -70,26 +65,7 @@ class ProfileFragment : BaseFragment() { private fun setupViewableData() { val user = ApiPrefs.user - - if(ProfileUtils.shouldLoadAltAvatarImage(user?.avatarUrl)) { - val initials = ProfileUtils.getUserInitials(user?.shortName ?: "") - val color = requireContext().getColorCompat(R.color.backgroundDark) - val drawable = TextDrawable.builder() - .beginConfig() - .height(requireContext().resources.getDimensionPixelSize(R.dimen.profileAvatarSize)) - .width(requireContext().resources.getDimensionPixelSize(R.dimen.profileAvatarSize)) - .toUpperCase() - .useFont(Typeface.DEFAULT_BOLD) - .textColor(color) - .endConfig() - .buildRound(initials, Color.WHITE) - usersAvatar.borderColor = requireContext().getColorCompat(R.color.borderDark) - usersAvatar.borderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6F, requireContext().resources.displayMetrics).toInt() - usersAvatar.setImageDrawable(drawable) - } else { - Glide.with(requireContext()).load(user?.avatarUrl).into(usersAvatar) - } - + ProfileUtils.loadAvatarForUser(usersAvatar, user?.shortName, user?.avatarUrl, 0) usersName.text = Pronouns.span(user?.shortName, user?.pronouns) usersEmail.text = user?.primaryEmail usersBio.text = user?.bio diff --git a/apps/teacher/src/main/java/com/instructure/teacher/holders/AssigneeViewHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/holders/AssigneeViewHolder.kt index c2f5a3eb0c..78f915bda9 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/holders/AssigneeViewHolder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/holders/AssigneeViewHolder.kt @@ -15,12 +15,10 @@ */ package com.instructure.teacher.holders -import android.content.Context import android.graphics.Color -import android.graphics.Typeface import android.graphics.drawable.ColorDrawable -import android.util.TypedValue import android.view.View +import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.instructure.canvasapi2.models.Group @@ -28,15 +26,12 @@ import com.instructure.canvasapi2.models.Section import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.Pronouns import com.instructure.pandautils.utils.ProfileUtils -import com.instructure.pandautils.utils.TextDrawable import com.instructure.pandautils.utils.setGone import com.instructure.pandautils.utils.setVisible import com.instructure.teacher.R import com.instructure.teacher.models.AssigneeCategory import com.instructure.teacher.models.EveryoneAssignee import com.instructure.teacher.presenters.AssigneeListPresenter -import com.instructure.teacher.utils.getColorCompat -import de.hdodenhof.circleimageview.CircleImageView import kotlinx.android.synthetic.main.adapter_assignee.view.* import kotlinx.android.synthetic.main.adapter_assignee_header.view.* @@ -76,7 +71,7 @@ class AssigneeItemViewHolder(view: View) : AssigneeViewHolder(view) { if (presenter.isEveryone) { setChecked(true) } else { - setItemAvatar(context, itemName, assigneeAvatarImageView) + setItemAvatar(itemName, assigneeAvatarImageView) } setOnClickListener { presenter.toggleIsEveryone(adapterPosition) } } @@ -97,7 +92,7 @@ class AssigneeItemViewHolder(view: View) : AssigneeViewHolder(view) { if (item.id in presenter.selectedSections) { setChecked(true) } else { - setItemAvatar(context, item.name, assigneeAvatarImageView) + setItemAvatar(item.name, assigneeAvatarImageView) } setOnClickListener { presenter.toggleSection(item.id, adapterPosition) } } @@ -107,30 +102,16 @@ class AssigneeItemViewHolder(view: View) : AssigneeViewHolder(view) { if (item.id in presenter.selectedGroups) { setChecked(true) } else { - setItemAvatar(context, item.name ?: "", assigneeAvatarImageView) + setItemAvatar(item.name.orEmpty(), assigneeAvatarImageView) } setOnClickListener { presenter.toggleGroup(item.id, adapterPosition) } } } } - private fun setItemAvatar(context: Context, itemName: String, circleImageView: CircleImageView) { - val initials = ProfileUtils.getUserInitials(itemName) - val color = context.getColorCompat(R.color.textDark) - val drawable = TextDrawable.builder() - .beginConfig() - .height(context.resources.getDimensionPixelSize(com.instructure.pandautils.R.dimen.avatar_size)) - .width(context.resources.getDimensionPixelSize(com.instructure.pandautils.R.dimen.avatar_size)) - .toUpperCase() - .useFont(Typeface.DEFAULT_BOLD) - .textColor(color) - .endConfig() - .buildRound(initials, Color.TRANSPARENT) - circleImageView.borderColor = color - circleImageView.borderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.resources.displayMetrics).toInt() - circleImageView.setImageDrawable(drawable) + private fun setItemAvatar(itemName: String, imageView: ImageView) { + ProfileUtils.loadAvatarForUser(imageView, itemName, null, 0) } - } class AssigneeTypeViewHolder(view: View) : AssigneeViewHolder(view) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/holders/AttendanceViewHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/holders/AttendanceViewHolder.kt index 7919cdf299..bf7287fcba 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/holders/AttendanceViewHolder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/holders/AttendanceViewHolder.kt @@ -44,7 +44,7 @@ class AttendanceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { basicUser.name = attendance.student?.name basicUser.pronouns = attendance.student?.pronouns basicUser.avatarUrl = attendance.student?.avatarUrl - ProfileUtils.loadAvatarForUser(studentAvatar, basicUser) + ProfileUtils.loadAvatarForUser(studentAvatar, basicUser.name, basicUser.avatarUrl) // Set student name userName.text = attendance.student?.let { Pronouns.span(it.name, it.pronouns) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/holders/RecipientViewHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/holders/RecipientViewHolder.kt index 9b14eafd8a..850960379e 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/holders/RecipientViewHolder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/holders/RecipientViewHolder.kt @@ -16,9 +16,10 @@ package com.instructure.teacher.holders import android.content.Context +import android.content.res.ColorStateList import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.view.View +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.instructure.canvasapi2.models.Recipient import com.instructure.canvasapi2.utils.Pronouns @@ -39,7 +40,9 @@ class RecipientViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun setChecked(isChecked: Boolean = true) { if (isChecked) { setBackgroundColor(selectionColor and SELECTION_TRANSPARENCY_MASK) - avatar.setImageDrawable(ColorDrawable(selectionColor)) + avatar.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle)?.apply { + mutate().setTintList(ColorStateList.valueOf(selectionColor)) + }) checkMarkImageView.setVisible() ColorUtils.colorIt(Color.WHITE, checkMarkImageView) } else { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/ViewExtensions.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/ViewExtensions.kt index 1a74d6109b..3d6bfbd01a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/ViewExtensions.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/ViewExtensions.kt @@ -25,6 +25,7 @@ import android.text.SpannableString import android.text.style.URLSpan import android.view.MenuItem import android.view.View +import android.widget.ImageView import android.widget.TextView import androidx.annotation.DrawableRes import androidx.annotation.MenuRes @@ -37,12 +38,9 @@ import com.instructure.pandautils.utils.isTablet import com.instructure.pandautils.utils.requestAccessibilityFocus import com.instructure.teacher.R import com.instructure.teacher.activities.InternalWebViewActivity -import com.instructure.teacher.mobius.common.ui.MobiusView import com.instructure.teacher.router.RouteMatcher -import de.hdodenhof.circleimageview.CircleImageView - -fun CircleImageView.setAnonymousAvatar() = setImageResource(R.drawable.ic_user_avatar) +fun ImageView.setAnonymousAvatar() = setImageResource(R.drawable.ic_user_avatar) /** * Loads the given resource as this Toolbar's icon, assigns it the given content description, and diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/CommentView.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/CommentView.kt index b0cb286ae9..c7a5b7266f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/view/CommentView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/view/CommentView.kt @@ -47,7 +47,7 @@ class CommentView @JvmOverloads constructor( fun setCommentBubbleColor(@ColorInt color: Int) = commentTextView.setBubbleColor(color) fun setAvatar(avatarUrl: String?, userName: String) { - ProfileUtils.loadAvatarForUser(avatarView, userName, avatarUrl ?: "") + ProfileUtils.loadAvatarForUser(avatarView, userName, avatarUrl.orEmpty()) } var commentTextColor: Int diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/FileFolderPublishedStatusIconView.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/FileFolderPublishedStatusIconView.kt index 455d445dae..4162596c1e 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/view/FileFolderPublishedStatusIconView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/view/FileFolderPublishedStatusIconView.kt @@ -17,17 +17,17 @@ package com.instructure.teacher.view import android.content.Context -import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat import android.util.AttributeSet import android.view.Gravity import android.widget.FrameLayout import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import com.bumptech.glide.Glide import com.instructure.canvasapi2.models.FileFolder import com.instructure.pandautils.utils.DP import com.instructure.teacher.R import com.instructure.teacher.utils.getColorCompat -import com.squareup.picasso.Picasso class FileFolderPublishedStatusIconView @JvmOverloads constructor( context: Context, @@ -108,7 +108,7 @@ class FileFolderPublishedStatusIconView @JvmOverloads constructor( } fun setImage(thumbnailUrl: String) { - Picasso.with(context).load(thumbnailUrl).into(mAssignmentIcon) + Glide.with(context).load(thumbnailUrl).into(mAssignmentIcon) } fun setPublishedStatus(fileFolder: FileFolder) { diff --git a/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml b/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml index 197809466d..bae44b516c 100644 --- a/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml +++ b/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml @@ -74,7 +74,7 @@ android:gravity="center_vertical" android:orientation="horizontal"> - - + android:background="@drawable/avatar_circular_border_thin" + android:backgroundTint="@color/textDark" + android:padding="@dimen/avatar_border_width_thin" /> + app:srcCompat="@drawable/ic_checkmark" + app:tint="@color/white" /> diff --git a/apps/teacher/src/main/res/layout/adapter_attendance.xml b/apps/teacher/src/main/res/layout/adapter_attendance.xml index f8226ed635..811c150472 100644 --- a/apps/teacher/src/main/res/layout/adapter_attendance.xml +++ b/apps/teacher/src/main/res/layout/adapter_attendance.xml @@ -26,7 +26,7 @@ android:paddingTop="4dp" android:paddingBottom="4dp"> - - - - - - - - + android:background="@drawable/avatar_circular_border_thick" + android:backgroundTint="@color/textDark" + android:padding="@dimen/avatar_border_width_thick" /> - + android:background="@drawable/avatar_circular_border_thick" + android:backgroundTint="@color/textDark" + android:padding="@dimen/avatar_border_width_thick" /> - - + android:importantForAccessibility="no" /> diff --git a/apps/teacher/src/main/res/layout/view_comment.xml b/apps/teacher/src/main/res/layout/view_comment.xml index e98a0df40e..eae5da9432 100644 --- a/apps/teacher/src/main/res/layout/view_comment.xml +++ b/apps/teacher/src/main/res/layout/view_comment.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - + android:layout_height="40dp" /> , @@ -63,7 +62,7 @@ class PreviousUsersAdapter( class PreviousUserHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(user: SignedInUser, onUserClick: () -> Unit, onUserRemove: () -> Unit) = with(itemView) { - loadAvatarForUser(usersAvatar, user.user.name, user.user.avatarUrl) + ProfileUtils.loadAvatarForUser(usersAvatar, user.user.name, user.user.avatarUrl, 0) userName.text = span(user.user.name, user.user.pronouns) schoolDomain.text = user.domain setOnClickListener { onUserClick() } diff --git a/libs/login-api-2/src/main/res/layout/adapter_previous_users.xml b/libs/login-api-2/src/main/res/layout/adapter_previous_users.xml index 6a97e13a08..e2fdbb316c 100644 --- a/libs/login-api-2/src/main/res/layout/adapter_previous_users.xml +++ b/libs/login-api-2/src/main/res/layout/adapter_previous_users.xml @@ -26,13 +26,15 @@ android:paddingEnd="16dp" android:background="?android:selectableItemBackground"> - + android:layout_centerVertical="true" + android:background="@drawable/avatar_circular_border_thin" + android:backgroundTint="@color/backgroundMedium" + android:padding="@dimen/avatar_border_width_thin" /> + + + + \ No newline at end of file diff --git a/libs/pandares/src/main/res/drawable/avatar_circular_border_thin.xml b/libs/pandares/src/main/res/drawable/avatar_circular_border_thin.xml new file mode 100644 index 0000000000..a8e278dbf1 --- /dev/null +++ b/libs/pandares/src/main/res/drawable/avatar_circular_border_thin.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/libs/pandares/src/main/res/values/dimens.xml b/libs/pandares/src/main/res/values/dimens.xml index 8c64099c82..73c8d44998 100644 --- a/libs/pandares/src/main/res/values/dimens.xml +++ b/libs/pandares/src/main/res/values/dimens.xml @@ -19,4 +19,6 @@ 127dp 195dp 60dp + 3dp + .5dp \ No newline at end of file diff --git a/libs/pandautils/build.gradle b/libs/pandautils/build.gradle index 823116b65c..265c313bec 100644 --- a/libs/pandautils/build.gradle +++ b/libs/pandautils/build.gradle @@ -122,10 +122,6 @@ dependencies { api Libs.ANDROID_SVG - // TODO These should be replaced in the future with Glide. - api 'com.squareup.picasso:picasso:2.5.2' - api 'de.hdodenhof:circleimageview:3.0.0' - api Libs.EXOPLAYER api (Libs.SCALE_IMAGE_VIEW) { exclude group: "androidx.exifinterface" diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt index 7e13316a7e..71ab242fe7 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt @@ -26,21 +26,12 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView +import androidx.annotation.Dimension import androidx.core.content.ContextCompat -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import com.instructure.canvasapi2.models.Author import com.instructure.canvasapi2.models.BasicUser import com.instructure.canvasapi2.models.Conversation -import com.instructure.canvasapi2.models.User import com.instructure.pandautils.R -import com.squareup.picasso.Callback -import com.squareup.picasso.Picasso -import de.hdodenhof.circleimageview.CircleImageView -import java.util.Locale +import java.util.* object ProfileUtils { @@ -66,66 +57,22 @@ object ProfileUtils { } } - fun loadAvatarForUser(avatar: CircleImageView, user: User) { - loadAvatarForUser(avatar, user.name, user.avatarUrl) - } - - fun loadAvatarForUser(avatar: CircleImageView, user: BasicUser) { - loadAvatarForUser(avatar, user.name, user.avatarUrl) - } - - fun loadAvatarForUser(avatar: CircleImageView, user: Author) { - loadAvatarForUser(avatar, user.displayName, user.avatarImageUrl) - } - - fun loadAvatarForUser(avatar: CircleImageView, name: String?, url: String?) { - val context = avatar.context - if (shouldLoadAltAvatarImage(url)) { - Picasso.with(context).cancelRequest(avatar) - avatar.setAvatarImage(context, name) - } else { - Picasso.with(context) - .load(url) - .fit() - .placeholder(R.drawable.recipient_avatar_placeholder) - .centerCrop() - .into(avatar, object : Callback { - override fun onSuccess() {} - - override fun onError() { - avatar.setAvatarImage(context, name) - } - }) - } - } - - fun loadAvatarForUser(imageView: ImageView, name: String?, url: String?) { - val context = imageView.context + fun loadAvatarForUser( + imageView: ImageView, + name: String?, + url: String?, + @Dimension altAvatarBorderWidth: Int = imageView.context.resources.getDimension(R.dimen.avatar_border_width_thin).toInt() + ) { if (shouldLoadAltAvatarImage(url)) { - val avatarDrawable = createAvatarDrawable(context, name ?: "") - imageView.setImageDrawable(avatarDrawable) + imageView.setImageDrawable(createAvatarDrawable(imageView.context, name.orEmpty(), altAvatarBorderWidth)) } else { - Glide.with(imageView) - .load(url) - .placeholder(R.drawable.recipient_avatar_placeholder) - .circleCrop() - .addListener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { - val avatarDrawable = createAvatarDrawable(context, name ?: "") - imageView.setImageDrawable(avatarDrawable) - return false - } - - override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { - return false - } - - }) - .into(imageView) + imageView.loadCircularImage(url, R.drawable.recipient_avatar_placeholder) { + imageView.setImageDrawable(createAvatarDrawable(imageView.context, name.orEmpty(), altAvatarBorderWidth)) + } } } - private fun createAvatarDrawable(context: Context, userName: String): Drawable { + private fun createAvatarDrawable(context: Context, userName: String, @Dimension borderWidth: Int): Drawable { val initials = getUserInitials(userName) val color = ContextCompat.getColor(context, R.color.textDark) return TextDrawable.builder() @@ -135,7 +82,7 @@ object ProfileUtils { .toUpperCase() .useFont(Typeface.DEFAULT_BOLD) .textColor(color) - .withBorder(context.DP(0.5f).toInt()) + .withBorder(borderWidth) .withBorderColor(color) .endConfig() .buildRound(initials, Color.WHITE) @@ -156,7 +103,7 @@ object ProfileUtils { * If there are more than two participants, a group avatar will be shown and accessibility will be disabled. */ fun configureAvatarForConversation( - avatar: CircleImageView, + avatar: ImageView, conversation: Conversation, onClick: ((BasicUser) -> Unit)? = null ) { 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 6dcac5a1f0..c5171d111e 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 @@ -54,10 +54,14 @@ import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target import com.bumptech.glide.signature.ObjectKey import com.instructure.canvasapi2.models.Attachment import com.instructure.canvasapi2.models.Course @@ -68,10 +72,9 @@ import com.instructure.canvasapi2.utils.tryOrNull import com.instructure.canvasapi2.utils.weave.WeaveJob import com.instructure.canvasapi2.utils.weave.weave import com.instructure.pandautils.R -import de.hdodenhof.circleimageview.CircleImageView import kotlinx.android.synthetic.main.abc_search_view.view.* import kotlinx.coroutines.delay -import java.util.Locale +import java.util.* /** Convenience extension for setting a click listener */ @Suppress("NOTHING_TO_INLINE") @@ -505,24 +508,6 @@ fun View.clearAvatarA11y() { setAccessibilityDelegate(null) } -@JvmName("setUserAvatarImage") -fun CircleImageView.setAvatarImage(context: Context, userName: String?) { - val initials = ProfileUtils.getUserInitials(userName) - val color = ContextCompat.getColor(context, R.color.textDark) - val drawable = TextDrawable.builder() - .beginConfig() - .height(context.resources.getDimensionPixelSize(com.instructure.pandautils.R.dimen.avatar_size)) - .width(context.resources.getDimensionPixelSize(com.instructure.pandautils.R.dimen.avatar_size)) - .toUpperCase() - .useFont(Typeface.DEFAULT_BOLD) - .textColor(color) - .endConfig() - .buildRound(initials, Color.WHITE) - this.borderColor = color - this.borderWidth = context.DP(0.5f).toInt() - this.setImageDrawable(drawable) -} - /** * Loads the given resource as this Toolbar's icon, assigns it the given content description, and * propagates its clicks to the provided function. @@ -771,4 +756,46 @@ fun View.fadeAnimationWithAction(action: () -> Unit) { }) startAnimation(fadeOutAnim) -} \ No newline at end of file +} + +/** + * Load model into ImageView as a circle image using Glide + * + * @param model - Any object supported by Glide (Uri, File, Bitmap, String, resource id as Int, ByteArray, and Drawable) + * @param placeholder - Placeholder drawable + * @param onFailure - Called when an exception occurs during a load + */ +@SuppressLint("CheckResult") +fun ImageView.loadCircularImage( + model: T, + placeholder: Int? = null, + onFailure: (() -> Unit)? = null +) { + Glide.with(context) + .asBitmap() + .load(model) + .apply { placeholder?.let { placeholder(it) } } + .apply(RequestOptions.circleCropTransform()) + .addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + onFailure?.invoke() + return true + } + + override fun onResourceReady( + resource: Bitmap?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + return false + } + }) + .into(this) +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/views/AttachmentView.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/views/AttachmentView.kt index 4ed424f0b6..3f04801181 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/views/AttachmentView.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/views/AttachmentView.kt @@ -16,19 +16,18 @@ package com.instructure.pandautils.views import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import androidx.core.content.ContextCompat +import android.graphics.PorterDuff import android.view.LayoutInflater import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView +import androidx.core.content.ContextCompat +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions import com.instructure.canvasapi2.models.Attachment import com.instructure.canvasapi2.models.RemoteFile import com.instructure.pandautils.R import com.instructure.pandautils.utils.onClick -import com.squareup.picasso.Picasso -import com.squareup.picasso.Transformation import kotlinx.android.synthetic.main.view_attachment.view.* import java.io.File @@ -93,27 +92,17 @@ class AttachmentView(context: Context) : FrameLayout(context) { private fun setThumbnail(path: String?) { if (path.isNullOrBlank()) return val file = File(path) - val picasso = Picasso.with(context) - val creator = if (file.exists() && file.isFile) picasso.load(file) else picasso.load(path) - creator.fit().centerCrop().transform(ATTACHMENT_PREVIEW_TRANSFORMER).into(previewImage) + Glide.with(context) + .load(if (file.exists() && file.isFile) file else path) + .apply(RequestOptions.centerCropTransform()) + .into(previewImage) + previewImage.setColorFilter( + 0xBB9B9B9B.toInt(), + PorterDuff.Mode.SRC_OVER + ) } companion object { - /** Picasso transformation to apply gray overlay on thumbnail */ - @JvmField - val ATTACHMENT_PREVIEW_TRANSFORMER: Transformation = object : Transformation { - override fun transform(source: Bitmap?): Bitmap? { - if (source == null) return null - val mutableSource = source.copy(source.config, true) - source.recycle() - val canvas = Canvas(mutableSource) - canvas.drawColor(0xBB9B9B9B.toInt()) - return mutableSource - } - - override fun key(): String = "gray-overlay" - } - fun setColorAndIcon( context: Context, contentType: String?, diff --git a/libs/pandautils/src/main/res/layout/adapter_recipient_search_result.xml b/libs/pandautils/src/main/res/layout/adapter_recipient_search_result.xml index 7224d674da..e6a5daaaec 100644 --- a/libs/pandautils/src/main/res/layout/adapter_recipient_search_result.xml +++ b/libs/pandautils/src/main/res/layout/adapter_recipient_search_result.xml @@ -23,7 +23,7 @@ android:paddingStart="12dp" android:paddingEnd="12dp"> - Date: Fri, 23 Sep 2022 16:16:13 +0200 Subject: [PATCH 08/73] [MBL-16262][Student] Comment on an announcement will put that on top of the announcement list refs: MBL-16262 affects: Student release note: none --- .../com/instructure/student/fragment/DiscussionListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt index 6ef8af3715..9a4351a229 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt @@ -345,7 +345,7 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.CLOSED_FOR_COMMENTS, it) } else -> { - recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.UNPINNED, it) + if (!isAnnouncement) recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.UNPINNED, it) } } } From 4fca23f9c6f76375414fed0b314a73baabbed82e Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:58:55 +0200 Subject: [PATCH 09/73] [MBL-16048][Teacher] - Refactor and merge Teacher AssignmentE2E test (#1725) Delete merged test case. Extend AssignmentE2E test with due date, assigned to, publish,unpublish,republish, multiple dates functions. Add some additional assertions. Remove saveAssignment from edit functions (and so minor refactor needed on some interaction tests because of that). refs: MBL-16048 affects: Teacher release note: none --- .../ui/EditAssignmentDetailsPageTest.kt | 2 + .../teacher/ui/e2e/AssignmentE2ETest.kt | 120 ++++++++++-------- .../teacher/ui/pages/AssignmentDetailsPage.kt | 25 +++- .../ui/pages/AssignmentDueDatesPage.kt | 20 ++- .../ui/pages/EditAssignmentDetailsPage.kt | 27 +++- 5 files changed, 131 insertions(+), 63 deletions(-) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/EditAssignmentDetailsPageTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/EditAssignmentDetailsPageTest.kt index 775392f3f5..ce658550b7 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/EditAssignmentDetailsPageTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/EditAssignmentDetailsPageTest.kt @@ -61,6 +61,7 @@ class EditAssignmentDetailsPageTest : TeacherTest() { editAssignmentDetailsPage.clickAssignmentNameEditText() val newAssignmentName = randomString() editAssignmentDetailsPage.editAssignmentName(newAssignmentName) + editAssignmentDetailsPage.saveAssignment() assignmentDetailsPage.assertAssignmentNameChanged(newAssignmentName) } @@ -71,6 +72,7 @@ class EditAssignmentDetailsPageTest : TeacherTest() { editAssignmentDetailsPage.clickPointsPossibleEditText() val newPoints = randomDouble() editAssignmentDetailsPage.editAssignmentPoints(newPoints) + editAssignmentDetailsPage.saveAssignment() val stringPoints = NumberHelper.formatDecimal(newPoints, 1, true) assignmentDetailsPage.assertAssignmentPointsChanged(stringPoints) } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt index 213ff3cccb..10442ff67b 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt @@ -23,10 +23,13 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 +import com.instructure.espresso.assertContainsText +import com.instructure.espresso.page.onViewWithId 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.teacher.R import com.instructure.teacher.ui.utils.* import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @@ -36,9 +39,7 @@ class AssignmentE2ETest : TeacherTest() { override fun displaysPageObjects() = Unit - override fun enableAndConfigureAccessibilityChecks() { - //We don't want to see accessibility errors on E2E tests - } + override fun enableAndConfigureAccessibilityChecks() = Unit @E2E @Test @@ -82,6 +83,21 @@ class AssignmentE2ETest : TeacherTest() { assignmentDetailsPage.assertNotSubmitted(3,3) assignmentDetailsPage.assertNeedsGrading(0,3) + Log.d(STEP_TAG,"Publish ${assignment[0].name} assignment. Click on Save.") + assignmentDetailsPage.openEditPage() + editAssignmentDetailsPage.clickPublishSwitch() + editAssignmentDetailsPage.saveAssignment() + + Log.d(STEP_TAG,"Refresh the page. Assert that ${assignment[0].name} assignment has been published.") + assignmentDetailsPage.refresh() + assignmentDetailsPage.waitForRender() + assignmentDetailsPage.assertPublishedStatus(false) + + Log.d(STEP_TAG,"Open Edit Page and re-publish the assignment, then click on Save.") + assignmentDetailsPage.openEditPage() + editAssignmentDetailsPage.clickPublishSwitch() + editAssignmentDetailsPage.saveAssignment() + Log.d(PREPARATION_TAG,"Seed a submission for ${student.name} student.") seedAssignmentSubmission( submissionSeeds = listOf(SubmissionsApi.SubmissionSeedInfo( @@ -126,78 +142,80 @@ class AssignmentE2ETest : TeacherTest() { assignmentDetailsPage.assertNotSubmitted(1,3) assignmentDetailsPage.assertNeedsGrading(1,3) assignmentDetailsPage.assertHasGraded(1,3) - } - - @E2E - @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) - fun testEditAssignmentE2E() { - - Log.d(PREPARATION_TAG, "Seeding data.") - val data = seedData(teachers = 1, courses = 1, students = 1, favoriteCourses = 1) - val teacher = data.teachersList[0] - val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") - tokenLogin(teacher) - dashboardPage.waitForRender() - - Log.d(STEP_TAG,"Navigate to ${course.name} course's Assignments Tab and assert that there isn't any assignment displayed.") - dashboardPage.openCourse(course.name) - courseBrowserPage.openAssignmentsTab() - assignmentListPage.assertDisplaysNoAssignmentsView() - - Log.d(PREPARATION_TAG,"Seeding assignment.") - val assignment = seedAssignments( - courseId = course.id, - dueAt = 1.days.fromNow.iso8601, - submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY), - teacherToken = teacher.token, - pointsPossible = 15.0 - ) - - Log.d(STEP_TAG,"Refresh Assignment List Page and assert that the previously seeded ${assignment[0].name} assignment has been displayed.") - assignmentListPage.refresh() - assignmentListPage.assertHasAssignment(assignment[0]) - - Log.d(STEP_TAG,"Click on ${assignment[0].name} assignment and assert the numbers of 'Not Submitted' and 'Needs Grading' submissions.") + val newAssignmentName = "New Assignment Name" + Log.d(STEP_TAG,"Edit ${assignment[0].name} assignment's name to: $newAssignmentName.") assignmentListPage.clickAssignment(assignment[0]) assignmentDetailsPage.waitForRender() assignmentDetailsPage.assertAssignmentDetails(assignment[0]) - assignmentDetailsPage.assertNotSubmitted(1,1) - assignmentDetailsPage.assertNeedsGrading(0,1) - assignmentDetailsPage.assertSubmissionTypeOnlineTextEntry() - val newAssignmentName = "New Assignment Name" - Log.d(STEP_TAG,"Edit ${assignment[0].name} assignment's name to: $newAssignmentName.") + Log.d(STEP_TAG,"Open Edit Page again. Change the assignment's name to $newAssignmentName and it's description to 'assignment test description', then save.") assignmentDetailsPage.openEditPage() editAssignmentDetailsPage.clickAssignmentNameEditText() editAssignmentDetailsPage.editAssignmentName(newAssignmentName) + editAssignmentDetailsPage.editDescription("assignment test description") + editAssignmentDetailsPage.saveAssignment() - Log.d(STEP_TAG,"Refresh the page. Assert that the name has been changed to $newAssignmentName.") + Log.d(STEP_TAG,"Refresh the page. Assert that the name and description of the assignment has been changed to $newAssignmentName.") assignmentDetailsPage.refresh() assignmentDetailsPage.waitForRender() assignmentDetailsPage.assertAssignmentNameChanged(newAssignmentName) + assignmentDetailsPage.assertDisplaysDescription("assignment test description") Log.d(STEP_TAG,"Edit $newAssignmentName assignment's points to 20.") assignmentDetailsPage.openEditPage() editAssignmentDetailsPage.clickPointsPossibleEditText() editAssignmentDetailsPage.editAssignmentPoints(20.0) + Log.d(STEP_TAG,"Change grade type to 'Percentage'.") + editAssignmentDetailsPage.clickOnDisplayGradeAsSpinner() + editAssignmentDetailsPage.selectGradeType("Percentage") + + Log.d(STEP_TAG,"Click on the 'Due Time' section and edit the hour and minutes to 1:30 PM." + + "Assert that the changes has been applied on Edit Assignment Details page.") + editAssignmentDetailsPage.clickEditDueDate() + editAssignmentDetailsPage.editDate(2022,12,12) + editAssignmentDetailsPage.clickEditDueTime() + editAssignmentDetailsPage.editTime(1, 30) + editAssignmentDetailsPage.assertTimeChanged(1, 30, R.id.dueTime) + + Log.d(STEP_TAG,"Click on 'Assigned To' spinner and select ${student.name} besides 'Everyone'." + + "Assert that ${student.name} and 'Everyone else' is selected as well.") + editAssignmentDetailsPage.editAssignees() + assigneeListPage.assertAssigneesSelected(listOf("Everyone")) + assigneeListPage.toggleAssignees(listOf(student.name)) + val expectedAssignees = listOf(student.name, "Everyone else") + assigneeListPage.assertAssigneesSelected(expectedAssignees) + + Log.d(STEP_TAG,"Save and close the assignee list. Assert that on the Assignment Details Page both the ${student.name} and the 'Everyone else' values are set.") + assigneeListPage.saveAndClose() + val assignText = editAssignmentDetailsPage.onViewWithId(R.id.assignTo) + for (assignee in expectedAssignees) assignText.assertContainsText(assignee) + + Log.d(STEP_TAG,"Save the assignment.") + editAssignmentDetailsPage.saveAssignment() + Log.d(STEP_TAG,"Refresh the page. Assert that the points of $newAssignmentName assignment has been changed to 20.") assignmentDetailsPage.refresh() assignmentDetailsPage.waitForRender() assignmentDetailsPage.assertAssignmentPointsChanged("20") - Log.d(STEP_TAG,"Publish $newAssignmentName assignment. Click on Save.") - assignmentDetailsPage.openEditPage() - editAssignmentDetailsPage.clickPublishSwitch() - editAssignmentDetailsPage.saveAssignment() + Log.d(STEP_TAG,"Assert that there are multiple due dates set, so the 'Multiple Due Dates' string is displayed on the 'Due Dates' section.") + assignmentDetailsPage.assertMultipleDueDates() - Log.d(STEP_TAG,"Refresh the page. Assert that $newAssignmentName assignment has been published.") - assignmentDetailsPage.refresh() - assignmentDetailsPage.waitForRender() - assignmentDetailsPage.assertPublishedStatus(false) + Log.d(STEP_TAG,"Open Due Dates Page and assert that there are 2 different due dates set.") + assignmentDetailsPage.openAllDatesPage() + assignmentDueDatesPage.assertDueDatesCount(2) + + Log.d(STEP_TAG,"Assert that there is a due date set for '${student.name}' student especially and another one for everyone else.") + assignmentDueDatesPage.assertDueFor(student.name) + assignmentDueDatesPage.assertDueFor(R.string.everyone_else) + + val dueDateForEveryoneElse = "Dec 12 at 1:30 AM" + val dueDateForStudentSpecially = "Dec 12 at 9:30 AM" + Log.d(STEP_TAG,"Assert that the there is a due date with '$dueDateForEveryoneElse' value and another one with '$dueDateForStudentSpecially'.") + assignmentDueDatesPage.assertDueDateTime("Due $dueDateForEveryoneElse") + assignmentDueDatesPage.assertDueDateTime("Due $dueDateForStudentSpecially") } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDetailsPage.kt index beae94a041..3d68c6d2c7 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDetailsPage.kt @@ -17,12 +17,16 @@ package com.instructure.teacher.ui.pages import androidx.test.InstrumentationRegistry -import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.web.assertion.WebViewAssertions +import androidx.test.espresso.web.sugar.Web +import androidx.test.espresso.web.webdriver.DriverAtoms +import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvasapi2.models.Assignment import com.instructure.dataseeding.model.AssignmentApiModel import com.instructure.espresso.* import com.instructure.espresso.page.* import com.instructure.teacher.R +import org.hamcrest.Matchers @Suppress("unused") class AssignmentDetailsPage : BasePage(pageResId = R.id.assignmentDetailsPage) { @@ -143,6 +147,15 @@ class AssignmentDetailsPage : BasePage(pageResId = R.id.assignmentDetailsPage) { pointsTextView.assertContainsText(newAssignmentPoints) } + fun assertDisplaysDescription(text: String) { + descriptionWebView.assertVisible() + Web.onWebView().withElement( + DriverAtoms.findElement( + Locator.XPATH, + "//div[@id='content' and contains(text(),'$text')]" + ) + ).check(WebViewAssertions.webMatches(DriverAtoms.getText(), Matchers.comparesEqualTo(text))) } + fun assertNeedsGrading(actual: Int = 1, outOf: Int = 1) { val resources = InstrumentationRegistry.getTargetContext() ungradedDonutWrapper.assertHasContentDescription(resources.getString(R.string.content_description_submission_donut_needs_grading).format(actual, outOf)) @@ -158,7 +171,11 @@ class AssignmentDetailsPage : BasePage(pageResId = R.id.assignmentDetailsPage) { gradedDonutWrapper.assertHasContentDescription(resources.getString(R.string.content_description_submission_donut_graded).format(actual, outOf)) } - fun scrollToSubmissionType() { + fun viewAllSubmission() { + onView(withId(R.id.viewAllSubmissions)).click() + } + + private fun scrollToSubmissionType() { scrollTo(R.id.submissionTypesTextView) } @@ -178,6 +195,10 @@ class AssignmentDetailsPage : BasePage(pageResId = R.id.assignmentDetailsPage) { } } + fun assertMultipleDueDates() { + onView(withId(R.id.otherDueDateTextView) + withText(R.string.multiple_due_dates)).assertDisplayed() + } + private fun assertAssignmentDetails(assignmentName: String, published: Boolean) { assignmentNameTextView.assertHasText(assignmentName) assertPublishedStatus(published) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDueDatesPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDueDatesPage.kt index 230336a2fc..11a8f521a4 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDueDatesPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/AssignmentDueDatesPage.kt @@ -16,11 +16,9 @@ package com.instructure.teacher.ui.pages import com.instructure.espresso.* -import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.* -import com.instructure.espresso.page.onViewWithId -import com.instructure.espresso.page.onViewWithText import com.instructure.teacher.R @Suppress("unused") @@ -41,6 +39,10 @@ class AssignmentDueDatesPage : BasePage(pageResId = R.id.dueDatesPage) { onViewWithId(R.id.dueDateTextView).assertDisplayed().assertHasText(R.string.no_due_date) } + fun assertDueDatesCount(expectedCount: Int) { + recyclerView.check(RecyclerViewItemCountAssertion(expectedCount)) + } + fun assertDisplaysSingleDueDate() { recyclerView.check(RecyclerViewItemCountAssertion(1)) assertLabelsDisplayedOnce() @@ -49,6 +51,18 @@ class AssignmentDueDatesPage : BasePage(pageResId = R.id.dueDatesPage) { onViewWithId(R.id.dueDateTextView).assertDisplayed().assertNotHasText(R.string.no_due_date) } + fun assertDueFor(dueForString: Int) { + onView(withId(R.id.dueForTextView) + withText(dueForString)).assertDisplayed() + } + + fun assertDueFor(dueForString: String) { + onView(withId(R.id.dueForTextView) + withText(dueForString)).assertDisplayed() + } + + fun assertDueDateTime(dueDateString: String) { + onView(withId(R.id.dueDateTextView) + withText(dueDateString)) + } + fun assertDisplaysAvailabilityDates() { recyclerView.check(RecyclerViewItemCountAssertion(1)) assertLabelsDisplayedOnce() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditAssignmentDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditAssignmentDetailsPage.kt index cc0c5db1d7..56d6ab030e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditAssignmentDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditAssignmentDetailsPage.kt @@ -16,20 +16,21 @@ package com.instructure.teacher.ui.pages +import android.widget.DatePicker +import android.widget.TimePicker +import androidx.test.espresso.Espresso import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility -import android.widget.DatePicker -import android.widget.TimePicker -import androidx.test.espresso.Espresso -import com.instructure.canvasapi2.utils.DateHelper -import com.instructure.espresso.* import com.instructure.canvas.espresso.has import com.instructure.canvas.espresso.hasTextInputLayoutErrorText import com.instructure.canvas.espresso.withIndex +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.espresso.* import com.instructure.espresso.page.* import com.instructure.teacher.R +import com.instructure.teacher.ui.utils.TypeInRCETextEditor import com.instructure.teacher.view.AssignmentOverrideView import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matchers @@ -46,6 +47,7 @@ class EditAssignmentDetailsPage : BasePage() { private val descriptionWebView by OnViewWithId(R.id.descriptionWebView, autoAssert = false) private val noDescriptionTextView by OnViewWithId(R.id.noDescriptionTextView, autoAssert = false) private val overlayContainer by OnViewWithId(R.id.overrideContainer, autoAssert = false) + private val contentRceView by WaitForViewWithId(R.id.rce_webView, autoAssert = false) fun saveAssignment() { saveButton.click() @@ -63,14 +65,12 @@ class EditAssignmentDetailsPage : BasePage() { fun editAssignmentName(newName: String) { assignmentNameEditText.replaceText(newName) Espresso.closeSoftKeyboard() - saveAssignment() } fun editAssignmentPoints(newPoints: Double) { val df = DecimalFormat("#") pointsPossibleEditText.replaceText(df.format(newPoints)) Espresso.closeSoftKeyboard() - saveAssignment() } fun editAssignees() = waitScrollClick(R.id.assignTo) @@ -134,4 +134,17 @@ class EditAssignmentDetailsPage : BasePage() { fun assertNoAssigneesErrorShown() { onView(withIndex(withId(R.id.assignToTextInput), 1)).check(matches(hasTextInputLayoutErrorText(R.string.assignee_blank_error))) } + + fun clickOnDisplayGradeAsSpinner() { + onView(withId(R.id.displayGradeAsSpinner)).click() + } + + fun selectGradeType(gradeType: String) { + onView(withText(gradeType)).click() + } + + fun editDescription(newDescription: String) { + contentRceView.perform(TypeInRCETextEditor(newDescription)) + } + } From fe00b27bb74106d7efd29aa66d3f9609ea8634eb Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:16:14 +0200 Subject: [PATCH 10/73] [Hackweek][Student] AssignmentList filters (#1724) refs: none affects: Student release note: Added the option to filter assignments on the Assignment List. test plan: On the toolbar the filter button should be visible. When setting a filter a badge should be added to the icon. Check possible filters: - All - Late - Missing - Graded - Upcoming --- .../AssignmentListByDateRecyclerAdapter.kt | 21 ++- .../AssignmentListByTypeRecyclerAdapter.kt | 21 ++- .../AssignmentListRecyclerAdapter.kt | 26 +++- .../fragment/AssignmentListFragment.kt | 128 +++++++++++++----- .../main/res/menu/menu_assignment_list.xml | 24 ++++ apps/student/src/main/res/values/arrays.xml | 8 ++ libs/pandares/src/main/res/values/strings.xml | 8 ++ .../instructure/pandautils/views/EmptyView.kt | 13 +- 8 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 apps/student/src/main/res/menu/menu_assignment_list.xml diff --git a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByDateRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByDateRecyclerAdapter.kt index e05ea31f9d..51dc99259b 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByDateRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByDateRecyclerAdapter.kt @@ -18,8 +18,11 @@ package com.instructure.student.adapter.assignment import android.content.Context -import com.instructure.canvasapi2.models.* -import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.AssignmentGroup +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.canvasapi2.utils.filterWithQuery +import com.instructure.canvasapi2.utils.toDate import com.instructure.pandarecycler.util.GroupSortedList import com.instructure.pandarecycler.util.Types import com.instructure.student.R @@ -35,8 +38,9 @@ class AssignmentListByDateRecyclerAdapter( context: Context, canvasContext: CanvasContext, adapterToAssignmentsCallback: AdapterToAssignmentsCallback, - isTesting: Boolean = false -) : AssignmentListRecyclerAdapter(context, canvasContext, adapterToAssignmentsCallback, isTesting) { + isTesting: Boolean = false, + filter: AssignmentListFilter = AssignmentListFilter.ALL +) : AssignmentListRecyclerAdapter(context, canvasContext, adapterToAssignmentsCallback, isTesting, filter) { private val overdue = AssignmentGroup(name = context.getString(R.string.overdueAssignments), position = HEADER_POSITION_OVERDUE) private val upcoming = AssignmentGroup(name = context.getString(R.string.upcomingAssignments), position = HEADER_POSITION_UPCOMING) @@ -69,6 +73,15 @@ class AssignmentListByDateRecyclerAdapter( // endtodo assignmentGroup.assignments .filterWithQuery(searchQuery, Assignment::name) + .filter { + when (filter) { + AssignmentListFilter.ALL -> true + AssignmentListFilter.MISSING -> it.isMissing() + AssignmentListFilter.LATE -> it.submission?.late ?: false + AssignmentListFilter.GRADED -> it.submission?.isGraded ?: false + AssignmentListFilter.UPCOMING -> !it.isSubmitted && it.dueDate?.after(Date()) ?: false + } + } .forEach { assignment -> val dueAt = assignment.dueAt val submission = assignment.submission diff --git a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByTypeRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByTypeRecyclerAdapter.kt index 569115e6c1..15f21199ee 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByTypeRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListByTypeRecyclerAdapter.kt @@ -24,17 +24,30 @@ import com.instructure.canvasapi2.utils.filterWithQuery import com.instructure.pandarecycler.util.GroupSortedList import com.instructure.pandarecycler.util.Types import com.instructure.student.interfaces.AdapterToAssignmentsCallback +import java.util.* class AssignmentListByTypeRecyclerAdapter( context: Context, canvasContext: CanvasContext, adapterToAssignmentsCallback: AdapterToAssignmentsCallback, - isTesting: Boolean = false -) : AssignmentListRecyclerAdapter(context, canvasContext, adapterToAssignmentsCallback, isTesting) { + isTesting: Boolean = false, + filter: AssignmentListFilter = AssignmentListFilter.ALL +) : AssignmentListRecyclerAdapter(context, canvasContext, adapterToAssignmentsCallback, isTesting, filter) { override fun populateData() { - assignmentGroups.forEach { assignmentGroup -> - val filteredAssignments = assignmentGroup.assignments.filterWithQuery(searchQuery, Assignment::name) + assignmentGroups + .forEach { assignmentGroup -> + val filteredAssignments = assignmentGroup.assignments + .filterWithQuery(searchQuery, Assignment::name) + .filter { + when (filter) { + AssignmentListFilter.ALL -> true + AssignmentListFilter.MISSING -> it.isMissing() + AssignmentListFilter.LATE -> it.submission?.late ?: false + AssignmentListFilter.GRADED -> it.submission?.isGraded ?: false + AssignmentListFilter.UPCOMING -> !it.isSubmitted && it.dueDate?.after(Date()) ?: false + } + } addOrUpdateAllItems(assignmentGroup, filteredAssignments) } isAllPagesLoaded = true diff --git a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListRecyclerAdapter.kt index 57620483b9..4118c9024c 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/assignment/AssignmentListRecyclerAdapter.kt @@ -23,7 +23,9 @@ import com.instructure.canvasapi2.StatusCallback import com.instructure.canvasapi2.managers.AssignmentManager import com.instructure.canvasapi2.managers.CourseManager import com.instructure.canvasapi2.models.* -import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.utils.ApiType +import com.instructure.canvasapi2.utils.LinkHeaders +import com.instructure.canvasapi2.utils.Logger import com.instructure.canvasapi2.utils.weave.WeaveJob import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -45,7 +47,8 @@ abstract class AssignmentListRecyclerAdapter ( context: Context, private val canvasContext: CanvasContext, private val adapterToAssignmentsCallback: AdapterToAssignmentsCallback, - isTesting: Boolean = false + isTesting: Boolean = false, + filter: AssignmentListFilter = AssignmentListFilter.ALL ) : ExpandableRecyclerAdapter( context, AssignmentGroup::class.java, @@ -57,6 +60,16 @@ abstract class AssignmentListRecyclerAdapter ( private var apiJob: WeaveJob? = null protected var assignmentGroups: List = emptyList() + var filter: AssignmentListFilter = AssignmentListFilter.ALL + set(value) { + field = value + if (isAllPagesLoaded) { + clear() + populateData() + onCallbackFinished(ApiType.CACHE) + } + } + var searchQuery: String = "" set(value) { field = value @@ -70,6 +83,7 @@ abstract class AssignmentListRecyclerAdapter ( init { isExpandedByDefault = true isDisplayEmptyCell = true + this.filter = filter if (!isTesting) loadData() } @@ -230,4 +244,12 @@ abstract class AssignmentListRecyclerAdapter ( super.cancel() apiJob?.cancel() } +} + +enum class AssignmentListFilter { + ALL, + LATE, + MISSING, + GRADED, + UPCOMING } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/fragment/AssignmentListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/AssignmentListFragment.kt index 91edffab83..a0b0b17c4d 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/AssignmentListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/AssignmentListFragment.kt @@ -20,13 +20,17 @@ package com.instructure.student.fragment import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle +import android.os.Handler import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.AdapterView import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.badge.BadgeDrawable +import com.google.android.material.badge.BadgeUtils import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.GradingPeriod @@ -44,6 +48,7 @@ import com.instructure.student.R import com.instructure.student.adapter.TermSpinnerAdapter import com.instructure.student.adapter.assignment.AssignmentListByDateRecyclerAdapter import com.instructure.student.adapter.assignment.AssignmentListByTypeRecyclerAdapter +import com.instructure.student.adapter.assignment.AssignmentListFilter import com.instructure.student.adapter.assignment.AssignmentListRecyclerAdapter import com.instructure.student.interfaces.AdapterToAssignmentsCallback import com.instructure.student.mobius.assignmentDetails.ui.AssignmentDetailsFragment @@ -51,6 +56,7 @@ import com.instructure.student.router.RouteMatcher import com.instructure.student.util.StudentPrefs import kotlinx.android.synthetic.main.assignment_list_layout.* +@com.google.android.material.badge.ExperimentalBadgeUtils @ScreenView(SCREEN_VIEW_ASSIGNMENT_LIST) @PageView(url = "{canvasContext}/assignments") class AssignmentListFragment : ParentFragment(), Bookmarkable { @@ -60,6 +66,11 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { private lateinit var recyclerAdapter: AssignmentListRecyclerAdapter private var termAdapter: TermSpinnerAdapter? = null + private var filterPosition = 0 + private var filter = AssignmentListFilter.ALL + + private var badgeDrawable: BadgeDrawable? = null + private var sortOrder: AssignmentsSortOrder get() { val preferenceKey = StudentPrefs.getString("sortBy_${canvasContext.contextId}", AssignmentsSortOrder.SORT_BY_TIME.preferenceKey) @@ -91,7 +102,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { } override fun onRefreshFinished() { - if(!isAdded) return // Refresh can finish after user has left screen, causing emptyView to be null + if (!isAdded) return // Refresh can finish after user has left screen, causing emptyView to be null setRefreshing(false) if (recyclerAdapter.size() == 0) { setEmptyView(emptyView, R.drawable.ic_panda_space, R.string.noAssignments, R.string.noAssignmentsSubtext) @@ -116,12 +127,12 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { sortByButton.contentDescription = getString(sortOrder.contentDescriptionRes) configureRecyclerView( - view, - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView + view, + requireContext(), + recyclerAdapter, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView ) appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, i -> @@ -136,11 +147,23 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { setupSortByButton() } + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (!hidden) { + updateBadge() + } + } + + override fun onResume() { + super.onResume() + updateBadge() + } + private fun createRecyclerAdapter(): AssignmentListRecyclerAdapter { return if (sortOrder == AssignmentsSortOrder.SORT_BY_TIME) { - AssignmentListByDateRecyclerAdapter(requireContext(), canvasContext, adapterToAssignmentsCallback) + AssignmentListByDateRecyclerAdapter(requireContext(), canvasContext, adapterToAssignmentsCallback, filter = filter) } else { - AssignmentListByTypeRecyclerAdapter(requireContext(), canvasContext, adapterToAssignmentsCallback) + AssignmentListByTypeRecyclerAdapter(requireContext(), canvasContext, adapterToAssignmentsCallback, filter = filter) } } @@ -148,10 +171,10 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { sortByButton.onClick { val checkedItemIndex = sortOrder.index AlertDialog.Builder(requireContext(), R.style.AccentDialogTheme) - .setTitle(R.string.sortByDialogTitle) - .setSingleChoiceItems(R.array.assignmentsSortByOptions, checkedItemIndex, this@AssignmentListFragment::sortOrderSelected) - .setNegativeButton(R.string.sortByDialogCancel) { dialog, _ -> dialog.dismiss() } - .show() + .setTitle(R.string.sortByDialogTitle) + .setSingleChoiceItems(R.array.assignmentsSortByOptions, checkedItemIndex, this@AssignmentListFragment::sortOrderSelected) + .setNegativeButton(R.string.sortByDialogCancel) { dialog, _ -> dialog.dismiss() } + .show() } } @@ -169,8 +192,40 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { } } + private fun showAssignmentFilterDialog() { + AlertDialog.Builder(requireContext(), R.style.AccentDialogTheme) + .setTitle(R.string.filterAssignmentDialogTitle) + .setSingleChoiceItems(R.array.assignmentsFilterOptions, filterPosition, this@AssignmentListFragment::filterSelected) + .setNegativeButton(R.string.filterAssignmentsDialogCancel) { dialog, _ -> dialog.dismiss() } + .show() + } + + private fun filterSelected(dialog: DialogInterface, index: Int) { + dialog.dismiss() + filterPosition = index + filter = AssignmentListFilter.values()[index] + recyclerAdapter.filter = filter + updateBadge() + } + + private fun updateBadge() { + Handler().postDelayed({ + if (badgeDrawable == null) { + badgeDrawable = BadgeDrawable.create(requireContext()).apply { + backgroundColor = ThemePrefs.accentColor + } + } + if (filterPosition == 0) { + BadgeUtils.detachBadgeDrawable(badgeDrawable, toolbar, R.id.menu_filter_assignments) + } else { + BadgeUtils.attachBadgeDrawable(badgeDrawable!!, toolbar, R.id.menu_filter_assignments) + } + }, 100) + } + + override fun applyTheme() { - setupToolbarMenu(toolbar) + setupToolbarMenu(toolbar, R.menu.menu_assignment_list) toolbar.title = title() toolbar.setupAsBackButton(this) toolbar.addSearch(getString(R.string.searchAssignmentsHint)) { query -> @@ -184,13 +239,22 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { ViewStyler.themeToolbarColored(requireActivity(), toolbar, canvasContext) } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_filter_assignments -> { + showAssignmentFilterDialog() + } + } + return super.onOptionsItemSelected(item) + } + private fun setupGradingPeriods(periods: List) { val hasGradingPeriods = periods.isNotEmpty() val adapter = TermSpinnerAdapter( - requireContext(), - android.R.layout.simple_spinner_dropdown_item, - periods + allTermsGradingPeriod, - hasGradingPeriods + requireContext(), + android.R.layout.simple_spinner_dropdown_item, + periods + allTermsGradingPeriod, + hasGradingPeriods ) termSpinner.isEnabled = hasGradingPeriods termAdapter = adapter @@ -227,12 +291,12 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) configureRecyclerView( - requireView(), - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView, + requireView(), + requireContext(), + recyclerAdapter, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView, R.string.noAssignments ) if (recyclerAdapter.size() == 0) { @@ -274,18 +338,18 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { } enum class AssignmentsSortOrder( - val index: Int, - val preferenceKey: String, - @StringRes val buttonTextRes: Int, - @StringRes val contentDescriptionRes: Int, - @StringRes val orderSelectedAnnouncement: Int, - val analyticsKey: String) { + val index: Int, + val preferenceKey: String, + @StringRes val buttonTextRes: Int, + @StringRes val contentDescriptionRes: Int, + @StringRes val orderSelectedAnnouncement: Int, + val analyticsKey: String) { SORT_BY_TIME(0, "time", R.string.sortByTime, R.string.a11y_sortByTimeButton, - R.string.a11y_assignmentsSortedByTime, AnalyticsEventConstants.ASSIGNMENT_LIST_SORT_BY_TIME_SELECTED), + R.string.a11y_assignmentsSortedByTime, AnalyticsEventConstants.ASSIGNMENT_LIST_SORT_BY_TIME_SELECTED), SORT_BY_TYPE(1, "type", R.string.sortByType, R.string.a11y_sortByTypeButton, - R.string.a11y_assignmentsSortedByType, AnalyticsEventConstants.ASSIGNMENT_LIST_SORT_BY_TYPE_SELECTED); + R.string.a11y_assignmentsSortedByType, AnalyticsEventConstants.ASSIGNMENT_LIST_SORT_BY_TYPE_SELECTED); companion object { fun fromPreferenceKey(key: String?): AssignmentsSortOrder { diff --git a/apps/student/src/main/res/menu/menu_assignment_list.xml b/apps/student/src/main/res/menu/menu_assignment_list.xml new file mode 100644 index 0000000000..514498c334 --- /dev/null +++ b/apps/student/src/main/res/menu/menu_assignment_list.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/apps/student/src/main/res/values/arrays.xml b/apps/student/src/main/res/values/arrays.xml index f11c9955c2..264de5aee2 100644 --- a/apps/student/src/main/res/values/arrays.xml +++ b/apps/student/src/main/res/values/arrays.xml @@ -20,4 +20,12 @@ @string/sortByDialogTimeOption @string/sortByDialogTypeOption + + + @string/filterAssignmentAll + @string/filterAssignmentLate + @string/filterAssignmentMissing + @string/filterAssignmentGraded + @string/filterAssignmentUpcoming + \ No newline at end of file diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 4e54b56730..6ab5281a96 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1309,4 +1309,12 @@ %1$s, %2$s Cancel Upload? This will cancel your upload. + Filter assignments + Filter Assignments + Cancel + All + Late + Missing + Graded + Upcoming diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/views/EmptyView.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/views/EmptyView.kt index b80cd60247..fe8900657c 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/views/EmptyView.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/views/EmptyView.kt @@ -30,7 +30,6 @@ import androidx.core.content.ContextCompat import com.instructure.pandarecycler.interfaces.EmptyInterface import com.instructure.pandautils.R import com.instructure.pandautils.utils.isGone -import com.instructure.pandautils.utils.isVisible import com.instructure.pandautils.utils.setGone import com.instructure.pandautils.utils.setVisible import kotlinx.android.synthetic.main.empty_view.view.* @@ -79,6 +78,18 @@ open class EmptyView @JvmOverloads constructor( // we don't have an image for the empty state we want the title to be centered instead. if (image.isGone) { centerTitle() + } else { + resetTitle() + } + } + + private fun resetTitle() { + emptyViewLayout?.let { + val constraintSet = ConstraintSet() + constraintSet.clone(it) + constraintSet.connect(R.id.textViews, ConstraintSet.TOP, R.id.titleTop, ConstraintSet.BOTTOM) + constraintSet.clear(R.id.textViews, ConstraintSet.BOTTOM) + constraintSet.applyTo(it) } } From 4b0ed70e98608521c8daed7952f87d327b157290 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Mon, 3 Oct 2022 14:04:42 +0200 Subject: [PATCH 11/73] [MBL-16269][Student][Teacher] Updated PSPDFKit (#1728) refs: MBL-16269 affects: Student, Teacher release note: None * Upgrade to 8.3.0 * Updated vault commit ref. * Removed text from stamp picker. --- android-vault | 2 +- apps/student/build.gradle | 5 ----- .../src/main/java/com/instructure/student/util/FileUtils.kt | 1 + apps/student/src/main/res/values/styles.xml | 2 +- apps/teacher/build.gradle | 5 ----- apps/teacher/src/main/res/values/styles.xml | 2 +- buildSrc/src/main/java/GlobalDependencies.kt | 2 +- libs/annotations/src/main/res/values-ar/strings.xml | 1 + .../annotations/src/main/res/values-b+da+instk12/strings.xml | 1 + .../src/main/res/values-b+en+AU+unimelb/strings.xml | 1 + .../src/main/res/values-b+en+GB+instukhe/strings.xml | 1 + .../annotations/src/main/res/values-b+nb+instk12/strings.xml | 1 + .../annotations/src/main/res/values-b+sv+instk12/strings.xml | 1 + libs/annotations/src/main/res/values-b+zh+HK/strings.xml | 1 + libs/annotations/src/main/res/values-b+zh+Hans/strings.xml | 1 + libs/annotations/src/main/res/values-b+zh+Hant/strings.xml | 1 + libs/annotations/src/main/res/values-ca/strings.xml | 1 + libs/annotations/src/main/res/values-cy/strings.xml | 1 + libs/annotations/src/main/res/values-da/strings.xml | 1 + libs/annotations/src/main/res/values-de/strings.xml | 1 + libs/annotations/src/main/res/values-en-rAU/strings.xml | 1 + libs/annotations/src/main/res/values-en-rCA/strings.xml | 1 + libs/annotations/src/main/res/values-en-rCY/strings.xml | 1 + libs/annotations/src/main/res/values-en-rGB/strings.xml | 1 + libs/annotations/src/main/res/values-es-rES/strings.xml | 1 + libs/annotations/src/main/res/values-es/strings.xml | 1 + libs/annotations/src/main/res/values-fi/strings.xml | 1 + libs/annotations/src/main/res/values-fr-rCA/strings.xml | 1 + libs/annotations/src/main/res/values-fr/strings.xml | 1 + libs/annotations/src/main/res/values-ht/strings.xml | 1 + libs/annotations/src/main/res/values-is/strings.xml | 1 + libs/annotations/src/main/res/values-it/strings.xml | 1 + libs/annotations/src/main/res/values-ja/strings.xml | 1 + libs/annotations/src/main/res/values-mi/strings.xml | 1 + libs/annotations/src/main/res/values-nb/strings.xml | 1 + libs/annotations/src/main/res/values-nl/strings.xml | 1 + libs/annotations/src/main/res/values-pl/strings.xml | 1 + libs/annotations/src/main/res/values-pt-rBR/strings.xml | 1 + libs/annotations/src/main/res/values-pt-rPT/strings.xml | 1 + libs/annotations/src/main/res/values-ru/strings.xml | 1 + libs/annotations/src/main/res/values-sl/strings.xml | 1 + libs/annotations/src/main/res/values-sv/strings.xml | 1 + libs/annotations/src/main/res/values-th/strings.xml | 1 + libs/annotations/src/main/res/values-vi/strings.xml | 1 + libs/annotations/src/main/res/values-zh/strings.xml | 1 + libs/annotations/src/main/res/values/strings.xml | 1 + 46 files changed, 44 insertions(+), 14 deletions(-) diff --git a/android-vault b/android-vault index eea182a4b6..ceed6c1136 160000 --- a/android-vault +++ b/android-vault @@ -1 +1 @@ -Subproject commit eea182a4b626678f271e42d73c100dd53a84611b +Subproject commit ceed6c1136054f1aa424382d37185857acf19d3d diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 10be6c70f8..7d86ad0261 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -182,11 +182,6 @@ android { resolutionStrategy.force Libs.ANDROIDX_ANNOTATION resolutionStrategy.force Libs.KOTLIN_COROUTINES_CORE - - // Some libraries want to resolve never versions of this library that requires targetSdkVersion 31. - // Once we upgrade the targetSdkVersion this should be removed. - resolutionStrategy.force 'androidx.core:core:1.6.0' - resolutionStrategy.force 'androidx.core:core-ktx:1.6.0' } /* diff --git a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt index 428ccbd03d..c50a961d90 100644 --- a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt +++ b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt @@ -84,6 +84,7 @@ object FileUtils { pspdfActivityConfiguration = PdfActivityConfiguration.Builder(context) .scrollDirection(PageScrollDirection.HORIZONTAL) .showThumbnailGrid() + .setDocumentInfoViewSeparated(false) .setThumbnailBarMode(ThumbnailBarMode.THUMBNAIL_BAR_MODE_PINNED) .enableDocumentEditor() .enabledAnnotationTools(annotationCreationList) diff --git a/apps/student/src/main/res/values/styles.xml b/apps/student/src/main/res/values/styles.xml index 0575a7b29c..6b679b34e4 100644 --- a/apps/student/src/main/res/values/styles.xml +++ b/apps/student/src/main/res/values/styles.xml @@ -84,7 +84,7 @@ diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index cd41f1c69c..f0723785de 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -127,11 +127,6 @@ android { force 'android.arch.lifecycle:runtime:1.0.3' force Libs.KOTLIN_COROUTINES_CORE - - // Some libraries want to resolve never versions of this library that requires targetSdkVersion 31. - // Once we upgrade the targetSdkVersion this should be removed. - force 'androidx.core:core:1.6.0' - force 'androidx.core:core-ktx:1.6.0' } } diff --git a/apps/teacher/src/main/res/values/styles.xml b/apps/teacher/src/main/res/values/styles.xml index 4e5327fe3a..30e6841958 100644 --- a/apps/teacher/src/main/res/values/styles.xml +++ b/apps/teacher/src/main/res/values/styles.xml @@ -75,7 +75,7 @@ diff --git a/buildSrc/src/main/java/GlobalDependencies.kt b/buildSrc/src/main/java/GlobalDependencies.kt index 3d6a0fa5b4..830779cea2 100644 --- a/buildSrc/src/main/java/GlobalDependencies.kt +++ b/buildSrc/src/main/java/GlobalDependencies.kt @@ -30,7 +30,7 @@ object Versions { /* Others */ const val APOLLO = "2.5.9" const val CRASHLYTICS = "17.2.1" - const val PSPDFKIT = "8.1.0" + const val PSPDFKIT = "8.3.0" const val PHOTO_VIEW = "2.3.0" const val MOBIUS = "1.2.1" const val SQLDELIGHT = "1.4.3" diff --git a/libs/annotations/src/main/res/values-ar/strings.xml b/libs/annotations/src/main/res/values-ar/strings.xml index 3e25e5b89e..27ab666226 100644 --- a/libs/annotations/src/main/res/values-ar/strings.xml +++ b/libs/annotations/src/main/res/values-ar/strings.xml @@ -37,5 +37,6 @@ جارٍ الإرسال لون الملاحظة تمت إزالة %1$s بواسطة %2$s + diff --git a/libs/annotations/src/main/res/values-b+da+instk12/strings.xml b/libs/annotations/src/main/res/values-b+da+instk12/strings.xml index f47ffbc676..0f0e98ee0e 100644 --- a/libs/annotations/src/main/res/values-b+da+instk12/strings.xml +++ b/libs/annotations/src/main/res/values-b+da+instk12/strings.xml @@ -37,5 +37,6 @@ Sender Bemærkningens farve Fjernet %1$s af %2$s + diff --git a/libs/annotations/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/annotations/src/main/res/values-b+en+AU+unimelb/strings.xml index 1f0bdefc5e..d220a46a5d 100644 --- a/libs/annotations/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/libs/annotations/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -38,5 +38,6 @@ Note Colour Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-b+en+GB+instukhe/strings.xml b/libs/annotations/src/main/res/values-b+en+GB+instukhe/strings.xml index d8596c0679..a163fff555 100644 --- a/libs/annotations/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/libs/annotations/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -36,5 +36,6 @@ Sending Note colour Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-b+nb+instk12/strings.xml b/libs/annotations/src/main/res/values-b+nb+instk12/strings.xml index 75023d68ee..a7a39aced7 100644 --- a/libs/annotations/src/main/res/values-b+nb+instk12/strings.xml +++ b/libs/annotations/src/main/res/values-b+nb+instk12/strings.xml @@ -37,5 +37,6 @@ Sender Notatfarge Fjernet %1$s av %2$s + diff --git a/libs/annotations/src/main/res/values-b+sv+instk12/strings.xml b/libs/annotations/src/main/res/values-b+sv+instk12/strings.xml index d104c77450..43adaf6db0 100644 --- a/libs/annotations/src/main/res/values-b+sv+instk12/strings.xml +++ b/libs/annotations/src/main/res/values-b+sv+instk12/strings.xml @@ -36,5 +36,6 @@ Skickar Anteckningsfärg Borttagen %1$s av %2$s + diff --git a/libs/annotations/src/main/res/values-b+zh+HK/strings.xml b/libs/annotations/src/main/res/values-b+zh+HK/strings.xml index 9b8c7db567..afcd217149 100644 --- a/libs/annotations/src/main/res/values-b+zh+HK/strings.xml +++ b/libs/annotations/src/main/res/values-b+zh+HK/strings.xml @@ -38,5 +38,6 @@ 注釋顏色 %1$s 由 %2$s移除 + diff --git a/libs/annotations/src/main/res/values-b+zh+Hans/strings.xml b/libs/annotations/src/main/res/values-b+zh+Hans/strings.xml index 0e33334040..a10a031b4c 100644 --- a/libs/annotations/src/main/res/values-b+zh+Hans/strings.xml +++ b/libs/annotations/src/main/res/values-b+zh+Hans/strings.xml @@ -38,5 +38,6 @@ 注释颜色 移除%1$s者 %2$s + diff --git a/libs/annotations/src/main/res/values-b+zh+Hant/strings.xml b/libs/annotations/src/main/res/values-b+zh+Hant/strings.xml index 9b8c7db567..afcd217149 100644 --- a/libs/annotations/src/main/res/values-b+zh+Hant/strings.xml +++ b/libs/annotations/src/main/res/values-b+zh+Hant/strings.xml @@ -38,5 +38,6 @@ 注釋顏色 %1$s 由 %2$s移除 + diff --git a/libs/annotations/src/main/res/values-ca/strings.xml b/libs/annotations/src/main/res/values-ca/strings.xml index 977537b1e5..057c242d06 100644 --- a/libs/annotations/src/main/res/values-ca/strings.xml +++ b/libs/annotations/src/main/res/values-ca/strings.xml @@ -37,5 +37,6 @@ S\'està enviant Color de la nota Eliminat el %1$s per %2$s + diff --git a/libs/annotations/src/main/res/values-cy/strings.xml b/libs/annotations/src/main/res/values-cy/strings.xml index f4861d16bf..4d9d848ff9 100644 --- a/libs/annotations/src/main/res/values-cy/strings.xml +++ b/libs/annotations/src/main/res/values-cy/strings.xml @@ -37,5 +37,6 @@ Wrthi’n anfon Lliw y Nodyn %2$s wedi tynnu %1$s + diff --git a/libs/annotations/src/main/res/values-da/strings.xml b/libs/annotations/src/main/res/values-da/strings.xml index a22a87e323..0ce90ee7a3 100644 --- a/libs/annotations/src/main/res/values-da/strings.xml +++ b/libs/annotations/src/main/res/values-da/strings.xml @@ -38,5 +38,6 @@ Bemærkningens farve Fjernet %1$s af %2$s + diff --git a/libs/annotations/src/main/res/values-de/strings.xml b/libs/annotations/src/main/res/values-de/strings.xml index ddf968d659..4a3fedae2c 100644 --- a/libs/annotations/src/main/res/values-de/strings.xml +++ b/libs/annotations/src/main/res/values-de/strings.xml @@ -38,5 +38,6 @@ Hinweisfarbe %1$s entfernt von %2$s + diff --git a/libs/annotations/src/main/res/values-en-rAU/strings.xml b/libs/annotations/src/main/res/values-en-rAU/strings.xml index 1f0bdefc5e..d220a46a5d 100644 --- a/libs/annotations/src/main/res/values-en-rAU/strings.xml +++ b/libs/annotations/src/main/res/values-en-rAU/strings.xml @@ -38,5 +38,6 @@ Note Colour Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-en-rCA/strings.xml b/libs/annotations/src/main/res/values-en-rCA/strings.xml index 44e3188fec..9fc2b43536 100644 --- a/libs/annotations/src/main/res/values-en-rCA/strings.xml +++ b/libs/annotations/src/main/res/values-en-rCA/strings.xml @@ -36,5 +36,6 @@ Sending Note Color Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-en-rCY/strings.xml b/libs/annotations/src/main/res/values-en-rCY/strings.xml index d8596c0679..a163fff555 100644 --- a/libs/annotations/src/main/res/values-en-rCY/strings.xml +++ b/libs/annotations/src/main/res/values-en-rCY/strings.xml @@ -36,5 +36,6 @@ Sending Note colour Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-en-rGB/strings.xml b/libs/annotations/src/main/res/values-en-rGB/strings.xml index bdcd9eca2e..2900a79513 100644 --- a/libs/annotations/src/main/res/values-en-rGB/strings.xml +++ b/libs/annotations/src/main/res/values-en-rGB/strings.xml @@ -38,5 +38,6 @@ Note colour Removed %1$s by %2$s + diff --git a/libs/annotations/src/main/res/values-es-rES/strings.xml b/libs/annotations/src/main/res/values-es-rES/strings.xml index f4025e3bb2..9389dd259f 100644 --- a/libs/annotations/src/main/res/values-es-rES/strings.xml +++ b/libs/annotations/src/main/res/values-es-rES/strings.xml @@ -37,5 +37,6 @@ Enviando Color de la nota Eliminado %1$s por %2$s + diff --git a/libs/annotations/src/main/res/values-es/strings.xml b/libs/annotations/src/main/res/values-es/strings.xml index 632af30961..1f3ae8cc72 100644 --- a/libs/annotations/src/main/res/values-es/strings.xml +++ b/libs/annotations/src/main/res/values-es/strings.xml @@ -37,5 +37,6 @@ Enviando Color de la nota Eliminado el %1$s por %2$s + diff --git a/libs/annotations/src/main/res/values-fi/strings.xml b/libs/annotations/src/main/res/values-fi/strings.xml index f4b993cda0..1e86351397 100644 --- a/libs/annotations/src/main/res/values-fi/strings.xml +++ b/libs/annotations/src/main/res/values-fi/strings.xml @@ -36,5 +36,6 @@ Lähetetään Huomautuksen väri Kohteen %1$s poistaja %2$s + diff --git a/libs/annotations/src/main/res/values-fr-rCA/strings.xml b/libs/annotations/src/main/res/values-fr-rCA/strings.xml index 009090f78a..4fe13175fa 100644 --- a/libs/annotations/src/main/res/values-fr-rCA/strings.xml +++ b/libs/annotations/src/main/res/values-fr-rCA/strings.xml @@ -38,5 +38,6 @@ Couleur de note Retiré %1$s par %2$s + diff --git a/libs/annotations/src/main/res/values-fr/strings.xml b/libs/annotations/src/main/res/values-fr/strings.xml index 32f45da7da..f751bb5b31 100644 --- a/libs/annotations/src/main/res/values-fr/strings.xml +++ b/libs/annotations/src/main/res/values-fr/strings.xml @@ -38,5 +38,6 @@ Couleur de la note Supprimé %1$s par %2$s + diff --git a/libs/annotations/src/main/res/values-ht/strings.xml b/libs/annotations/src/main/res/values-ht/strings.xml index d60711fd1b..4b5c8ff167 100644 --- a/libs/annotations/src/main/res/values-ht/strings.xml +++ b/libs/annotations/src/main/res/values-ht/strings.xml @@ -38,5 +38,6 @@ Note Koulè Elimine %1$s pa %2$s + diff --git a/libs/annotations/src/main/res/values-is/strings.xml b/libs/annotations/src/main/res/values-is/strings.xml index b5531e10de..cbd9350234 100644 --- a/libs/annotations/src/main/res/values-is/strings.xml +++ b/libs/annotations/src/main/res/values-is/strings.xml @@ -37,5 +37,6 @@ Sendi Litur glósu Fjarlægt %1$s af %2$s + diff --git a/libs/annotations/src/main/res/values-it/strings.xml b/libs/annotations/src/main/res/values-it/strings.xml index 6a1c5f216c..5f2fa6cab4 100644 --- a/libs/annotations/src/main/res/values-it/strings.xml +++ b/libs/annotations/src/main/res/values-it/strings.xml @@ -36,5 +36,6 @@ Invio in corso Colore nota Rimosso il %1$s da %2$s + diff --git a/libs/annotations/src/main/res/values-ja/strings.xml b/libs/annotations/src/main/res/values-ja/strings.xml index ce4c759081..f3731a2b6f 100644 --- a/libs/annotations/src/main/res/values-ja/strings.xml +++ b/libs/annotations/src/main/res/values-ja/strings.xml @@ -38,5 +38,6 @@ ノートの色 %1$sにより除去済%2$s + diff --git a/libs/annotations/src/main/res/values-mi/strings.xml b/libs/annotations/src/main/res/values-mi/strings.xml index 1567a45db4..05f1b504ef 100644 --- a/libs/annotations/src/main/res/values-mi/strings.xml +++ b/libs/annotations/src/main/res/values-mi/strings.xml @@ -37,5 +37,6 @@ E tuku ana Tuhi Tae Kua tangohia %1$s e %2$s + diff --git a/libs/annotations/src/main/res/values-nb/strings.xml b/libs/annotations/src/main/res/values-nb/strings.xml index 75023d68ee..a7a39aced7 100644 --- a/libs/annotations/src/main/res/values-nb/strings.xml +++ b/libs/annotations/src/main/res/values-nb/strings.xml @@ -37,5 +37,6 @@ Sender Notatfarge Fjernet %1$s av %2$s + diff --git a/libs/annotations/src/main/res/values-nl/strings.xml b/libs/annotations/src/main/res/values-nl/strings.xml index 5ed72b4e5d..6554f4e836 100644 --- a/libs/annotations/src/main/res/values-nl/strings.xml +++ b/libs/annotations/src/main/res/values-nl/strings.xml @@ -38,5 +38,6 @@ Kleur van opmerking %1$s verwijderd door %2$s + diff --git a/libs/annotations/src/main/res/values-pl/strings.xml b/libs/annotations/src/main/res/values-pl/strings.xml index b2bbd855d6..bc857a8d29 100644 --- a/libs/annotations/src/main/res/values-pl/strings.xml +++ b/libs/annotations/src/main/res/values-pl/strings.xml @@ -38,5 +38,6 @@ Kolor notatki Usunięte %1$s przez %2$s + diff --git a/libs/annotations/src/main/res/values-pt-rBR/strings.xml b/libs/annotations/src/main/res/values-pt-rBR/strings.xml index a277feb6a6..a1b7319c8c 100644 --- a/libs/annotations/src/main/res/values-pt-rBR/strings.xml +++ b/libs/annotations/src/main/res/values-pt-rBR/strings.xml @@ -36,5 +36,6 @@ Enviando Cor da observação Removido %1$s por %2$s + diff --git a/libs/annotations/src/main/res/values-pt-rPT/strings.xml b/libs/annotations/src/main/res/values-pt-rPT/strings.xml index 03fbe87b99..6a5f47db1f 100644 --- a/libs/annotations/src/main/res/values-pt-rPT/strings.xml +++ b/libs/annotations/src/main/res/values-pt-rPT/strings.xml @@ -38,5 +38,6 @@ Observar cor Removido %1$s por %2$s + diff --git a/libs/annotations/src/main/res/values-ru/strings.xml b/libs/annotations/src/main/res/values-ru/strings.xml index 98ead02248..9fc6aac87e 100644 --- a/libs/annotations/src/main/res/values-ru/strings.xml +++ b/libs/annotations/src/main/res/values-ru/strings.xml @@ -38,5 +38,6 @@ Отметить цвет %1$s удален %2$s + diff --git a/libs/annotations/src/main/res/values-sl/strings.xml b/libs/annotations/src/main/res/values-sl/strings.xml index 46b285d354..68f4f28674 100644 --- a/libs/annotations/src/main/res/values-sl/strings.xml +++ b/libs/annotations/src/main/res/values-sl/strings.xml @@ -37,5 +37,6 @@ Pošiljanje Barva opombe Odstranjeno %1$s, odstranil %2$s + diff --git a/libs/annotations/src/main/res/values-sv/strings.xml b/libs/annotations/src/main/res/values-sv/strings.xml index d104c77450..43adaf6db0 100644 --- a/libs/annotations/src/main/res/values-sv/strings.xml +++ b/libs/annotations/src/main/res/values-sv/strings.xml @@ -36,5 +36,6 @@ Skickar Anteckningsfärg Borttagen %1$s av %2$s + diff --git a/libs/annotations/src/main/res/values-th/strings.xml b/libs/annotations/src/main/res/values-th/strings.xml index d26603b218..816c1d248d 100644 --- a/libs/annotations/src/main/res/values-th/strings.xml +++ b/libs/annotations/src/main/res/values-th/strings.xml @@ -37,5 +37,6 @@ กำลังส่ง สีหมายเหตุ ลบ %1$s โดย %2$s + diff --git a/libs/annotations/src/main/res/values-vi/strings.xml b/libs/annotations/src/main/res/values-vi/strings.xml index 4daa087ba8..b9f3a5cd43 100644 --- a/libs/annotations/src/main/res/values-vi/strings.xml +++ b/libs/annotations/src/main/res/values-vi/strings.xml @@ -37,5 +37,6 @@ Đang Gửi Màu Ghi Chú Đã bị gỡ %1$s bởi %2$s + diff --git a/libs/annotations/src/main/res/values-zh/strings.xml b/libs/annotations/src/main/res/values-zh/strings.xml index 0e33334040..a10a031b4c 100644 --- a/libs/annotations/src/main/res/values-zh/strings.xml +++ b/libs/annotations/src/main/res/values-zh/strings.xml @@ -38,5 +38,6 @@ 注释颜色 移除%1$s者 %2$s + diff --git a/libs/annotations/src/main/res/values/strings.xml b/libs/annotations/src/main/res/values/strings.xml index 44e3188fec..9fc2b43536 100644 --- a/libs/annotations/src/main/res/values/strings.xml +++ b/libs/annotations/src/main/res/values/strings.xml @@ -36,5 +36,6 @@ Sending Note Color Removed %1$s by %2$s + From 435ae20b38dcf5ecd6f073c921e98a1a87633447 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:05:31 +0200 Subject: [PATCH 12/73] [Student][Teacher] Fixed RestRetryInterceptor (#1733) refs: MBL-16299 affects: Student, Teacher release note: none --- .../com/instructure/dataseeding/util/RestRetryInterceptor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/RestRetryInterceptor.kt b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/RestRetryInterceptor.kt index a68d6b1ae9..c4d65eb0d7 100644 --- a/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/RestRetryInterceptor.kt +++ b/automation/dataseedingapi/src/main/kotlin/com/instructure/dataseeding/util/RestRetryInterceptor.kt @@ -33,6 +33,7 @@ object RestRetryInterceptor : Interceptor { var response = chain.proceed(request) while (response.failed && attempt <= MAX_RETRIES) { RetryBackoff.wait(attempt) + response.close() response = chain.proceed(request) attempt += 1 } From 8cd805bd7707c4c7affbdcb41b69e74ad9ce4666 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:09:07 +0200 Subject: [PATCH 13/73] [MBL-16234][Student] - Share extension E2E test (#1730) * Implement Share Extension E2E test. Rename typo in assertAssignmentSubmissionSuccess method. Use string resource instead of case insensitive text on Turn In and Upload buttons. * extract share extension test filenames into variables remove password log from all the test cases. refs: MBL-16234 affects: Student release note: none --- .../student/ui/e2e/AnnouncementsE2ETest.kt | 2 +- .../student/ui/e2e/AssignmentsE2ETest.kt | 10 +- .../student/ui/e2e/BookmarksE2ETest.kt | 2 +- .../student/ui/e2e/CollaborationsE2ETest.kt | 2 +- .../student/ui/e2e/ConferencesE2ETest.kt | 2 +- .../student/ui/e2e/DashboardE2ETest.kt | 3 +- .../student/ui/e2e/DiscussionsE2ETest.kt | 2 +- .../student/ui/e2e/FilesE2ETest.kt | 8 +- .../student/ui/e2e/GradesE2ETest.kt | 2 +- .../student/ui/e2e/InboxE2ETest.kt | 4 +- .../student/ui/e2e/LoginE2ETest.kt | 16 +- .../student/ui/e2e/ModulesE2ETest.kt | 8 +- .../student/ui/e2e/NotificationsE2ETest.kt | 2 +- .../student/ui/e2e/PagesE2ETest.kt | 2 +- .../student/ui/e2e/PeopleE2ETest.kt | 5 +- .../student/ui/e2e/QuizzesE2ETest.kt | 2 +- .../student/ui/e2e/SettingsE2ETest.kt | 9 +- .../student/ui/e2e/ShareExtensionE2ETest.kt | 231 ++++++++++++++++++ .../student/ui/e2e/SyllabusE2ETest.kt | 2 +- .../instructure/student/ui/e2e/TodoE2ETest.kt | 2 +- .../ShareExtensionInteractionTest.kt | 2 +- .../student/ui/pages/FileUploadPage.kt | 7 +- .../ui/pages/ShareExtensionStatusPage.kt | 16 +- .../teacher/ui/e2e/AnnouncementsE2ETest.kt | 2 +- .../teacher/ui/e2e/AssignmentE2ETest.kt | 2 +- .../teacher/ui/e2e/CourseSettingsE2ETest.kt | 2 +- .../teacher/ui/e2e/DashboardE2ETest.kt | 2 +- .../teacher/ui/e2e/DiscussionsE2ETest.kt | 2 +- .../teacher/ui/e2e/FilesE2ETest.kt | 2 +- .../teacher/ui/e2e/InboxE2ETest.kt | 2 +- .../teacher/ui/e2e/LoginE2ETest.kt | 10 +- .../teacher/ui/e2e/ModulesE2ETest.kt | 8 +- .../teacher/ui/e2e/PagesE2ETest.kt | 2 +- .../teacher/ui/e2e/PeopleE2ETest.kt | 8 +- .../instructure/teacher/ui/e2e/QuizE2ETest.kt | 2 +- .../teacher/ui/e2e/SettingsE2ETest.kt | 10 +- .../teacher/ui/e2e/SpeedGraderE2ETest.kt | 2 +- .../teacher/ui/e2e/SyllabusE2ETest.kt | 2 +- .../instructure/teacher/ui/e2e/TodoE2ETest.kt | 2 +- 39 files changed, 316 insertions(+), 85 deletions(-) create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt index 6b03be97c9..6a980dbeaa 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt @@ -52,7 +52,7 @@ class AnnouncementsE2ETest : StudentTest() { val announcement = data.announcementsList[0] val secondAnnouncement = data.announcementsList[1] - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt index 871eca2058..972fef2d13 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt @@ -77,7 +77,7 @@ class AssignmentsE2ETest: StudentTest() { dueAt = 1.days.fromNow.iso8601 )) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -166,7 +166,7 @@ class AssignmentsE2ETest: StudentTest() { excused = false ) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -200,7 +200,7 @@ class AssignmentsE2ETest: StudentTest() { allowedExtensions = listOf("txt", "pdf", "jpg") )) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -342,7 +342,7 @@ class AssignmentsE2ETest: StudentTest() { excused = false ) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -389,7 +389,7 @@ class AssignmentsE2ETest: StudentTest() { )) )) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt index e05d57161a..f18380ab0c 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt @@ -68,7 +68,7 @@ class BookmarksE2ETest : StudentTest() { dueAt = 1.days.fromNow.iso8601 )) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt index 6676a8a364..c1f0c7e521 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt @@ -37,7 +37,7 @@ class CollaborationsE2ETest: StudentTest() { val student = data.studentsList[0] val course = data.coursesList[0] - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt index 8beae2e7d6..0e4b170ac8 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt @@ -43,7 +43,7 @@ class ConferencesE2ETest: StudentTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt index 40acdb7f5f..ea25f2bf4c 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt @@ -28,7 +28,6 @@ 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 junit.framework.Assert.assertEquals import org.junit.Test @HiltAndroidTest @@ -64,7 +63,7 @@ class DashboardE2ETest : StudentTest() { Log.d(PREPARATION_TAG,"Create group membership for ${student.name} student.") GroupsApi.createGroupMembership(group.id, student.id, teacher.token) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt index cd2238f1e5..e8f3650e4a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt @@ -77,7 +77,7 @@ class DiscussionsE2ETest: StudentTest() { token = teacher.token ) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt index fc6ce70ea3..d274e63fd2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt @@ -35,11 +35,7 @@ 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.utils.StudentTest -import com.instructure.student.ui.utils.ViewUtils -import com.instructure.student.ui.utils.seedData -import com.instructure.student.ui.utils.tokenLogin -import com.instructure.student.ui.utils.uploadTextFile +import com.instructure.student.ui.utils.* import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.io.File @@ -113,7 +109,7 @@ class FilesE2ETest: StudentTest() { token = student.token ) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt index 81856f5373..1511a783d3 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt @@ -85,7 +85,7 @@ class GradesE2ETest: StudentTest() { Log.d(STEP_TAG,"Publish the previously made quiz.") val quiz = QuizzesApi.createAndPublishQuiz(course.id, teacher.token, quizQuestions) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt index 95e09eecd4..d49a85c131 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt @@ -63,7 +63,7 @@ class InboxE2ETest: StudentTest() { GroupsApi.createGroupMembership(group.id, student1.id, teacher.token) GroupsApi.createGroupMembership(group.id, student2.id, teacher.token) - Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId} , password: ${student1.password}") + Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId}.") tokenLogin(student1) dashboardPage.waitForRender() dashboardPage.assertDisplaysCourse(course) @@ -124,7 +124,7 @@ class InboxE2ETest: StudentTest() { Log.d(STEP_TAG,"Log out with ${student1.name} student.") dashboardPage.logOut() - Log.d(STEP_TAG,"Login with user: ${student2.name}, login id: ${student2.loginId} , password: ${student2.password}") + Log.d(STEP_TAG,"Login with user: ${student2.name}, login id: ${student2.loginId}.") tokenLogin(student2) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt index b0b9bb3748..899e9d8dc6 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt @@ -57,7 +57,7 @@ class LoginE2ETest : StudentTest() { val student1 = data.studentsList[0] val student2 = data.studentsList[1] - Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId} , password: ${student1.password}") + Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId}.") loginWithUser(student1) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -66,7 +66,7 @@ class LoginE2ETest : StudentTest() { Log.d(STEP_TAG,"Log out with ${student1.name} student.") dashboardPage.logOut() - Log.d(STEP_TAG,"Login with user: ${student2.name}, login id: ${student2.loginId} , password: ${student2.password}") + Log.d(STEP_TAG,"Login with user: ${student2.name}, login id: ${student2.loginId}.") loginWithUser(student2) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -78,7 +78,7 @@ class LoginE2ETest : StudentTest() { Log.d(STEP_TAG,"Assert that the previously logins has been displayed.") loginLandingPage.assertDisplaysPreviousLogins() - Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId} , password: ${student1.password}") + Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId}.") loginWithUser(student1) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -136,7 +136,7 @@ class LoginE2ETest : StudentTest() { val course = data.coursesList[0] val parent = parentData.parentsList[0] //Test with Parent user. parents don't show up in the "People" page so we can't verify their role. - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") loginWithUser(student) Log.d(STEP_TAG,"Validate ${student.name} user's role as a Student.") @@ -148,7 +148,7 @@ class LoginE2ETest : StudentTest() { Log.d(STEP_TAG,"Log out with ${student.name} student.") dashboardPage.logOut() - Log.d(STEP_TAG,"Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG,"Login with user: ${teacher.name}, login id: ${teacher.loginId}.") loginWithUser(teacher) Log.d(STEP_TAG,"Validate ${teacher.name} user's role as a Teacher.") @@ -160,7 +160,7 @@ class LoginE2ETest : StudentTest() { Log.d(STEP_TAG,"Log out with ${teacher.name} teacher.") dashboardPage.logOut() - Log.d(STEP_TAG,"Login with user: ${ta.name}, login id: ${ta.loginId} , password: ${ta.password}") + Log.d(STEP_TAG,"Login with user: ${ta.name}, login id: ${ta.loginId}.") loginWithUser(ta) Log.d(STEP_TAG,"Validate ${ta.name} user's role as a TA.") @@ -172,7 +172,7 @@ class LoginE2ETest : StudentTest() { Log.d(STEP_TAG,"Log out with ${ta.name} teacher assistant.") dashboardPage.logOut() - Log.d(STEP_TAG,"Login with user: ${parent.name}, login id: ${parent.loginId} , password: ${parent.password}") + Log.d(STEP_TAG,"Login with user: ${parent.name}, login id: ${parent.loginId}.") loginWithUser(parent) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -217,7 +217,7 @@ class LoginE2ETest : StudentTest() { enrollmentService = enrollmentsService ) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") loginWithUser(student) Log.d(STEP_TAG,"Attempt to sign into our vanity domain, and validate ${student.name} user's role as a Student.") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt index 03c4305a3a..e7db107328 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt @@ -19,11 +19,7 @@ package com.instructure.student.ui.e2e import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E -import com.instructure.dataseeding.api.AssignmentsApi -import com.instructure.dataseeding.api.DiscussionTopicsApi -import com.instructure.dataseeding.api.ModulesApi -import com.instructure.dataseeding.api.PagesApi -import com.instructure.dataseeding.api.QuizzesApi +import com.instructure.dataseeding.api.* import com.instructure.dataseeding.model.ModuleItemTypes import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days @@ -165,7 +161,7 @@ class ModulesE2ETest: StudentTest() { contentId = discussionTopic1.id.toString() ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt index ab8d53bfd4..6743ac91bd 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt @@ -98,7 +98,7 @@ class NotificationsE2ETest : StudentTest() { Log.d(PREPARATION_TAG,"Create and publish a quiz with the previously seeded questions.") QuizzesApi.createAndPublishQuiz(course.id, teacher.token, quizQuestions) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt index 0ab9529ddc..203a32fd48 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt @@ -79,7 +79,7 @@ class PagesE2ETest: StudentTest() { body = "

Front Page Text

" ) - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt index 7d1eb54def..4451f3b26b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt @@ -17,7 +17,6 @@ package com.instructure.student.ui.e2e import android.util.Log -import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E import com.instructure.panda_annotations.FeatureCategory import com.instructure.panda_annotations.Priority @@ -55,7 +54,7 @@ class PeopleE2ETest : StudentTest() { val student1 = data.studentsList[0] val student2 = data.studentsList[1] - Log.d(STEP_TAG, "Login with user: ${student1.name}, login id: ${student1.loginId} , password: ${student1.password}") + Log.d(STEP_TAG, "Login with user: ${student1.name}, login id: ${student1.loginId}.") tokenLogin(student1) dashboardPage.waitForRender() @@ -96,7 +95,7 @@ class PeopleE2ETest : StudentTest() { Log.d(STEP_TAG,"Sign out with ${student1.name} student.") dashboardPage.logOut() - Log.d(STEP_TAG, "Login with user: ${student2.name}, login id: ${student2.loginId} , password: ${student2.password}") + Log.d(STEP_TAG, "Login with user: ${student2.name}, login id: ${student2.loginId}.") tokenLogin(student2) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt index d7b9a5260b..200cf16b51 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt @@ -120,7 +120,7 @@ class QuizzesE2ETest: StudentTest() { val quizPublished = createAndPublishQuiz(course.id, teacher.token, quizQuestions) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt index aed91b26e3..9d1e86c595 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt @@ -53,7 +53,6 @@ class SettingsE2ETest : StudentTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") tokenLogin(student) dashboardPage.waitForRender() @@ -115,7 +114,7 @@ class SettingsE2ETest : StudentTest() { val student = data.studentsList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -164,7 +163,7 @@ class SettingsE2ETest : StudentTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -186,7 +185,7 @@ class SettingsE2ETest : StudentTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -219,7 +218,7 @@ class SettingsE2ETest : StudentTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt new file mode 100644 index 0000000000..80d8ae2cf3 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2022 - 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 + +import android.content.Intent +import android.net.Uri +import android.util.Log +import androidx.core.content.FileProvider +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector +import com.instructure.canvas.espresso.E2E +import com.instructure.dataseeding.api.AssignmentsApi +import com.instructure.dataseeding.model.GradingType +import com.instructure.dataseeding.model.SubmissionType +import com.instructure.dataseeding.util.days +import com.instructure.dataseeding.util.fromNow +import com.instructure.dataseeding.util.iso8601 +import com.instructure.pandautils.utils.Const +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.ViewUtils +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.Test +import java.io.File + +@HiltAndroidTest +class ShareExtensionE2ETest: StudentTest() { + + override fun displaysPageObjects() = Unit + override fun enableAndConfigureAccessibilityChecks() = Unit + + @E2E + @Test + fun shareExtensionE2ETest() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val jpgTestFileName = "sample.jpg" + val pdfTestFileName = "samplepdf.pdf" + val uri = setupFileOnDevice(jpgTestFileName) + val uri2 = setupFileOnDevice(pdfTestFileName) + val student = data.studentsList[0] + val course = data.coursesList[0] + val teacher = data.teachersList[0] + + Log.d(PREPARATION_TAG,"Seeding 'Text Entry' assignment for ${course.name} course.") + val testAssignmentOne = AssignmentsApi.createAssignment( + AssignmentsApi.CreateAssignmentRequest( + courseId = course.id, + submissionTypes = listOf(SubmissionType.ONLINE_UPLOAD), + gradingType = GradingType.POINTS, + teacherToken = teacher.token, + pointsPossible = 15.0, + dueAt = 1.days.fromNow.iso8601 + )) + + AssignmentsApi.createAssignment( + AssignmentsApi.CreateAssignmentRequest( + courseId = course.id, + submissionTypes = listOf(SubmissionType.ONLINE_UPLOAD), + gradingType = GradingType.POINTS, + teacherToken = teacher.token, + pointsPossible = 30.0, + dueAt = 1.days.fromNow.iso8601 + )) + + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + + Log.d(STEP_TAG, "Press 'Home' button on the device so it will take the Student application into the background.") + device.pressHome() + + Log.d(STEP_TAG," Share the '$jpgTestFileName' and '$pdfTestFileName' files from the following uris: '$uri' and '$uri2'.") + shareMultipleFiles(arrayListOf(uri, uri2)) + + Log.d(STEP_TAG,"Click on the Canvas Student app.") + device.findObject(UiSelector().text("Canvas")).click() + device.waitForIdle() + + Log.d(STEP_TAG,"Assert that the Share Extension Page is displayed and the " + + "'My Files' button is selected by default, and so the '${student.name}' username is displayed as well.") + shareExtensionTargetPage.assertPageObjects() + shareExtensionTargetPage.assertFilesCheckboxIsSelected() + shareExtensionTargetPage.assertUserName(student.name) + + Log.d(STEP_TAG, "Select 'Upload as Submission' and assert that the corresponding course and assignment are displayed within the spinners.") + shareExtensionTargetPage.selectSubmission() + shareExtensionTargetPage.assertCourseSelectorDisplayedWithCourse(course.name) + shareExtensionTargetPage.assertAssignmentSelectorDisplayedWithAssignment(testAssignmentOne.name) + + Log.d(STEP_TAG, "Click on 'Next' button.") + shareExtensionTargetPage.pressNext() + + Log.d(STEP_TAG, "Assert that the File Upload page is displayed with the corresponding title.") + fileUploadPage.assertPageObjects() + fileUploadPage.assertDialogTitle("Submission") + + Log.d(STEP_TAG, "Click on 'Turn In' button to upload both of the files.") + fileUploadPage.clickTurnIn() + + Log.d(STEP_TAG, "Assert that the submission upload was successful.") + shareExtensionStatusPage.assertPageObjects() + shareExtensionStatusPage.assertAssignmentSubmissionSuccess() + + Log.d(STEP_TAG, "Click on 'Done' button.") + shareExtensionStatusPage.clickOnDone() + + Log.d(STEP_TAG, "Click 'Recent Apps' device button and bring Canvas Student into the foreground again." + + "Assert that the Dashboard Page is displayed.") + device.pressRecentApps() + device.findObject(UiSelector().descriptionContains("Canvas")).click() + + Log.d(STEP_TAG, "Assert that the Dashboard Page is displayed. Select ${course.name} and navigate to Assignments Page.") + dashboardPage.assertPageObjects() + dashboardPage.selectCourse(course) + courseBrowserPage.selectAssignments() + + Log.d(STEP_TAG, "Click on $testAssignmentOne assignment and refresh the Assignment Details Page." + + "Assert that the $testAssignmentOne assignment's status is 'Submitted'.") + assignmentListPage.clickAssignment(testAssignmentOne) + assignmentDetailsPage.refresh() + assignmentDetailsPage.assertAssignmentSubmitted() + + Log.d(STEP_TAG, "Press 'Home' button on the device so it will take the Student application into the background.") + device.pressHome() + + Log.d(STEP_TAG," Share the '$jpgTestFileName' and '$pdfTestFileName' files from the following uris: '$uri' and '$uri2'.") + shareMultipleFiles(arrayListOf(uri, uri2)) + + Log.d(STEP_TAG,"Click on the Canvas Student app.") + device.findObject(UiSelector().text("Canvas")).click() + device.waitForIdle() + + Log.d(STEP_TAG,"Assert that the Share Extension Page is displayed and the " + + "'My Files' button is selected by default, and so the '${student.name}' username is displayed as well.") + shareExtensionTargetPage.assertPageObjects() + shareExtensionTargetPage.assertFilesCheckboxIsSelected() + shareExtensionTargetPage.assertUserName(student.name) + + Log.d(STEP_TAG, "Press 'Next' button.") + shareExtensionTargetPage.pressNext() + + Log.d(STEP_TAG,"Assert that the title of the File Upload Page is correct and both of the shared files are displayed.") + fileUploadPage.assertPageObjects() + fileUploadPage.assertDialogTitle("Upload To My Files") + fileUploadPage.assertFileDisplayed(jpgTestFileName) + fileUploadPage.assertFileDisplayed(pdfTestFileName) + + Log.d(STEP_TAG,"Remove '$pdfTestFileName' file and assert that it's not displayed any more on the list but the other file is displayed.") + fileUploadPage.removeFile(pdfTestFileName) + fileUploadPage.assertFileNotDisplayed(pdfTestFileName) + fileUploadPage.assertFileDisplayed("$pdfTestFileName.jpg") + + Log.d(STEP_TAG, "Click on 'Upload' button to upload the file.") + fileUploadPage.clickUpload() + + Log.d(STEP_TAG, "Assert that the file upload (into my 'Files') was successful.") + shareExtensionStatusPage.assertPageObjects() + shareExtensionStatusPage.assertFileUploadSuccess() + + Log.d(STEP_TAG, "Click on 'Done' button.") + shareExtensionStatusPage.clickOnDone() + + Log.d(STEP_TAG, "Click 'Recent Apps' device button and bring Canvas Student into the foreground again." + + "Assert that the Dashboard Page is displayed.") + device.pressRecentApps() + device.findObject(UiSelector().descriptionContains("Canvas")).click() + + Log.d(STEP_TAG, "Press back button to navigate back to the Dashboard Page.") + assignmentDetailsPage.assertPageObjects() + ViewUtils.pressBackButton(3) + + Log.d(STEP_TAG, "Navigate to (Global) Files Page.") + dashboardPage.assertPageObjects() + Thread.sleep(4000) //Make sure that the toast message has disappeared. + dashboardPage.gotoGlobalFiles() + + Log.d(STEP_TAG, "Assert that the 'unfiled' directory is displayed." + + "Click on it, and assert that the previously uploaded file ($jpgTestFileName) is displayed within the folder.") + fileListPage.assertItemDisplayed("unfiled") + fileListPage.selectItem("unfiled") + fileListPage.assertItemDisplayed(jpgTestFileName) + + } + + private fun setupFileOnDevice(fileName: String): Uri { + copyAssetFileToExternalCache(activityRule.activity, fileName) + + val dir = activityRule.activity.externalCacheDir + val file = File(dir?.path, fileName) + + val instrumentationContext = InstrumentationRegistry.getInstrumentation().context + return FileProvider.getUriForFile( + instrumentationContext, + "com.instructure.candroid" + Const.FILE_PROVIDER_AUTHORITY, + file + ) + } + + private fun shareMultipleFiles(uris: ArrayList) { + val intent = Intent().apply { + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) + type = "*/*" + } + + val chooser = Intent.createChooser(intent, null) + chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + InstrumentationRegistry.getInstrumentation().context.startActivity(chooser) + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt index 5be307ab37..1df4dbb0b1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt @@ -55,7 +55,7 @@ class SyllabusE2ETest: StudentTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt index c0bda5daec..7e0eb1714a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt @@ -75,7 +75,7 @@ class TodoE2ETest: StudentTest() { dueAt = 8.days.fromNow.iso8601) ) - Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt index 7bae222dd1..9386aa4ca4 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt @@ -230,7 +230,7 @@ class ShareExtensionInteractionTest : StudentTest() { fileUploadPage.clickTurnIn() shareExtensionStatusPage.assertPageObjects() - shareExtensionStatusPage.assertAssignemntSubmissionSuccess() + shareExtensionStatusPage.assertAssignmentSubmissionSuccess() } private fun createMockData(): MockCanvas { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt index 71a5238bee..e39fa3a3cc 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt @@ -22,15 +22,12 @@ import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.withId -import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertDisplayed import com.instructure.espresso.click import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.onViewWithText import com.instructure.espresso.page.plus -import com.instructure.espresso.page.waitForViewWithId -import com.instructure.espresso.page.withAncestor import com.instructure.espresso.page.withDescendant import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo @@ -57,11 +54,11 @@ class FileUploadPage : BasePage() { } fun clickUpload() { - onView(allOf(isAssignableFrom(Button::class.java),containsTextCaseInsensitive("upload"))).click() + onView(allOf(isAssignableFrom(Button::class.java), withText(R.string.upload))).click() } fun clickTurnIn() { - onView(containsTextCaseInsensitive("turn in")).click() + onView(withText(R.string.turnIn)).click() } fun removeFile(filename: String) { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ShareExtensionStatusPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ShareExtensionStatusPage.kt index 0e94d5a7cb..83d8ba9a25 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ShareExtensionStatusPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ShareExtensionStatusPage.kt @@ -18,7 +18,10 @@ package com.instructure.student.ui.pages import com.instructure.espresso.WaitForViewWithId import com.instructure.espresso.assertHasText +import com.instructure.espresso.click import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.withId import com.instructure.student.R class ShareExtensionStatusPage : BasePage() { @@ -27,9 +30,20 @@ class ShareExtensionStatusPage : BasePage() { private val subtitle by WaitForViewWithId(R.id.subtitle) private val description by WaitForViewWithId(R.id.description) - fun assertAssignemntSubmissionSuccess() { + fun assertAssignmentSubmissionSuccess() { dialogTitle.assertHasText(R.string.submission) subtitle.assertHasText(R.string.submissionSuccessTitle) description.assertHasText(R.string.submissionSuccessMessage) } + + fun assertFileUploadSuccess() { + dialogTitle.assertHasText(R.string.fileUpload) + subtitle.assertHasText(R.string.fileUploadSuccess) + description.assertHasText(R.string.filesUploadedSuccessfully) + } + + fun clickOnDone() { + onView(withId(R.id.doneButton)).click() + } + } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AnnouncementsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AnnouncementsE2ETest.kt index 743e8b1ebc..b8102ee75e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AnnouncementsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AnnouncementsE2ETest.kt @@ -50,7 +50,7 @@ class AnnouncementsE2ETest : TeacherTest() { val announcement = data.announcementsList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt index 10442ff67b..316778c991 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/AssignmentE2ETest.kt @@ -53,7 +53,7 @@ class AssignmentE2ETest : TeacherTest() { val student = data.studentsList[0] val gradedStudent = data.studentsList[1] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/CourseSettingsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/CourseSettingsE2ETest.kt index 1cfc9d2da9..14b0753b88 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/CourseSettingsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/CourseSettingsE2ETest.kt @@ -52,7 +52,7 @@ class CourseSettingsE2ETest : TeacherTest() { val firstCourse = data.coursesList[0] val secondCourse = data.coursesList[1] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) Log.d(STEP_TAG, "Open ${firstCourse.name} course and click on Course Settings button.") diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DashboardE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DashboardE2ETest.kt index 1d4a119183..26512953d4 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DashboardE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DashboardE2ETest.kt @@ -48,7 +48,7 @@ class DashboardE2ETest : TeacherTest() { val course1 = data.coursesList[0] val course2 = data.coursesList[1] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() dashboardPage.assertPageObjects() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt index 788a7a6a80..99fb564d98 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt @@ -46,7 +46,7 @@ class DiscussionsE2ETest : TeacherTest() { val course = data.coursesList[0] val discussion = data.discussionsList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt index f755422921..ecd4f75249 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt @@ -116,7 +116,7 @@ class FilesE2ETest: TeacherTest() { token = student.token ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/InboxE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/InboxE2ETest.kt index a68d861ce8..3cdd76d8a9 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/InboxE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/InboxE2ETest.kt @@ -42,7 +42,7 @@ class InboxE2ETest : TeacherTest() { Log.d(PREPARATION_TAG, "Create group membership for ${student1.name} student to the group: ${group.name}.") GroupsApi.createGroupMembership(group.id, student1.id, teacher.token) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() dashboardPage.assertDisplaysCourse(course) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/LoginE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/LoginE2ETest.kt index 4467a12511..ba2f529c3d 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/LoginE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/LoginE2ETest.kt @@ -57,7 +57,7 @@ class LoginE2ETest : TeacherTest() { Log.d(STEP_TAG,"Click on 'Next' button on the Toolbar.") loginFindSchoolPage.clickToolbarNextMenuItem() - Log.d(STEP_TAG,"Login with user: ${teacher1.name}, login id: ${teacher1.loginId} , password: ${teacher1.password}") + Log.d(STEP_TAG,"Login with user: ${teacher1.name}, login id: ${teacher1.loginId}.") loginSignInPage.loginAs(teacher1) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -78,7 +78,7 @@ class LoginE2ETest : TeacherTest() { Log.d(STEP_TAG,"Click on 'Next' button on the Toolbar.") loginFindSchoolPage.clickToolbarNextMenuItem() - Log.d(STEP_TAG,"Login with user: ${teacher2.name}, login id: ${teacher2.loginId} , password: ${teacher2.password}") + Log.d(STEP_TAG,"Login with user: ${teacher2.name}, login id: ${teacher2.loginId}.") loginSignInPage.loginAs(teacher2) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -99,7 +99,7 @@ class LoginE2ETest : TeacherTest() { Log.d(STEP_TAG,"Click on 'Next' button on the Toolbar.") loginFindSchoolPage.clickToolbarNextMenuItem() - Log.d(STEP_TAG,"Login with user: ${teacher1.name}, login id: ${teacher1.loginId} , password: ${teacher1.password}") + Log.d(STEP_TAG,"Login with user: ${teacher1.name}, login id: ${teacher1.loginId}.") loginSignInPage.loginAs(teacher1) Log.d(STEP_TAG,"Assert that the Dashboard Page is the landing page and it is loaded successfully.") @@ -142,7 +142,7 @@ class LoginE2ETest : TeacherTest() { Log.d(STEP_TAG,"Enter domain: ${parent.domain}.") loginFindSchoolPage.clickToolbarNextMenuItem() - Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") loginSignInPage.loginAs(student) Log.d(STEP_TAG,"Assert that the user has been landed on 'Not a teacher?' Page.") @@ -166,7 +166,7 @@ class LoginE2ETest : TeacherTest() { Log.d(STEP_TAG,"Assert that the Login page has been displayed.") loginSignInPage.assertPageObjects() - Log.d(STEP_TAG,"Login with user: ${parent.name}, login id: ${parent.loginId} , password: ${parent.password}") + Log.d(STEP_TAG,"Login with user: ${parent.name}, login id: ${parent.loginId}.") loginSignInPage.loginAs(parent) Log.d(STEP_TAG,"Assert that the user has been landed on 'Not a teacher?' Page.") diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt index e3e35ac233..a5861e1bbf 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt @@ -1,10 +1,10 @@ package com.instructure.teacher.ui.e2e import android.util.Log -import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E -import com.instructure.canvas.espresso.refresh -import com.instructure.dataseeding.api.* +import com.instructure.dataseeding.api.AssignmentsApi +import com.instructure.dataseeding.api.ModulesApi +import com.instructure.dataseeding.api.QuizzesApi import com.instructure.dataseeding.model.ModuleItemTypes import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days @@ -38,7 +38,7 @@ class ModulesE2ETest : TeacherTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() dashboardPage.assertDisplaysCourse(course) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt index f271eb90ac..66de5ce460 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt @@ -59,7 +59,7 @@ class PagesE2ETest : TeacherTest() { body = "

Front Page Text

" ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PeopleE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PeopleE2ETest.kt index 162dffae72..52120c04bf 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PeopleE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PeopleE2ETest.kt @@ -21,13 +21,13 @@ import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E import com.instructure.dataseeding.api.SubmissionsApi import com.instructure.dataseeding.model.SubmissionType +import com.instructure.dataseeding.util.days +import com.instructure.dataseeding.util.fromNow +import com.instructure.dataseeding.util.iso8601 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.dataseeding.util.days -import com.instructure.dataseeding.util.fromNow -import com.instructure.dataseeding.util.iso8601 import com.instructure.teacher.ui.utils.* import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @@ -82,7 +82,7 @@ class PeopleE2ETest: TeacherTest() { excused = false ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) Log.d(STEP_TAG,"Open ${course.name} course and navigate to People Page.") diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/QuizE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/QuizE2ETest.kt index c4c719aa11..11cf2fe334 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/QuizE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/QuizE2ETest.kt @@ -46,7 +46,7 @@ class QuizE2ETest: TeacherTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt index f64998d7ee..472f481bee 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt @@ -50,7 +50,7 @@ class SettingsE2ETest : TeacherTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val teacher = data.teachersList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() @@ -103,7 +103,7 @@ class SettingsE2ETest : TeacherTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() @@ -152,7 +152,7 @@ class SettingsE2ETest : TeacherTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val teacher = data.teachersList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() @@ -174,7 +174,7 @@ class SettingsE2ETest : TeacherTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val teacher = data.teachersList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() @@ -199,7 +199,7 @@ class SettingsE2ETest : TeacherTest() { val data = seedData(students = 1, teachers = 1, courses = 1) val teacher = data.teachersList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SpeedGraderE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SpeedGraderE2ETest.kt index 258a7ae1ae..908de08abd 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SpeedGraderE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SpeedGraderE2ETest.kt @@ -101,7 +101,7 @@ class SpeedGraderE2ETest : TeacherTest() { excused = false ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) Log.d(STEP_TAG,"Open ${course.name} course and navigate to Assignments Page.") diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SyllabusE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SyllabusE2ETest.kt index f3122d2a5e..b3b1ad29ca 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SyllabusE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SyllabusE2ETest.kt @@ -32,7 +32,7 @@ class SyllabusE2ETest : TeacherTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt index 5c41d4fecf..475ce6bf3d 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt @@ -70,7 +70,7 @@ class TodoE2ETest : TeacherTest() { studentToken = student.token ) - Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId} , password: ${teacher.password}") + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(teacher) dashboardPage.waitForRender() From 3c4839ea96084ccab282546e746216ebbe1f9d9f Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:32:08 +0200 Subject: [PATCH 14/73] [MBL-16274][Student] Inconsistent reordering of announcement list after view refs: MBL-16274 affects: Student release note: Fixed an announcement list reordering bug. --- .../com/instructure/student/fragment/DiscussionListFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt index 9a4351a229..b020383bba 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt @@ -335,6 +335,7 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { @Suppress("unused") @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onDiscussionTopicCountChange(event: DiscussionTopicHeaderEvent) { + if (isAnnouncement) return event.get { // Gets written over on phones - added also to {@link #onRefreshFinished()} when { @@ -345,7 +346,7 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.CLOSED_FOR_COMMENTS, it) } else -> { - if (!isAnnouncement) recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.UNPINNED, it) + recyclerAdapter.addOrUpdateItem(DiscussionListRecyclerAdapter.UNPINNED, it) } } } From 4bc36340db97aae719bc5e85c0a131db3edfad0b Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:33:17 +0200 Subject: [PATCH 15/73] [MBL-15693][Student] Course announcement notifications refs: MBL-15693 affects: Student release note: Added notification count badge. * Course announcement notifications * Fixed tests * Refresh notifications when needed * Fixed comments * Fixed comments --- .../student/activity/CallbackActivity.kt | 18 +++++++++++- .../student/activity/NavigationActivity.kt | 6 ++-- .../NotificationListRecyclerAdapter.kt | 8 +++-- .../fragment/DiscussionDetailsFragment.kt | 1 + .../fragment/NotificationListFragment.kt | 29 +++++++++++++++++-- .../student/holders/NotificationViewHolder.kt | 10 +++---- .../NotificationAdapterToFragmentCallback.kt | 1 + .../AssignmentDetailsPresenter.kt | 20 ++++++++++--- .../res/layout/viewholder_notification.xml | 12 ++++++-- .../canvasapi2/apis/DiscussionAPI.kt | 9 +++++- .../instructure/canvasapi2/apis/InboxApi.kt | 10 +++---- .../canvasapi2/apis/SubmissionAPI.kt | 16 ++++++++++ .../canvasapi2/apis/UnreadCountAPI.kt | 2 +- .../canvasapi2/managers/DiscussionManager.kt | 10 +++++++ .../canvasapi2/managers/InboxManager.kt | 6 ++-- .../canvasapi2/managers/SubmissionManager.kt | 10 +++++++ .../canvasapi2/managers/UnreadCountManager.kt | 6 ++++ .../pact/canvas/apis/InboxApiPactTests.kt | 4 +-- libs/pandares/src/main/res/values/strings.xml | 5 ++++ .../pandautils/utils/Extensions.kt | 6 +++- 20 files changed, 158 insertions(+), 31 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt index ff7e145dd8..ea6c6fe56c 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt @@ -38,18 +38,20 @@ import com.instructure.student.BuildConfig import com.instructure.student.R import com.instructure.student.flutterChannels.FlutterComm import com.instructure.student.fragment.InboxFragment +import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.service.StudentPageViewService import com.instructure.student.util.StudentPrefs import kotlinx.coroutines.Job import retrofit2.Call import retrofit2.Response -abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountInvalidated { +abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountInvalidated, NotificationListFragment.OnNotificationCountInvalidated { private var loadInitialDataJob: Job? = null abstract fun gotLaunchDefinitions(launchDefinitions: List?) abstract fun updateUnreadCount(unreadCount: Int) + abstract fun updateNotificationCount(notificationCount: Int) abstract fun initialCoreDataLoadingComplete() override fun onCreate(savedInstanceState: Bundle?) { @@ -123,6 +125,8 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI // get unread count of conversations getUnreadMessageCount() + getUnreadNotificationCount() + initialCoreDataLoadingComplete() } catch { initialCoreDataLoadingComplete() @@ -137,6 +141,14 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI } } + private fun getUnreadNotificationCount() { + UnreadCountManager.getUnreadNotificationCount(object : StatusCallback>() { + override fun onResponse(data: Call>, response: Response>) { + updateNotificationCount(response.body()?.sumOf { it.unreadCount.orDefault() }.orDefault()) + } + }, true) + } + private val themeCallback = object : StatusCallback() { override fun onResponse(response: Response, linkHeaders: LinkHeaders, type: ApiType) { //store the theme @@ -201,6 +213,10 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI } } + override fun invalidateNotificationCount() { + getUnreadNotificationCount() + } + /** * This will fetch the user forcing a network request */ diff --git a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt index c5311a36c8..8d32f87378 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt @@ -23,7 +23,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Color -import android.graphics.Typeface import android.os.Bundle import android.os.Handler import android.view.MenuItem @@ -46,7 +45,6 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.airbnb.lottie.LottieAnimationView -import com.bumptech.glide.Glide import com.google.android.material.bottomnavigation.BottomNavigationView import com.instructure.canvasapi2.CanvasRestAdapter import com.instructure.canvasapi2.managers.CourseManager @@ -1025,6 +1023,10 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. updateBottomBarBadge(R.id.bottomNavigationInbox, unreadCount, R.plurals.a11y_inboxUnreadCount) } + override fun updateNotificationCount(notificationCount: Int) { + updateBottomBarBadge(R.id.bottomNavigationNotifications, notificationCount, R.plurals.a11y_notificationsUnreadCount) + } + private fun updateBottomBarBadge(@IdRes menuItemId: Int, count: Int, @PluralsRes quantityContentDescription: Int? = null) { if (count > 0) { bottomBar.getOrCreateBadge(menuItemId).number = count diff --git a/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt index b6d5845683..ff1cf533e7 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt @@ -303,7 +303,8 @@ class NotificationListRecyclerAdapter( notifyDataSetChanged() } } - } + }, + false ) } @@ -319,7 +320,10 @@ class NotificationListRecyclerAdapter( streamItem.id, object : StatusCallback() { override fun onResponse(response: Response, linkHeaders: LinkHeaders, type: ApiType) { - if (response.body()!!.isHidden) removeItem(streamItem) + if (response.body()!!.isHidden) { + removeItem(streamItem) + adapterToFragmentCallback.onItemRemoved() + } } override fun onFail(call: Call?, error: Throwable, response: Response<*>?) { diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt index fdc6ba1ea4..71c4cbf429 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt @@ -405,6 +405,7 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { private fun setupHeaderWebView() { setupWebView(discussionTopicHeaderWebView) discussionTopicHeaderWebView.addJavascriptInterface(JSDiscussionHeaderInterface(), "accessor") + DiscussionManager.markDiscussionTopicRead(canvasContext, getTopicId(), object : StatusCallback() {}) } @SuppressLint("SetJavaScriptEnabled") diff --git a/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt index a0fd308b16..9eeaabb39b 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt @@ -24,6 +24,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.fragment.app.FragmentManager import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.models.StreamItem.Type.* import com.instructure.canvasapi2.utils.ApiPrefs @@ -47,7 +48,7 @@ import kotlinx.android.synthetic.main.panda_recycler_refresh_layout.* @ScreenView(SCREEN_VIEW_NOTIFICATION_LIST) @PageView -class NotificationListFragment : ParentFragment(), Bookmarkable { +class NotificationListFragment : ParentFragment(), Bookmarkable, FragmentManager.OnBackStackChangedListener { @PageViewUrl @Suppress("unused") @@ -79,6 +80,7 @@ class NotificationListFragment : ParentFragment(), Bookmarkable { emptyView.setGuidelines(.28f,.6f,.73f,.12f,.88f) } } + (activity as? OnNotificationCountInvalidated)?.invalidateNotificationCount() } override fun onShowEditView(isVisible: Boolean) { @@ -88,12 +90,15 @@ class NotificationListFragment : ParentFragment(), Bookmarkable { override fun onShowErrorCrouton(message: Int) { showToast(message) } + + override fun onItemRemoved() { + (activity as? OnNotificationCountInvalidated)?.invalidateNotificationCount() + } } // Used to help determine if the bottom bar should be highlighted fun isCourseOrGroup(): Boolean = canvasContext.isCourseOrGroup - override fun title(): String = getString(if (canvasContext.isCourse || canvasContext.isGroup) R.string.homePageIdForNotifications else R.string.notifications) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? @@ -119,10 +124,25 @@ class NotificationListFragment : ParentFragment(), Bookmarkable { cancelButton.setOnClickListener { recyclerAdapter.cancelButtonClicked() } applyTheme() + + activity?.supportFragmentManager?.addOnBackStackChangedListener(this) + } + + private var shouldRefreshOnResume = false + + override fun onBackStackChanged() { + if (activity?.supportFragmentManager?.fragments?.lastOrNull()?.javaClass == this.javaClass) { + if (shouldRefreshOnResume) { + swipeRefreshLayout.isRefreshing = true + recyclerAdapter.refresh() + shouldRefreshOnResume = false + } + } } override fun onDestroyView() { recyclerAdapter.cancel() + activity?.supportFragmentManager?.removeOnBackStackChangedListener(this) super.onDestroyView() } @@ -184,12 +204,17 @@ class NotificationListFragment : ParentFragment(), Bookmarkable { return false } addFragmentForStreamItem(streamItem, activity as ParentActivity, false) + shouldRefreshOnResume = !streamItem.isReadState return true } override val bookmark: Bookmarker get() = Bookmarker(canvasContext.isCourseOrGroup, canvasContext) + interface OnNotificationCountInvalidated { + fun invalidateNotificationCount() + } + companion object { fun addFragmentForStreamItem(streamItem: StreamItem, context: Context, fromWidget: Boolean) { if (fromWidget) { diff --git a/apps/student/src/main/java/com/instructure/student/holders/NotificationViewHolder.kt b/apps/student/src/main/java/com/instructure/student/holders/NotificationViewHolder.kt index 10c978c857..17e2fa47bb 100644 --- a/apps/student/src/main/java/com/instructure/student/holders/NotificationViewHolder.kt +++ b/apps/student/src/main/java/com/instructure/student/holders/NotificationViewHolder.kt @@ -25,14 +25,11 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.StreamItem -import com.instructure.pandautils.utils.ColorKeeper -import com.instructure.pandautils.utils.ThemePrefs -import com.instructure.pandautils.utils.setGone -import com.instructure.pandautils.utils.setVisible +import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.adapter.NotificationListRecyclerAdapter -import com.instructure.student.util.BinderUtils import com.instructure.student.interfaces.NotificationAdapterToFragmentCallback +import com.instructure.student.util.BinderUtils import kotlinx.android.synthetic.main.viewholder_notification.view.* class NotificationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -155,8 +152,11 @@ class NotificationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) // Read/Unread if (item.isReadState) { title.setTypeface(null, Typeface.NORMAL) + unreadMark.setInvisible() } else { title.setTypeface(null, Typeface.BOLD) + unreadMark.setVisible() + unreadMark.setImageDrawable(ColorUtils.colorIt(ThemePrefs.accentColor, unreadMark.drawable)) } } diff --git a/apps/student/src/main/java/com/instructure/student/interfaces/NotificationAdapterToFragmentCallback.kt b/apps/student/src/main/java/com/instructure/student/interfaces/NotificationAdapterToFragmentCallback.kt index 0171efd8cf..068a2a7fbf 100644 --- a/apps/student/src/main/java/com/instructure/student/interfaces/NotificationAdapterToFragmentCallback.kt +++ b/apps/student/src/main/java/com/instructure/student/interfaces/NotificationAdapterToFragmentCallback.kt @@ -21,4 +21,5 @@ interface NotificationAdapterToFragmentCallback { fun onRefreshFinished() fun onShowEditView(isVisible: Boolean) fun onShowErrorCrouton(message: Int) + fun onItemRemoved() = Unit } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsPresenter.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsPresenter.kt index 4635932219..90f466d5a1 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsPresenter.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsPresenter.kt @@ -17,10 +17,14 @@ package com.instructure.student.mobius.assignmentDetails import android.content.Context -import android.view.accessibility.AccessibilityManager import androidx.core.content.ContextCompat +import com.instructure.canvasapi2.StatusCallback +import com.instructure.canvasapi2.managers.SubmissionManager import com.instructure.canvasapi2.models.* -import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.canvasapi2.utils.NumberHelper +import com.instructure.canvasapi2.utils.isRtl +import com.instructure.canvasapi2.utils.isValid import com.instructure.pandautils.utils.AssignmentUtils2 import com.instructure.student.R import com.instructure.student.Submission @@ -31,8 +35,7 @@ import com.instructure.student.mobius.assignmentDetails.ui.QuizDescriptionViewSt import com.instructure.student.mobius.assignmentDetails.ui.gradeCell.GradeCellViewState import com.instructure.student.mobius.common.ui.Presenter import java.text.DateFormat -import java.util.Date -import java.util.Locale +import java.util.* object AssignmentDetailsPresenter : Presenter { override fun present(model: AssignmentDetailsModel, context: Context): AssignmentDetailsViewState { @@ -55,10 +58,19 @@ object AssignmentDetailsPresenter : Presenter() {}) + } + private fun presentLoadedState( assignment: Assignment, quiz: Quiz?, diff --git a/apps/student/src/main/res/layout/viewholder_notification.xml b/apps/student/src/main/res/layout/viewholder_notification.xml index 481642070b..9cae4a96d3 100644 --- a/apps/student/src/main/res/layout/viewholder_notification.xml +++ b/apps/student/src/main/res/layout/viewholder_notification.xml @@ -16,12 +16,20 @@ --> + style="@style/AdapterItem"> + + diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/DiscussionAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/DiscussionAPI.kt index 20f669f202..23875fb693 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/DiscussionAPI.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/DiscussionAPI.kt @@ -27,7 +27,6 @@ import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.models.postmodels.DiscussionEntryPostBody import com.instructure.canvasapi2.models.postmodels.DiscussionTopicPostBody import com.instructure.canvasapi2.utils.APIHelper -import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody @@ -88,6 +87,9 @@ object DiscussionAPI { @POST("{contextType}/{contextId}/discussion_topics/{topicId}/entries/{entryId}/rating") fun rateDiscussionEntry(@Path("contextType") contextType: String, @Path("contextId") contextId: Long, @Path("topicId") topicId: Long, @Path("entryId") entryId: Long, @Query("rating") rating: Int): Call + @PUT("{contextType}/{contextId}/discussion_topics/{topicId}/read") + fun markDiscussionTopicRead(@Path("contextType") contextType: String, @Path("contextId") contextId: Long, @Path("topicId") topicId: Long): Call + @PUT("{contextType}/{contextId}/discussion_topics/{topicId}/entries/{entryId}/read") fun markDiscussionTopicEntryRead(@Path("contextType") contextType: String, @Path("contextId") contextId: Long, @Path("topicId") topicId: Long, @Path("entryId") entryId: Long): Call @@ -270,6 +272,11 @@ object DiscussionAPI { callback.addCall(adapter.build(DiscussionInterface::class.java, params).rateDiscussionEntry(contextType, canvasContext.id, topicId, entryId, rating)).enqueue(callback) } + fun markDiscussionTopicRead(adapter: RestBuilder, canvasContext: CanvasContext, topicId: Long, callback: StatusCallback, params: RestParams) { + val contextType = CanvasContext.getApiContext(canvasContext) + callback.addCall(adapter.build(DiscussionInterface::class.java, params).markDiscussionTopicRead(contextType, canvasContext.id, topicId)).enqueue(callback) + } + fun markDiscussionTopicEntryRead(adapter: RestBuilder, canvasContext: CanvasContext, topicId: Long, entryId: Long, callback: StatusCallback, params: RestParams) { val contextType = CanvasContext.getApiContext(canvasContext) callback.addCall(adapter.build(DiscussionInterface::class.java, params).markDiscussionTopicEntryRead(contextType, canvasContext.id, topicId, entryId)).enqueue(callback) diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/InboxApi.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/InboxApi.kt index 34eab7952f..1603303c17 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/InboxApi.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/InboxApi.kt @@ -66,7 +66,7 @@ object InboxApi { @Field("bulk_message") isBulk: Int): Call> @GET("conversations/{conversationId}?include[]=participant_avatars") - fun getConversation(@Path("conversationId") conversationId: Long): Call + fun getConversation(@Path("conversationId") conversationId: Long, @Query("auto_mark_as_read") markAsRead: Boolean): Call @PUT("conversations/{conversationId}") fun updateConversation(@Path("conversationId") conversationId: Long, @Query("conversation[workflow_state]") workflowState: String, @Query("conversation[starred]") isStarred: Boolean?): Call @@ -90,8 +90,8 @@ object InboxApi { fun markConversationAsUnread(@Query("conversation_ids[]") conversationId: Long, @Query("event") conversationEvent: String): Call } - fun getConversation(adapter: RestBuilder, callback: StatusCallback, params: RestParams, conversationId: Long) { - callback.addCall(adapter.build(InboxInterface::class.java, params).getConversation(conversationId)).enqueue(callback) + fun getConversation(adapter: RestBuilder, callback: StatusCallback, params: RestParams, conversationId: Long, markAsRead: Boolean) { + callback.addCall(adapter.build(InboxInterface::class.java, params).getConversation(conversationId, markAsRead)).enqueue(callback) } fun getConversations(scope: Scope, adapter: RestBuilder, callback: StatusCallback>, params: RestParams) { @@ -145,7 +145,7 @@ object InboxApi { } @Throws(IOException::class) - fun getConversation(adapter: RestBuilder, params: RestParams, conversationId: Long): Response { - return adapter.build(InboxInterface::class.java, params).getConversation(conversationId).execute() + fun getConversation(adapter: RestBuilder, params: RestParams, conversationId: Long, markAsRead: Boolean): Response { + return adapter.build(InboxInterface::class.java, params).getConversation(conversationId, markAsRead).execute() } } diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/SubmissionAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/SubmissionAPI.kt index 6a682599db..fbc5594eec 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/SubmissionAPI.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/SubmissionAPI.kt @@ -131,6 +131,12 @@ object SubmissionAPI { @GET("courses/{courseId}/assignments/{assignmentId}/submission_summary") fun getSubmissionSummary(@Path("courseId") courseId: Long, @Path("assignmentId") assignmentId: Long): Call + + @PUT("courses/{courseId}/assignments/{assignmentId}/submissions/self/read") + fun markSubmissionAsRead( + @Path("courseId") courseId: Long, + @Path("assignmentId") assignmentId: Long + ): Call } fun getSingleSubmission(courseId: Long, assignmentId: Long, studentId: Long, adapter: RestBuilder, callback: StatusCallback, params: RestParams) { @@ -212,6 +218,16 @@ object SubmissionAPI { ).enqueue(callback) } + fun markSubmissionAsRead( + adapter: RestBuilder, + params: RestParams, + courseId: Long, + assignmentId: Long, + callback: StatusCallback + ) { + callback.addCall(adapter.build(SubmissionInterface::class.java, params).markSubmissionAsRead(courseId, assignmentId)).enqueue(callback) + } + private fun generateRubricAssessmentQueryMap(rubricAssessment: Map): Map { val map = mutableMapOf() for ((criterionIdKey, ratingValue) in rubricAssessment) { diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UnreadCountAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UnreadCountAPI.kt index 0976f05353..c7b54c8bff 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UnreadCountAPI.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UnreadCountAPI.kt @@ -16,7 +16,7 @@ internal object UnreadCountAPI { @GET("conversations/unread_count") fun getUnreadConversationCount(): Call - @GET("users/self/activity_stream/summary") + @GET("users/self/activity_stream/summary?only_active_courses=true") fun getNotificationsCount(): Call> @GET("users/self/observer_alerts/unread_count") diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/DiscussionManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/DiscussionManager.kt index 65810e4509..24aee6a99c 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/DiscussionManager.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/DiscussionManager.kt @@ -134,6 +134,16 @@ object DiscussionManager { DiscussionAPI.rateDiscussionEntry(adapter, canvasContext, topicId, entryId, rating, callback, params) } + fun markDiscussionTopicRead( + canvasContext: CanvasContext, + topicId: Long, + callback: StatusCallback + ) { + val adapter = RestBuilder(callback) + val params = RestParams() + DiscussionAPI.markDiscussionTopicRead(adapter, canvasContext, topicId, callback, params) + } + fun markDiscussionTopicEntryRead( canvasContext: CanvasContext, topicId: Long, diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/InboxManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/InboxManager.kt index f48379ffc5..e8c4d2ce7d 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/InboxManager.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/InboxManager.kt @@ -66,10 +66,10 @@ object InboxManager { InboxApi.getConversationsFiltered(scope, canvasContext, adapter, callback, params) } - fun getConversation(conversationId: Long, forceNetwork: Boolean, callback: StatusCallback) { + fun getConversation(conversationId: Long, forceNetwork: Boolean, callback: StatusCallback, markAsRead: Boolean = true) { val adapter = RestBuilder(callback) val params = RestParams(isForceReadFromNetwork = forceNetwork) - InboxApi.getConversation(adapter, callback, params, conversationId) + InboxApi.getConversation(adapter, callback, params, conversationId, markAsRead) } fun starConversation( @@ -142,7 +142,7 @@ object InboxManager { val adapter = RestBuilder() val params = RestParams(isForceReadFromNetwork = forceNetwork) return try { - val response = InboxApi.getConversation(adapter, params, conversationId) + val response = InboxApi.getConversation(adapter, params, conversationId, markAsRead = true) if (response.isSuccessful) response.body() else null } catch (e: IOException) { null diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/SubmissionManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/SubmissionManager.kt index f47d5144e8..6a6f115e5b 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/SubmissionManager.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/SubmissionManager.kt @@ -274,6 +274,16 @@ object SubmissionManager { postStudentAnnotationSubmission(canvasContext, assignmentId, annotatableAttachmentId, it) } + fun markSubmissionAsRead( + courseId: Long, + assignmentId: Long, + callback: StatusCallback + ) { + val adapter = RestBuilder(callback) + val params = RestParams(isForceReadFromNetwork = true) + SubmissionAPI.markSubmissionAsRead(adapter, params, courseId, assignmentId, callback) + } + private fun postStudentAnnotationSubmission( canvasContext: CanvasContext, assignmentId: Long, diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UnreadCountManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UnreadCountManager.kt index 47ac3a2371..2f7f31a2ab 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UnreadCountManager.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UnreadCountManager.kt @@ -22,6 +22,7 @@ import com.instructure.canvasapi2.builders.RestBuilder import com.instructure.canvasapi2.builders.RestParams import com.instructure.canvasapi2.models.UnreadConversationCount import com.instructure.canvasapi2.models.UnreadCount +import com.instructure.canvasapi2.models.UnreadNotificationCount object UnreadCountManager { @@ -37,4 +38,9 @@ object UnreadCountManager { UnreadCountAPI.getUnreadAlertCount(adapter, params, studentId, callback) } + fun getUnreadNotificationCount(callback: StatusCallback>, forceNetwork: Boolean) { + val adapter = RestBuilder(callback) + val params = RestParams(isForceReadFromNetwork = forceNetwork) + UnreadCountAPI.getUnreadNotificationsCount(adapter, params, callback) + } } diff --git a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/InboxApiPactTests.kt b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/InboxApiPactTests.kt index 0301ef4187..8be259fbe6 100644 --- a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/InboxApiPactTests.kt +++ b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/InboxApiPactTests.kt @@ -221,7 +221,7 @@ class InboxApiPactTests : ApiPactTestBase() { // populated. Make sure that the provider_state is set up accordingly. // - val getOneConversationQuery = "include[]=participant_avatars" + val getOneConversationQuery = "include[]=participant_avatars&auto_mark_as_read=true" val getOneConversationPath = "/api/v1/conversations/1" val getOneConversationFieldConfig = PactConversationFieldConfig( includeMessages = true, @@ -255,7 +255,7 @@ class InboxApiPactTests : ApiPactTestBase() { fun `grab a specific conversation`() { val service = createService() - val getConversationCall = service.getConversation(1) + val getConversationCall = service.getConversation(1, true) val getConversationResult = getConversationCall.execute() assertQueryParamsAndPath(getConversationCall, getOneConversationQuery, getOneConversationPath) diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 6ab5281a96..4c7caea78d 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1283,6 +1283,11 @@ %s unread messages + + %s unread notification + %s unread notifications + + No annotation selected Email Notifications diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Extensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Extensions.kt index c5f286cf86..e38481a2a1 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Extensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Extensions.kt @@ -39,4 +39,8 @@ fun Long.humanReadableByteCount(): String { fun Long?.orDefault(default: Long = 0): Long { return this ?: default -} \ No newline at end of file +} + +fun Int?.orDefault(default: Int = 0): Int { + return this ?: default +} From 096d624f9dd5824070038a3ba62e881147eb0429 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:28:42 +0200 Subject: [PATCH 16/73] [MBL-16270][All] Improve mobile login (#1727) refs: MBL-16270 affects: All release note: Improved the login flow by saving the last institution's login page. * Added recent school to login page. * Save only after login. * Parent changes. * Added styles for landscape layout. * Fixed teacher and added changes according to product feedback. * Clear login prefs in tests. --- .../lib/l10n/app_localizations.dart | 6 + .../lib/network/utils/api_prefs.dart | 25 ++++ .../lib/router/panda_router.dart | 5 +- .../domain_search/domain_search_screen.dart | 9 +- .../lib/screens/login_landing_screen.dart | 121 +++++++++++------- .../screens/web_login/web_login_screen.dart | 14 +- .../ui/utils/StudentActivityTestRule.kt | 2 + .../activity/LoginLandingPageActivity.kt | 4 +- .../ui/utils/TeacherActivityTestRule.kt | 2 + .../activities/LoginLandingPageActivity.kt | 2 +- .../activities/BaseLoginFindSchoolActivity.kt | 1 + .../BaseLoginLandingPageActivity.kt | 62 +++++++-- .../activities/BaseLoginSignInActivity.kt | 3 + .../loginapi/login/util/LoginPrefs.kt | 15 +++ .../drawable/bg_button_rounded_outline.xml | 3 +- .../activity_login_landing_page.xml | 27 +++- .../layout/activity_login_landing_page.xml | 31 ++++- .../src/main/res/values/strings.xml | 1 + .../src/main/res/values/styles.xml | 23 ++++ 19 files changed, 278 insertions(+), 78 deletions(-) create mode 100644 libs/login-api-2/src/main/java/com/instructure/loginapi/login/util/LoginPrefs.kt diff --git a/apps/flutter_parent/lib/l10n/app_localizations.dart b/apps/flutter_parent/lib/l10n/app_localizations.dart index 2d06702a2d..f8c753fd9d 100644 --- a/apps/flutter_parent/lib/l10n/app_localizations.dart +++ b/apps/flutter_parent/lib/l10n/app_localizations.dart @@ -296,6 +296,12 @@ class AppLocalizations { desc: 'Text for the find-my-school button', ); + String get findAnotherSchool => Intl.message( + 'Find another school', + name: 'findAnotherSchool', + desc: 'Text for the find-another-school button', + ); + /// Domain search screen String get domainSearchInputHint => Intl.message( diff --git a/apps/flutter_parent/lib/network/utils/api_prefs.dart b/apps/flutter_parent/lib/network/utils/api_prefs.dart index 1b44ba5dc4..a4fb8d0a99 100644 --- a/apps/flutter_parent/lib/network/utils/api_prefs.dart +++ b/apps/flutter_parent/lib/network/utils/api_prefs.dart @@ -19,9 +19,11 @@ import 'dart:ui' as ui; import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'; import 'package:flutter/material.dart'; import 'package:flutter_parent/models/login.dart'; +import 'package:flutter_parent/models/school_domain.dart'; import 'package:flutter_parent/models/serializers.dart'; import 'package:flutter_parent/models/user.dart'; import 'package:flutter_parent/network/api/auth_api.dart'; +import 'package:flutter_parent/screens/web_login/web_login_screen.dart'; import 'package:flutter_parent/utils/db/calendar_filter_db.dart'; import 'package:flutter_parent/utils/db/reminder_db.dart'; import 'package:flutter_parent/utils/notification_util.dart'; @@ -29,6 +31,7 @@ import 'package:flutter_parent/utils/service_locator.dart'; import 'package:intl/intl.dart'; import 'package:package_info/package_info.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tuple/tuple.dart'; import 'dio_config.dart'; @@ -42,6 +45,8 @@ class ApiPrefs { static const String KEY_LOGINS = 'logins'; static const String KEY_RATING_DONT_SHOW_AGAIN = 'dont_show_again'; static const String KEY_RATING_NEXT_SHOW_DATE = 'next_show_date'; + static const String KEY_LAST_ACCOUNT = 'last_account'; + static const String KEY_LAST_ACCOUNT_LOGIN_FLOW = 'last_account_login_flow'; static EncryptedSharedPreferences _prefs; static PackageInfo _packageInfo; @@ -167,6 +172,26 @@ class ApiPrefs { return _prefs.getStringList(KEY_LOGINS)?.map((it) => deserialize(json.decode(it)))?.toList() ?? []; } + static setLastAccount(SchoolDomain lastAccount, LoginFlow loginFlow) { + _checkInit(); + final lastAccountJson = json.encode(serialize(lastAccount)); + _prefs.setString(KEY_LAST_ACCOUNT, lastAccountJson); + _prefs.setInt(KEY_LAST_ACCOUNT_LOGIN_FLOW, loginFlow.index); + } + + static Tuple2 getLastAccount() { + _checkInit(); + if (!_prefs.containsKey(KEY_LAST_ACCOUNT)) return null; + + final accountJson = _prefs.getString(KEY_LAST_ACCOUNT); + if (accountJson == null || accountJson.isEmpty) return null; + + final lastAccount = deserialize(json.decode(accountJson)); + final loginFlow = _prefs.containsKey(KEY_LAST_ACCOUNT_LOGIN_FLOW) ? LoginFlow.values[_prefs.getInt(KEY_LAST_ACCOUNT_LOGIN_FLOW)] : LoginFlow.normal; + + return Tuple2(lastAccount, loginFlow); + } + static Future removeLogin(Login login) => removeLoginByUuid(login.uuid); static Future removeLoginByUuid(String uuid) async { diff --git a/apps/flutter_parent/lib/router/panda_router.dart b/apps/flutter_parent/lib/router/panda_router.dart index 882314e745..232e20029d 100644 --- a/apps/flutter_parent/lib/router/panda_router.dart +++ b/apps/flutter_parent/lib/router/panda_router.dart @@ -115,10 +115,11 @@ class PandaRouter { static String loginWeb( String domain, { + String accountName = '', String authenticationProvider = null, LoginFlow loginFlow = LoginFlow.normal, }) => - '$_loginWeb?${_RouterKeys.domain}=${Uri.encodeQueryComponent(domain)}&${_RouterKeys.authenticationProvider}=$authenticationProvider&${_RouterKeys.loginFlow}=${loginFlow.toString()}'; + '$_loginWeb?${_RouterKeys.domain}=${Uri.encodeQueryComponent(domain)}&${_RouterKeys.accountName}=${Uri.encodeQueryComponent(accountName)}&${_RouterKeys.authenticationProvider}=$authenticationProvider&${_RouterKeys.loginFlow}=${loginFlow.toString()}'; static String notParent() => '/not_parent'; @@ -325,6 +326,7 @@ class PandaRouter { return WebLoginScreen( params[_RouterKeys.domain][0], + accountName: params[_RouterKeys.accountName][0], authenticationProvider: authProvider, loginFlow: loginFlow, ); @@ -507,6 +509,7 @@ class _RouterKeys { static final calendarView = 'view_name'; static final courseId = 'courseId'; static final domain = 'domain'; + static final accountName = 'accountName'; static final eventId = 'eventId'; static final infoText = 'infoText'; static final isCreatingAccount = 'isCreatingAccount'; diff --git a/apps/flutter_parent/lib/screens/domain_search/domain_search_screen.dart b/apps/flutter_parent/lib/screens/domain_search/domain_search_screen.dart index df3622532b..06a752f13a 100644 --- a/apps/flutter_parent/lib/screens/domain_search/domain_search_screen.dart +++ b/apps/flutter_parent/lib/screens/domain_search/domain_search_screen.dart @@ -195,8 +195,11 @@ class _DomainSearchScreenState extends State { var item = _schoolDomains[index]; return ListTile( title: Text(item.name), - onTap: () => locator().pushRoute(context, - PandaRouter.loginWeb(item.domain, authenticationProvider: item.authenticationProvider)), + onTap: () { + final accountName = (item.name == null || item.name.isEmpty) ? item.domain : item.name; + return locator().pushRoute(context, + PandaRouter.loginWeb(item.domain, accountName: accountName, authenticationProvider: item.authenticationProvider)); + }, ); }, ), @@ -280,6 +283,6 @@ class _DomainSearchScreenState extends State { if (regExp.hasMatch(domain)) domain = regExp.stringMatch(domain); if (domain.startsWith('www.')) domain = domain.substring(4); // Strip off www. if they typed it if (!domain.contains('.') || domain.endsWith('.beta')) domain += '.instructure.com'; - locator().pushRoute(context, PandaRouter.loginWeb(domain, loginFlow: widget.loginFlow)); + locator().pushRoute(context, PandaRouter.loginWeb(domain, accountName: domain, loginFlow: widget.loginFlow)); } } diff --git a/apps/flutter_parent/lib/screens/login_landing_screen.dart b/apps/flutter_parent/lib/screens/login_landing_screen.dart index b9c9d02bd6..47ca747713 100644 --- a/apps/flutter_parent/lib/screens/login_landing_screen.dart +++ b/apps/flutter_parent/lib/screens/login_landing_screen.dart @@ -17,6 +17,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_parent/l10n/app_localizations.dart'; import 'package:flutter_parent/models/login.dart'; +import 'package:flutter_parent/models/school_domain.dart'; import 'package:flutter_parent/network/utils/analytics.dart'; import 'package:flutter_parent/network/utils/api_prefs.dart'; import 'package:flutter_parent/router/panda_router.dart'; @@ -38,6 +39,7 @@ import 'package:flutter_parent/utils/service_locator.dart'; import 'package:flutter_parent/utils/snickers.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:tuple/tuple.dart'; class LoginLandingScreen extends StatelessWidget { final GlobalKey _scaffoldKey = GlobalKey(); @@ -100,6 +102,7 @@ class LoginLandingScreen extends StatelessWidget { } Widget _body(BuildContext context) { + final lastLoginAccount = ApiPrefs.getLastAccount(); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -109,24 +112,23 @@ class LoginLandingScreen extends StatelessWidget { semanticsLabel: L10n(context).canvasLogoLabel, ), SizedBox(height: 64), - ButtonTheme( - minWidth: 260, - child: RaisedButton( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - L10n(context).findSchool, - style: TextStyle(fontSize: 16), - ), - ), - color: Theme.of(context).accentColor, - textColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), - onPressed: () { - onFindSchoolPressed(context); - }, - ), - ), + if (lastLoginAccount == null) + _filledButton(context, L10n(context).findSchool, () { + onFindSchoolPressed(context); + }), + if (lastLoginAccount != null) + _filledButton( + context, + lastLoginAccount.item1.name == null || lastLoginAccount.item1.name.isEmpty + ? lastLoginAccount.item1.domain + : lastLoginAccount.item1.name, () { + onSavedSchoolPressed(context, lastLoginAccount); + }), + SizedBox(height: 16), + if (lastLoginAccount != null) + _outlineButton(context, L10n(context).findAnotherSchool, () { + onFindSchoolPressed(context); + }), SizedBox(height: 8), if (_hasCameras()) _qrLogin(context), ], @@ -134,6 +136,51 @@ class LoginLandingScreen extends StatelessWidget { ); } + Widget _filledButton( + BuildContext context, String title, VoidCallback onPressed) { + return ButtonTheme( + minWidth: 260, + child: RaisedButton( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + title, + style: TextStyle(fontSize: 16), + ), + ), + color: Theme.of(context).accentColor, + textColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4))), + onPressed: onPressed, + ), + ); + } + + Widget _outlineButton( + BuildContext context, String title, VoidCallback onPressed) { + return ButtonTheme( + minWidth: 260, + child: OutlinedButton( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + title, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ), + side: BorderSide( + width: 1, color: ParentTheme.of(context).onSurfaceColor), + ), + onPressed: onPressed, + ), + ); + } + bool _hasCameras() { return ApiPrefs.getCameraCount() != null && ApiPrefs.getCameraCount() != 0; } @@ -233,36 +280,18 @@ class LoginLandingScreen extends StatelessWidget { ); } - Widget _helpRequestButton(BuildContext context) { - return Semantics( - label: L10n(context).loginHelpHint, - child: GestureDetector( - onTap: () { - locator().logEvent(AnalyticsEventConstants.HELP_LOGIN); - ErrorReportDialog.asDialog(context, - title: L10n(context).loginHelpTitle, - subject: L10n(context).loginHelpSubject, - severity: ErrorReportSeverity.BLOCKING, - includeEmail: true, - hideSeverityPicker: true); - }, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Align( - child: Icon( - CanvasIcons.question, - color: ParentColors.ash, - ), - alignment: Alignment.topRight, - ), - ), - ), - ); - } - onFindSchoolPressed(BuildContext context) { LoginFlow flow = LoginFlow.values[loginFlowIndex % LoginFlow.values.length]; - locator().pushRoute(context, PandaRouter.domainSearch(loginFlow: flow)); + locator() + .pushRoute(context, PandaRouter.domainSearch(loginFlow: flow)); + } + + onSavedSchoolPressed(BuildContext context, Tuple2 lastAccount) { + locator().pushRoute( + context, + PandaRouter.loginWeb(lastAccount.item1.domain, + accountName: lastAccount.item1.name, + loginFlow: lastAccount.item2)); } void _changeLoginFlow(BuildContext context) { diff --git a/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart b/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart index 5743495bc0..c63a4cd388 100644 --- a/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart +++ b/apps/flutter_parent/lib/screens/web_login/web_login_screen.dart @@ -18,6 +18,7 @@ import 'package:device_info/device_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_parent/l10n/app_localizations.dart'; import 'package:flutter_parent/models/mobile_verify_result.dart'; +import 'package:flutter_parent/models/school_domain.dart'; import 'package:flutter_parent/network/utils/analytics.dart'; import 'package:flutter_parent/network/utils/api_prefs.dart'; import 'package:flutter_parent/router/panda_router.dart'; @@ -37,8 +38,8 @@ enum LoginFlow { } class WebLoginScreen extends StatefulWidget { - WebLoginScreen( - this.domain, { + WebLoginScreen(this.domain, { + this.accountName, this.user, this.pass, this.authenticationProvider, @@ -47,6 +48,7 @@ class WebLoginScreen extends StatefulWidget { }) : super(key: key); final String user; + final String accountName; final String pass; final String domain; final String authenticationProvider; @@ -200,7 +202,13 @@ class _WebLoginScreenState extends State { AnalyticsEventConstants.LOGIN_SUCCESS, extras: {AnalyticsParamConstants.DOMAIN_PARAM: result.baseUrl}, ); - locator().pushRouteAndClearStack(context, PandaRouter.rootSplash()); + final lastAccount = new SchoolDomain((builder) => + builder + ..domain = widget.domain + ..name = widget.accountName); + ApiPrefs.setLastAccount(lastAccount, widget.loginFlow); + locator().pushRouteAndClearStack( + context, PandaRouter.rootSplash()); }).catchError((_) { locator().logEvent( AnalyticsEventConstants.LOGIN_FAILURE, diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt index bace30a97b..f14c13ddba 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt @@ -21,6 +21,7 @@ import android.content.Context import com.instructure.student.util.CacheControlFlags import com.instructure.student.util.StudentPrefs import com.instructure.espresso.InstructureActivityTestRule +import com.instructure.loginapi.login.util.LoginPrefs import com.instructure.loginapi.login.util.PreviousUsersUtils import com.instructure.pandautils.utils.PandaAppResetter import com.instructure.pandautils.utils.ThemePrefs @@ -32,6 +33,7 @@ class StudentActivityTestRule(activityClass: Class) : Instructu StudentPrefs.clearPrefs() CacheControlFlags.clearPrefs() PreviousUsersUtils.clear(context) + LoginPrefs.clearPrefs() // We need to set this true so the theme selector won't stop our tests. ThemePrefs.themeSelectionShown = true diff --git a/apps/student/src/main/java/com/instructure/student/activity/LoginLandingPageActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/LoginLandingPageActivity.kt index 72700c9f86..2f4ad68d00 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/LoginLandingPageActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/LoginLandingPageActivity.kt @@ -63,8 +63,8 @@ class LoginLandingPageActivity : BaseLoginLandingPageActivity() { return ContextCompat.getColor(this, R.color.login_studentAppTheme) } - override fun signInActivityIntent(snickerDoodle: SnickerDoodle): Intent { - return SignInActivity.createIntent(this, AccountDomain(snickerDoodle.domain)) + override fun signInActivityIntent(accountDomain: AccountDomain): Intent { + return SignInActivity.createIntent(this, accountDomain) } override fun loginWithQRCodeEnabled(): Boolean = true diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt index 1b41aeca71..aa47deb4fc 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt @@ -19,6 +19,7 @@ package com.instructure.teacher.ui.utils import android.app.Activity import android.content.Context import com.instructure.espresso.InstructureActivityTestRule +import com.instructure.loginapi.login.util.LoginPrefs import com.instructure.loginapi.login.util.PreviousUsersUtils import com.instructure.pandautils.utils.PandaAppResetter import com.instructure.pandautils.utils.ThemePrefs @@ -30,6 +31,7 @@ class TeacherActivityTestRule(activityClass: Class) : Instructur PandaAppResetter.reset(context) TeacherPrefs.safeClearPrefs() PreviousUsersUtils.clear(context) + LoginPrefs.clearPrefs() // We need to set this true so the theme selector won't stop our tests. ThemePrefs.themeSelectionShown = true diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/LoginLandingPageActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/LoginLandingPageActivity.kt index 2ce658f6c6..79f67d75ee 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/LoginLandingPageActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/LoginLandingPageActivity.kt @@ -51,7 +51,7 @@ class LoginLandingPageActivity : BaseLoginLandingPageActivity() { override fun themeColor(): Int = ContextCompat.getColor(this, R.color.login_teacherAppTheme) - override fun signInActivityIntent(snickerDoodle: SnickerDoodle): Intent = SignInActivity.createIntent(this, AccountDomain(snickerDoodle.domain)) + override fun signInActivityIntent(accountDomain: AccountDomain): Intent = SignInActivity.createIntent(this, accountDomain) override fun startApp() { val intent = launchApplicationMainActivityIntent() diff --git a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginFindSchoolActivity.kt b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginFindSchoolActivity.kt index 1de4fa365f..595f50a5bb 100644 --- a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginFindSchoolActivity.kt +++ b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginFindSchoolActivity.kt @@ -150,6 +150,7 @@ abstract class BaseLoginFindSchoolActivity : AppCompatActivity(), ErrorReportDia mNextActionButton!!.isEnabled = false mNextActionButton!!.setTextColor(ContextCompat.getColor(this@BaseLoginFindSchoolActivity, R.color.backgroundMedium)) + domainInput.requestFocus() domainInput.setOnEditorActionListener { _, _, _ -> validateDomain(AccountDomain(domainInput!!.text.toString())) true diff --git a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginLandingPageActivity.kt b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginLandingPageActivity.kt index 7f444b601e..c766311cd7 100644 --- a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginLandingPageActivity.kt +++ b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginLandingPageActivity.kt @@ -39,6 +39,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.gson.Gson import com.instructure.canvasapi2.apis.ErrorReportAPI +import com.instructure.canvasapi2.models.AccountDomain import com.instructure.canvasapi2.models.ErrorReportPreFill import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.Analytics @@ -57,7 +58,9 @@ import com.instructure.loginapi.login.util.Const.MOBILE_VERIFY_FLOW import com.instructure.loginapi.login.util.Const.NORMAL_FLOW import com.instructure.loginapi.login.util.Const.SNICKER_DOODLES import com.instructure.loginapi.login.util.Const.URL_CANVAS_NETWORK +import com.instructure.loginapi.login.util.LoginPrefs import com.instructure.loginapi.login.util.PreviousUsersUtils +import com.instructure.loginapi.login.util.SavedLoginInfo import com.instructure.loginapi.login.viewmodel.LoginViewModel import com.instructure.pandautils.mvvm.Event import com.instructure.pandautils.utils.* @@ -74,7 +77,7 @@ abstract class BaseLoginLandingPageActivity : AppCompatActivity(), ErrorReportDi protected abstract fun beginFindSchoolFlow(): Intent - protected abstract fun signInActivityIntent(snickerDoodle: SnickerDoodle): Intent + protected abstract fun signInActivityIntent(accountDomain: AccountDomain): Intent protected abstract fun beginCanvasNetworkFlow(url: String): Intent @@ -102,22 +105,13 @@ abstract class BaseLoginLandingPageActivity : AppCompatActivity(), ErrorReportDi loadPreviousUsers() setupGesture() setupSnickerDoodles() + setupButtons() } private fun bindViews() { // Only show the what's new text if the app supports it changesLayout.visibility = if (appChangesLink() != null) View.VISIBLE else View.GONE - findMySchool.onClick { - if (APIHelper.hasNetworkConnection()) { - val intent = beginFindSchoolFlow() - intent.putExtra(Const.CANVAS_LOGIN, canvasLogin) - startActivity(intent) - } else { - NoInternetConnectionDialog.show(supportFragmentManager) - } - } - canvasNetwork.onClick { if (APIHelper.hasNetworkConnection()) { val intent = beginCanvasNetworkFlow(URL_CANVAS_NETWORK) @@ -358,7 +352,7 @@ abstract class BaseLoginLandingPageActivity : AppCompatActivity(), ErrorReportDi drawerRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, true) drawerRecyclerView.adapter = SnickerDoodleAdapter(snickerDoodles) { snickerDoodle -> drawerLayout.closeDrawers() - val intent = signInActivityIntent(snickerDoodle) + val intent = signInActivityIntent(AccountDomain(snickerDoodle.domain)) intent.putExtra(SNICKER_DOODLES, snickerDoodle) startActivity(intent) finish() @@ -369,6 +363,50 @@ abstract class BaseLoginLandingPageActivity : AppCompatActivity(), ErrorReportDi } } + private fun setupButtons() { + val lastSavedLogin = LoginPrefs.lastSavedLogin + if (lastSavedLogin != null) { + openRecentSchool?.visibility = View.VISIBLE + findAnotherSchool?.visibility = View.VISIBLE + findMySchool?.visibility = View.GONE + + openRecentSchool?.text = if (lastSavedLogin.accountDomain.name.isNullOrEmpty()) { + lastSavedLogin.accountDomain.domain + } else { + lastSavedLogin.accountDomain.name + } + openRecentSchool?.onClick { openRecentSchool(lastSavedLogin) } + + findAnotherSchool?.onClick { findSchool() } + } else { + openRecentSchool?.visibility = View.GONE + findAnotherSchool?.visibility = View.GONE + findMySchool?.visibility = View.VISIBLE + + findMySchool?.onClick { findSchool() } + } + } + + private fun openRecentSchool(lastSavedLogin: SavedLoginInfo) { + if (APIHelper.hasNetworkConnection()) { + val intent = signInActivityIntent(lastSavedLogin.accountDomain) + intent.putExtra(Const.CANVAS_LOGIN, lastSavedLogin.canvasLogin) + startActivity(intent) + } else { + NoInternetConnectionDialog.show(supportFragmentManager) + } + } + + private fun findSchool() { + if (APIHelper.hasNetworkConnection()) { + val intent = beginFindSchoolFlow() + intent.putExtra(Const.CANVAS_LOGIN, canvasLogin) + startActivity(intent) + } else { + NoInternetConnectionDialog.show(supportFragmentManager) + } + } + override fun onTicketPost() { toast(R.string.errorReportThankyou) } diff --git a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginSignInActivity.kt b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginSignInActivity.kt index 848211bd80..8839e4e587 100644 --- a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginSignInActivity.kt +++ b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginSignInActivity.kt @@ -66,7 +66,9 @@ import com.instructure.loginapi.login.util.Const.CANVAS_LOGIN_FLOW import com.instructure.loginapi.login.util.Const.MASQUERADE_FLOW import com.instructure.loginapi.login.util.Const.MOBILE_VERIFY_FLOW import com.instructure.loginapi.login.util.Const.SNICKER_DOODLES +import com.instructure.loginapi.login.util.LoginPrefs import com.instructure.loginapi.login.util.PreviousUsersUtils.add +import com.instructure.loginapi.login.util.SavedLoginInfo import com.instructure.loginapi.login.viewmodel.LoginViewModel import com.instructure.pandautils.mvvm.Event import com.instructure.pandautils.utils.* @@ -452,6 +454,7 @@ abstract class BaseLoginSignInActivity : AppCompatActivity(), OnAuthenticationSe ) add(this@BaseLoginSignInActivity, user) refreshWidgets() + LoginPrefs.lastSavedLogin = SavedLoginInfo(accountDomain, canvasLogin) handleLaunchApplicationMainActivityIntent() } } diff --git a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/util/LoginPrefs.kt b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/util/LoginPrefs.kt new file mode 100644 index 0000000000..2a11152c7f --- /dev/null +++ b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/util/LoginPrefs.kt @@ -0,0 +1,15 @@ +package com.instructure.loginapi.login.util + +import com.instructure.canvasapi2.models.AccountDomain +import com.instructure.canvasapi2.utils.BooleanPref +import com.instructure.canvasapi2.utils.GsonPref +import com.instructure.canvasapi2.utils.IntPref +import com.instructure.canvasapi2.utils.LongPref +import com.instructure.canvasapi2.utils.PrefManager +import com.instructure.pandautils.dialogs.RatingDialog + +data class SavedLoginInfo(val accountDomain: AccountDomain, val canvasLogin: Int) + +object LoginPrefs : PrefManager("loginPrefs") { + var lastSavedLogin by GsonPref(SavedLoginInfo::class.java, null) +} \ No newline at end of file diff --git a/libs/login-api-2/src/main/res/drawable/bg_button_rounded_outline.xml b/libs/login-api-2/src/main/res/drawable/bg_button_rounded_outline.xml index 0071462ec5..bafbe7dbd1 100644 --- a/libs/login-api-2/src/main/res/drawable/bg_button_rounded_outline.xml +++ b/libs/login-api-2/src/main/res/drawable/bg_button_rounded_outline.xml @@ -19,8 +19,7 @@ - - + diff --git a/libs/login-api-2/src/main/res/layout-land/activity_login_landing_page.xml b/libs/login-api-2/src/main/res/layout-land/activity_login_landing_page.xml index d1cc7aba67..e71c00f174 100644 --- a/libs/login-api-2/src/main/res/layout-land/activity_login_landing_page.xml +++ b/libs/login-api-2/src/main/res/layout-land/activity_login_landing_page.xml @@ -50,13 +50,34 @@ android:layout_width="64dp" android:layout_height="64dp" android:importantForAccessibility="no" + android:layout_marginBottom="20dp" app:srcCompat="@drawable/ic_canvas_logo"/> +