From daac6b90a3bdbccaa65814d8de678af5cf0d02ec Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:16:33 +0200 Subject: [PATCH] Release/teacher 1.14.0 42 (#1328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release Student 6.12.0 (228) (#1287) * Release Student 6.11.0 (226) * Release Student 6.11.1 (227) * Version bump. * [Student][MBL-15286] Assignment list crash * Release Student 6.12.0 228 (#1282) Co-authored-by: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Co-authored-by: balintbartok <72031065+balintbartok@users.noreply.github.com> * [MBL-15436][Student] Standard Dashboard showing for K5 users when offline (#1289) refs: MBL-15436 affects: Student release note: none * Cache canvasForElementary feature flag after a successful request. * Fixed LoginViewModelTest. * Fixed build in teacher. * Release Teacher 1.13.1 (41) (#1288) * Release Teacher 1.13.0 (40) * Release Teacher 1.13.1 41 (#1283) Co-authored-by: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> * [MBL-15444][Student] Fix reintroduced bug (#1292) refs: MBL-15444 affects: Student release note: none test plan: See in MBL-15334 * [MBL-15391][Student][Teacher] Logout if the access token is invalid (#1290) * [MBL-15391][Student][Teacher] Logout if the access token is invalid refs: MBL-15391 affects: Student, Teacher release note: Fixed a bug when the access is deleted the user would not be logged out of the app. test plan: Login to either app, then delete the access token on the web (Account > Settings > Approved integrations). On startup the user should be directed to the login screen instead of the Dashboard. Test with QR login as well. Also smoke test masquerading. * removed unused function * fix UI test issues * CR * fix submodule tests * [MBL-15346][Student] K5 Course cards (#1286) refs: MBL-15346 affects: Student release note: none * Added first version of course card with course image, name and color set. * Added announcements and assignments due text to course cards. * Implemented actions. * Added span count calculation and spacing for grid layout. * Fetch colors on dashboard refresh. * Refresh Dashboard when going back from assignments. * Added color caching and coloring of course cards. Added basic unit test for course cards. * Request only the latest announcement for homeroom courses and course cards. * Changed course card text logic. Get the missing and due today assignments from the planner and user api instead of requesting all the assignments for all courses. * Added basic EmptyView. * Added planner override dismissed check for missing assignments. * Added tests for HomeroomViewModel.kt. * Added tests for CourseCardCreator.kt. Moved Spannable creation to ItemViewModel. * Card design corrections. Teacher build fix. * String change. Cleaned up unused stuff. * Added empty message if we have homeroom announcements but no subjects. Changed homeroom tab icon. * PR changes. * Do not update announcements when updating submission info. * [MBL-15265][Teacher] Sliding panel a11y fix (#1291) refs: MBL-15265 affects: Teacher release note: none test plan: Go to SubmissionContentView if the SlidingPanel is anchored or expanded the content behind should not be focusable by TalkBack * [Student][MBL-15437] Font doesn't change back to system font on K5 logout in some cases refs: MBL-15437 affects: Student release note: none * [MBL-15459][Student] K5 WebView font change #1295 refs: MBL-15459 affects: Student release note: none * [Student][MBL-15347] K5 Homeroom UI tests (#1294) refs: MBL-15347 affects: Student release note: none * Added tests for the K5 dashboard skeleton (navigation to dashboard, tabs navigaiton, navigation drawer) * Added UI tests for displaying items on the dashboard and announcement opening. * Added UI tests for todo texts and assignment opening. * Fixed tests on smaller screens. * Test fix. * Fix scrollview in Homeroom screen. * Fixed scrollTo action to allow it on NestedScrollView. * [MBL-15464][Student] K5 Due today text not shown #1298 refs: MBL-15464 affects: Student release note: none * [MBL-15434][Teacher] Free draw annotations disappear after submission swipes #1301 refs: MBL-15434 affects: Teacher release note: Fixed a bug, where annotations weren't visible when going back to the previous document in Speed Grader. * [MBL-15427][Teacher] Anonymous survey submissions and student names are visible #1302 refs: MBL-15427 affects: Teacher release note: Fixed a bug where student names were visible in anonymous surveys. * [MBL-15398][Student] Notify the user that the annotation type submission is not yet supported (#1300) refs: MBL-15398 affects: Student release note: Fixed a bug, where assignments show as locked when student annotation submission is possible. * Added student annotation type to submission types on the assignment details screen. * Added student annotation type to submission types on the empty submission screen. * Hide open in browser button if link is null to prevent crashes. * Added unit tests * Added empty submission content view if the submission is student annotation. * [MBL-15477][Student] K5 Feature Flag Change #1304 refs: MBL-15477 affects: Student release note: none * [MBL-14877][Student][Teacher] Update notifications (#1299) refs: MBL-14877 affects: Student, Teacher release note: Added the option to get notifications about available updates. test plan: Ping me for install URLs. Install the test version from the internal sharing. Open the updated version's internal sharing page, but DO NOT install it. After launching the app the notifications should be visible. * Change Dashboard E2E check * fix Homeroom landscape test * MBL-15431][Student] K5 subject cards positioning refs: MBL-15431 affects: Student release note: none * lowres landscape discussion reply fix * [MBL-15461][Student] K5 Drawer menu items (#1306) refs: MBL-15461 affects: Student release note: none * Changed drawer items. * Fixed tests. * Fixed settings opening. * move landscape tests from LowRes * discussions landscape test fix * stub landscape annotation refs: affects: release note: test plan: * typo fix * [MBL-15430][Student] Students cannot submit in courses with past or future section dates #1307 refs: MBL-15430 affects: Student release note: Fixed a bug where assignments couldn't be submitted for courses with past or future sections. * [MBL-15487][Student][Teacher] Added updatePriority to releases (#1311) refs: MBL-15487 affects: Student, Teacher release note: none test plan: Check the changes in the Deploy workflow * [MBL-13496][Ops] Jira automation script added (#1312) refs: MBL-13496 affects: All release note: none test plan: * [MBL-15517][Student][Teacher] Fix LocaleTransformer (#1316) refs: MBL-15517 affects: Teacher, Student release note: none * Fixed LocaleTransformer.kt * Fixed flaky UI test. * Fixed flaky UI test. * [Teacher][MBL-15441] Saving content removes semicolons #1315 refs: MBL-15441 affects: Teacher release note: Fixed a bug where saving page content removes styling. * [All][MBL-15413] Migrate from JCenter (#1309) refs: MBL-15413 affects: All release note: none * Changed jcenter dependencies to mavenCentral. * Fixed relinker and paperdb dependencies. * Added repositories closure to student build.gradle * Removed gradle plugins maven repo and unnecessary maven repos. * Reverted the gradle plugins maven repo * Updated FlexboxLayout. * Updated richeditor version. * Added camerakit dependency from Jitpack.io * Resolve exoplayer dependency through jitpack.io. * Replaced FirebaseJobDispatcher with WorkManager. * Changed Barista dependency. * [MBL-15450][Teacher] Added code-cov support for Teacher (#1317) * [MBL-15450][Teacher] Added flank.yml for e2e coverage refs: MBL-15450 affects: Teacher release note: none test plan: * added combined report generation task * [MBL-15496][Student][Teacher] Change message request params to fields (#1318) refs: MBL-15496 affects: Student, Teacher release note: none * Changed query params to fields in requests for sending messages. * Ignore pact tests. * [MBL-15515] Update vault ref (#1321) * Hide k5 tabs. (#1320) refs: MBL-15522 affects: Student release note: none * [MBL-15525][Student] Switch K5 mode on and off refs: MBL-15525 affects: Student release note: none * Update translations (#1324) * Update translations * fix strings Co-authored-by: Ákos Hermann * Updated version. * Fixed login issue. Co-authored-by: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Co-authored-by: balintbartok <72031065+balintbartok@users.noreply.github.com> Co-authored-by: inst-danger Co-authored-by: Tamas Kozmer --- android-vault | 2 +- apps/build.gradle | 7 +- apps/flutter_parent/android/build.gradle | 4 +- apps/flutter_parent/lib/l10n/res/intl_th.arb | 2185 +++++++++++++++++ .../android/build.gradle | 4 +- .../webview_flutter/android/build.gradle | 4 +- apps/student/build.gradle | 20 +- apps/student/flank_landscape.yml | 4 +- .../espresso/StudentHiltTestApplication.kt | 23 + .../student/espresso/StudentHiltTestRunner.kt | 29 + .../student/ui/LoginFindSchoolPageTest.kt | 2 + .../student/ui/LoginLandingPageTest.kt | 2 + .../student/ui/LoginSignInPageTest.kt | 2 + .../student/ui/e2e/AssignmentsE2ETest.kt | 2 + .../student/ui/e2e/CollaborationsE2ETest.kt | 2 + .../student/ui/e2e/ConferencesE2ETest.kt | 2 + .../student/ui/e2e/DashboardE2ETest.kt | 2 + .../student/ui/e2e/DiscussionsE2ETest.kt | 2 + .../student/ui/e2e/EventsE2ETest.kt | 2 + .../student/ui/e2e/FilesE2ETest.kt | 2 + .../student/ui/e2e/GradesE2ETest.kt | 2 + .../student/ui/e2e/InboxE2ETest.kt | 2 + .../student/ui/e2e/LoginE2ETest.kt | 2 + .../student/ui/e2e/ModulesE2ETest.kt | 2 + .../student/ui/e2e/PagesE2ETest.kt | 2 + .../student/ui/e2e/PeopleE2ETest.kt | 2 + .../student/ui/e2e/QuizzesE2ETest.kt | 2 + .../student/ui/e2e/SettingsE2ETest.kt | 2 + .../student/ui/e2e/ShardE2ETest.kt | 2 + .../student/ui/e2e/SyllabusE2ETest.kt | 2 + .../instructure/student/ui/e2e/TodoE2ETest.kt | 2 + .../AnnouncementInteractionTest.kt | 3 +- .../AssignmentDetailsInteractionTest.kt | 2 + .../AssignmentListInteractionTest.kt | 2 + .../ui/interaction/BookmarkInteractionTest.kt | 2 + .../ui/interaction/CalendarInteractionTest.kt | 2 + .../ui/interaction/CommentsInteractionTest.kt | 2 + .../ui/interaction/CourseInteractionTest.kt | 2 + .../interaction/DashboardInteractionTest.kt | 3 +- .../interaction/DiscussionsInteractionTest.kt | 2 + .../ElementaryDashboardInteractionTest.kt | 107 + .../interaction/GroupLinksInteractionTest.kt | 2 + .../ui/interaction/HomeroomInteractionTest.kt | 312 +++ .../interaction/InAppUpdateInteractionTest.kt | 321 +++ .../ui/interaction/InboxInteractionTest.kt | 2 + .../ui/interaction/LoginInteractionTest.kt | 2 + .../ui/interaction/ModuleInteractionTest.kt | 2 + .../NavigationDrawerInteractionTest.kt | 8 +- .../ui/interaction/PdfInteractionTest.kt | 2 + .../ui/interaction/PeopleInteractionTest.kt | 2 + .../PickerSubmissionUploadInteractionTest.kt | 2 + .../ProfileSettingsInteractionTest.kt | 2 + .../PushNotificationInteractionTest.kt | 2 + .../ui/interaction/SettingsInteractionTest.kt | 2 + .../SubmissionDetailsInteractionTest.kt | 2 + .../ui/interaction/SyllabusInteractionTest.kt | 2 + .../ui/interaction/TodoInteractionTest.kt | 2 + .../interaction/UserFilesInteractionTest.kt | 3 +- .../student/ui/pages/AnnouncementListPage.kt | 35 + .../student/ui/pages/CanvasWebViewPage.kt | 30 +- .../student/ui/pages/DashboardPage.kt | 2 +- .../student/ui/pages/DiscussionDetailsPage.kt | 8 + .../ui/pages/ElementaryDashboardPage.kt | 110 + .../student/ui/pages/GradesPage.kt | 11 +- .../student/ui/pages/HomeroomPage.kt | 123 + .../student/ui/pages/ResourcesPage.kt | 23 + .../student/ui/pages/SchedulePage.kt | 23 + .../AssignmentDetailsRenderTest.kt | 2 + .../ConferenceDetailsRenderTest.kt | 2 + .../renderTests/ConferenceListRenderTest.kt | 2 + .../DiscussionSubmissionViewRenderTest.kt | 2 + .../MediaSubmissionViewRenderTest.kt | 2 + .../ui/renderTests/PairObserverRenderTest.kt | 2 + .../PickerSubmissionUploadRenderTest.kt | 2 + .../QuizSubmissionViewRenderTest.kt | 2 + .../SubmissionCommentsRenderTest.kt | 2 + ...SubmissionDetailsEmptyContentRenderTest.kt | 2 + .../SubmissionDetailsRenderTest.kt | 2 + .../renderTests/SubmissionFilesRenderTest.kt | 2 + .../renderTests/SubmissionRubricRenderTest.kt | 2 + .../ui/renderTests/SyllabusRenderTest.kt | 2 + .../TextSubmissionUploadRenderTest.kt | 2 + .../TextSubmissionViewRenderTest.kt | 2 + .../UploadStatusSubmissionRenderTest.kt | 2 + .../UrlSubmissionUploadRenderTest.kt | 2 + .../UrlSubmissionViewRenderTest.kt | 2 + .../renderTests/views/GradeCellRenderTest.kt | 2 + .../student/ui/utils/StudentTest.kt | 16 + .../student/ui/utils/StudentTestExtensions.kt | 15 + .../student/activity/LoginActivity.kt | 17 +- .../activity/LoginLandingPageActivity.kt | 4 +- .../student/activity/NavigationActivity.kt | 25 +- .../student/activity/SignInActivity.kt | 4 +- .../adapter/AllCoursesRecyclerAdapter.kt | 86 - .../adapter/EditFavoritesRecyclerAdapter.kt | 155 -- .../di/{ => elementary}/HomeroomModule.kt | 2 +- .../fragment/ApplicationSettingsFragment.kt | 27 +- .../fragment/DiscussionDetailsFragment.kt | 6 +- .../fragment/FlutterCalendarFragment.kt | 7 +- .../fragment/InboxComposeMessageFragment.kt | 8 +- .../fragment/NotificationListFragment.kt | 2 +- .../fragment/UnsupportedFeatureFragment.kt | 20 +- .../AssignmentDetailsEffectHandler.kt | 1 + .../assignmentDetails/SubmissionUtils.kt | 1 + .../SubmissionDetailsModels.kt | 1 + .../SubmissionDetailsUpdate.kt | 2 + ...missionDetailsEmptyContentEffectHandler.kt | 1 + .../ui/SubmissionDetailsEmptyContentView.kt | 9 + .../ui/SubmissionDetailsView.kt | 7 +- .../ui/AssignmentDetailsFragment.kt | 7 +- .../ui/AssignmentDetailsView.kt | 11 + .../ui/AssignmentDetailsViewState.kt | 3 +- .../elementary/ElementaryDashboardFragment.kt | 20 +- .../elementary/StudentHomeroomRouter.kt | 22 + .../ElementaryNavigationBehavior.kt | 4 +- .../student/navigation/NavigationBehavior.kt | 2 +- .../instructure/student/util/AppManager.kt | 166 +- .../student/util/BaseAppManager.kt | 219 ++ .../student/util/FeatureFlagPrefs.kt | 3 + .../fragment_elementary_dashboard.xml | 1 + .../res/layout/dialog_submission_picker.xml | 20 + .../layout/fragment_application_settings.xml | 48 + .../layout/fragment_elementary_dashboard.xml | 1 + .../layout/fragment_submission_message.xml | 1 + .../layout/fragment_unsupported_feature.xml | 1 + .../src/main/res/layout/navigation_drawer.xml | 18 - .../AssignmentDetailsEffectHandlerTest.kt | 33 +- ...ionDetailsEmptyContentEffectHandlerTest.kt | 35 +- apps/teacher/build.gradle | 53 +- apps/teacher/flank_coverage.yml | 2 + apps/teacher/flank_e2e_coverage.yml | 35 + .../teacher/ui/AddMessagePageTest.kt | 2 + .../teacher/ui/AllCoursesListPageTest.kt | 2 + .../teacher/ui/AnnouncementsListPageTest.kt | 2 + .../teacher/ui/AssigneeListPageTest.kt | 2 + .../teacher/ui/AssignmentDetailsPageTest.kt | 2 + .../teacher/ui/AssignmentDueDatesPageTest.kt | 2 + .../teacher/ui/AssignmentListPageTest.kt | 2 + .../ui/AssignmentSubmissionListPageTest.kt | 2 + .../teacher/ui/ChooseRecipientsPageTest.kt | 2 + .../teacher/ui/CourseBrowserPageTest.kt | 2 + .../teacher/ui/CourseSettingsPageTest.kt | 2 + .../teacher/ui/CoursesListPageTest.kt | 2 + .../instructure/teacher/ui/DataSeedingTest.kt | 2 + .../com/instructure/teacher/ui/DeviceTest.kt | 2 + .../teacher/ui/DiscussionsListPageTest.kt | 2 + .../ui/EditAssignmentDetailsPageTest.kt | 2 + .../teacher/ui/EditCoursesPageTest.kt | 2 + .../teacher/ui/EditQuizDetailsPageTest.kt | 2 + .../teacher/ui/EditSyllabusPageTest.kt | 2 + .../teacher/ui/InAppUpdatePageTest.kt | 321 +++ .../teacher/ui/InboxMessagePageTest.kt | 2 + .../instructure/teacher/ui/InboxPageTest.kt | 2 + .../teacher/ui/LoginFindSchoolPageTest.kt | 2 + .../teacher/ui/LoginLandingPageTest.kt | 2 + .../teacher/ui/LoginSignInPageTest.kt | 2 + .../teacher/ui/NavDrawerPageTest.kt | 2 + .../teacher/ui/NotATeacherPageTest.kt | 3 +- .../teacher/ui/PageListPageTest.kt | 2 + .../teacher/ui/QuizDetailsPageTest.kt | 2 + .../teacher/ui/QuizListPageTest.kt | 2 + .../teacher/ui/QuizSubmissionListPageTest.kt | 2 + .../teacher/ui/SpeedGraderCommentsPageTest.kt | 2 + .../teacher/ui/SpeedGraderFilesPageTest.kt | 2 + .../teacher/ui/SpeedGraderGradePageTest.kt | 2 + .../teacher/ui/SpeedGraderPageTest.kt | 2 + .../ui/SpeedGraderQuizSubmissionPageTest.kt | 2 + .../teacher/ui/StudentContextPageTest.kt | 2 + .../teacher/ui/SyllabusPageTest.kt | 2 + .../teacher/ui/e2e/AnnouncementsE2ETest.kt | 2 + .../teacher/ui/e2e/AssignmentE2ETest.kt | 2 + .../teacher/ui/e2e/DashboardE2ETest.kt | 2 + .../teacher/ui/e2e/DiscussionsE2ETest.kt | 2 + .../teacher/ui/e2e/InboxE2ETest.kt | 2 + .../teacher/ui/e2e/ModulesE2ETest.kt | 2 + .../teacher/ui/e2e/PagesE2ETest.kt | 2 + .../teacher/ui/e2e/PeopleE2ETest.kt | 2 + .../instructure/teacher/ui/e2e/QuizE2ETest.kt | 2 + .../teacher/ui/e2e/SpeedGraderE2ETest.kt | 2 + .../teacher/ui/e2e/SyllabusE2ETest.kt | 2 + .../instructure/teacher/ui/e2e/TodoE2ETest.kt | 2 + .../ui/espresso/TeacherHiltTestApplication.kt | 23 + .../ui/espresso/TeacherHiltTestRunner.kt | 28 + .../ui/renderTests/CalendarEventRenderTest.kt | 2 + .../ui/renderTests/EditSyllabusRenderTest.kt | 2 + .../ui/renderTests/ModuleListRenderTest.kt | 2 + .../ui/renderTests/PostGradeRenderTest.kt | 2 + .../ui/renderTests/SyllabusRenderTest.kt | 2 + .../teacher/ui/utils/TeacherTest.kt | 5 + .../teacher/activities/InitActivity.kt | 7 + .../teacher/activities/LoginActivity.kt | 10 +- .../activities/LoginLandingPageActivity.kt | 6 +- .../teacher/activities/SignInActivity.kt | 5 +- .../teacher/activities/SpeedGraderActivity.kt | 24 +- .../adapters/SubmissionContentAdapter.kt | 4 + .../di/{ => elementary}/HomeroomModule.kt | 2 +- .../elementary/TeacherHomeroomRouter.kt | 10 + .../AssignmentSubmissionListFragment.kt | 2 +- .../fragments/DiscussionsDetailsFragment.kt | 7 +- .../teacher/fragments/QuizDetailsFragment.kt | 3 +- .../teacher/presenters/AddMessagePresenter.kt | 7 +- .../presenters/QuizDetailsPresenter.kt | 2 +- .../instructure/teacher/utils/AppManager.kt | 77 +- .../teacher/utils/BaseAppManager.kt | 91 + .../instructure/teacher/utils/TeacherPrefs.kt | 2 +- .../teacher/view/SubmissionContentView.kt | 50 +- .../src/main/res/values-th/strings.xml | 903 +++++++ automation/dataseedingapi/build.gradle | 4 +- automation/espresso/build.gradle | 28 +- .../canvas/espresso/CanvasRunner.kt | 18 - .../instructure/canvas/espresso/CanvasTest.kt | 2 +- .../espresso/StubLandscapeAnnotation.kt | 22 + .../canvas/espresso/mockCanvas/MockCanvas.kt | 75 +- .../mockCanvas/endpoints/ApiEndpoint.kt | 15 + .../endpoints/ConversationEndpoints.kt | 17 +- .../mockCanvas/endpoints/UserEndpoints.kt | 36 + .../espresso/NestedScrollViewExtension.kt | 38 + .../espresso/ViewInteractionExtensions.kt | 5 +- .../espresso/page/PageExtensions.kt | 2 + buildSrc/build.gradle.kts | 2 +- buildSrc/src/main/java/GlobalDependencies.kt | 12 +- .../buildtools/transform/LocaleTransformer.kt | 14 +- libs/annotations/build.gradle | 10 +- .../annotations/PdfSubmissionView.kt | 94 +- .../src/main/res/values-th/strings.xml | 41 + libs/canvas-api-2/build.gradle | 2 +- .../canvasapi2/SubmissionStateTypeAdapter.kt | 37 + .../canvasapi2/apis/AnnouncementAPI.kt | 10 +- .../canvasapi2/apis/FeaturesAPI.kt | 9 - .../instructure/canvasapi2/apis/InboxApi.kt | 43 +- .../instructure/canvasapi2/apis/PlannerAPI.kt | 26 + .../instructure/canvasapi2/apis/UserAPI.kt | 16 + .../instructure/canvasapi2/di/ApiModule.kt | 17 + .../managers/AnnouncementManager.kt | 10 +- .../canvasapi2/managers/CanvaDocsManager.kt | 2 +- .../canvasapi2/managers/CourseManager.kt | 3 +- .../canvasapi2/managers/FeaturesManager.kt | 10 - .../canvasapi2/managers/PlannerManager.kt | 44 + .../canvasapi2/managers/UserManager.kt | 16 +- .../canvasapi2/models/Assignment.kt | 9 +- .../instructure/canvasapi2/models/Course.kt | 6 +- .../canvasapi2/models/PlannerItem.kt | 6 +- .../{FeatureFlags.kt => PlannerOverride.kt} | 11 +- .../instructure/canvasapi2/models/Section.kt | 4 +- .../canvasapi2/models/SubmissionState.kt | 32 + .../com/instructure/canvasapi2/models/User.kt | 4 +- .../instructure/canvasapi2/utils/Analytics.kt | 1 + .../instructure/canvasapi2/utils/ApiPrefs.kt | 5 +- .../canvasapi2/utils/CanvasApiExtensions.kt | 6 + .../canvasapi2/utils/MasqueradeHelper.kt | 5 +- .../utils/pageview/PageViewUploadService.kt | 12 +- .../src/main/res/values-ar/strings.xml | 1 + .../main/res/values-b+da+instk12/strings.xml | 1 + .../res/values-b+en+AU+unimelb/strings.xml | 1 + .../main/res/values-b+nb+instk12/strings.xml | 1 + .../main/res/values-b+sv+instk12/strings.xml | 1 + .../src/main/res/values-ca/strings.xml | 1 + .../src/main/res/values-cy/strings.xml | 1 + .../src/main/res/values-da/strings.xml | 1 + .../src/main/res/values-de/strings.xml | 1 + .../src/main/res/values-en-rAU/strings.xml | 1 + .../src/main/res/values-en-rCA/strings.xml | 3 +- .../src/main/res/values-en-rCY/strings.xml | 1 + .../src/main/res/values-en-rGB/strings.xml | 1 + .../src/main/res/values-es/strings.xml | 1 + .../src/main/res/values-fi/strings.xml | 1 + .../src/main/res/values-fr-rCA/strings.xml | 1 + .../src/main/res/values-fr/strings.xml | 1 + .../src/main/res/values-ht/strings.xml | 1 + .../src/main/res/values-is/strings.xml | 1 + .../src/main/res/values-it/strings.xml | 1 + .../src/main/res/values-ja/strings.xml | 1 + .../src/main/res/values-mi/strings.xml | 1 + .../src/main/res/values-nb/strings.xml | 1 + .../src/main/res/values-nl/strings.xml | 1 + .../src/main/res/values-pl/strings.xml | 1 + .../src/main/res/values-pt-rBR/strings.xml | 3 +- .../src/main/res/values-pt-rPT/strings.xml | 1 + .../src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-sl/strings.xml | 1 + .../src/main/res/values-sv/strings.xml | 1 + .../src/main/res/values-th/strings.xml | 80 + .../src/main/res/values-zh-rHK/strings.xml | 1 + .../src/main/res/values-zh/strings.xml | 1 + .../src/main/res/values/strings.xml | 1 + .../pact/canvas/apis/InboxApiPactTests.kt | 3 + .../instructure/canvasapi2/unit/CourseTest.kt | 20 +- .../lib/l10n/res/intl_th.arb | 367 +++ .../login/activities/BaseLoginInitActivity.kt | 24 +- .../BaseLoginLandingPageActivity.kt | 7 +- .../login/viewmodel/LoginViewModel.kt | 21 +- .../src/main/res/values-th/strings.xml | 130 + .../login/viewmodel/LoginViewModelTest.kt | 6 +- .../panda_annotations/TestMetaData.kt | 2 +- .../src/main/res/drawable/ic_homeroom.xml | 13 +- .../res/drawable/ic_homeroom_announcement.xml | 10 + .../main/res/drawable/ic_homeroom_todo.xml | 9 + .../res/drawable/ic_student_annotation.xml | 12 + .../src/main/res/values-ar/strings.xml | 88 +- .../main/res/values-b+da+instk12/strings.xml | 57 +- .../res/values-b+en+AU+unimelb/strings.xml | 89 +- .../main/res/values-b+nb+instk12/strings.xml | 33 +- .../main/res/values-b+sv+instk12/strings.xml | 32 + .../src/main/res/values-ca/strings.xml | 58 +- .../src/main/res/values-cy/strings.xml | 89 +- .../src/main/res/values-da/strings.xml | 89 +- .../src/main/res/values-de/strings.xml | 89 +- .../src/main/res/values-en-rAU/strings.xml | 89 +- .../src/main/res/values-en-rCA/strings.xml | 31 + .../src/main/res/values-en-rCY/strings.xml | 57 +- .../src/main/res/values-en-rGB/strings.xml | 89 +- .../src/main/res/values-es/strings.xml | 32 + .../src/main/res/values-fi/strings.xml | 32 + .../src/main/res/values-fr-rCA/strings.xml | 32 + .../src/main/res/values-fr/strings.xml | 32 + .../src/main/res/values-ht/strings.xml | 32 + .../src/main/res/values-is/strings.xml | 32 + .../src/main/res/values-it/strings.xml | 32 + .../src/main/res/values-ja/strings.xml | 32 + .../src/main/res/values-mi/strings.xml | 35 +- .../src/main/res/values-nb/strings.xml | 32 + .../src/main/res/values-nl/strings.xml | 32 + .../src/main/res/values-pl/strings.xml | 32 + .../src/main/res/values-pt-rBR/strings.xml | 32 + .../src/main/res/values-pt-rPT/strings.xml | 32 + .../src/main/res/values-ru/strings.xml | 32 + .../src/main/res/values-sl/strings.xml | 35 +- .../src/main/res/values-sv/strings.xml | 32 + .../src/main/res/values-th/strings.xml | 1198 +++++++++ .../src/main/res/values-zh-rHK/strings.xml | 32 + .../src/main/res/values-zh/strings.xml | 32 + libs/pandares/src/main/res/values/strings.xml | 17 +- libs/pandautils/build.gradle | 9 +- libs/pandautils/src/main/AndroidManifest.xml | 8 - .../src/main/assets/html_wrapper_k5.html | 83 + .../pandautils/binding/BindingAdapters.kt | 29 +- .../pandautils/di/ApplicationModule.kt | 20 + .../pandautils/di/FeatureFlagModule.kt | 7 +- .../instructure/pandautils/di/UpdateModule.kt | 53 + .../di/elementary/HomeroomViewModelModule.kt | 22 + .../ElementaryDashboardPagerAdapter.kt | 4 + .../elementary/homeroom/CourseCardCreator.kt | 155 ++ .../elementary/homeroom/HomeroomFragment.kt | 57 + .../elementary/homeroom/HomeroomRouter.kt | 10 + .../elementary/homeroom/HomeroomViewData.kt | 14 +- .../elementary/homeroom/HomeroomViewModel.kt | 89 +- ...wModel.kt => AnnouncementItemViewModel.kt} | 2 +- .../itemviewmodels/CourseCardItemViewModel.kt | 49 + .../receivers/PushExternalReceiver.kt | 4 +- .../PushNotificationRegistrationService.kt | 121 - .../PushNotificationRegistrationWorker.kt | 117 + .../pandautils/typeface/TypefaceBehavior.kt | 6 + .../pandautils/update/UpdateManager.kt | 155 ++ .../pandautils/update/UpdatePrefs.kt | 35 + .../instructure/pandautils/utils/A11yUtils.kt | 5 + .../pandautils/utils/ColorKeeper.kt | 8 + .../com/instructure/pandautils/utils/Const.kt | 2 + .../pandautils/utils/FeatureFlagProvider.kt | 19 +- .../pandautils/utils/ViewExtensions.kt | 15 +- .../pandautils/views/CanvasWebView.kt | 3 +- .../pandautils/views/ItemSpacingDecoration.kt | 32 + .../src/main/res/layout/fragment_grades.xml | 4 +- .../src/main/res/layout/fragment_homeroom.xml | 36 +- .../main/res/layout/fragment_resources.xml | 4 +- .../src/main/res/layout/fragment_schedule.xml | 4 +- .../src/main/res/layout/item_announcement.xml | 3 +- .../src/main/res/layout/item_course_card.xml | 155 +- .../src/main/res/values-ar/strings.xml | 6 +- .../main/res/values-b+da+instk12/strings.xml | 6 +- .../res/values-b+en+AU+unimelb/strings.xml | 6 +- .../main/res/values-b+nb+instk12/strings.xml | 6 +- .../main/res/values-b+sv+instk12/strings.xml | 6 +- .../src/main/res/values-ca/strings.xml | 6 +- .../src/main/res/values-cy/strings.xml | 6 +- .../src/main/res/values-da/strings.xml | 6 +- .../src/main/res/values-de/strings.xml | 6 +- .../src/main/res/values-en-rAU/strings.xml | 6 +- .../src/main/res/values-en-rCA/strings.xml | 6 +- .../src/main/res/values-en-rCY/strings.xml | 6 +- .../src/main/res/values-en-rGB/strings.xml | 6 +- .../src/main/res/values-es/strings.xml | 6 +- .../src/main/res/values-fi/strings.xml | 6 +- .../src/main/res/values-fr-rCA/strings.xml | 6 +- .../src/main/res/values-fr/strings.xml | 6 +- .../src/main/res/values-ht/strings.xml | 6 +- .../src/main/res/values-is/strings.xml | 6 +- .../src/main/res/values-it/strings.xml | 6 +- .../src/main/res/values-ja/strings.xml | 6 +- .../src/main/res/values-mi/strings.xml | 6 +- .../src/main/res/values-nb/strings.xml | 6 +- .../src/main/res/values-nl/strings.xml | 6 +- .../src/main/res/values-pl/strings.xml | 6 +- .../src/main/res/values-pt-rBR/strings.xml | 6 +- .../src/main/res/values-pt-rPT/strings.xml | 6 +- .../src/main/res/values-ru/strings.xml | 6 +- .../src/main/res/values-sl/strings.xml | 6 +- .../src/main/res/values-sv/strings.xml | 6 +- .../src/main/res/values-sw720dp/dimens.xml | 20 + .../src/main/res/values-th/strings.xml | 438 ++++ .../src/main/res/values-zh-rHK/strings.xml | 6 +- .../src/main/res/values-zh/strings.xml | 6 +- .../pandautils/src/main/res/values/dimens.xml | 2 + .../src/main/res/values/strings.xml | 5 + .../homeroom/CourseCardCreatorTest.kt | 249 ++ .../homeroom/HomeroomViewModelTest.kt | 170 +- .../utils/FeatureFlagProviderTest.kt | 66 +- libs/rceditor/build.gradle | 4 +- .../java/instructure/rceditor/RCEUtils.kt | 20 - .../src/main/res/values-th/strings.xml | 73 + scripts/update-jira-issues.js | 127 + 410 files changed, 11924 insertions(+), 1367 deletions(-) create mode 100644 apps/flutter_parent/lib/l10n/res/intl_th.arb create mode 100644 apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestApplication.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestRunner.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InAppUpdateInteractionTest.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/pages/AnnouncementListPage.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/pages/ElementaryDashboardPage.kt rename libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/CourseCardViewModel.kt => apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt (64%) create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/pages/HomeroomPage.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/pages/ResourcesPage.kt create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/pages/SchedulePage.kt delete mode 100644 apps/student/src/main/java/com/instructure/student/adapter/AllCoursesRecyclerAdapter.kt delete mode 100644 apps/student/src/main/java/com/instructure/student/adapter/EditFavoritesRecyclerAdapter.kt rename apps/student/src/main/java/com/instructure/student/di/{ => elementary}/HomeroomModule.kt (96%) create mode 100644 apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt create mode 100644 apps/teacher/flank_e2e_coverage.yml create mode 100644 apps/teacher/src/androidTest/java/com/instructure/teacher/ui/InAppUpdatePageTest.kt create mode 100644 apps/teacher/src/androidTest/java/com/instructure/teacher/ui/espresso/TeacherHiltTestApplication.kt create mode 100644 apps/teacher/src/androidTest/java/com/instructure/teacher/ui/espresso/TeacherHiltTestRunner.kt rename apps/teacher/src/main/java/com/instructure/teacher/di/{ => elementary}/HomeroomModule.kt (96%) create mode 100644 apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt create mode 100644 apps/teacher/src/main/res/values-th/strings.xml create mode 100644 automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/StubLandscapeAnnotation.kt create mode 100644 automation/espresso/src/main/kotlin/com/instructure/espresso/NestedScrollViewExtension.kt create mode 100644 libs/annotations/src/main/res/values-th/strings.xml create mode 100644 libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/SubmissionStateTypeAdapter.kt create mode 100644 libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/PlannerAPI.kt create mode 100644 libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt rename libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/{FeatureFlags.kt => PlannerOverride.kt} (80%) create mode 100644 libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/SubmissionState.kt create mode 100644 libs/canvas-api-2/src/main/res/values-th/strings.xml create mode 100644 libs/flutter_student_embed/lib/l10n/res/intl_th.arb create mode 100644 libs/login-api-2/src/main/res/values-th/strings.xml create mode 100644 libs/pandares/src/main/res/drawable/ic_homeroom_announcement.xml create mode 100644 libs/pandares/src/main/res/drawable/ic_homeroom_todo.xml create mode 100644 libs/pandares/src/main/res/drawable/ic_student_annotation.xml create mode 100644 libs/pandares/src/main/res/values-th/strings.xml create mode 100644 libs/pandautils/src/main/assets/html_wrapper_k5.html create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/di/UpdateModule.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/di/elementary/HomeroomViewModelModule.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreator.kt rename libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/{AnnouncementViewModel.kt => AnnouncementItemViewModel.kt} (97%) create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/CourseCardItemViewModel.kt delete mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/services/PushNotificationRegistrationService.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/services/PushNotificationRegistrationWorker.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/update/UpdateManager.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/update/UpdatePrefs.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/views/ItemSpacingDecoration.kt create mode 100644 libs/pandautils/src/main/res/values-sw720dp/dimens.xml create mode 100644 libs/pandautils/src/main/res/values-th/strings.xml create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreatorTest.kt create mode 100644 libs/rceditor/src/main/res/values-th/strings.xml create mode 100644 scripts/update-jira-issues.js diff --git a/android-vault b/android-vault index 39c3a5110a..fa19a2e866 160000 --- a/android-vault +++ b/android-vault @@ -1 +1 @@ -Subproject commit 39c3a5110ab8fafca721661f01091e995677e920 +Subproject commit fa19a2e86680e200bea5c27eb0168b751cf98124 diff --git a/apps/build.gradle b/apps/build.gradle index 7ae1213e76..82f0c84012 100644 --- a/apps/build.gradle +++ b/apps/build.gradle @@ -23,9 +23,7 @@ buildscript { google() mavenCentral() maven { url "https://www.jitpack.io" } - jcenter() maven { url "https://plugins.gradle.org/m2/" } - maven { url "https://dl.bintray.com/instructure/maven" } } dependencies { @@ -34,7 +32,6 @@ buildscript { classpath Plugins.GOOGLE_SERVICES classpath Plugins.KOTLIN classpath Plugins.FIREBASE_CRASHLYTICS - classpath Plugins.BUILD_SCAN if (project.coverageEnabled) { classpath Plugins.JACOCO_ANDROID } classpath Plugins.SQLDELIGHT classpath Plugins.HILT @@ -45,7 +42,7 @@ allprojects { repositories { google() mavenCentral() - maven { url "https://www.jitpack.io" } + maven { url 'https://jitpack.io' } maven { credentials { username pspdfMavenUser @@ -54,8 +51,6 @@ allprojects { url 'https://customers.pspdfkit.com/maven/' } maven { url "https://maven.google.com/" } - jcenter() - maven { url "https://dl.bintray.com/instructure/maven" } } } diff --git a/apps/flutter_parent/android/build.gradle b/apps/flutter_parent/android/build.gradle index 7599f7ecbe..485b50d68e 100644 --- a/apps/flutter_parent/android/build.gradle +++ b/apps/flutter_parent/android/build.gradle @@ -2,7 +2,7 @@ buildscript { ext.kotlin_version = '1.2.71' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -16,7 +16,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/apps/flutter_parent/lib/l10n/res/intl_th.arb b/apps/flutter_parent/lib/l10n/res/intl_th.arb new file mode 100644 index 0000000000..e7261f0072 --- /dev/null +++ b/apps/flutter_parent/lib/l10n/res/intl_th.arb @@ -0,0 +1,2185 @@ +{ + "@@last_modified": "2020-09-18T11:03:20.748250", + "alertsLabel": "แจ้งเตือน", + "@alertsLabel": { + "description": "The label for the Alerts tab", + "type": "text", + "placeholders": {} + }, + "calendarLabel": "ปฏิทิน", + "@calendarLabel": { + "description": "The label for the Calendar tab", + "type": "text", + "placeholders": {} + }, + "coursesLabel": "บทเรียน", + "@coursesLabel": { + "description": "The label for the Courses tab", + "type": "text", + "placeholders": {} + }, + "No Students": "ไม่มีผู้เรียน", + "@No Students": { + "description": "Text for when an observer has no students they are observing", + "type": "text", + "placeholders": {} + }, + "Tap to show student selector": "กดเพื่อแสดงตัวเลือกผู้เรียน", + "@Tap to show student selector": { + "description": "Semantics label for the area that will show the student selector when tapped", + "type": "text", + "placeholders": {} + }, + "Tap to pair with a new student": "กดเพื่อเข้าคู่กับผู้เรียนใหม่", + "@Tap to pair with a new student": { + "description": "Semantics label for the add student button in the student selector", + "type": "text", + "placeholders": {} + }, + "Tap to select this student": "กดเพื่อเลือกผู้เรียนนี้", + "@Tap to select this student": { + "description": "Semantics label on individual students in the student switcher", + "type": "text", + "placeholders": {} + }, + "Manage Students": "จัดการผู้เรียน", + "@Manage Students": { + "description": "Label text for the Manage Students nav drawer button as well as the title for the Manage Students screen", + "type": "text", + "placeholders": {} + }, + "Help": "ความช่วยเหลือ", + "@Help": { + "description": "Label text for the help nav drawer button", + "type": "text", + "placeholders": {} + }, + "Log Out": "ล็อกเอาท์", + "@Log Out": { + "description": "Label text for the Log Out nav drawer button", + "type": "text", + "placeholders": {} + }, + "Switch Users": "สลับผู้ใช้", + "@Switch Users": { + "description": "Label text for the Switch Users nav drawer button", + "type": "text", + "placeholders": {} + }, + "appVersion": "v. {version}", + "@appVersion": { + "description": "App version shown in the navigation drawer", + "type": "text", + "placeholders": { + "version": {} + } + }, + "Are you sure you want to log out?": "แน่ใจว่าต้องการล็อกเอาท์หรือไม่", + "@Are you sure you want to log out?": { + "description": "Confirmation message displayed when the user tries to log out", + "type": "text", + "placeholders": {} + }, + "Calendars": "ปฏิทิน", + "@Calendars": { + "description": "Label for button that lets users select which calendars to display", + "type": "text", + "placeholders": {} + }, + "nextMonth": "เดือนถัดไป: {month}", + "@nextMonth": { + "description": "Label for the button that switches the calendar to the next month", + "type": "text", + "placeholders": { + "month": {} + } + }, + "previousMonth": "เดือนก่อนหน้า: {month}", + "@previousMonth": { + "description": "Label for the button that switches the calendar to the previous month", + "type": "text", + "placeholders": { + "month": {} + } + }, + "nextWeek": "สัปดาห์ถัดไป เริ่มต้น {date}", + "@nextWeek": { + "description": "Label for the button that switches the calendar to the next week", + "type": "text", + "placeholders": { + "date": {} + } + }, + "previousWeek": "สัปดาห์ก่อนหน้า เริ่มต้น {date}", + "@previousWeek": { + "description": "Label for the button that switches the calendar to the previous week", + "type": "text", + "placeholders": { + "date": {} + } + }, + "selectedMonthLabel": "เดือน {month}", + "@selectedMonthLabel": { + "description": "Accessibility label for the button that expands/collapses the month view", + "type": "text", + "placeholders": { + "month": {} + } + }, + "expand": "ขยาย", + "@expand": { + "description": "Accessibility label for the on-tap hint for the button that expands/collapses the month view", + "type": "text", + "placeholders": {} + }, + "collapse": "ย่อ", + "@collapse": { + "description": "Accessibility label for the on-tap hint for the button that expands/collapses the month view", + "type": "text", + "placeholders": {} + }, + "pointsPossible": "{points} คะแนนที่เป็นไปได้", + "@pointsPossible": { + "description": "Screen reader label used for the points possible for an assignment, quiz, etc.", + "type": "text", + "placeholders": { + "points": {} + } + }, + "No Events Today!": "ไม่มีกิจกรรมในวันนี้!", + "@No Events Today!": { + "description": "Title displayed when there are no calendar events for the current day", + "type": "text", + "placeholders": {} + }, + "It looks like a great day to rest, relax, and recharge.": "ดูเหมือนนี่จะเป็นวันที่เหมาะสำหรับพัก ผ่อนคลายและเติมพลัง", + "@It looks like a great day to rest, relax, and recharge.": { + "description": "Message displayed when there are no calendar events for the current day", + "type": "text", + "placeholders": {} + }, + "There was an error loading your student's calendar": "มีข้อผิดพลาดในการโหลดปฏิทินผู้เรียนของคุณ", + "@There was an error loading your student's calendar": { + "description": "Message displayed when calendar events could not be loaded for the current student", + "type": "text", + "placeholders": {} + }, + "Tap to favorite the courses you want to see on the Calendar. Select up to 10.": "กดเลือกเพื่อกำหนดรายการบทเรียนโปรดที่คุณต้องการดูในปฏิทิน เลือกได้สูงสุด 10 รายการ", + "@Tap to favorite the courses you want to see on the Calendar. Select up to 10.": { + "description": "Description text on calendar filter screen.", + "type": "text", + "placeholders": {} + }, + "You may only choose 10 calendars to display": "คุณสามารถเลือกปฏิทินได้เพียง 10 รายการที่จะจัดแสดง", + "@You may only choose 10 calendars to display": { + "description": "Error text when trying to select more than 10 calendars", + "type": "text", + "placeholders": {} + }, + "You must select at least one calendar to display": "คุณจะต้องเลือกปฏิทินอย่างน้อยหนึ่งรายการที่จะจัดแสดง", + "@You must select at least one calendar to display": { + "description": "Error text when trying to de-select all calendars", + "type": "text", + "placeholders": {} + }, + "Planner Note": "หมายเหตุสำหรับแผนงาน", + "@Planner Note": { + "description": "Label used for notes in the planner", + "type": "text", + "placeholders": {} + }, + "Go to today": "ไปที่วันนี้", + "@Go to today": { + "description": "Accessibility label used for the today button in the planner", + "type": "text", + "placeholders": {} + }, + "Previous Logins": "การล็อกอินก่อนหน้า", + "@Previous Logins": { + "description": "Label for the list of previous user logins", + "type": "text", + "placeholders": {} + }, + "canvasLogoLabel": "โลโก้ Canvas", + "@canvasLogoLabel": { + "description": "The semantics label for the Canvas logo", + "type": "text", + "placeholders": {} + }, + "findSchool": "ค้นหาสถานศึกษา", + "@findSchool": { + "description": "Text for the find-my-school button", + "type": "text", + "placeholders": {} + }, + "domainSearchInputHint": "กรอกชื่อสถานศึกษาหรือเขตพื้นที่...", + "@domainSearchInputHint": { + "description": "Input hint for the text box on the domain search screen", + "type": "text", + "placeholders": {} + }, + "noDomainResults": "ไม่พบสถานศึกษาที่ตรงกับ “{query}”", + "@noDomainResults": { + "description": "Message shown to users when the domain search query did not return any results", + "type": "text", + "placeholders": { + "query": {} + } + }, + "domainSearchHelpLabel": "จะค้นหาสถานศึกษาหรือเขตพื้นที่ของฉันได้อย่างไร", + "@domainSearchHelpLabel": { + "description": "Label for the help button on the domain search screen", + "type": "text", + "placeholders": {} + }, + "canvasGuides": "คู่มือ Canvas", + "@canvasGuides": { + "description": "Proper name for the Canvas Guides. This will be used in the domainSearchHelpBody text and will be highlighted and clickable", + "type": "text", + "placeholders": {} + }, + "canvasSupport": "บริการจาก Canvas", + "@canvasSupport": { + "description": "Proper name for Canvas Support. This will be used in the domainSearchHelpBody text and will be highlighted and clickable", + "type": "text", + "placeholders": {} + }, + "domainSearchHelpBody": "ลองค้นหาชื่อสถานศึกษาหรือเขตพื้นที่ที่คุณพยายามสืบค้น เช่น “Smith Private School” หรือ “Smith County Schools” นอกจากนี้คุณยังสามารถกรอกโดเมน Canvas ได้โดยตรง เช่น “smith.instructure.com.”\n\nดูรายละเอียดเพิ่มเติมในการค้นหาบัญชี Canvas สำหรับสถาบันของคุณโดยเข้าไปที่ {canvasGuides} ติดต่อ {canvasSupport} หรือติดต่อสถานศึกษาของคุณเพื่อขอความช่วยเหลือ", + "@domainSearchHelpBody": { + "description": "The body text shown in the help dialog on the domain search screen", + "type": "text", + "placeholders": { + "canvasGuides": {}, + "canvasSupport": {} + } + }, + "Uh oh!": "โอ๊ะ โอ!", + "@Uh oh!": { + "description": "Title of the screen that shows when a crash has occurred", + "type": "text", + "placeholders": {} + }, + "We’re not sure what happened, but it wasn’t good. Contact us if this keeps happening.": "เราไม่แน่ใจว่าเกิดอะไรขึ้น แต่เชื่อว่าไม่ดี ติดต่อเราหากยังเกิดปัญหานี้อยู่", + "@We’re not sure what happened, but it wasn’t good. Contact us if this keeps happening.": { + "description": "Message shown when a crash has occurred", + "type": "text", + "placeholders": {} + }, + "Contact Support": "บริการสำหรับการติดต่อ", + "@Contact Support": { + "description": "Label for the button that allows users to contact support after a crash has occurred", + "type": "text", + "placeholders": {} + }, + "View error details": "ดูรายละเอียดข้อผิดพลาด", + "@View error details": { + "description": "Label for the button that allowed users to view crash details", + "type": "text", + "placeholders": {} + }, + "Restart app": "รีสตาร์ทแอพ", + "@Restart app": { + "description": "Label for the button that will restart the entire application", + "type": "text", + "placeholders": {} + }, + "Application version": "เวอร์ชั่นแอพพลิเคชั่น", + "@Application version": { + "description": "Label for the application version displayed in the crash details", + "type": "text", + "placeholders": {} + }, + "Device model": "รุ่นอุปกรณ์", + "@Device model": { + "description": "Label for the device model displayed in the crash details", + "type": "text", + "placeholders": {} + }, + "Android OS version": "เวอร์ชั่น Android OS", + "@Android OS version": { + "description": "Label for the Android operating system version displayed in the crash details", + "type": "text", + "placeholders": {} + }, + "Full error message": "ข้อความแจ้งข้อผิดพลาดเต็ม", + "@Full error message": { + "description": "Label for the full error message displayed in the crash details", + "type": "text", + "placeholders": {} + }, + "Inbox": "กล่องจดหมาย", + "@Inbox": { + "description": "Title for the Inbox screen", + "type": "text", + "placeholders": {} + }, + "There was an error loading your inbox messages.": "มีข้อผิดพลาดในการโหลดข้อความในกล่องจดหมายของคุณ", + "@There was an error loading your inbox messages.": { + "type": "text", + "placeholders": {} + }, + "No Subject": "ไม่มีหัวเรื่อง", + "@No Subject": { + "description": "Title used for inbox messages that have no subject", + "type": "text", + "placeholders": {} + }, + "Unable to fetch courses. Please check your connection and try again.": "ไม่สามารถสืบค้นบทเรียนได้ กรุณาตรวจสอบการเชื่อมต่อของคุณและลองใหม่อีกครั้ง", + "@Unable to fetch courses. Please check your connection and try again.": { + "description": "Message shown when an error occured while loading courses", + "type": "text", + "placeholders": {} + }, + "Choose a course to message": "เลือกบทเรียนที่จะส่งข้อความ", + "@Choose a course to message": { + "description": "Header in the course list shown when the user is choosing which course to associate with a new message", + "type": "text", + "placeholders": {} + }, + "Inbox Zero": "Inbox Zero", + "@Inbox Zero": { + "description": "Title of the message shown when there are no inbox messages", + "type": "text", + "placeholders": {} + }, + "You’re all caught up!": "แย่แล้ว!", + "@You’re all caught up!": { + "description": "Subtitle of the message shown when there are no inbox messages", + "type": "text", + "placeholders": {} + }, + "There was an error loading recipients for this course": "มีข้อผิดพลาดในการโหลดผู้รับสำหรับบทเรียนนี้", + "@There was an error loading recipients for this course": { + "description": "Message shown when attempting to create a new message but the recipients list failed to load", + "type": "text", + "placeholders": {} + }, + "Unable to send message. Check your connection and try again.": "ไม่สามารถส่งข้อความได้ ตรวจสอบการเชื่อมต่อและลองใหม่อีกครั้ง", + "@Unable to send message. Check your connection and try again.": { + "description": "Message show when there was an error creating or sending a new message", + "type": "text", + "placeholders": {} + }, + "Unsaved changes": "การเปลี่ยนแปลงที่ไม่ได้บันทึก", + "@Unsaved changes": { + "description": "Title of the dialog shown when the user tries to leave with unsaved changes", + "type": "text", + "placeholders": {} + }, + "Are you sure you wish to close this page? Your unsent message will be lost.": "แน่ใจว่าต้องการปิดหน้าเพจนี้หรือไม่ ข้อความที่ไม่ได้ส่งของคุณจะหายไป", + "@Are you sure you wish to close this page? Your unsent message will be lost.": { + "description": "Body text of the dialog shown when the user tries leave with unsaved changes", + "type": "text", + "placeholders": {} + }, + "New message": "ข้อความใหม่", + "@New message": { + "description": "Title of the new-message screen", + "type": "text", + "placeholders": {} + }, + "Add attachment": "เพิ่มเอกสารแนบ", + "@Add attachment": { + "description": "Tooltip for the add-attachment button in the new-message screen", + "type": "text", + "placeholders": {} + }, + "Send message": "ส่งข้อความ", + "@Send message": { + "description": "Tooltip for the send-message button in the new-message screen", + "type": "text", + "placeholders": {} + }, + "Select recipients": "เลือกผู้รับ", + "@Select recipients": { + "description": "Tooltip for the button that allows users to select message recipients", + "type": "text", + "placeholders": {} + }, + "No recipients selected": "ไม่ได้เลือกผู้รับ", + "@No recipients selected": { + "description": "Hint displayed when the user has not selected any message recipients", + "type": "text", + "placeholders": {} + }, + "Message subject": "หัวเรื่องข้อความ", + "@Message subject": { + "description": "Hint text displayed in the input field for the message subject", + "type": "text", + "placeholders": {} + }, + "Message": "ข้อความ", + "@Message": { + "description": "Hint text displayed in the input field for the message body", + "type": "text", + "placeholders": {} + }, + "Recipients": "ผู้รับ", + "@Recipients": { + "description": "Label for message recipients", + "type": "text", + "placeholders": {} + }, + "plusRecipientCount": "+{count}", + "@plusRecipientCount": { + "description": "Shows the number of recipients that are selected but not displayed on screen.", + "type": "text", + "placeholders": { + "count": { + "example": 5 + } + } + }, + "Failed. Tap for options.": "ล้มเหลว กดเพื่อดูตัวเลือก", + "@Failed. Tap for options.": { + "description": "Short message shown on a message attachment when uploading has failed", + "type": "text", + "placeholders": {} + }, + "courseForWhom": "สำหรับ {studentShortName}", + "@courseForWhom": { + "description": "Describes for whom a course is for (i.e. for Bill)", + "type": "text", + "placeholders": { + "studentShortName": {} + } + }, + "messageLinkPostscript": "เกี่ยวกับ: {studentName}, {linkUrl}", + "@messageLinkPostscript": { + "description": "A postscript appended to new messages that clarifies which student is the subject of the message and also includes a URL for the related Canvas component (course, assignment, event, etc).", + "type": "text", + "placeholders": { + "studentName": {}, + "linkUrl": {} + } + }, + "There was an error loading this conversation": "มีข้อผิดพลาดในการโหลดการพูดคุยนี้", + "@There was an error loading this conversation": { + "description": "Message shown when a conversation fails to load", + "type": "text", + "placeholders": {} + }, + "Reply": "ตอบกลับ", + "@Reply": { + "description": "Button label for replying to a conversation", + "type": "text", + "placeholders": {} + }, + "Reply All": "ตอบกลับทั้งหมด", + "@Reply All": { + "description": "Button label for replying to all conversation participants", + "type": "text", + "placeholders": {} + }, + "Unknown User": "ผู้ใช้ที่ไม่รู้จัก", + "@Unknown User": { + "description": "Label used where the user name is not known", + "type": "text", + "placeholders": {} + }, + "me": "ฉัน", + "@me": { + "description": "First-person pronoun (i.e. 'me') that will be used in message author info, e.g. 'Me to 4 others' or 'Jon Snow to me'", + "type": "text", + "placeholders": {} + }, + "authorToRecipient": "{authorName} ถึง {recipientName}", + "@authorToRecipient": { + "description": "Author info for a single-recipient message; includes both the author name and the recipient name.", + "type": "text", + "placeholders": { + "authorName": {}, + "recipientName": {} + } + }, + "authorToNOthers": "{howMany,plural, =1{{authorName} ถึงคนอื่นอีก 1 ราย}other{{authorName} กับคนอื่นอีก {howMany} ราย}}", + "@authorToNOthers": { + "description": "Author info for a mutli-recipient message; includes the author name and the number of recipients", + "type": "text", + "placeholders": { + "authorName": {}, + "howMany": {} + } + }, + "authorToRecipientAndNOthers": "{howMany,plural, =1{{authorName} กับ {recipientName} และคนอื่นอีก 1 ราย}other{{authorName} กับ {recipientName} และคนอื่นอีก {howMany} ราย}}", + "@authorToRecipientAndNOthers": { + "description": "Author info for a multi-recipient message; includes the author name, one recipient name, and the number of other recipients", + "type": "text", + "placeholders": { + "authorName": {}, + "recipientName": {}, + "howMany": {} + } + }, + "Download": "ดาวน์โหลด", + "@Download": { + "description": "Label for the button that will begin downloading a file", + "type": "text", + "placeholders": {} + }, + "Open with another app": "เปิดโดยใช้แอพอื่น", + "@Open with another app": { + "description": "Label for the button that will allow users to open a file with another app", + "type": "text", + "placeholders": {} + }, + "There are no installed applications that can open this file": "ไม่มีแอพพลิเคชั่นติดตั้งที่เปิดไฟล์นี้ได้", + "@There are no installed applications that can open this file": { + "type": "text", + "placeholders": {} + }, + "Unsupported File": "ไฟล์ไม่รองรับ", + "@Unsupported File": { + "type": "text", + "placeholders": {} + }, + "This file is unsupported and can’t be viewed through the app": "ไม่รองรับไฟล์นี้และไม่สามารถเปิดดูได้ผ่านแอพนี้", + "@This file is unsupported and can’t be viewed through the app": { + "type": "text", + "placeholders": {} + }, + "Unable to play this media file": "ไม่สามารถเปิดเล่นไฟล์สื่อนี้", + "@Unable to play this media file": { + "description": "Message shown when audio or video media could not be played", + "type": "text", + "placeholders": {} + }, + "Unable to load this image": "ไม่สามารถโหลดภาพนี้", + "@Unable to load this image": { + "description": "Message shown when an image file could not be loaded or displayed", + "type": "text", + "placeholders": {} + }, + "There was an error loading this file": "มีข้อผิดพลาดในการโหลดไฟล์นี้", + "@There was an error loading this file": { + "description": "Message shown when a file could not be loaded or displayed", + "type": "text", + "placeholders": {} + }, + "No Courses": "ไม่มีบทเรียน", + "@No Courses": { + "description": "Title for having no courses", + "type": "text", + "placeholders": {} + }, + "Your student’s courses might not be published yet.": "บทเรียนสำหรับผู้เรียนของคุณอาจยังไม่ได้เผยแพร่", + "@Your student’s courses might not be published yet.": { + "description": "Message for having no courses", + "type": "text", + "placeholders": {} + }, + "There was an error loading your student’s courses.": "มีข้อผิดพลาดในการโหลดบทเรียนสำหรับผู้เรียน", + "@There was an error loading your student’s courses.": { + "description": "Message displayed when the list of student courses could not be loaded", + "type": "text", + "placeholders": {} + }, + "No Grade": "ไม่มีเกรด", + "@No Grade": { + "description": "Message shown when there is currently no grade available for a course", + "type": "text", + "placeholders": {} + }, + "Filter by": "กรองจาก", + "@Filter by": { + "description": "Title for list of terms to filter grades by", + "type": "text", + "placeholders": {} + }, + "Grades": "เกรด", + "@Grades": { + "description": "Label for the \"Grades\" tab in course details", + "type": "text", + "placeholders": {} + }, + "Syllabus": "หลักสูตร", + "@Syllabus": { + "description": "Label for the \"Syllabus\" tab in course details", + "type": "text", + "placeholders": {} + }, + "Front Page": "หน้าแรก", + "@Front Page": { + "description": "Label for the \"Front Page\" tab in course details", + "type": "text", + "placeholders": {} + }, + "Summary": "สรุป", + "@Summary": { + "description": "Label for the \"Summary\" tab in course details", + "type": "text", + "placeholders": {} + }, + "Send a message about this course": "ส่งข้อความเกี่ยวกับบทเรียนนี้", + "@Send a message about this course": { + "description": "Accessibility hint for the course messaage floating action button", + "type": "text", + "placeholders": {} + }, + "Total Grade": "เกรดรวม", + "@Total Grade": { + "description": "Label for the total grade in the course", + "type": "text", + "placeholders": {} + }, + "Graded": "ให้เกรดแล้ว", + "@Graded": { + "description": "Label for assignments that have been graded", + "type": "text", + "placeholders": {} + }, + "Submitted": "จัดส่งแล้ว", + "@Submitted": { + "description": "Label for assignments that have been submitted", + "type": "text", + "placeholders": {} + }, + "Not Submitted": "ไม่ได้จัดส่ง", + "@Not Submitted": { + "description": "Label for assignments that have not been submitted", + "type": "text", + "placeholders": {} + }, + "Late": "ล่าช้า", + "@Late": { + "description": "Label for assignments that have been marked late or submitted late", + "type": "text", + "placeholders": {} + }, + "Missing": "ขาดหาย", + "@Missing": { + "description": "Label for assignments that have been marked missing or are not submitted and past the due date", + "type": "text", + "placeholders": {} + }, + "-": "-", + "@-": { + "description": "Value representing no score for student submission", + "type": "text", + "placeholders": {} + }, + "All Grading Periods": "ระยะเวลาการให้เกรดทั้งหมด", + "@All Grading Periods": { + "description": "Label for selecting all grading periods", + "type": "text", + "placeholders": {} + }, + "No Assignments": "ไม่มีภารกิจ", + "@No Assignments": { + "description": "Title for the no assignments message", + "type": "text", + "placeholders": {} + }, + "It looks like assignments haven't been created in this space yet.": "ดูเหมือนจะยังไม่ได้จัดทำภารกิจในพื้นที่นี้", + "@It looks like assignments haven't been created in this space yet.": { + "description": "Message for no assignments", + "type": "text", + "placeholders": {} + }, + "There was an error loading the summary details for this course.": "มีข้อผิดพลาดในการโหลดรายละเอียดสรุปสำหรับบทเรียนนี้", + "@There was an error loading the summary details for this course.": { + "description": "Message shown when the course summary could not be loaded", + "type": "text", + "placeholders": {} + }, + "No Summary": "ไม่มีข้อมูลสรุป", + "@No Summary": { + "description": "Title displayed when there are no items in the course summary", + "type": "text", + "placeholders": {} + }, + "This course does not have any assignments or calendar events yet.": "บทเรียนนี้ไม่มีภารกิจหรือกิจกรรมในปฏิทินในตอนนี้", + "@This course does not have any assignments or calendar events yet.": { + "description": "Message displayed when there are no items in the course summary", + "type": "text", + "placeholders": {} + }, + "gradeFormatScoreOutOfPointsPossible": "{score} / {pointsPossible}", + "@gradeFormatScoreOutOfPointsPossible": { + "description": "Formatted string for a student score out of the points possible", + "type": "text", + "placeholders": { + "score": {}, + "pointsPossible": {} + } + }, + "contentDescriptionScoreOutOfPointsPossible": "{score} จา {pointsPossible} คะแนน", + "@contentDescriptionScoreOutOfPointsPossible": { + "description": "Formatted string for a student score out of the points possible", + "type": "text", + "placeholders": { + "score": {}, + "pointsPossible": {} + } + }, + "gradesSubjectMessage": "เกี่ยวกับ: {studentName}, เกรด", + "@gradesSubjectMessage": { + "description": "The subject line for a message to a teacher regarding a student's grades", + "type": "text", + "placeholders": { + "studentName": {} + } + }, + "syllabusSubjectMessage": "เกี่ยวกับ: {studentName}, หลักสูตร", + "@syllabusSubjectMessage": { + "description": "The subject line for a message to a teacher regarding a course syllabus", + "type": "text", + "placeholders": { + "studentName": {} + } + }, + "frontPageSubjectMessage": "เกี่ยวกับ: {studentName}, หน้าแรก", + "@frontPageSubjectMessage": { + "description": "The subject line for a message to a teacher regarding a course front page", + "type": "text", + "placeholders": { + "studentName": {} + } + }, + "assignmentSubjectMessage": "เกี่ยวกับ: {studentName}, ภารกิจ - {assignmentName}", + "@assignmentSubjectMessage": { + "description": "The subject line for a message to a teacher regarding a student's assignment", + "type": "text", + "placeholders": { + "studentName": {}, + "assignmentName": {} + } + }, + "eventSubjectMessage": "เกี่ยวกับ: {studentName}, กิจกรรม - {eventTitle}", + "@eventSubjectMessage": { + "description": "The subject line for a message to a teacher regarding a calendar event", + "type": "text", + "placeholders": { + "studentName": {}, + "eventTitle": {} + } + }, + "There is no page information available.": "ไม่มีข้อมูลเพจ", + "@There is no page information available.": { + "description": "Description for when no page information is available", + "type": "text", + "placeholders": {} + }, + "Assignment Details": "รายละเอียดภารกิจ", + "@Assignment Details": { + "description": "Title for the page that shows details for an assignment", + "type": "text", + "placeholders": {} + }, + "assignmentTotalPoints": "{points} คะแนน", + "@assignmentTotalPoints": { + "description": "Label used for the total points the assignment is worth", + "type": "text", + "placeholders": { + "points": {} + } + }, + "assignmentTotalPointsAccessible": "{points} คะแนน", + "@assignmentTotalPointsAccessible": { + "description": "Screen reader label used for the total points the assignment is worth", + "type": "text", + "placeholders": { + "points": {} + } + }, + "Due": "ครบกำหนด", + "@Due": { + "description": "Label for an assignment due date", + "type": "text", + "placeholders": {} + }, + "Grade": "เกรด", + "@Grade": { + "description": "Label for the section that displays an assignment's grade", + "type": "text", + "placeholders": {} + }, + "Locked": "ล็อคแล้ว", + "@Locked": { + "description": "Label for when an assignment is locked", + "type": "text", + "placeholders": {} + }, + "assignmentLockedModule": "ภารกิจนี้ถูกล็อคโดยหน่วยการเรียน “{moduleName}”", + "@assignmentLockedModule": { + "description": "The locked description when an assignment is locked by a module", + "type": "text", + "placeholders": { + "moduleName": {} + } + }, + "Remind Me": "เตือนฉัน", + "@Remind Me": { + "description": "Label for the row to set reminders", + "type": "text", + "placeholders": {} + }, + "Set a date and time to be notified of this specific assignment.": "กำหนดวันที่และเวลาที่จะรับการแจ้งเตือนสำหรับภารกิจเฉพาะนี้", + "@Set a date and time to be notified of this specific assignment.": { + "description": "Description for row to set reminders", + "type": "text", + "placeholders": {} + }, + "You will be notified about this assignment on…": "คุณจะได้รับแจ้งเกี่ยวกับภารกิจใน....", + "@You will be notified about this assignment on…": { + "description": "Description for when a reminder is set", + "type": "text", + "placeholders": {} + }, + "Instructions": "คำแนะนำ", + "@Instructions": { + "description": "Label for the description of the assignment when it has quiz instructions", + "type": "text", + "placeholders": {} + }, + "Send a message about this assignment": "ส่งข้อความเกี่ยวกับภารกิจนี้", + "@Send a message about this assignment": { + "description": "Accessibility hint for the assignment messaage floating action button", + "type": "text", + "placeholders": {} + }, + "This app is not authorized for use.": "แอพนี้ไม่ได้รับอนุญาตให้ใช้งาน", + "@This app is not authorized for use.": { + "description": "The error shown when the app being used is not verified by Canvas", + "type": "text", + "placeholders": {} + }, + "The server you entered is not authorized for this app.": "เซิร์ฟเวอร์ที่คุณกรอกไม่ได้รับอนุญาตสำหรับแอพนี้", + "@The server you entered is not authorized for this app.": { + "description": "The error shown when the desired login domain is not verified by Canvas", + "type": "text", + "placeholders": {} + }, + "The user agent for this app is not authorized.": "ระบบตัวแทนของผู้ใช้สำหรับแอพนี้ไม่ได้รับอนุญาต", + "@The user agent for this app is not authorized.": { + "description": "The error shown when the user agent during verification is not verified by Canvas", + "type": "text", + "placeholders": {} + }, + "We were unable to verify the server for use with this app.": "เราไม่สามารถยืนยันเซิร์ฟเวอร์สำหรับใช้กับแอพนี้", + "@We were unable to verify the server for use with this app.": { + "description": "The generic error shown when we are unable to verify with Canvas", + "type": "text", + "placeholders": {} + }, + "Reminders": "การแจ้งเตือน", + "@Reminders": { + "description": "Name of the system notification channel for assignment and event reminders", + "type": "text", + "placeholders": {} + }, + "Notifications for reminders about assignments and calendar events": "การแจ้งข้อมูลสำหรับการแจ้งเตือนเกี่ยวกับภารกิจและกิจกรรมในปฏิทิน", + "@Notifications for reminders about assignments and calendar events": { + "description": "Description of the system notification channel for assignment and event reminders", + "type": "text", + "placeholders": {} + }, + "Reminders have changed!": "การแจ้งเตือนเปลี่ยนแปลงแล้ว!", + "@Reminders have changed!": { + "description": "Title of the dialog shown when the user needs to update their reminders", + "type": "text", + "placeholders": {} + }, + "In order to provide you with a better experience, we have updated how reminders work. You can add new reminders by viewing an assignment or calendar event and tapping the switch under the \"Remind Me\" section.\n\nBe aware that any reminders created with older versions of this app will not be compatible with the new changes and you will need to create them again.": "เพื่อให้คุณใช้งานได้สะดวกมากขึ้น เราได้มีการอัพเดตระบบแจ้งเตือนใหม่ คุณสามารถเพิ่มการแจ้งเตือนใหม่โดยดูภารกิจหรือกิจกรรมในปฏิทินและกดเลือกสลับการใช้งานได้จากหัวข้อ “เตือนฉัน”\n\nการแจ้งเตือนใด ๆ ที่จัดทำผ่านแอพนี้ในเวอร์ชั่นเก่าจะไม่รองรับการเปลี่ยนแปลงใหม่นี้และคุณจะต้องจัดทำชุดข้อมูลใหม่อีกครั้ง", + "@In order to provide you with a better experience, we have updated how reminders work. You can add new reminders by viewing an assignment or calendar event and tapping the switch under the \"Remind Me\" section.\n\nBe aware that any reminders created with older versions of this app will not be compatible with the new changes and you will need to create them again.": { + "type": "text", + "placeholders": {} + }, + "Not a parent?": "ไม่ใช่พ่อแม่ผู้ปกครองใช่หรือไม่", + "@Not a parent?": { + "description": "Title for the screen that shows when the user is not observing any students", + "type": "text", + "placeholders": {} + }, + "We couldn't find any students associated with this account": "เราไม่พบผู้เรียนที่เชื่อมโยงกับบัญชีนี้", + "@We couldn't find any students associated with this account": { + "description": "Subtitle for the screen that shows when the user is not observing any students", + "type": "text", + "placeholders": {} + }, + "Are you a student or teacher?": "คุณเป็นผู้เรียนหรือผู้สอน", + "@Are you a student or teacher?": { + "description": "Label for button that will show users the option to view other Canvas apps in the Play Store", + "type": "text", + "placeholders": {} + }, + "One of our other apps might be a better fit. Tap one to visit the Play Store.": "แอพอื่น ๆ บางส่วนของเราอาจเหมาะสมมากกว่า กดเลือกหนึ่งรายการเพื่อไปยัง Play Store", + "@One of our other apps might be a better fit. Tap one to visit the Play Store.": { + "description": "Description of options to view other Canvas apps in the Play Store", + "type": "text", + "placeholders": {} + }, + "Return to Login": "กลับไปที่ล็อกอิน", + "@Return to Login": { + "description": "Label for the button that returns the user to the login screen", + "type": "text", + "placeholders": {} + }, + "STUDENT": "ผู้เรียน", + "@STUDENT": { + "description": "The \"student\" portion of the \"Canvas Student\" app name, in all caps. \"Canvas\" is excluded in this context as it will be displayed to the user as a wordmark image", + "type": "text", + "placeholders": {} + }, + "TEACHER": "ผู้สอน", + "@TEACHER": { + "description": "The \"teacher\" portion of the \"Canvas Teacher\" app name, in all caps. \"Canvas\" is excluded in this context as it will be displayed to the user as a wordmark image", + "type": "text", + "placeholders": {} + }, + "Canvas Student": "Canvas Student", + "@Canvas Student": { + "description": "The name of the Canvas Student app. Only \"Student\" should be translated as \"Canvas\" is a brand name in this context and should not be translated.", + "type": "text", + "placeholders": {} + }, + "Canvas Teacher": "Canvas Teacher", + "@Canvas Teacher": { + "description": "The name of the Canvas Teacher app. Only \"Teacher\" should be translated as \"Canvas\" is a brand name in this context and should not be translated.", + "type": "text", + "placeholders": {} + }, + "No Alerts": "ไม่มีการแจ้งเตือน", + "@No Alerts": { + "description": "The title for the empty message to show to users when there are no alerts for the student.", + "type": "text", + "placeholders": {} + }, + "There’s nothing to be notified of yet.": "ไม่มีข้อมูลที่จะแจ้งในตอนนี้", + "@There’s nothing to be notified of yet.": { + "description": "The empty message to show to users when there are no alerts for the student.", + "type": "text", + "placeholders": {} + }, + "dismissAlertLabel": "ล้มเลิก {alertTitle}", + "@dismissAlertLabel": { + "description": "Accessibility label to dismiss an alert", + "type": "text", + "placeholders": { + "alertTitle": {} + } + }, + "Course Announcement": "ประกาศแจ้งสำหรับบทเรียน", + "@Course Announcement": { + "description": "Title for alerts when there is a course announcement", + "type": "text", + "placeholders": {} + }, + "Institution Announcement": "ประกาศของสถาบัน", + "@Institution Announcement": { + "description": "Title for alerts when there is an institution announcement", + "type": "text", + "placeholders": {} + }, + "assignmentGradeAboveThreshold": "เกรดภารกิจมากกว่า {threshold}", + "@assignmentGradeAboveThreshold": { + "description": "Title for alerts when an assignment grade is above the threshold value", + "type": "text", + "placeholders": { + "threshold": {} + } + }, + "assignmentGradeBelowThreshold": "เกรดภารกิจต่ำกว่า {threshold}", + "@assignmentGradeBelowThreshold": { + "description": "Title for alerts when an assignment grade is below the threshold value", + "type": "text", + "placeholders": { + "threshold": {} + } + }, + "courseGradeAboveThreshold": "เกรดบทเรียนมากกว่า {threshold}", + "@courseGradeAboveThreshold": { + "description": "Title for alerts when a course grade is above the threshold value", + "type": "text", + "placeholders": { + "threshold": {} + } + }, + "courseGradeBelowThreshold": "เกรดบทเรียนน้อยกว่า {threshold}", + "@courseGradeBelowThreshold": { + "description": "Title for alerts when a course grade is below the threshold value", + "type": "text", + "placeholders": { + "threshold": {} + } + }, + "Settings": "ค่าปรับตั้ง", + "@Settings": { + "description": "Title for the settings screen", + "type": "text", + "placeholders": {} + }, + "Theme": "ธีม", + "@Theme": { + "description": "Label for the light/dark theme section in the settings page", + "type": "text", + "placeholders": {} + }, + "Dark Mode": "โหมดมืด", + "@Dark Mode": { + "description": "Label for the button that enables dark mode", + "type": "text", + "placeholders": {} + }, + "Light Mode": "โหมดสว่าง", + "@Light Mode": { + "description": "Label for the button that enables light mode", + "type": "text", + "placeholders": {} + }, + "High Contrast Mode": "โหมดคอนทราสต์สูง", + "@High Contrast Mode": { + "description": "Label for the switch that toggles high contrast mode", + "type": "text", + "placeholders": {} + }, + "Use Dark Theme in Web Content": "ใช้ธีมมืดในเนื้อหาบนเว็บ", + "@Use Dark Theme in Web Content": { + "description": "Label for the switch that toggles dark mode for webviews", + "type": "text", + "placeholders": {} + }, + "Appearance": "ลักษณะภายนอก", + "@Appearance": { + "description": "Label for the appearance section in the settings page", + "type": "text", + "placeholders": {} + }, + "Successfully submitted!": "ส่งเสร็จสิ้น!", + "@Successfully submitted!": { + "description": "Title displayed in the grade cell for an assignment that has been submitted", + "type": "text", + "placeholders": {} + }, + "submissionStatusSuccessSubtitle": "ภารกิจนี้ถูกจัดส่งแล้วเมื่อ {date} เวลา {time} และกำลังรอการให้เกรด", + "@submissionStatusSuccessSubtitle": { + "description": "Subtitle displayed in the grade cell for an assignment that has been submitted and is awaiting a grade", + "type": "text", + "placeholders": { + "date": {}, + "time": {} + } + }, + "outOfPoints": "{howMany,plural, =1{จาก 1 คะแนน}other{จาก {points} คะแนน}}", + "@outOfPoints": { + "description": "Description for an assignment grade that has points without a current scoroe", + "type": "text", + "placeholders": { + "points": {}, + "howMany": {} + } + }, + "Excused": "ได้รับการยกเว้น", + "@Excused": { + "description": "Grading status for an assignment marked as excused", + "type": "text", + "placeholders": {} + }, + "Complete": "เสร็จสิ้น", + "@Complete": { + "description": "Grading status for an assignment marked as complete", + "type": "text", + "placeholders": {} + }, + "Incomplete": "ไม่เสร็จสิ้น", + "@Incomplete": { + "description": "Grading status for an assignment marked as incomplete", + "type": "text", + "placeholders": {} + }, + "minus": "ลบ", + "@minus": { + "description": "Screen reader-friendly replacement for the \"-\" character in letter grades like \"A-\"", + "type": "text", + "placeholders": {} + }, + "latePenalty": "โทษปรับล่าช้า (-{pointsLost})", + "@latePenalty": { + "description": "Text displayed when a late penalty has been applied to the assignment", + "type": "text", + "placeholders": { + "pointsLost": {} + } + }, + "finalGrade": "เกรดสรุป: {grade}", + "@finalGrade": { + "description": "Text that displays the final grade of an assignment", + "type": "text", + "placeholders": { + "grade": {} + } + }, + "Alert Settings": "ค่าการแจ้งเตือน", + "@Alert Settings": { + "type": "text", + "placeholders": {} + }, + "Alert me when…": "แจ้งเตือนฉันเมื่อ...", + "@Alert me when…": { + "description": "Header for the screen where the observer chooses the thresholds that will determine when they receive alerts (e.g. when an assignment is graded below 70%)", + "type": "text", + "placeholders": {} + }, + "Course grade below": "บทเรียน เกรดน้อยกว่า", + "@Course grade below": { + "description": "Label describing the threshold for when the course grade is below a certain percentage", + "type": "text", + "placeholders": {} + }, + "Course grade above": "บทเรียน เกรดมากกว่า", + "@Course grade above": { + "description": "Label describing the threshold for when the course grade is above a certain percentage", + "type": "text", + "placeholders": {} + }, + "Assignment missing": "ไม่มีภารกิจ", + "@Assignment missing": { + "type": "text", + "placeholders": {} + }, + "Assignment grade below": "เกรดภารกิจน้อยกว่า", + "@Assignment grade below": { + "description": "Label describing the threshold for when an assignment is graded below a certain percentage", + "type": "text", + "placeholders": {} + }, + "Assignment grade above": "เกรดภารกิจมากกว่า", + "@Assignment grade above": { + "description": "Label describing the threshold for when an assignment is graded above a certain percentage", + "type": "text", + "placeholders": {} + }, + "Course Announcements": "ประกาศเกี่ยวกับบทเรียน", + "@Course Announcements": { + "type": "text", + "placeholders": {} + }, + "Institution Announcements": "ประกาศของสถาบัน", + "@Institution Announcements": { + "type": "text", + "placeholders": {} + }, + "Never": "ไม่เลย", + "@Never": { + "description": "Indication that tells the user they will not receive alert notifications of a specific kind", + "type": "text", + "placeholders": {} + }, + "Grade percentage": "เปอร์เซ็นต์เกรด", + "@Grade percentage": { + "type": "text", + "placeholders": {} + }, + "There was an error loading your student's alerts.": "มีข้อผิดพลาดในการโหลดการแจ้งเตือนสำหรับผู้เรียนของคุณ", + "@There was an error loading your student's alerts.": { + "type": "text", + "placeholders": {} + }, + "Must be below 100": "จะต้องต่ำกว่า 100", + "@Must be below 100": { + "type": "text", + "placeholders": {} + }, + "mustBeBelowN": "จะต้องต่ำกว่า {percentage}", + "@mustBeBelowN": { + "description": "Validation error to the user that they must choose a percentage below 'n'", + "type": "text", + "placeholders": { + "percentage": { + "example": 5 + } + } + }, + "mustBeAboveN": "จะต้องมากกว่า {percentage}", + "@mustBeAboveN": { + "description": "Validation error to the user that they must choose a percentage above 'n'", + "type": "text", + "placeholders": { + "percentage": { + "example": 5 + } + } + }, + "Select Student Color": "เลือกสีของผู้เรียน", + "@Select Student Color": { + "description": "Title for screen that allows users to assign a color to a specific student", + "type": "text", + "placeholders": {} + }, + "Electric, blue": "อิเล็คทริค, น้ำเงิน", + "@Electric, blue": { + "description": "Name of the Electric (blue) color", + "type": "text", + "placeholders": {} + }, + "Plum, Purple": "พลัม, ม่วง", + "@Plum, Purple": { + "description": "Name of the Plum (purple) color", + "type": "text", + "placeholders": {} + }, + "Barney, Fuschia": "บาร์นีย์, ฟูเชีย", + "@Barney, Fuschia": { + "description": "Name of the Barney (fuschia) color", + "type": "text", + "placeholders": {} + }, + "Raspberry, Red": "ราสเบอร์รี่, แดง", + "@Raspberry, Red": { + "description": "Name of the Raspberry (red) color", + "type": "text", + "placeholders": {} + }, + "Fire, Orange": "ไฟเออร์, ส้ม", + "@Fire, Orange": { + "description": "Name of the Fire (orange) color", + "type": "text", + "placeholders": {} + }, + "Shamrock, Green": "ชัมร็อค, เขียว", + "@Shamrock, Green": { + "description": "Name of the Shamrock (green) color", + "type": "text", + "placeholders": {} + }, + "An error occurred while saving your selection. Please try again.": "เกิดข้อผิดพลาดขณะบันทึกรายการที่คุณเลือก กรุณาลองใหม่อีกครั้งในภายหลัง", + "@An error occurred while saving your selection. Please try again.": { + "type": "text", + "placeholders": {} + }, + "changeStudentColorLabel": "เปลี่ยนสีสำหรับ {studentName}", + "@changeStudentColorLabel": { + "description": "Accessibility label for the button that lets users change the color associated with a specific student", + "type": "text", + "placeholders": { + "studentName": {} + } + }, + "Teacher": "ผู้สอน", + "@Teacher": { + "description": "Label for the Teacher enrollment type", + "type": "text", + "placeholders": {} + }, + "Student": "ผู้เรียน", + "@Student": { + "description": "Label for the Student enrollment type", + "type": "text", + "placeholders": {} + }, + "TA": "TA", + "@TA": { + "description": "Label for the Teaching Assistant enrollment type (also known as Teacher Aid or Education Assistant), reduced to a short acronym/initialism if appropriate.", + "type": "text", + "placeholders": {} + }, + "Observer": "ผู้สังเกตการณ์", + "@Observer": { + "description": "Label for the Observer enrollment type", + "type": "text", + "placeholders": {} + }, + "Use Camera": "ใช้กล้อง", + "@Use Camera": { + "description": "Label for the action item that lets the user capture a photo using the device camera", + "type": "text", + "placeholders": {} + }, + "Upload File": "อัพโหลดไฟล์", + "@Upload File": { + "description": "Label for the action item that lets the user upload a file from their device", + "type": "text", + "placeholders": {} + }, + "Choose from Gallery": "เลือกจากแกลเลอรี่", + "@Choose from Gallery": { + "description": "Label for the action item that lets the user select a photo from their device gallery", + "type": "text", + "placeholders": {} + }, + "Preparing…": "กำลังจัดเตรียม...", + "@Preparing…": { + "description": "Message shown while a file is being prepared to attach to a message", + "type": "text", + "placeholders": {} + }, + "Add student with…": "เพิ่มผู้เรียนกับ...", + "@Add student with…": { + "type": "text", + "placeholders": {} + }, + "Add Student": "เพิ่มผู้เรียน", + "@Add Student": { + "type": "text", + "placeholders": {} + }, + "You are not observing any students.": "คุณไม่ได้สังเกตการณ์ผู้เรียนรายใด", + "@You are not observing any students.": { + "type": "text", + "placeholders": {} + }, + "There was an error loading your students.": "มีข้อผิดพลาดในการโหลดผู้เรียนของคุณ", + "@There was an error loading your students.": { + "type": "text", + "placeholders": {} + }, + "Pairing Code": "รหัสเข้าคู่", + "@Pairing Code": { + "type": "text", + "placeholders": {} + }, + "Students can obtain a pairing code through the Canvas website": "ผู้เรียนสามารถขอรหัสเข้าคู่ได้จากเว็บไซต์ Canvas", + "@Students can obtain a pairing code through the Canvas website": { + "type": "text", + "placeholders": {} + }, + "Enter the student pairing code provided to you. If the pairing code doesn't work, it may have expired": "กรอกรหัสเข้าคู่ของผู้เรียนที่แจ้งไว้กับคุณ หากรหัสเข้าคู่ไม่สามารถใช้ได้ แสดงว่าหมดอายุแล้ว", + "@Enter the student pairing code provided to you. If the pairing code doesn't work, it may have expired": { + "type": "text", + "placeholders": {} + }, + "Your code is incorrect or expired.": "รหัสของคุณไม่ถูกต้องหรือหมดอายุแล้ว", + "@Your code is incorrect or expired.": { + "type": "text", + "placeholders": {} + }, + "Something went wrong trying to create your account, please reach out to your school for assistance.": "มีบางอย่างผิดพลาดขณะพยายามจัดทำบัญชีผู้ใช้ของคุณ กรุณาติดต่อสถานศึกษาของคุณเพื่อขอความช่วยเหลือ", + "@Something went wrong trying to create your account, please reach out to your school for assistance.": { + "type": "text", + "placeholders": {} + }, + "QR Code": "รหัส QR", + "@QR Code": { + "type": "text", + "placeholders": {} + }, + "Students can create a QR code using the Canvas Student app on their mobile device": "ผู้เรียนสามารถจัดทำรหัส QR โดยใช้แอพ Canvas Student ผ่านอุปกรณ์พกพาของตน", + "@Students can create a QR code using the Canvas Student app on their mobile device": { + "type": "text", + "placeholders": {} + }, + "Add new student": "เพิ่มผู้เรียนใหม่", + "@Add new student": { + "description": "Semantics label for the FAB on the Manage Students Screen", + "type": "text", + "placeholders": {} + }, + "Select": "เลือก", + "@Select": { + "description": "Hint text to tell the user to choose one of two options", + "type": "text", + "placeholders": {} + }, + "I have a Canvas account": "ฉันมีบัญชี Canvas", + "@I have a Canvas account": { + "description": "Option to select for users that have a canvas account", + "type": "text", + "placeholders": {} + }, + "I don't have a Canvas account": "ฉันไม่มีบัญชี Canvas", + "@I don't have a Canvas account": { + "description": "Option to select for users that don't have a canvas account", + "type": "text", + "placeholders": {} + }, + "Create Account": "จัดทำบัญชีผู้ใช้", + "@Create Account": { + "description": "Button text for account creation confirmation", + "type": "text", + "placeholders": {} + }, + "Full Name": "ชื่อนามสกุล", + "@Full Name": { + "type": "text", + "placeholders": {} + }, + "Email Address": "อีเมลแอดเดรส", + "@Email Address": { + "type": "text", + "placeholders": {} + }, + "Password": "รหัสผ่าน", + "@Password": { + "type": "text", + "placeholders": {} + }, + "Full Name…": "ชื่อนามสกุล...", + "@Full Name…": { + "description": "hint label for inside form field", + "type": "text", + "placeholders": {} + }, + "Email…": "อีเมล...", + "@Email…": { + "description": "hint label for inside form field", + "type": "text", + "placeholders": {} + }, + "Password…": "รหัสผ่าน...", + "@Password…": { + "description": "hint label for inside form field", + "type": "text", + "placeholders": {} + }, + "Please enter full name": "กรุณาระบุชื่อและนามสกุล", + "@Please enter full name": { + "description": "Error message for form field", + "type": "text", + "placeholders": {} + }, + "Please enter an email address": "กรุณากรอกอีเมลแอดเดรส", + "@Please enter an email address": { + "description": "Error message for form field", + "type": "text", + "placeholders": {} + }, + "Please enter a valid email address": "กรุณากรอกอีเมลแอดเดรสที่ถูกต้อง", + "@Please enter a valid email address": { + "description": "Error message for form field", + "type": "text", + "placeholders": {} + }, + "Password is required": "ต้องระบุรหัสผ่าน", + "@Password is required": { + "description": "Error message for form field", + "type": "text", + "placeholders": {} + }, + "Password must contain at least 8 characters": "รหัสผ่านต้องยาวอย่างน้อย 8 ตัวอักษร", + "@Password must contain at least 8 characters": { + "description": "Error message for form field", + "type": "text", + "placeholders": {} + }, + "qrCreateAccountTos": "หลังจากกดเลือก “จัดทำบัญชีผู้ใช้” คุณยินยอมภายใต้ {termsOfService} และรับทราบเกี่ยวกับ {privacyPolicy}", + "@qrCreateAccountTos": { + "description": "The text show on the account creation screen", + "type": "text", + "placeholders": { + "termsOfService": {}, + "privacyPolicy": {} + } + }, + "Terms of Service": "เงื่อนไขการให้บริการ", + "@Terms of Service": { + "description": "Label for the Canvas Terms of Service agreement. This will be used in the qrCreateAccountTos text and will be highlighted and clickable", + "type": "text", + "placeholders": {} + }, + "Privacy Policy": "นโยบายความเป็นส่วนตัว", + "@Privacy Policy": { + "description": "Label for the Canvas Privacy Policy agreement. This will be used in the qrCreateAccountTos text and will be highlighted and clickable", + "type": "text", + "placeholders": {} + }, + "View the Privacy Policy": "ดูนโยบายความเป็นส่วนตัว", + "@View the Privacy Policy": { + "type": "text", + "placeholders": {} + }, + "Already have an account? ": "มีบัญชีอยู่แล้วหรือไม่ ", + "@Already have an account? ": { + "description": "Part of multiline text span, includes AccountSignIn1-2, in that order", + "type": "text", + "placeholders": {} + }, + "Sign In": "ลงชื่อเข้าใช้", + "@Sign In": { + "description": "Part of multiline text span, includes AccountSignIn1-2, in that order", + "type": "text", + "placeholders": {} + }, + "Hide Password": "ซ่อนรหัสผ่าน", + "@Hide Password": { + "description": "content description for password hide button", + "type": "text", + "placeholders": {} + }, + "Show Password": "แสดงรหัสผ่าน", + "@Show Password": { + "description": "content description for password show button", + "type": "text", + "placeholders": {} + }, + "Terms of Service Link": "ลิงค์เงื่อนไขการให้บริการ", + "@Terms of Service Link": { + "description": "content description for terms of service link", + "type": "text", + "placeholders": {} + }, + "Privacy Policy Link": "ลิงค์นโยบายความเป็นส่วนตัว", + "@Privacy Policy Link": { + "description": "content description for privacy policy link", + "type": "text", + "placeholders": {} + }, + "Event": "กิจกรรม", + "@Event": { + "description": "Title for the event details screen", + "type": "text", + "placeholders": {} + }, + "Date": "วันที่", + "@Date": { + "description": "Label for the event date", + "type": "text", + "placeholders": {} + }, + "Location": "ตำแหน่ง", + "@Location": { + "description": "Label for the location information", + "type": "text", + "placeholders": {} + }, + "No Location Specified": "ไม่ได้ระบุตำแหน่ง", + "@No Location Specified": { + "description": "Description for events that do not have a location", + "type": "text", + "placeholders": {} + }, + "eventTime": "{startAt} - {endAt}", + "@eventTime": { + "description": "The time the event is happening, example: \"2:00 pm - 4:00 pm\"", + "type": "text", + "placeholders": { + "startAt": {}, + "endAt": {} + } + }, + "Set a date and time to be notified of this event.": "กำหนดวันที่และเวลาที่จะแจ้งเกี่ยวกับกิจกรรมนี้", + "@Set a date and time to be notified of this event.": { + "description": "Description for row to set event reminders", + "type": "text", + "placeholders": {} + }, + "You will be notified about this event on…": "คุณจะได้รรับแจ้งเกี่ยวกับกิจกรรมนี้เมื่อ...", + "@You will be notified about this event on…": { + "description": "Description for when an event reminder is set", + "type": "text", + "placeholders": {} + }, + "Share Your Love for the App": "แบ่งปันความชื่นชอบที่คุณมีเกี่ยวกับแอพ", + "@Share Your Love for the App": { + "description": "Label for option to open the app store", + "type": "text", + "placeholders": {} + }, + "Tell us about your favorite parts of the app": "บอกให้เราทราบเกี่ยวกับส่วนที่คุณชอบมากที่สุดเกี่ยวกับแอพ", + "@Tell us about your favorite parts of the app": { + "description": "Description for option to open the app store", + "type": "text", + "placeholders": {} + }, + "Legal": "ประเด็นทางกฎหมาย", + "@Legal": { + "description": "Label for legal information option", + "type": "text", + "placeholders": {} + }, + "Privacy policy, terms of use, open source": "นโยบายความเป็นส่วนตัว, เงื่อนไขการใช้งาน, สาธารณะ", + "@Privacy policy, terms of use, open source": { + "description": "Description for legal information option", + "type": "text", + "placeholders": {} + }, + "Idea for Canvas Parent App [Android]": "แนวคิดสำหรับแอพ Canvas Parent [Android]", + "@Idea for Canvas Parent App [Android]": { + "description": "The subject for the email to request a feature", + "type": "text", + "placeholders": {} + }, + "The following information will help us better understand your idea:": "ข้อมูลต่อไปนี้จะช่วยให้เราเข้าใจเกี่ยวกับแนวคิดของคุณได้ดียิ่งขึ้น:", + "@The following information will help us better understand your idea:": { + "description": "The header for the users information that is attached to a feature request", + "type": "text", + "placeholders": {} + }, + "Domain:": "โดเมน:", + "@Domain:": { + "description": "The label for the Canvas domain of the logged in user", + "type": "text", + "placeholders": {} + }, + "User ID:": "ID ผู้ใช้:", + "@User ID:": { + "description": "The label for the Canvas user ID of the logged in user", + "type": "text", + "placeholders": {} + }, + "Email:": "อีเมล:", + "@Email:": { + "description": "The label for the eamil of the logged in user", + "type": "text", + "placeholders": {} + }, + "Locale:": "พื้นที่:", + "@Locale:": { + "description": "The label for the locale of the logged in user", + "type": "text", + "placeholders": {} + }, + "Terms of Use": "เงื่อนไขการใช้งาน", + "@Terms of Use": { + "description": "Label for the terms of use", + "type": "text", + "placeholders": {} + }, + "Canvas on GitHub": "Canvas on GitHub", + "@Canvas on GitHub": { + "description": "Label for the button that opens the Canvas project on GitHub's website", + "type": "text", + "placeholders": {} + }, + "There was a problem loading the Terms of Use": "มีปัญหาในการโหลดเงื่อนไขการใช้งาน", + "@There was a problem loading the Terms of Use": { + "type": "text", + "placeholders": {} + }, + "Device": "อุปกรณ์", + "@Device": { + "description": "Label used for device manufacturer/model in the error report", + "type": "text", + "placeholders": {} + }, + "OS Version": "เวอร์ชั่น OS", + "@OS Version": { + "description": "Label used for device operating system version in the error report", + "type": "text", + "placeholders": {} + }, + "Version Number": "เลขเวอร์ชั่น", + "@Version Number": { + "description": "Label used for the app version number in the error report", + "type": "text", + "placeholders": {} + }, + "Report A Problem": "แจ้งปัญหา", + "@Report A Problem": { + "description": "Title used for generic dialog to report problems", + "type": "text", + "placeholders": {} + }, + "Subject": "วิชา", + "@Subject": { + "description": "Label used for Subject text field", + "type": "text", + "placeholders": {} + }, + "A subject is required.": "ต้องระบุหัวเรื่อง", + "@A subject is required.": { + "description": "Error shown when the subject field is empty", + "type": "text", + "placeholders": {} + }, + "An email address is required.": "ต้องระบุอีเมลแอดเดรส", + "@An email address is required.": { + "description": "Error shown when the email field is empty", + "type": "text", + "placeholders": {} + }, + "Description": "รายละเอียด", + "@Description": { + "description": "Label used for Description text field", + "type": "text", + "placeholders": {} + }, + "A description is required.": "ต้องระบุรายละเอียด", + "@A description is required.": { + "description": "Error shown when the description field is empty", + "type": "text", + "placeholders": {} + }, + "How is this affecting you?": "สิ่งนี้มีผลกับคุณอย่างไร", + "@How is this affecting you?": { + "description": "Label used for the dropdown to select how severe the issue is", + "type": "text", + "placeholders": {} + }, + "send": "ส่ง", + "@send": { + "description": "Label used for send button when reporting a problem", + "type": "text", + "placeholders": {} + }, + "Just a casual question, comment, idea, suggestion…": "แค่คำถาม ความเห็น แนวคิดหรือข้อเสนอแนะทั่ว ๆ ไป...", + "@Just a casual question, comment, idea, suggestion…": { + "type": "text", + "placeholders": {} + }, + "I need some help but it's not urgent.": "ฉันต้องการความช่วยเหลือ แต่ไม่เร่งด่วนอะไร", + "@I need some help but it's not urgent.": { + "type": "text", + "placeholders": {} + }, + "Something's broken but I can work around it to get what I need done.": "มีบางอย่างไม่ถูกต้อง แต่ฉันสามารถแก้ไขได้", + "@Something's broken but I can work around it to get what I need done.": { + "type": "text", + "placeholders": {} + }, + "I can't get things done until I hear back from you.": "ฉันไม่สามารถดำเนินการใด ๆ ได้จนกว่าจะได้รับการติดต่อกลับจากคุณ", + "@I can't get things done until I hear back from you.": { + "type": "text", + "placeholders": {} + }, + "EXTREME CRITICAL EMERGENCY!!": "กรณีฉุกเฉินอย่างยิ่ง!!", + "@EXTREME CRITICAL EMERGENCY!!": { + "type": "text", + "placeholders": {} + }, + "Not Graded": "ไม่ได้ลงเกรด", + "@Not Graded": { + "description": "Description for an assignment has not been graded.", + "type": "text", + "placeholders": {} + }, + "Login flow: Normal": "โครงสร้างการล็อกอิน: ปกติ", + "@Login flow: Normal": { + "description": "Description for the normal login flow", + "type": "text", + "placeholders": {} + }, + "Login flow: Canvas": "โครงสร้างการล็อกอิน: Canvas", + "@Login flow: Canvas": { + "description": "Description for the Canvas login flow", + "type": "text", + "placeholders": {} + }, + "Login flow: Site Admin": "โครงสร้างการล็อกอิน: ผู้ดูแลไซต์", + "@Login flow: Site Admin": { + "description": "Description for the Site Admin login flow", + "type": "text", + "placeholders": {} + }, + "Login flow: Skip mobile verify": "โครงสร้างการล็อกอิน: ข้ามการยืนยันผ่านอุปกรณ์พกพา", + "@Login flow: Skip mobile verify": { + "description": "Description for the login flow that skips domain verification for mobile", + "type": "text", + "placeholders": {} + }, + "Act As User": "ดำเนินการในฐานะผู้ใช้", + "@Act As User": { + "description": "Label for the button that allows the user to act (masquerade) as another user", + "type": "text", + "placeholders": {} + }, + "Stop Acting as User": "หยุดดำเนินการในฐานะผู้ใช้", + "@Stop Acting as User": { + "description": "Label for the button that allows the user to stop acting (masquerading) as another user", + "type": "text", + "placeholders": {} + }, + "actingAsUser": "กำลังกำลังดำเนินการในฐานะ {userName}", + "@actingAsUser": { + "description": "Message shown while acting (masquerading) as another user", + "type": "text", + "placeholders": { + "userName": {} + } + }, + "\"Act as\" is essentially logging in as this user without a password. You will be able to take any action as if you were this user, and from other users' points of views, it will be as if this user performed them. However, audit logs record that you were the one who performed the actions on behalf of this user.": "“ดำเนินการในฐานะ” เป็นการล็อกอินเป็นผู้ใช้รายนี้โดยไม่มีรหัสผ่าน คุณสามารถดำเนินการใด ๆ ก็ได้เสมือนเป็นผู้ใช้รายนี้ และผู้ใช้อื่น ๆ จะเข้าใจว่าผู้ใช้รายนี้เป็นผู้ดำเนินการ อย่างไรก็ตาม บันทึกประวัติจะมีจัดทำไว้เพื่อแจ้งว่าคุณเป็นบุคคลที่ดำเนินการในนามของผู้ใช้รายนี้", + "@\"Act as\" is essentially logging in as this user without a password. You will be able to take any action as if you were this user, and from other users' points of views, it will be as if this user performed them. However, audit logs record that you were the one who performed the actions on behalf of this user.": { + "type": "text", + "placeholders": {} + }, + "Domain": "โดเมน", + "@Domain": { + "description": "Text field hint for domain url input", + "type": "text", + "placeholders": {} + }, + "You must enter a valid domain": "คุณจะต้องกรอกโดเมนที่ถูกต้อง", + "@You must enter a valid domain": { + "description": "Message displayed for domain input error", + "type": "text", + "placeholders": {} + }, + "User ID": "ID ผู้ใช้", + "@User ID": { + "description": "Text field hint for user ID input", + "type": "text", + "placeholders": {} + }, + "You must enter a user id": "คุณจะต้องกรอก id ผู้ใช้", + "@You must enter a user id": { + "description": "Message displayed for user Id input error", + "type": "text", + "placeholders": {} + }, + "There was an error trying to act as this user. Please check the Domain and User ID and try again.": "มีข้อผิดพลาดในการดำเนินการในฐานะผู้ใช้รายนี้ กรุณาตรวจสอบโดเมนและ ID ผู้ใช้ และลองใหม่อีกครั้ง", + "@There was an error trying to act as this user. Please check the Domain and User ID and try again.": { + "type": "text", + "placeholders": {} + }, + "endMasqueradeMessage": "คุณจะหยุดดำเนินการในฐานะ {userName} และกลับไปที่บัญชีเดิมของคุณ", + "@endMasqueradeMessage": { + "description": "Confirmation message displayed when the user wants to stop acting (masquerading) as another user", + "type": "text", + "placeholders": { + "userName": {} + } + }, + "endMasqueradeLogoutMessage": "คุณจะหยุดดำเนินการในฐานะ {userName} และออกจากระบบ", + "@endMasqueradeLogoutMessage": { + "description": "Confirmation message displayed when the user wants to stop acting (masquerading) as another user and will be logged out.", + "type": "text", + "placeholders": { + "userName": {} + } + }, + "How are we doing?": "เราเป็นอย่างไรบ้าง", + "@How are we doing?": { + "description": "Title for dialog asking user to rate the app out of 5 stars.", + "type": "text", + "placeholders": {} + }, + "Don't show again": "ไม่ต้องแสดงอีก", + "@Don't show again": { + "description": "Button to prevent the rating dialog from showing again.", + "type": "text", + "placeholders": {} + }, + "What can we do better?": "เราสามารถทำอะไรให้ดีกว่านี้", + "@What can we do better?": { + "description": "Hint text for providing a comment with the rating.", + "type": "text", + "placeholders": {} + }, + "Send Feedback": "ส่งความเห็น", + "@Send Feedback": { + "description": "Button to send rating with feedback", + "type": "text", + "placeholders": {} + }, + "ratingDialogEmailSubject": "ข้อเสนอแนะสำหรับ Android - Canvas Parent {version}", + "@ratingDialogEmailSubject": { + "description": "The subject for an email to provide feedback for CanvasParent.", + "type": "text", + "placeholders": { + "version": {} + } + }, + "starRating": "{position,plural, =1{{position} ดาว}other{{position} ดาว}}", + "@starRating": { + "description": "Accessibility label for the 1 stars to 5 stars rating", + "type": "text", + "placeholders": { + "position": { + "example": 1 + } + } + }, + "Student Pairing": "การเข้าคู่ผู้เรียน", + "@Student Pairing": { + "description": "Title for the screen where users can pair to students using a QR code", + "type": "text", + "placeholders": {} + }, + "Open Canvas Student": "เปิด Canvas Student", + "@Open Canvas Student": { + "description": "Title for QR pairing tutorial screen instructing users to open the Canvas Student app", + "type": "text", + "placeholders": {} + }, + "You'll need to open your student's Canvas Student app to continue. Go into Main Menu > Settings > Pair with Observer and scan the QR code you see there.": "คุณจะต้องเปิดแอพ Canvas Student ของผู้เรียนเพื่อดำเนินการต่อ ไปที่เมนูหลัก > ค่าปรับตั้ง > เข้าคู่กับผู้สังเกตการณ์ และสแกนรหัส QR ที่ปรากฏขึ้น", + "@You'll need to open your student's Canvas Student app to continue. Go into Main Menu > Settings > Pair with Observer and scan the QR code you see there.": { + "description": "Message explaining how QR code pairing works", + "type": "text", + "placeholders": {} + }, + "Screenshot showing location of pairing QR code generation in the Canvas Student app": "ภาพหน้าจอแสดงตำแหน่งการจัดทำรหัส QR สำหรับเข้าคู่ในแอพ Canvas Student", + "@Screenshot showing location of pairing QR code generation in the Canvas Student app": { + "description": "Content Description for qr pairing tutorial screenshot", + "type": "text", + "placeholders": {} + }, + "Expired QR Code": "รหัส QR หมดอายุ", + "@Expired QR Code": { + "description": "Error title shown when the users scans a QR code that has expired", + "type": "text", + "placeholders": {} + }, + "The QR code you scanned may have expired. Refresh the code on the student's device and try again.": "รหัส QR ที่คุณสแกนอาจหมดอายุแล้ว รีเฟรชรหัสจากอุปกรณ์ของผู้เรียนแล้วลองใหม่อีกครั้ง", + "@The QR code you scanned may have expired. Refresh the code on the student's device and try again.": { + "type": "text", + "placeholders": {} + }, + "A network error occurred when adding this student. Check your connection and try again.": "เกิดข้อผิดพลาดทางเครือข่ายขณะเพิ่มผู้เรียนนี้ ตรวจสอบการเชื่อมต่อและลองใหม่อีกครั้ง", + "@A network error occurred when adding this student. Check your connection and try again.": { + "type": "text", + "placeholders": {} + }, + "Invalid QR Code": "รหัส QR ไม่ถูกต้อง", + "@Invalid QR Code": { + "description": "Error title shown when the user scans an invalid QR code", + "type": "text", + "placeholders": {} + }, + "Incorrect Domain": "โดเมนไม่ถูกต้อง", + "@Incorrect Domain": { + "description": "Error title shown when the users scane a QR code for a student that belongs to a different domain", + "type": "text", + "placeholders": {} + }, + "The student you are trying to add belongs to a different school. Log in or create an account with that school to scan this code.": "ผู้เรียนที่คุณพยายามเพิ่มเป็นของสถานศึกษาอื่น ล็อกอินหรือจัดทำบัญชีผู้ใช้กับทางสถานศึกษาดังกล่าวเพื่อสแกนรหัสนี้", + "@The student you are trying to add belongs to a different school. Log in or create an account with that school to scan this code.": { + "type": "text", + "placeholders": {} + }, + "Camera Permission": "สิทธิ์ใช้งานกล้อง", + "@Camera Permission": { + "description": "Error title shown when the user wans to scan a QR code but has denied the camera permission", + "type": "text", + "placeholders": {} + }, + "This will unpair and remove all enrollments for this student from your account.": "นี่เป็นการเลิกการเข้าคู่และลบการลงทะเบียนทั้งหมดสำหรับผู้เรียนนี้จากบัญชีของคุณ", + "@This will unpair and remove all enrollments for this student from your account.": { + "description": "Confirmation message shown when the user tries to delete a student from their account", + "type": "text", + "placeholders": {} + }, + "There was a problem removing this student from your account. Please check your connection and try again.": "มีปัญหาในการลบผู้เรียนนี้จากบัญชีของคุณ กรุณาตรวจสอบการเชื่อมต่อของคุณและลองใหม่อีกครั้ง", + "@There was a problem removing this student from your account. Please check your connection and try again.": { + "type": "text", + "placeholders": {} + }, + "Cancel": "ยกเลิก", + "@Cancel": { + "type": "text", + "placeholders": {} + }, + "next": "ถัดไป", + "@next": { + "type": "text", + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders": {} + }, + "Yes": "ใช่", + "@Yes": { + "type": "text", + "placeholders": {} + }, + "No": "ไม่", + "@No": { + "type": "text", + "placeholders": {} + }, + "Retry": "ลองใหม่", + "@Retry": { + "type": "text", + "placeholders": {} + }, + "Delete": "ลบ", + "@Delete": { + "description": "Label used for general delete/remove actions", + "type": "text", + "placeholders": {} + }, + "Done": "เสร็จสิ้น", + "@Done": { + "description": "Label for general done/finished actions", + "type": "text", + "placeholders": {} + }, + "Refresh": "รีเฟรช", + "@Refresh": { + "description": "Label for button to refresh data from the web", + "type": "text", + "placeholders": {} + }, + "View Description": "ดูรายละเอียด", + "@View Description": { + "description": "Button to view the description for an event or assignment", + "type": "text", + "placeholders": {} + }, + "expanded": "ขยายแล้ว", + "@expanded": { + "description": "Description for the accessibility reader for list groups that are expanded", + "type": "text", + "placeholders": {} + }, + "collapsed": "ย่อแล้ว", + "@collapsed": { + "description": "Description for the accessibility reader for list groups that are expanded", + "type": "text", + "placeholders": {} + }, + "An unexpected error occurred": "เกิดข้อผิดพลาดที่ไม่คาดคิด", + "@An unexpected error occurred": { + "type": "text", + "placeholders": {} + }, + "No description": "ไม่มีรายละเอียด", + "@No description": { + "description": "Message used when the assignment has no description", + "type": "text", + "placeholders": {} + }, + "Launch External Tool": "เรียกใช้ชุดเครื่องมือจากภายนอก", + "@Launch External Tool": { + "description": "Button text added to webviews to let users open external tools in their browser", + "type": "text", + "placeholders": {} + }, + "Interactions on this page are limited by your institution.": "การโต้ตอบในหน้าเพจนี้จำกัดไว้สำหรับสถาบันของคุณเท่านั้น", + "@Interactions on this page are limited by your institution.": { + "description": "Message describing how the webview has limited access due to an instution setting", + "type": "text", + "placeholders": {} + }, + "dateAtTime": "{date} ที่ {time}", + "@dateAtTime": { + "description": "The string to format dates", + "type": "text", + "placeholders": { + "date": {}, + "time": {} + } + }, + "dueDateAtTime": "ครบกำหนด {date} เมื่อ {time}", + "@dueDateAtTime": { + "description": "The string to format due dates", + "type": "text", + "placeholders": { + "date": {}, + "time": {} + } + }, + "No Due Date": "ไม่มีวันครบกำหนด", + "@No Due Date": { + "description": "Label for assignments that do not have a due date", + "type": "text", + "placeholders": {} + }, + "Filter": "ตัวกรอง", + "@Filter": { + "description": "Label for buttons to filter what items are visible", + "type": "text", + "placeholders": {} + }, + "unread": "ไม่ได้อ่าน", + "@unread": { + "description": "Label for things that are marked as unread", + "type": "text", + "placeholders": {} + }, + "unreadCount": "{count} ที่ไม่ได้อ่าน", + "@unreadCount": { + "description": "Formatted string for when there are a number of unread items", + "type": "text", + "placeholders": { + "count": {} + } + }, + "badgeNumberPlus": "{count}+", + "@badgeNumberPlus": { + "description": "Formatted string for when too many items are being notified in a badge, generally something like: 99+", + "type": "text", + "placeholders": { + "count": {} + } + }, + "There was an error loading this announcement": "มีข้อผิดพลาดในการโหลดประกาศนี้", + "@There was an error loading this announcement": { + "description": "Message shown when an announcement detail screen fails to load", + "type": "text", + "placeholders": {} + }, + "Network error": "ข้อผิดพลาดเครือข่าย", + "@Network error": { + "type": "text", + "placeholders": {} + }, + "Under Construction": "อยู่ระหว่างจัดทำ", + "@Under Construction": { + "type": "text", + "placeholders": {} + }, + "We are currently building this feature for your viewing pleasure.": "เรากำลังจัดทำคุณสมบัตินี้เพื่อให้คุณรับชมได้อย่างสะดวก", + "@We are currently building this feature for your viewing pleasure.": { + "type": "text", + "placeholders": {} + }, + "Request Login Help Button": "ปุ่มขอความช่วยเหลือในการล็อกอิน", + "@Request Login Help Button": { + "description": "Accessibility hint for button that opens help dialog for a login help request", + "type": "text", + "placeholders": {} + }, + "Request Login Help": "ขอความช่วยเหลือในการล็อกอิน", + "@Request Login Help": { + "description": "Title of help dialog for a login help request", + "type": "text", + "placeholders": {} + }, + "I'm having trouble logging in": "ฉันมีปัญหาในการล็อกอิน", + "@I'm having trouble logging in": { + "description": "Subject of help dialog for a login help request", + "type": "text", + "placeholders": {} + }, + "An error occurred when trying to display this link": "เกิดข้อผิดพลาดขณะพยายามแสดงลิงค์นี้", + "@An error occurred when trying to display this link": { + "description": "Error message shown when a link can't be opened", + "type": "text", + "placeholders": {} + }, + "We are unable to display this link, it may belong to an institution you currently aren't logged in to.": "เราไม่สามารถแสดงลิงค์นี้ เนื่องจากอาจเป็นของสถาบันที่คุณไม่ได้ล็อกอินอยู่ในปัจจุบัน", + "@We are unable to display this link, it may belong to an institution you currently aren't logged in to.": { + "description": "Description for error page shown when clicking a link", + "type": "text", + "placeholders": {} + }, + "Link Error": "ข้อผิดพลาดลิงค์", + "@Link Error": { + "description": "Title for error page shown when clicking a link", + "type": "text", + "placeholders": {} + }, + "Open In Browser": "เปิดในเบราเซอร์", + "@Open In Browser": { + "description": "Text for button to open a link in the browswer", + "type": "text", + "placeholders": {} + }, + "You'll find the QR code on the web in your account profile. Click 'QR for Mobile Login' in the list.": "คุณจะพบรหัส QR ในเว็บจากโพรไฟล์บัญชีของคุณ คลิกที่ “QR สำหรับล็อกอินผ่านอุปกรณ์พกพา” จากรายการ", + "@You'll find the QR code on the web in your account profile. Click 'QR for Mobile Login' in the list.": { + "description": "Text for qr login tutorial screen", + "type": "text", + "placeholders": {} + }, + "Locate QR Code": "ระบุตำแหน่งรหัส QR", + "@Locate QR Code": { + "description": "Text for qr login button", + "type": "text", + "placeholders": {} + }, + "Please scan a QR code generated by Canvas": "กรุณาสแกนรหัส QR ที่จัดทำโดย Canvas", + "@Please scan a QR code generated by Canvas": { + "description": "Text for qr login error with incorrect qr code", + "type": "text", + "placeholders": {} + }, + "There was an error logging in. Please generate another QR Code and try again.": "มีข้อผิดพลาดในการล็อกอิน กรุณาจัดทำรหัส QR อื่นและลองใหม่อีกครั้ง", + "@There was an error logging in. Please generate another QR Code and try again.": { + "description": "Text for qr login error", + "type": "text", + "placeholders": {} + }, + "Screenshot showing location of QR code generation in browser": "ภาพหน้าจอแสดงตำแหน่งการจัดทำรหัส QR ในเบราเซอร์", + "@Screenshot showing location of QR code generation in browser": { + "description": "Content Description for qr login tutorial screenshot", + "type": "text", + "placeholders": {} + }, + "QR scanning requires camera access": "การสแกน QR จะต้องใช้กล้อง", + "@QR scanning requires camera access": { + "description": "placeholder for camera error for QR code scan", + "type": "text", + "placeholders": {} + } +} \ No newline at end of file diff --git a/apps/flutter_parent/plugins/encrypted_shared_preferences/android/build.gradle b/apps/flutter_parent/plugins/encrypted_shared_preferences/android/build.gradle index fc637c9714..0aaa7b0e81 100644 --- a/apps/flutter_parent/plugins/encrypted_shared_preferences/android/build.gradle +++ b/apps/flutter_parent/plugins/encrypted_shared_preferences/android/build.gradle @@ -4,7 +4,7 @@ version '1.0-SNAPSHOT' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -15,7 +15,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/apps/flutter_parent/plugins/webview_flutter/android/build.gradle b/apps/flutter_parent/plugins/webview_flutter/android/build.gradle index 23936394ef..1e4b00e7f1 100644 --- a/apps/flutter_parent/plugins/webview_flutter/android/build.gradle +++ b/apps/flutter_parent/plugins/webview_flutter/android/build.gradle @@ -4,7 +4,7 @@ version '1.0-SNAPSHOT' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -15,7 +15,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 8b5f708da1..966a9784d0 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -26,6 +26,7 @@ apply from: '../../gradle/coverage.gradle' apply plugin: 'com.squareup.sqldelight' apply plugin: 'dagger.hilt.android.plugin' +def updatePriority = 0 def coverageEnabled = project.hasProperty('coverage') if (coverageEnabled) { @@ -58,12 +59,12 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 227 - versionName = '6.11.1' + versionCode = 228 + versionName = '6.12.0' vectorDrawables.useSupportLibrary = true multiDexEnabled = true - testInstrumentationRunner 'com.instructure.canvas.espresso.CanvasRunner' + testInstrumentationRunner 'com.instructure.student.espresso.StudentHiltTestRunner' testInstrumentationRunnerArguments disableAnalytics: 'true' buildConfigField "boolean", "IS_TESTING", "false" @@ -213,7 +214,7 @@ android { android, new MasqueradeUITransformer('com.instructure.student.activity.NavigationActivity.class'), new PageViewTransformer(), - new LocaleTransformer(), + new LocaleTransformer(project), new FlutterA11yOffsetTransformer(), new FlutterTextureDisconnectFix() ) @@ -231,6 +232,10 @@ android { buildFeatures { dataBinding true } + + hilt { + enableTransformForLocalTests = true + } } dependencies { @@ -310,9 +315,6 @@ dependencies { implementation Libs.PLAY_CORE implementation Libs.PLAY_CORE_KTX - /* Job Scheduler */ - implementation Libs.FIREBASE_JOB_DISPATCHER - /* Database */ implementation "com.squareup.sqldelight:android-driver:1.4.3" @@ -330,6 +332,10 @@ dependencies { /* DI */ implementation Libs.HILT kapt Libs.HILT_COMPILER + androidTestImplementation Libs.HILT_TESTING + kaptAndroidTestQa Libs.HILT_TESTING_COMPILER + + androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3' implementation "com.google.firebase:firebase-crashlytics-ndk:17.2.1" } diff --git a/apps/student/flank_landscape.yml b/apps/student/flank_landscape.yml index ba1199ceb1..8096fd6d79 100644 --- a/apps/student/flank_landscape.yml +++ b/apps/student/flank_landscape.yml @@ -12,9 +12,9 @@ gcloud: record-video: true timeout: 60m test-targets: - - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub + - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.StubLandscape device: - - model: NexusLowRes + - model: Nexus6P version: 25 locale: en_US orientation: landscape diff --git a/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestApplication.kt b/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestApplication.kt new file mode 100644 index 0000000000..9d6d33910d --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestApplication.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.instructure.student.espresso + +import com.instructure.student.util.BaseAppManager +import dagger.hilt.android.testing.CustomTestApplication + +@CustomTestApplication(BaseAppManager::class) +interface StudentHiltTestApplication \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestRunner.kt b/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestRunner.kt new file mode 100644 index 0000000000..bb466c5008 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/espresso/StudentHiltTestRunner.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.instructure.student.espresso + +import android.app.Application +import android.content.Context +import com.instructure.canvas.espresso.CanvasRunner +import dagger.hilt.android.testing.HiltTestApplication + +class StudentHiltTestRunner : CanvasRunner() { + + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, StudentHiltTestApplication_Application::class.java.name, context) + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginFindSchoolPageTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginFindSchoolPageTest.kt index 2166236c9d..3e59511e4a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginFindSchoolPageTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginFindSchoolPageTest.kt @@ -17,8 +17,10 @@ package com.instructure.student.ui import com.instructure.student.ui.utils.StudentTest +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class LoginFindSchoolPageTest: StudentTest() { // Runs live; no MockCanvas diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginLandingPageTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginLandingPageTest.kt index 2fb1250d39..c4e6f156b2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginLandingPageTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginLandingPageTest.kt @@ -18,8 +18,10 @@ package com.instructure.student.ui import com.instructure.student.ui.utils.StudentTest import com.instructure.espresso.filters.P1 +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class LoginLandingPageTest: StudentTest() { // Runs live; no MockCanvas diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginSignInPageTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginSignInPageTest.kt index 8808db55fd..a09366509d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/LoginSignInPageTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/LoginSignInPageTest.kt @@ -18,8 +18,10 @@ package com.instructure.student.ui import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.enterDomain +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class LoginSignInPageTest: StudentTest() { // Runs live; no MockCanvas 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 1c28da0a17..f19e8695cb 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 @@ -36,9 +36,11 @@ import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin import com.instructure.student.ui.utils.uploadTextFile +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Rule import org.junit.Test +@HiltAndroidTest class AssignmentsE2ETest: StudentTest() { override fun displaysPageObjects() = Unit 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 f348b5c731..ddf44b5d4f 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 @@ -9,6 +9,7 @@ import com.instructure.student.ui.pages.CollaborationsPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test /** @@ -16,6 +17,7 @@ import org.junit.Test * We make no attempt to actually start a collaboration. * This test could break if changes are made to the web page that we bring up. */ +@HiltAndroidTest class CollaborationsE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 f42dfe5a3c..9fe0d38fe6 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 @@ -10,8 +10,10 @@ import com.instructure.student.ui.pages.ConferencesPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class ConferencesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 a5e715dbc8..c936147809 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 @@ -26,9 +26,11 @@ import com.instructure.panda_annotations.TestMetaData 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 class DashboardE2ETest : StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 b7ca7ba5ee..7110e1a383 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 @@ -28,8 +28,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class DiscussionsE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/EventsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/EventsE2ETest.kt index 21e4eb8d4a..50ad0e4ade 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/EventsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/EventsE2ETest.kt @@ -16,8 +16,10 @@ import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedAssignments import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class EventsE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 304bb8899b..dea26eccf1 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 @@ -41,12 +41,14 @@ import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin import com.instructure.student.ui.utils.uploadTextFile +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.io.File import java.io.FileWriter // Tests that files (assignment uploads, assignment comment attachments, discussion attachments) // are properly displayed +@HiltAndroidTest class FilesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 9e7ef377cf..d9fcb6ab32 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 @@ -24,8 +24,10 @@ import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class GradesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 8fb9a83e88..02fe645387 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 @@ -27,8 +27,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class InboxE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 7b7469d426..c79f3fe5ce 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 @@ -30,8 +30,10 @@ import com.instructure.dataseeding.util.CanvasRestAdapter import com.instructure.panda_annotations.* import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class LoginE2ETest : StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 16dd481d5a..44571d2e1a 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 @@ -35,8 +35,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class ModulesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 c8d5a5c346..ce20c4ceb0 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 @@ -28,8 +28,10 @@ import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class PagesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 23c908f340..92bc754f1f 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 @@ -26,8 +26,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class PeopleE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 c54c918aae..38e314f322 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 @@ -44,9 +44,11 @@ import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.Matchers.containsString import org.junit.Test +@HiltAndroidTest class QuizzesE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 487df31c51..4b80ff1a06 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 @@ -27,8 +27,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class SettingsE2ETest : StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShardE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShardE2ETest.kt index a28e612fcf..8f90364da7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShardE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShardE2ETest.kt @@ -6,8 +6,10 @@ 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 dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class ShardE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 be7582d8de..0d270ee43c 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 @@ -32,8 +32,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class SyllabusE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 1e92bb8693..5f734e9055 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 @@ -13,9 +13,11 @@ import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedAssignments 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.util.Calendar +@HiltAndroidTest class TodoE2ETest: StudentTest() { override fun displaysPageObjects() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt index 7fdfe478f7..f5942cb2e7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt @@ -17,7 +17,6 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator -import com.instructure.canvas.espresso.Stub import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addCoursePermissions import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse @@ -33,8 +32,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class AnnouncementInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt index d0adb26743..3ee24c2e4a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt @@ -26,10 +26,12 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Assert.assertNotNull import org.junit.Test import java.util.* +@HiltAndroidTest class AssignmentDetailsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt index 001b04e780..26a7b82099 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt @@ -24,8 +24,10 @@ 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 dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class AssignmentListInteractionTest : StudentTest() { @Test diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt index bd3c7beef6..5ac9d0a61e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt @@ -28,8 +28,10 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class BookmarkInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CalendarInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CalendarInteractionTest.kt index 674eba987e..ae6153df60 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CalendarInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CalendarInteractionTest.kt @@ -21,8 +21,10 @@ 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 dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class CalendarInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CommentsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CommentsInteractionTest.kt index c14fef5f6d..65fd96f8db 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CommentsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CommentsInteractionTest.kt @@ -21,8 +21,10 @@ 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 dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class CommentsInteractionTest: StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt index d7b7944464..8c5502ece1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt @@ -34,9 +34,11 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers.containsString import org.junit.Test +@HiltAndroidTest class CourseInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt index d943ef799e..fe373f005a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt @@ -27,10 +27,11 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import junit.framework.TestCase.assertNotNull import org.junit.Test - +@HiltAndroidTest class DashboardInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt index f5fb26fadd..81cc22044a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt @@ -37,10 +37,12 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Assert.assertNotNull import org.junit.Test // Note: Tests course discussions, not group discussions. +@HiltAndroidTest class DiscussionsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt new file mode 100644 index 0000000000..dab48f2c11 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.interaction + +import com.instructure.canvas.espresso.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.init +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigPrefs +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.tokenLoginElementary +import com.instructure.student.util.FeatureFlagPrefs +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.Test + +@HiltAndroidTest +class ElementaryDashboardInteractionTest : StudentTest() { + + override fun displaysPageObjects() = Unit + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testNavigateToElementaryDashboard() { + // User should be able to tap and navigate to dashboard page + goToElementaryDashboard(courseCount = 1, favoriteCourseCount = 1) + elementaryDashboardPage.assertPageObjects() + elementaryDashboardPage.clickInboxTab() + inboxPage.goToDashboard() + elementaryDashboardPage.assertToolbarTitle() + elementaryDashboardPage.assertHomeroomTabVisibleAndSelected() + } + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testTabsNavigation() { + goToElementaryDashboard(courseCount = 1, favoriteCourseCount = 1) + elementaryDashboardPage.assertHomeroomTabVisibleAndSelected() + homeroomPage.assertPageObjects() + + elementaryDashboardPage.selectScheduleTab() + elementaryDashboardPage.assertScheduleTabVisibleAndSelected() + schedulePage.assertPageObjects() + + elementaryDashboardPage.selectGradesTab() + elementaryDashboardPage.assertGradesTabVisibleAndSelected() + gradesPage.assertPageObjects() + + elementaryDashboardPage.selectResourcesTab() + elementaryDashboardPage.assertResourcesTabVisibleAndSelected() + resourcesPage.assertPageObjects() + + elementaryDashboardPage.selectHomeroomTab() + elementaryDashboardPage.assertHomeroomTabVisibleAndSelected() + homeroomPage.assertPageObjects() + } + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOnlyElementarySpecificNavigationItemsShownInTheNavigationDrawer() { + goToElementaryDashboard(courseCount = 1, favoriteCourseCount = 1) + elementaryDashboardPage.openDrawer() + elementaryDashboardPage.assertElementaryMenuItemsShownInDrawer() + elementaryDashboardPage.assertNotElementaryMenuItemsDontShowInDrawer() + } + + private fun goToElementaryDashboard( + courseCount: Int = 1, + pastCourseCount: Int = 0, + favoriteCourseCount: Int = 0, + announcementCount: Int = 0): MockCanvas { + + // We have to add this delay to be sure that the remote config is already fetched before we want to override remote config values. + Thread.sleep(3000) + RemoteConfigPrefs.putString(RemoteConfigParam.K5_DESIGN.rc_name, "true") + FeatureFlagPrefs.showInProgressK5Tabs = true + + val data = MockCanvas.init( + studentCount = 1, + courseCount = courseCount, + pastCourseCount = pastCourseCount, + favoriteCourseCount = favoriteCourseCount, + accountNotificationCount = announcementCount) + + val student = data.students[0] + val token = data.tokenFor(student)!! + tokenLoginElementary(data.domain, token, student) + elementaryDashboardPage.waitForRender() + return data + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt index d0796d1134..7288017c9d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt @@ -40,8 +40,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class GroupLinksInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt new file mode 100644 index 0000000000..4f3160188b --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.interaction + +import com.instructure.canvas.espresso.StubLandscape +import com.instructure.canvas.espresso.mockCanvas.* +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.Enrollment +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigPrefs +import com.instructure.espresso.page.getStringFromResource +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.R +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.tokenLoginElementary +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.Test + +@HiltAndroidTest +class HomeroomInteractionTest : StudentTest() { + + override fun displaysPageObjects() = Unit + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testAnnouncementsAndCoursesShowUpOnHomeroom() { + val data = createMockDataWithHomeroomCourse(courseCount = 3) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + val homeroomAnnouncement = data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + val student = data.students[0] + homeroomPage.assertWelcomeText(student.shortName!!) + homeroomPage.assertAnnouncementDisplayed(homeroomCourse.name, homeroomAnnouncement.title!!, homeroomAnnouncement.message!!) + + homeroomPage.assertCourseItemsCount(3) + data.courses.values + .filter { !it.homeroomCourse } + .forEach { + homeroomPage.assertCourseDisplayed(it.name, homeroomPage.getStringFromResource(R.string.nothingDueToday), "") + } + } + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOnlyCoursesShowUpOnHomeroomIfNoHomeroomAnnouncement() { + val data = createMockDataWithHomeroomCourse(courseCount = 3, homeroomCourseCount = 0) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + val student = data.students[0] + homeroomPage.assertWelcomeText(student.shortName!!) + homeroomPage.assertAnnouncementNotDisplayed() + + homeroomPage.assertCourseItemsCount(3) + data.courses.values + .filter { !it.homeroomCourse } + .forEach { + homeroomPage.assertCourseDisplayed(it.name, homeroomPage.getStringFromResource(R.string.nothingDueToday), "") + } + } + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOnlyAnnouncementShowsUpOnHomeroomIfNoHomeroomAnnouncement() { + val data = createMockDataWithHomeroomCourse() + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + val homeroomAnnouncement = data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + goToHomeroom(data) + + val student = data.students[0] + homeroomPage.assertWelcomeText(student.shortName!!) + homeroomPage.assertAnnouncementDisplayed(homeroomCourse.name, homeroomAnnouncement.title!!, homeroomAnnouncement.message!!) + + homeroomPage.assertCourseItemsCount(0) + homeroomPage.assertNoSubjectsTextDisplayed() + } + + @Test + @TestMetaData(Priority.P2, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testEmptyState() { + val data = createMockDataWithHomeroomCourse() + + goToHomeroom(data) + + homeroomPage.assertHomeroomContentNotDisplayed() + homeroomPage.assertCourseItemsCount(0) + homeroomPage.assertEmptyViewDisplayed() + } + + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testRefresh() { + val data = createMockDataWithHomeroomCourse() + + goToHomeroom(data) + + homeroomPage.assertHomeroomContentNotDisplayed() + homeroomPage.assertCourseItemsCount(0) + homeroomPage.assertEmptyViewDisplayed() + + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + val homeroomAnnouncement = data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + val student = data.students[0] + data.addCourseWithEnrollment(student, Enrollment.EnrollmentType.Student) + data.addCourseWithEnrollment(student, Enrollment.EnrollmentType.Student) + + homeroomPage.refresh() + + homeroomPage.assertWelcomeText(student.shortName!!) + homeroomPage.assertAnnouncementDisplayed(homeroomCourse.name, homeroomAnnouncement.title!!, homeroomAnnouncement.message!!) + + homeroomPage.assertCourseItemsCount(2) + data.courses.values + .filter { !it.homeroomCourse } + .forEach { + homeroomPage.assertCourseDisplayed(it.name, homeroomPage.getStringFromResource(R.string.nothingDueToday), "") + } + } + + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOpenHomeroomCourseAnnouncements() { + val data = createMockDataWithHomeroomCourse(courseCount = 3) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + val homeroomAnnouncement = data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + val student = data.students[0] + homeroomPage.assertWelcomeText(student.shortName!!) + homeroomPage.assertAnnouncementDisplayed(homeroomCourse.name, homeroomAnnouncement.title!!, homeroomAnnouncement.message!!) + + homeroomPage.openHomeroomAnnouncements() + + announcementListPage.assertToolbarTitle() + announcementListPage.assertAnnouncementTitleVisible(homeroomAnnouncement.title!!) + } + + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testShowCourseCardWithAnnouncement() { + val data = createMockDataWithHomeroomCourse(courseCount = 3) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + val courses = data.courses.values.filter { !it.homeroomCourse } + val courseAnnouncement = data.addDiscussionTopicToCourse(courses[0], user, isAnnouncement = true) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + homeroomPage.assertCourseDisplayed(courses[0].name, homeroomPage.getStringFromResource(R.string.nothingDueToday), courseAnnouncement.title!!) + homeroomPage.assertCourseDisplayed(courses[1].name, homeroomPage.getStringFromResource(R.string.nothingDueToday), "") + homeroomPage.assertCourseDisplayed(courses[2].name, homeroomPage.getStringFromResource(R.string.nothingDueToday), "") + } + + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOpenCourseAnnouncements() { + val data = createMockDataWithHomeroomCourse(courseCount = 1) + + val user = data.users.values.first() + val courses = data.courses.values.filter { !it.homeroomCourse } + val courseAnnouncement = data.addDiscussionTopicToCourse(courses[0], user, isAnnouncement = true) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + homeroomPage.openCourseAnnouncment(courseAnnouncement.title!!) + + discussionDetailsPage.assertPageObjects() + discussionDetailsPage.assertTitleText(courseAnnouncement.title!!) + } + + @Test + @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOpenCourse() { + val data = createMockDataWithHomeroomCourse(courseCount = 3) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + val courses = data.courses.values.filter { !it.homeroomCourse } + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + homeroomPage.openCourse(courses[0].name) + + courseBrowserPage.assertPageObjects() + courseBrowserPage.assertTitleCorrect(courses[0]) + } + + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testDueTodayAndMissingAssignments() { + val data = createMockDataWithHomeroomCourse(courseCount = 1) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + val courses = data.courses.values.filter { !it.homeroomCourse } + + data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + + // With the current implementation of MockCanvas, all the assignments will show up as due today and missing, because both Mock endpoints will return all the assignments. + // This cannot happen in normal circumstances, but for testing the UI it's fine. + // We can add a more sophisticated approach when other tests will need it. + // Veryfing the logic that one can be due today or missing only is covered by unit tests. + homeroomPage.assertToDoText("2 due today | 2 missing") + } + + @StubLandscape + @Test + @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION) + fun testOpenAssignments() { + val data = createMockDataWithHomeroomCourse(courseCount = 1) + val homeroomCourse = data.courses.values.first { it.homeroomCourse } + val user = data.users.values.first() + + data.addDiscussionTopicToCourse(homeroomCourse, user, isAnnouncement = true) + + val courses = data.courses.values.filter { !it.homeroomCourse } + + val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + + goToHomeroom(data) + + homeroomPage.assertPageObjects() + homeroomPage.openAssignments("2 due today | 2 missing") + + assignmentListPage.assertPageObjects() + assignmentListPage.assertHasAssignment(assignment1) + } + + private fun createMockDataWithHomeroomCourse( + courseCount: Int = 0, + pastCourseCount: Int = 0, + favoriteCourseCount: Int = 0, + announcementCount: Int = 0, + homeroomCourseCount: Int = 1): MockCanvas { + + // We have to add this delay to be sure that the remote config is already fetched before we want to override remote config values. + Thread.sleep(3000) + RemoteConfigPrefs.putString(RemoteConfigParam.K5_DESIGN.rc_name, "true") + + val data = MockCanvas.init( + studentCount = 1, + courseCount = courseCount, + pastCourseCount = pastCourseCount, + favoriteCourseCount = favoriteCourseCount, + accountNotificationCount = announcementCount, + homeroomCourseCount = homeroomCourseCount) + + return data + } + + private fun goToHomeroom(data: MockCanvas) { + val student = data.students[0] + val token = data.tokenFor(student)!! + tokenLoginElementary(data.domain, token, student) + elementaryDashboardPage.waitForRender() + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InAppUpdateInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InAppUpdateInteractionTest.kt new file mode 100644 index 0000000000..75bd92c7fb --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InAppUpdateInteractionTest.kt @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.instructure.student.ui.interaction + +import android.app.NotificationManager +import android.content.Context +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.google.android.play.core.appupdate.testing.FakeAppUpdateManager +import com.instructure.canvas.espresso.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.init +import com.instructure.canvasapi2.utils.toApiString +import com.instructure.pandautils.di.UpdateModule +import com.instructure.pandautils.update.UpdateManager +import com.instructure.pandautils.update.UpdatePrefs +import com.instructure.student.R +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.BindValue +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import org.junit.Test +import org.threeten.bp.OffsetDateTime + +@UninstallModules(UpdateModule::class) +@HiltAndroidTest +class InAppUpdateInteractionTest : StudentTest() { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + private val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + @BindValue + @JvmField + val appUpdateManager: FakeAppUpdateManager = FakeAppUpdateManager(context) + + @BindValue + @JvmField + val updatePrefs: UpdatePrefs = UpdatePrefs + + @BindValue + @JvmField + val updateManager: UpdateManager = UpdateManager(appUpdateManager, notificationManager, updatePrefs) + + private val uiDevice by lazy { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) } + + override fun displaysPageObjects()= Unit + + private fun goToDashboard() { + val data = MockCanvas.init( + studentCount = 1, + courseCount = 1, + favoriteCourseCount = 1, + createSections = true) + val user = data.students[0] + val token = data.tokenFor(user)!! + tokenLogin(data.domain, token, user) + dashboardPage.waitForRender() + } + + @Test + fun showFlexibleConfirmationDialogForFlexibleUpdate() { + updatePrefs.clearPrefs() + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun hideFlexibleConfirmationDialogIfUserDeclines() { + updatePrefs.clearPrefs() + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isConfirmationDialogVisible) + + appUpdateManager.userRejectsUpdate() + + assertFalse(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun hideFlexibleConfirmationDialogIfUserAccepts() { + updatePrefs.clearPrefs() + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isConfirmationDialogVisible) + + appUpdateManager.userAcceptsUpdate() + + assertFalse(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun hideFlexibleConfirmationDialogIfItHasBeenShownToday() { + with(updatePrefs) { + lastUpdateNotificationCount = 1 + lastUpdateNotificationVersionCode = 400 + lastUpdateNotificationDate = OffsetDateTime.now().toApiString()!! + } + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assertFalse(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun hideFlexibleConfirmationDialogIfItHasBeenShownTwice() { + with(updatePrefs) { + lastUpdateNotificationCount = 2 + lastUpdateNotificationVersionCode = 400 + lastUpdateNotificationDate = OffsetDateTime.now().toApiString()!! + } + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assertFalse(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun showFlexibleConfirmationDialogForNewVersion() { + with(updatePrefs) { + lastUpdateNotificationCount = 2 + lastUpdateNotificationVersionCode = 399 + lastUpdateNotificationDate = OffsetDateTime.now().toApiString()!! + } + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun hideFlexibleConfirmationIfItWasShownToday() { + with(updatePrefs) { + lastUpdateNotificationCount = 1 + lastUpdateNotificationVersionCode = 400 + lastUpdateNotificationDate = OffsetDateTime.now().toApiString()!! + hasShownThisStart = true + } + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assertFalse(appUpdateManager.isConfirmationDialogVisible) + } + + @Test + fun showImmediateFlow() { + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(4) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isImmediateFlowVisible) + } + + @Test + fun hideImmediateFlowIfItWasShownThisStart() { + with(updatePrefs) { + lastUpdateNotificationCount = 1 + lastUpdateNotificationVersionCode = 400 + lastUpdateNotificationDate = OffsetDateTime.now().minusDays(2).toApiString()!! + hasShownThisStart = true + } + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(4) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assertFalse(appUpdateManager.isImmediateFlowVisible) + } + + @Test + fun showNotificationOnFlexibleDownloadFinish() { + updatePrefs.clearPrefs() + val expectedTitle = context.getString(R.string.appUpdateReadyTitle) + val expectedDescription = context.getString(R.string.appUpdateReadyDescription) + + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isConfirmationDialogVisible) + + appUpdateManager.userAcceptsUpdate() + appUpdateManager.downloadStarts() + appUpdateManager.downloadCompletes() + + uiDevice.openNotification() + uiDevice.wait(Until.hasObject(By.textStartsWith(context.getString(R.string.student_app_name))), 2) + val title = uiDevice.findObject(By.text(expectedTitle)) + val description = uiDevice.findObject(By.text(expectedDescription)) + + assertEquals(expectedTitle, title.text) + assertEquals(expectedDescription, description.text) + + uiDevice.pressBack() + } + + @Test + fun flexibleUpdateCompletesIfAppRestarts() { + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(2) + setClientVersionStalenessDays(10) + userAcceptsUpdate() + downloadStarts() + downloadCompletes() + } + + goToDashboard() + + assert(appUpdateManager.isInstallSplashScreenVisible) + } + + @Test + fun immediateUpdateCompletion() { + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(4) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isImmediateFlowVisible) + + appUpdateManager.userAcceptsUpdate() + appUpdateManager.downloadStarts() + appUpdateManager.downloadCompletes() + + assert(appUpdateManager.isInstallSplashScreenVisible) + } + + @Test + fun hideImmediateUpdateFlowIfUserCancels() { + with(appUpdateManager) { + setUpdateAvailable(400) + setUpdatePriority(4) + setClientVersionStalenessDays(10) + } + + goToDashboard() + + assert(appUpdateManager.isImmediateFlowVisible) + + appUpdateManager.userRejectsUpdate() + + assertFalse(appUpdateManager.isImmediateFlowVisible) + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt index c898e7dc67..83c53f822b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt @@ -26,8 +26,10 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class InboxInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt index fab98f5b0e..2f6d9670c8 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt @@ -21,8 +21,10 @@ 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 dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class LoginInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt index 2adcf8c548..64a74afb6f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt @@ -51,9 +51,11 @@ import com.instructure.student.R import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.net.URLEncoder +@HiltAndroidTest class ModuleInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt index d1c946dc29..f1e3d4e8f3 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt @@ -39,10 +39,13 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import com.instructure.student.R +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers import org.junit.Before import org.junit.Test +@HiltAndroidTest class NavigationDrawerInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests @@ -164,10 +167,7 @@ class NavigationDrawerInteractionTest : StudentTest() { dashboardPage.goToHelp() helpPage.launchGuides() - canvasWebViewPage.runTextChecks( - // Potentially brittle -- the web content could be changed by another team - WebViewTextCheck(Locator.CLASS_NAME, "lia-panel-heading-bar-title", "Guides by Product", 25) - ) + canvasWebViewPage.verifyTitle(R.string.searchGuides) } // Should send an error report diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt index 6266d7595f..b156f61556 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt @@ -34,6 +34,7 @@ import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.io.File import java.io.FileOutputStream @@ -41,6 +42,7 @@ import java.io.InputStream import java.io.OutputStream import java.util.* +@HiltAndroidTest class PdfInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt index fd25f546e1..4d9b01cd38 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt @@ -26,8 +26,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class PeopleInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt index a57a36aab3..932e7ebbd7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt @@ -33,11 +33,13 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.core.AllOf import org.junit.Before import org.junit.Test import java.io.File +@HiltAndroidTest class PickerSubmissionUploadInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt index a9ee17151c..f0bd7b8659 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt @@ -12,10 +12,12 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test +@HiltAndroidTest class ProfileSettingsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PushNotificationInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PushNotificationInteractionTest.kt index 8cfb83203e..78e9e751b1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PushNotificationInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PushNotificationInteractionTest.kt @@ -30,9 +30,11 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.util.* +@HiltAndroidTest class PushNotificationInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt index c9e27eca6c..0034c69b61 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt @@ -38,10 +38,12 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers import org.junit.Before import org.junit.Test +@HiltAndroidTest class SettingsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt index cb7a57d533..157388af16 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt @@ -45,9 +45,11 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import java.util.* +@HiltAndroidTest class SubmissionDetailsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt index 712718dbc8..ae2f297c9f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt @@ -32,8 +32,10 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class SyllabusInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt index 3cf3959ccd..41be02348d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt @@ -32,8 +32,10 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class TodoInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt index a04e589aed..1b87659436 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt @@ -36,13 +36,14 @@ 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.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.core.AllOf.allOf import org.junit.Before import org.junit.Rule import org.junit.Test import java.io.File - +@HiltAndroidTest class UserFilesInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AnnouncementListPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AnnouncementListPage.kt new file mode 100644 index 0000000000..831b2f9fca --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AnnouncementListPage.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.pages + +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.matchers.WaitForViewMatcher +import com.instructure.espresso.page.* +import com.instructure.student.R + +class AnnouncementListPage : BasePage(R.id.discussionListPage) { + + fun assertToolbarTitle() { + WaitForViewMatcher.waitForView(withParent(R.id.discussionListToolbar) + withText(R.string.announcements)).assertDisplayed() + } + + fun assertAnnouncementTitleVisible(title: String) { + onView(withText(title) + isDisplayed()).assertDisplayed() + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt index 92547d360b..f0c70431c3 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt @@ -16,17 +16,15 @@ */ package com.instructure.student.ui.pages +import androidx.annotation.StringRes 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.onWebView -import androidx.test.espresso.web.webdriver.DriverAtoms.findElement -import androidx.test.espresso.web.webdriver.DriverAtoms.getText -import androidx.test.espresso.web.webdriver.DriverAtoms.webClick -import androidx.test.espresso.web.webdriver.DriverAtoms.webScrollIntoView +import androidx.test.espresso.web.webdriver.DriverAtoms.* import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.withElementRepeat -import com.instructure.espresso.page.BasePage +import com.instructure.espresso.assertVisible +import com.instructure.espresso.page.* import com.instructure.student.R import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.containsString @@ -35,16 +33,20 @@ import org.hamcrest.Matchers.containsString * An abstraction for operations on a full-screen (or mostly-full-screen) webpage. */ open class CanvasWebViewPage : BasePage(R.id.canvasWebView) { - fun runTextChecks(vararg checks : WebViewTextCheck) { - for(check in checks) { - if(check.repeatSecs != null) { + + fun verifyTitle(@StringRes title: Int) { + onView(withAncestor(R.id.toolbar) + withText(title)).assertVisible() + } + + fun runTextChecks(vararg checks: WebViewTextCheck) { + for (check in checks) { + if (check.repeatSecs != null) { onWebView(allOf(withId(R.id.canvasWebView), isDisplayed())) - .withElementRepeat(findElement(check.locatorType, check.locatorValue), check.repeatSecs) - .check(webMatches(getText(), containsString(check.textValue))) - } - else { + .withElementRepeat(findElement(check.locatorType, check.locatorValue), check.repeatSecs) + .check(webMatches(getText(), containsString(check.textValue))) + } else { onWebView(allOf(withId(R.id.canvasWebView), isDisplayed())) - .withElement(findElement(check.locatorType, check.locatorValue)) + .withElement(findElement(check.locatorType, check.locatorValue)) .check(webMatches(getText(), containsString(check.textValue))) } } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt index cdbe9a3b9b..4f54bfb56a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt @@ -83,7 +83,7 @@ class DashboardPage : BasePage(R.id.dashboardPage) { onView(withParent(R.id.toolbar) + withText(R.string.dashboard)).assertDisplayed() listView.assertDisplayed() onViewWithText("Courses").assertDisplayed() - onViewWithText("See All").assertDisplayed() + onViewWithText("Edit Dashboard").assertDisplayed() } fun assertDisplaysCourse(course: CourseApiModel) { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt index cf41c948ac..6c63b0d547 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt @@ -20,6 +20,7 @@ import android.os.SystemClock.sleep import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.swipeDown +import androidx.test.espresso.action.ViewActions.swipeUp import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast import androidx.test.espresso.matcher.ViewMatchers.withId @@ -43,6 +44,7 @@ import com.instructure.espresso.assertHasText import com.instructure.espresso.assertNotDisplayed import com.instructure.espresso.click import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo import com.instructure.student.R import com.instructure.student.ui.utils.TypeInRCETextEditor @@ -77,6 +79,7 @@ class DiscussionDetailsPage : BasePage(R.id.discussionDetailsPage) { } fun refresh() { + scrollToTop() onView(allOf(withId(R.id.swipeRefreshLayout), isDisplayingAtLeast(10))) .perform(withCustomConstraints(swipeDown(), isDisplayingAtLeast(10))) } @@ -310,6 +313,11 @@ class DiscussionDetailsPage : BasePage(R.id.discussionDetailsPage) { return false } } + + fun scrollToTop() { + onView(allOf(withId(R.id.swipeRefreshLayout), isDisplayingAtLeast(10))) + .perform(withCustomConstraints(swipeDown(), isDisplayingAtLeast(10))) + } } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ElementaryDashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ElementaryDashboardPage.kt new file mode 100644 index 0000000000..6b86ee2dd4 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ElementaryDashboardPage.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.pages + +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withContentDescription +import com.instructure.espresso.* +import com.instructure.espresso.page.* +import com.instructure.student.R +import kotlinx.android.synthetic.main.fragment_elementary_dashboard.view.* +import org.hamcrest.CoreMatchers + +class ElementaryDashboardPage : BasePage(R.id.elementaryDashboardPage) { + + private val toolbar by OnViewWithId(R.id.toolbar) + private val tabLayout by OnViewWithId(R.id.dashboardTabLayout) + private val pager by OnViewWithId(R.id.dashboardPager) + + private val hamburgerButtonMatcher = CoreMatchers.allOf(withContentDescription(R.string.navigation_drawer_open), isDisplayed()) + + fun assertToolbarTitle() { + onView(withParent(R.id.toolbar) + withText(R.string.dashboard) + isDisplayed()).assertDisplayed() + } + + fun clickInboxTab() { + onView(withId(R.id.bottomNavigationInbox)).click() + } + + fun selectHomeroomTab() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabHomeroom)) + .scrollTo() + .click() + } + + fun assertHomeroomTabVisibleAndSelected() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabHomeroom) + isDisplayed()).assertDisplayed() + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabHomeroom) + isDisplayed()).assertSelected() + } + + fun selectScheduleTab() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabSchedule)) + .scrollTo() + .click() + } + + fun assertScheduleTabVisibleAndSelected() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabSchedule) + isDisplayed()).assertDisplayed() + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabSchedule) + isDisplayed()).assertSelected() + } + + fun selectGradesTab() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabGrades)) + .scrollTo() + .click() + } + + fun assertGradesTabVisibleAndSelected() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabGrades) + isDisplayed()).assertDisplayed() + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabGrades) + isDisplayed()).assertSelected() + } + + fun selectResourcesTab() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabResources)) + .scrollTo() + .click() + } + + fun assertResourcesTabVisibleAndSelected() { + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabResources) + isDisplayed()).assertDisplayed() + onView(withAncestor(R.id.dashboardTabLayout) + withText(R.string.dashboardTabResources) + isDisplayed()).assertSelected() + } + + fun waitForRender() { + onView(hamburgerButtonMatcher).waitForCheck(matches(isDisplayed())) + } + + fun openDrawer() { + onView(hamburgerButtonMatcher).click() + } + + fun assertElementaryMenuItemsShownInDrawer() { + onView(withText(R.string.files)).assertDisplayed() + onView(withText(R.string.settings)).assertDisplayed() + onView(withText(R.string.help)).assertDisplayed() + onView(withText(R.string.changeUser)).assertDisplayed() + onView(withText(R.string.logout)).assertDisplayed() + } + + fun assertNotElementaryMenuItemsDontShowInDrawer() { + onView(withText(R.string.showGrades)).assertNotDisplayed() + onView(withText(R.string.colorOverlay)).assertNotDisplayed() + } +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/CourseCardViewModel.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt similarity index 64% rename from libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/CourseCardViewModel.kt rename to apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt index 72ad1dd6e5..576ad03129 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/itemviewmodels/CourseCardViewModel.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt @@ -14,13 +14,10 @@ * along with this program. If not, see . * */ -package com.instructure.pandautils.features.elementary.homeroom.itemviewmodels +package com.instructure.student.ui.pages -import com.instructure.pandautils.R -import com.instructure.pandautils.features.elementary.homeroom.CourseCardViewData -import com.instructure.pandautils.mvvm.ItemViewModel +import com.instructure.espresso.page.BasePage +import com.instructure.student.R -class CourseCardViewModel(val data: CourseCardViewData) : ItemViewModel { - - override val layoutId: Int = R.layout.item_course_card +class GradesPage : BasePage(R.id.gradesPage) { } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/HomeroomPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/HomeroomPage.kt new file mode 100644 index 0000000000..b9abb82543 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/HomeroomPage.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.pages + +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers +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.espresso.* +import com.instructure.espresso.page.* +import com.instructure.student.R +import org.hamcrest.Matchers + +class HomeroomPage : BasePage(R.id.homeroomPage) { + + private val swipeRefreshLayout by OnViewWithId(R.id.homeroomSwipeRefreshLayout) + private val welcomeText by OnViewWithId(R.id.welcomeText) + private val announcementsContainer by OnViewWithId(R.id.announcementsContainer) + private val mySubjectsTitle by OnViewWithId(R.id.mySubjectsTitle) + private val coursesRecyclerView by OnViewWithId(R.id.coursesRecyclerView) + private val noSubjectsText by OnViewWithId(R.id.noSubjectsText, autoAssert = false) + + fun assertWelcomeText(studentName: String) { + welcomeText.assertHasText(getStringFromResource(R.string.homeroomWelcomeMessage, studentName)) + } + + fun assertAnnouncementDisplayed(courseName: String, title: String, content: String) { + onView(withAncestor(R.id.announcementsContainer) + withText(courseName)).assertDisplayed() + onView(withAncestor(R.id.announcementsContainer) + withText(title)).assertDisplayed() + + Web.onWebView() + .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "html")) + .check(WebViewAssertions.webMatches(DriverAtoms.getText(), Matchers.comparesEqualTo(content))) + + onView(withAncestor(R.id.announcementsContainer) + withText(R.string.viewPreviousAnnouncements)) + .scrollTo() + .assertDisplayed() + } + + fun assertAnnouncementNotDisplayed() { + announcementsContainer.check(ViewAssertions.matches(ViewMatchers.hasChildCount(0))) + } + + fun assertCourseItemsCount(coursesCount: Int) { + coursesRecyclerView.check(RecyclerViewItemCountAssertion(coursesCount)) + } + + fun assertCourseDisplayed(courseName: String, todoText: String, announcementText: String) { + val titleMatcher = withId(R.id.courseNameText) + withText(courseName) + val todoTextMatcher = withId(R.id.todoText) + withText(todoText) + val announcementMatcher = withId(R.id.announcementText) + withText(announcementText) + + onView(withId(R.id.cardView) + withDescendant(titleMatcher) + withDescendant(todoTextMatcher) + withDescendant(announcementMatcher)) + .scrollTo() + .assertDisplayed() + } + + fun assertNoSubjectsTextDisplayed() { + noSubjectsText + .scrollTo() + .assertDisplayed() + .assertHasText(R.string.homeroomNoSubjects) + } + + fun assertHomeroomContentNotDisplayed() { + onViewWithId(R.id.homeroomContent).assertNotDisplayed() + } + + fun assertEmptyViewDisplayed() { + onViewWithId(R.id.emptyView).assertDisplayed() + onViewWithText(R.string.homeroomEmptyTitle).assertDisplayed() + onViewWithText(R.string.homeroomEmptyMessage).assertDisplayed() + } + + fun refresh() { + swipeRefreshLayout.swipeDown() + } + + fun openHomeroomAnnouncements() { + onViewWithId(R.id.viewPreviousAnnouncements) + .click() + } + + fun openCourseAnnouncment(announcementText: String) { + onView(withId(R.id.announcementText) + withText(announcementText)) + .scrollTo() + .click() + } + + fun openCourse(courseName: String) { + onView(withId(R.id.courseNameText) + withText(courseName)) + .scrollTo() + .click() + } + + fun assertToDoText(todoText: String) { + onView(withId(R.id.todoText) + withText(todoText)) + .scrollTo() + .assertDisplayed() + } + + fun openAssignments(todoText: String) { + onView(withId(R.id.todoText) + withText(todoText)) + .scrollTo() + .click() + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ResourcesPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ResourcesPage.kt new file mode 100644 index 0000000000..b6e52f050d --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ResourcesPage.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.pages + +import com.instructure.espresso.page.BasePage +import com.instructure.student.R + +class ResourcesPage : BasePage(R.id.resourcesPage) { +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/SchedulePage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/SchedulePage.kt new file mode 100644 index 0000000000..2f1d0f4f60 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/SchedulePage.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.instructure.student.ui.pages + +import com.instructure.espresso.page.BasePage +import com.instructure.student.R + +class SchedulePage : BasePage(R.id.schedulePage) { +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/AssignmentDetailsRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/AssignmentDetailsRenderTest.kt index 90bf1796f3..8ec5a3764b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/AssignmentDetailsRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/AssignmentDetailsRenderTest.kt @@ -34,6 +34,7 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.AssignmentDetailsModel import com.instructure.student.mobius.assignmentDetails.ui.AssignmentDetailsFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,6 +42,7 @@ import java.util.Calendar import java.util.Date import java.util.GregorianCalendar +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class AssignmentDetailsRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceDetailsRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceDetailsRenderTest.kt index 4006806a18..482fce4c16 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceDetailsRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceDetailsRenderTest.kt @@ -26,10 +26,12 @@ import com.instructure.student.mobius.conferences.conference_details.ui.Conferen import com.instructure.student.mobius.conferences.conference_details.ui.ConferenceDetailsViewState import com.instructure.student.mobius.conferences.conference_details.ui.ConferenceRecordingViewState import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class ConferenceDetailsRenderTest : StudentRenderTest() { private val canvasContext: CanvasContext = Course(id = 123L, name = "Test Course") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceListRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceListRenderTest.kt index 22d9f3efb2..9531404264 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceListRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/ConferenceListRenderTest.kt @@ -26,9 +26,11 @@ import com.instructure.student.mobius.conferences.conference_list.ui.ConferenceL import com.instructure.student.mobius.conferences.conference_list.ui.ConferenceListItemViewState import com.instructure.student.mobius.conferences.conference_list.ui.ConferenceListViewState import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class ConferenceListRenderTest : StudentRenderTest() { private val canvasContext: CanvasContext = Course(id = 123L, name = "Test Course") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt index c07490f9a3..d936842623 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt @@ -22,8 +22,10 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.DiscussionSubmissionViewFragment import com.instructure.student.ui.pages.renderPages.DiscussionSubmissionViewRenderPage +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class DiscussionSubmissionViewRenderTest : StudentRenderTest() { private val page = DiscussionSubmissionViewRenderPage() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/MediaSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/MediaSubmissionViewRenderTest.kt index cd2f6dffb6..39d46f1a95 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/MediaSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/MediaSubmissionViewRenderTest.kt @@ -31,9 +31,11 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.SubmissionDetailsContentType import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.MediaSubmissionViewFragment import com.instructure.student.ui.pages.renderPages.MediaSubmissionViewRenderPage +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class MediaSubmissionViewRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PairObserverRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PairObserverRenderTest.kt index 4776c5f617..9ee8af5e6e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PairObserverRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PairObserverRenderTest.kt @@ -27,10 +27,12 @@ import com.instructure.student.mobius.settings.pairobserver.ui.PairObserverFragm import com.instructure.student.mobius.syllabus.SyllabusModel import com.instructure.student.mobius.syllabus.ui.SyllabusFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class PairObserverRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt index 3013ed911b..95a91eea8e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt @@ -38,9 +38,11 @@ import com.instructure.student.mobius.assignmentDetails.submission.picker.ui.Pic import com.instructure.student.mobius.assignmentDetails.submission.picker.ui.PickerVisibilities import com.instructure.student.ui.pages.renderPages.PickerSubmissionUploadRenderPage import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class PickerSubmissionUploadRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/QuizSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/QuizSubmissionViewRenderTest.kt index 5d6d365cac..fb99ae36b4 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/QuizSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/QuizSubmissionViewRenderTest.kt @@ -22,8 +22,10 @@ import com.instructure.student.R import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.QuizSubmissionViewFragment import com.instructure.student.ui.pages.renderPages.QuizSubmissionViewRenderPage +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class QuizSubmissionViewRenderTest : StudentRenderTest() { private val url = "https://www.google.com" diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt index 972ea3e498..64551e8539 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt @@ -38,12 +38,14 @@ import com.instructure.student.mobius.assignmentDetails.submissionDetails.drawer import com.instructure.student.mobius.assignmentDetails.submissionDetails.ui.SubmissionDetailsTabData import com.instructure.student.ui.pages.renderPages.SubmissionCommentsRenderPage import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.util.* +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SubmissionCommentsRenderTest: StudentRenderTest() { private lateinit var user: User diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsEmptyContentRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsEmptyContentRenderTest.kt index 23ebf77966..40d39e788b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsEmptyContentRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsEmptyContentRenderTest.kt @@ -25,6 +25,7 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.emptySubmission.SubmissionDetailsEmptyContentModel import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.emptySubmission.ui.SubmissionDetailsEmptyContentFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -32,6 +33,7 @@ import org.threeten.bp.Month import org.threeten.bp.OffsetDateTime import org.threeten.bp.format.DateTimeFormatter +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SubmissionDetailsEmptyContentRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsRenderTest.kt index 14d955be15..9300d7e30f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionDetailsRenderTest.kt @@ -31,10 +31,12 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.SubmissionDetailsModel import com.instructure.student.mobius.assignmentDetails.submissionDetails.ui.SubmissionDetailsFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SubmissionDetailsRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionFilesRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionFilesRenderTest.kt index 3b1585b593..25a5c8ac80 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionFilesRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionFilesRenderTest.kt @@ -30,9 +30,11 @@ import com.instructure.student.mobius.assignmentDetails.submissionDetails.drawer import com.instructure.student.mobius.assignmentDetails.submissionDetails.ui.SubmissionDetailsTabData import com.instructure.student.ui.pages.renderPages.SubmissionFilesRenderPage import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SubmissionFilesRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionRubricRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionRubricRenderTest.kt index e9dc0f535e..2713887740 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionRubricRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionRubricRenderTest.kt @@ -39,10 +39,12 @@ import com.instructure.student.mobius.assignmentDetails.ui.gradeCell.GradeCellVi import com.instructure.student.ui.pages.renderPages.SubmissionRubricRenderPage import com.instructure.student.ui.utils.assertFontSizeSP import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers.not import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SubmissionRubricRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SyllabusRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SyllabusRenderTest.kt index 3cf9f58d23..6d354779c1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SyllabusRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SyllabusRenderTest.kt @@ -24,10 +24,12 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.syllabus.SyllabusModel import com.instructure.student.mobius.syllabus.ui.SyllabusFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class SyllabusRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionUploadRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionUploadRenderTest.kt index 87ba0c05e2..afe3a91efa 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionUploadRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionUploadRenderTest.kt @@ -24,9 +24,11 @@ import com.instructure.espresso.waitForCheck import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submission.text.ui.TextSubmissionUploadFragment import com.instructure.student.ui.pages.renderPages.TextSubmissionUploadRenderPage +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers.not import org.junit.Test +@HiltAndroidTest class TextSubmissionUploadRenderTest : StudentRenderTest() { private val page = TextSubmissionUploadRenderPage() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionViewRenderTest.kt index e850f2d2b1..b74816a351 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/TextSubmissionViewRenderTest.kt @@ -19,8 +19,10 @@ import android.os.Build import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.TextSubmissionViewFragment import com.instructure.student.ui.pages.renderPages.TextSubmissionViewRenderPage +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class TextSubmissionViewRenderTest : StudentRenderTest() { private val page = TextSubmissionViewRenderPage() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt index e8ed5d5d20..e5712097e7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt @@ -26,10 +26,12 @@ import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submission.file.UploadStatusSubmissionModel import com.instructure.student.mobius.assignmentDetails.submission.file.ui.UploadStatusSubmissionFragment import com.spotify.mobius.runners.WorkRunner +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class UploadStatusSubmissionRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionUploadRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionUploadRenderTest.kt index cfe792898f..4b1890beff 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionUploadRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionUploadRenderTest.kt @@ -25,9 +25,11 @@ import com.instructure.espresso.replaceText import com.instructure.espresso.waitForCheck import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submission.url.ui.UrlSubmissionUploadFragment +import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.Matchers.not import org.junit.Test +@HiltAndroidTest class UrlSubmissionUploadRenderTest : StudentRenderTest() { private val testUrl = "https://www.instructure.com" diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionViewRenderTest.kt index 67ef6d3035..4cc7c834ea 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UrlSubmissionViewRenderTest.kt @@ -19,8 +19,10 @@ import com.instructure.espresso.assertCompletelyDisplayed import com.instructure.espresso.assertHasText import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.UrlSubmissionViewFragment +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class UrlSubmissionViewRenderTest : StudentRenderTest() { private val testUrl = "https://www.instructure.com" diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt index c7f676f957..432f6a7c6c 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt @@ -23,9 +23,11 @@ import com.instructure.student.R import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.ui.gradeCell.GradeCellView import com.instructure.student.mobius.assignmentDetails.ui.gradeCell.GradeCellViewState +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test import org.junit.runner.RunWith +@HiltAndroidTest @RunWith(AndroidJUnit4::class) class GradeCellRenderTest : StudentRenderTest() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt index c6734a14da..6181b9b93f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt @@ -25,14 +25,21 @@ import androidx.test.espresso.ViewAction import androidx.test.espresso.matcher.ViewMatchers import com.instructure.canvas.espresso.CanvasTest import com.instructure.espresso.InstructureActivityTestRule +import com.instructure.espresso.ScreenshotTestRule import com.instructure.espresso.swipeRight import com.instructure.student.BuildConfig import com.instructure.student.R import com.instructure.student.activity.LoginActivity import com.instructure.student.ui.pages.* +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps import instructure.rceditor.RCETextEditor import org.hamcrest.Matcher import org.junit.Before +import org.junit.Rule +import org.junit.rules.RuleChain +import org.junit.rules.TestRule import java.io.File abstract class StudentTest : CanvasTest() { @@ -42,6 +49,9 @@ abstract class StudentTest : CanvasTest() { lateinit var originalActivity : Activity + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + // Sometimes activityRule.activity can get nulled out over time, probably as we // navigate away from the original login screen. Capture the activity here so // that we can reference it safely later. @@ -56,6 +66,7 @@ abstract class StudentTest : CanvasTest() { * Required for auto complete of page objects within tests */ val annotationCommentListPage = AnnotationCommentListPage() + val announcementListPage = AnnouncementListPage() val assignmentDetailsPage = AssignmentDetailsPage() val assignmentListPage = AssignmentListPage() val bookmarkPage = BookmarkPage() @@ -97,6 +108,11 @@ abstract class StudentTest : CanvasTest() { val syllabusPage = SyllabusPage() val todoPage = TodoPage() val urlSubmissionUploadPage = UrlSubmissionUploadPage() + val elementaryDashboardPage = ElementaryDashboardPage() + val homeroomPage = HomeroomPage() + val schedulePage = SchedulePage() + val gradesPage = GradesPage() + val resourcesPage = ResourcesPage() // A no-op interaction to afford us an easy, harmless way to get a11y checking to trigger. fun meaninglessSwipe() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt index 1030ee7b53..7a9107b612 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt @@ -131,6 +131,21 @@ fun StudentTest.tokenLogin(domain: String, token: String, user: User) { dashboardPage.assertPageObjects() } +fun StudentTest.tokenLoginElementary(domain: String, token: String, user: User) { + activityRule.runOnUiThread { + (originalActivity as LoginActivity).loginWithToken( + token, + domain, + user, + canvasForElementary = true + ) + } + // Sometimes, especially on slow FTL emulators, it can take a bit for the dashboard to show + // up after a token login. Add some tolerance for that. + waitForMatcherWithSleeps(withId(R.id.elementaryDashboardPage), 20000).check(matches(isDisplayed())) + elementaryDashboardPage.assertPageObjects() +} + fun StudentTest.routeTo(route: String) { val url = "canvas-student://${CanvasRestAdapter.canvasDomain}/$route" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) diff --git a/apps/student/src/main/java/com/instructure/student/activity/LoginActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/LoginActivity.kt index 2ac8d226e5..5b4771fed3 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/LoginActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/LoginActivity.kt @@ -26,19 +26,21 @@ import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.interactions.router.Route import com.instructure.loginapi.login.activities.BaseLoginInitActivity +import com.instructure.loginapi.login.tasks.LogoutTask import com.instructure.loginapi.login.util.QRLogin -import com.instructure.pandautils.services.PushNotificationRegistrationService +import com.instructure.pandautils.services.PushNotificationRegistrationWorker import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.Utils import com.instructure.student.BuildConfig import com.instructure.student.R +import com.instructure.student.tasks.StudentLogoutTask import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class LoginActivity : BaseLoginInitActivity() { override fun launchApplicationMainActivityIntent(): Intent { - PushNotificationRegistrationService.scheduleJob(this, ApiPrefs.isMasquerading) + PushNotificationRegistrationWorker.scheduleJob(this, ApiPrefs.isMasquerading) CookieManager.getInstance().flush() @@ -66,6 +68,10 @@ class LoginActivity : BaseLoginInitActivity() { override val isTesting: Boolean = BuildConfig.IS_TESTING + override fun logout() { + StudentLogoutTask(LogoutTask.Type.LOGOUT).execute() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -80,13 +86,16 @@ class LoginActivity : BaseLoginInitActivity() { * ONLY USE FOR UI TESTING * Skips the traditional login process by directly setting the domain, token, and user info. */ - fun loginWithToken(token: String, domain: String, user: User) { + fun loginWithToken(token: String, domain: String, user: User, canvasForElementary: Boolean = false) { ApiPrefs.accessToken = token ApiPrefs.domain = domain ApiPrefs.user = user ApiPrefs.userAgent = Utils.generateUserAgent(this, userAgent()) finish() - val intent = Intent(this, NavigationActivity.startActivityClass).apply { intent?.extras?.let { putExtras(it) } } + val intent = Intent(this, NavigationActivity.startActivityClass).apply { + intent?.extras?.let { putExtras(it) } + putExtra("canvas_for_elementary", canvasForElementary) + } startActivity(intent) } 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 06a000f936..23048877c9 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 @@ -26,14 +26,14 @@ import com.instructure.canvasapi2.models.AccountDomain import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.loginapi.login.activities.BaseLoginLandingPageActivity import com.instructure.loginapi.login.snicker.SnickerDoodle -import com.instructure.pandautils.services.PushNotificationRegistrationService +import com.instructure.pandautils.services.PushNotificationRegistrationWorker import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class LoginLandingPageActivity : BaseLoginLandingPageActivity() { override fun launchApplicationMainActivityIntent(): Intent { - PushNotificationRegistrationService.scheduleJob(this, ApiPrefs.isMasquerading) + PushNotificationRegistrationWorker.scheduleJob(this, ApiPrefs.isMasquerading) CookieManager.getInstance().flush() 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 14dc8a6ee9..d0fc9b66cd 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 @@ -26,7 +26,10 @@ import android.graphics.Color import android.graphics.Typeface import android.os.Bundle import android.os.Handler -import android.view.* +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.CompoundButton import android.widget.ImageView import android.widget.TextView @@ -68,6 +71,7 @@ import com.instructure.pandautils.features.help.HelpDialogFragment import com.instructure.pandautils.models.PushNotification import com.instructure.pandautils.receivers.PushExternalReceiver import com.instructure.pandautils.typeface.TypefaceBehavior +import com.instructure.pandautils.update.UpdateManager import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.dialog.BookmarkCreationDialog @@ -77,7 +81,10 @@ import com.instructure.student.fragment.* import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionUploadEffectHandler import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.emptySubmission.ui.SubmissionDetailsEmptyContentFragment import com.instructure.student.mobius.assignmentDetails.ui.AssignmentDetailsFragment -import com.instructure.student.navigation.* +import com.instructure.student.navigation.AccountMenuItem +import com.instructure.student.navigation.NavigationBehavior +import com.instructure.student.navigation.NavigationMenuItem +import com.instructure.student.navigation.OptionsMenuItem import com.instructure.student.router.RouteMatcher import com.instructure.student.router.RouteResolver import com.instructure.student.tasks.StudentLogoutTask @@ -94,6 +101,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import javax.inject.Inject + @AndroidEntryPoint @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.OnMasqueradingSet, @@ -109,6 +117,9 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. @Inject lateinit var typefaceBehavior: TypefaceBehavior + @Inject + lateinit var updateManager: UpdateManager + private var routeJob: WeaveJob? = null private var debounceJob: Job? = null private var drawerItemSelectedJob: Job? = null @@ -169,7 +180,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. R.id.navigationDrawerItem_stopMasquerading -> { MasqueradeHelper.stopMasquerading(startActivityClass) } - R.id.navigationDrawerSettings, R.id.navigationDrawerAccount -> startActivity(Intent(applicationContext, SettingsActivity::class.java)) + R.id.navigationDrawerSettings -> startActivity(Intent(applicationContext, SettingsActivity::class.java)) } } } @@ -194,6 +205,10 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. applyCurrentFragmentTheme() } + private fun checkAppUpdates() { + updateManager.checkForInAppUpdate(this) + } + private fun applyCurrentFragmentTheme() { Handler().post { (currentFragment as? FragmentInteractions)?.let { @@ -221,6 +236,8 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. appShortcutManager.make(this) setupNavDrawerItems() + + checkAppUpdates() } private fun setupNavDrawerItems() { @@ -234,7 +251,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. navigationDrawerItem_colorOverlay.setVisible(navigationBehavior.visibleOptionsMenuItems.contains(OptionsMenuItem.COLOR_OVERLAY)) optionsMenuItemsDivider.setVisible(navigationBehavior.visibleOptionsMenuItems.isNotEmpty()) - navigationDrawerAccount.setVisible(navigationBehavior.visibleAccountMenuItems.contains(AccountMenuItem.ACCOUNT)) navigationDrawerItem_help.setVisible(navigationBehavior.visibleAccountMenuItems.contains(AccountMenuItem.HELP)) navigationDrawerItem_changeUser.setVisible(navigationBehavior.visibleAccountMenuItems.contains(AccountMenuItem.CHANGE_USER)) navigationDrawerItem_logout.setVisible(navigationBehavior.visibleAccountMenuItems.contains(AccountMenuItem.LOGOUT)) @@ -408,7 +424,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. navigationDrawerItem_gauge.setOnClickListener(mNavigationDrawerItemClickListener) navigationDrawerItem_studio.setOnClickListener(mNavigationDrawerItemClickListener) navigationDrawerItem_bookmarks.setOnClickListener(mNavigationDrawerItemClickListener) - navigationDrawerAccount.setOnClickListener(mNavigationDrawerItemClickListener) navigationDrawerItem_changeUser.setOnClickListener(mNavigationDrawerItemClickListener) navigationDrawerItem_help.setOnClickListener(mNavigationDrawerItemClickListener) navigationDrawerItem_logout.setOnClickListener(mNavigationDrawerItemClickListener) diff --git a/apps/student/src/main/java/com/instructure/student/activity/SignInActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/SignInActivity.kt index 3f6e835fc7..51a93b3af9 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/SignInActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/SignInActivity.kt @@ -26,14 +26,14 @@ import com.instructure.student.widget.WidgetUpdater import com.instructure.canvasapi2.models.AccountDomain import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.loginapi.login.activities.BaseLoginSignInActivity -import com.instructure.pandautils.services.PushNotificationRegistrationService +import com.instructure.pandautils.services.PushNotificationRegistrationWorker import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class SignInActivity : BaseLoginSignInActivity() { override fun launchApplicationMainActivityIntent(): Intent { - PushNotificationRegistrationService.scheduleJob(this, ApiPrefs.isMasquerading) + PushNotificationRegistrationWorker.scheduleJob(this, ApiPrefs.isMasquerading) CookieManager.getInstance().flush() diff --git a/apps/student/src/main/java/com/instructure/student/adapter/AllCoursesRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/AllCoursesRecyclerAdapter.kt deleted file mode 100644 index e36ac0a836..0000000000 --- a/apps/student/src/main/java/com/instructure/student/adapter/AllCoursesRecyclerAdapter.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package com.instructure.student.adapter - -import android.app.Activity -import android.view.View -import android.widget.Toast -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.utils.APIHelper -import com.instructure.canvasapi2.utils.isInvited -import com.instructure.canvasapi2.utils.isNotDeleted -import com.instructure.canvasapi2.utils.weave.WeaveJob -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.student.R -import com.instructure.student.holders.CourseViewHolder -import com.instructure.student.interfaces.CourseAdapterToFragmentCallback - - -class AllCoursesRecyclerAdapter( - context: Activity, - private val mAdapterToFragmentCallback: CourseAdapterToFragmentCallback -) : BaseListRecyclerAdapter(context, Course::class.java) { - - init { - loadData() - } - - private var mApiCall: WeaveJob? = null - - override fun contextReady() = Unit - override fun setupCallbacks() = Unit - override fun itemLayoutResId(viewType: Int) = CourseViewHolder.HOLDER_RES_ID - override fun createViewHolder(v: View, viewType: Int) = CourseViewHolder(v) - - override fun bindHolder(model: Course, holder: CourseViewHolder, position: Int) { - holder.bind(model, mAdapterToFragmentCallback) - } - - override fun loadData() { - mApiCall?.cancel() - mApiCall = tryWeave { - val courses = awaitApi> { CourseManager.getCourses(isRefresh, it) } - .filter { !it.accessRestrictedByDate && !it.isInvited() && it.isNotDeleted() } - addAll(courses) - notifyDataSetChanged() - isAllPagesLoaded = true - if (itemCount == 0) adapterToRecyclerViewCallback.setIsEmpty(true) - mAdapterToFragmentCallback.onRefreshFinished() - } catch { - if (!APIHelper.hasNetworkConnection()) { - adapterToRecyclerViewCallback.setDisplayNoConnection(true) - } else { - adapterToRecyclerViewCallback.setIsEmpty(true) - Toast.makeText(context, R.string.errorOccurred, Toast.LENGTH_SHORT).show() - } - mAdapterToFragmentCallback.onRefreshFinished() - } - } - - override fun cancel() { - mApiCall?.cancel() - } - - override fun refresh() { - mApiCall?.cancel() - super.refresh() - } -} diff --git a/apps/student/src/main/java/com/instructure/student/adapter/EditFavoritesRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/EditFavoritesRecyclerAdapter.kt deleted file mode 100644 index 392ff643bc..0000000000 --- a/apps/student/src/main/java/com/instructure/student/adapter/EditFavoritesRecyclerAdapter.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.student.adapter - -import android.app.Activity -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.managers.GroupManager -import com.instructure.canvasapi2.models.CanvasComparable -import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.models.Group -import com.instructure.canvasapi2.utils.isInvited -import com.instructure.canvasapi2.utils.isNotDeleted -import com.instructure.canvasapi2.utils.weave.WeaveJob -import com.instructure.canvasapi2.utils.weave.awaitApis -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.pandarecycler.util.GroupSortedList -import com.instructure.student.holders.EditFavoritesCourseHeaderViewHolder -import com.instructure.student.holders.EditFavoritesCourseViewHolder -import com.instructure.student.holders.EditFavoritesGroupHeaderViewHolder -import com.instructure.student.holders.EditFavoritesGroupViewHolder -import com.instructure.student.interfaces.AdapterToFragmentCallback - -class EditFavoritesRecyclerAdapter( - context: Activity, - private val mAdapterToFragmentCallback: AdapterToFragmentCallback> -) : ExpandableRecyclerAdapter, RecyclerView.ViewHolder>( - context, - ItemType::class.java, - CanvasComparable::class.java -) { - - enum class ItemType { - COURSE_HEADER, - COURSE, - GROUP_HEADER, - GROUP - } - - private var mApiCalls: WeaveJob? = null - - init { - isExpandedByDefault = true - loadData() - } - - override fun createItemCallback(): GroupSortedList.ItemComparatorCallback> { - return object : GroupSortedList.ItemComparatorCallback> { - override fun compare(group: ItemType, o1: CanvasComparable<*>, o2: CanvasComparable<*>) = when { - o1 is Course && o2 is Course -> o1.compareTo(o2) - o1 is Group && o2 is Group -> o1.compareTo(o2) - else -> -1 - } - - override fun areContentsTheSame(oldItem: CanvasComparable<*>, newItem: CanvasComparable<*>) = false - - override fun areItemsTheSame(item1: CanvasComparable<*>, item2: CanvasComparable<*>) = when { - item1 is Course && item2 is Course -> item1.contextId.hashCode() == item2.contextId.hashCode() - item1 is Group && item2 is Group -> item1.contextId.hashCode() == item2.contextId.hashCode() - else -> false - } - - override fun getUniqueItemId(item: CanvasComparable<*>) = when (item) { - is Course -> item.contextId.hashCode().toLong() - is Group -> item.contextId.hashCode().toLong() - else -> -1L - } - - override fun getChildType(group: ItemType, item: CanvasComparable<*>) = when (item) { - is Course -> ItemType.COURSE.ordinal - is Group -> ItemType.GROUP.ordinal - else -> -1 - } - } - } - - override fun createGroupCallback(): GroupSortedList.GroupComparatorCallback { - return object : GroupSortedList.GroupComparatorCallback { - override fun compare(o1: ItemType, o2: ItemType) = o1.ordinal.compareTo(o2.ordinal) - override fun areContentsTheSame(oldGroup: ItemType, newGroup: ItemType) = oldGroup == newGroup - override fun areItemsTheSame(group1: ItemType, group2: ItemType) = group1 == group2 - override fun getUniqueGroupId(group: ItemType) = group.ordinal.toLong() - override fun getGroupType(group: ItemType) = group.ordinal - } - } - - override fun itemLayoutResId(viewType: Int): Int = when(ItemType.values()[viewType]) { - ItemType.COURSE_HEADER -> EditFavoritesCourseHeaderViewHolder.HOLDER_RES_ID - ItemType.COURSE -> EditFavoritesCourseViewHolder.HOLDER_RES_ID - ItemType.GROUP_HEADER -> EditFavoritesGroupHeaderViewHolder.HOLDER_RES_ID - ItemType.GROUP -> EditFavoritesGroupViewHolder.HOLDER_RES_ID - } - - override fun createViewHolder(v: View, viewType: Int) = when(ItemType.values()[viewType]) { - ItemType.COURSE_HEADER -> EditFavoritesCourseHeaderViewHolder(v) - ItemType.COURSE -> EditFavoritesCourseViewHolder(v) - ItemType.GROUP_HEADER -> EditFavoritesGroupHeaderViewHolder(v) - ItemType.GROUP -> EditFavoritesGroupViewHolder(v) - } - - override fun onBindChildHolder(holder: RecyclerView.ViewHolder, header: ItemType, item: CanvasComparable<*>) { - when { - holder is EditFavoritesCourseViewHolder && item is Course -> holder.bind(context,item, mAdapterToFragmentCallback) - holder is EditFavoritesGroupViewHolder && item is Group -> holder.bind(context, item, mAdapterToFragmentCallback) - } - } - - override fun onBindHeaderHolder(holder: RecyclerView.ViewHolder, header: ItemType, isExpanded: Boolean) = Unit - - override fun loadData() { - mApiCalls?.cancel() - mApiCalls = tryWeave { - val (rawCourses, rawGroups) = awaitApis,List>( - { CourseManager.getCourses(true, it) }, - { GroupManager.getAllGroups(it,true)}) - val validCourses = rawCourses.filter { !it.accessRestrictedByDate && !it.isInvited() && it.isNotDeleted() } - addOrUpdateAllItems(ItemType.COURSE_HEADER,validCourses) - val courseMap = rawCourses.associateBy { it.id } - val groups = rawGroups.filter { group -> group.isActive(courseMap[group.courseId]) } - - addOrUpdateAllItems(ItemType.GROUP_HEADER,groups) - notifyDataSetChanged() - isAllPagesLoaded = true - if (itemCount == 0) adapterToRecyclerViewCallback.setIsEmpty(true) - mAdapterToFragmentCallback.onRefreshFinished() - } catch { - onNoNetwork() - } - } - - override fun refresh() { - mApiCalls?.cancel() - super.refresh() - } - - override fun cancel() { - mApiCalls?.cancel() - } -} diff --git a/apps/student/src/main/java/com/instructure/student/di/HomeroomModule.kt b/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt similarity index 96% rename from apps/student/src/main/java/com/instructure/student/di/HomeroomModule.kt rename to apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt index 1eee7f24a4..9f97d2e0a0 100644 --- a/apps/student/src/main/java/com/instructure/student/di/HomeroomModule.kt +++ b/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt @@ -14,7 +14,7 @@ * along with this program. If not, see . * */ -package com.instructure.student.di +package com.instructure.student.di.elementary import androidx.fragment.app.FragmentActivity import com.instructure.pandautils.features.elementary.homeroom.HomeroomRouter diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt index 7ee833a399..936b4b7dc0 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt @@ -99,15 +99,24 @@ class ApplicationSettingsFragment : ParentFragment() { about.onClick { AlertDialog.Builder(requireContext()) - .setTitle(R.string.about) - .setView(R.layout.dialog_about) - .show() - .apply { - domain.text = ApiPrefs.domain - loginId.text = ApiPrefs.user!!.loginId - email.text = ApiPrefs.user!!.email ?: ApiPrefs.user!!.primaryEmail - version.text = "${getString(R.string.canvasVersionNum)} ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" - } + .setTitle(R.string.about) + .setView(R.layout.dialog_about) + .show() + .apply { + domain.text = ApiPrefs.domain + loginId.text = ApiPrefs.user!!.loginId + email.text = ApiPrefs.user!!.email ?: ApiPrefs.user!!.primaryEmail + version.text = "${getString(R.string.canvasVersionNum)} ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + } + } + + if (ApiPrefs.canvasForElementary) { + elementaryViewSwitch.isChecked = ApiPrefs.elementaryDashboardEnabledOverride + elementaryViewLayout.setVisible() + ViewStyler.themeSwitch(requireContext(), elementaryViewSwitch, ThemePrefs.brandColor) + elementaryViewSwitch.setOnCheckedChangeListener { _, isChecked -> + ApiPrefs.elementaryDashboardEnabledOverride = isChecked + } } if (BuildConfig.DEBUG) { 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 87511d502c..f586f055fd 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 @@ -748,7 +748,7 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { } private fun addAccessibilityButton() { - if (isAccessibilityEnabled() && discussionTopicHeader.htmlUrl != null) { + if (isAccessibilityEnabled(requireContext()) && discussionTopicHeader.htmlUrl != null) { alternateViewButton.visibility = View.VISIBLE alternateViewButton.setOnClickListener { RouteMatcher.route(requireActivity(), InternalWebviewFragment.makeRoute(canvasContext, discussionTopicHeader.htmlUrl!!, authenticate = true, shouldRouteInternally = false, allowRoutingTheSameUrlInternally = false, isUnsupportedFeature = false, allowUnsupportedRouting = false)) @@ -756,10 +756,6 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { } } - private fun isAccessibilityEnabled(): Boolean { - val am: AccessibilityManager? = requireContext().getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager? - return am?.isEnabled ?: false && am?.isTouchExplorationEnabled ?: false - } //endregion Functionality // region Bus Events diff --git a/apps/student/src/main/java/com/instructure/student/fragment/FlutterCalendarFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/FlutterCalendarFragment.kt index 77ab238315..3468d97569 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/FlutterCalendarFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/FlutterCalendarFragment.kt @@ -24,6 +24,7 @@ import com.google.gson.Gson import com.instructure.canvasapi2.models.PlannerItem import com.instructure.student.flutterChannels.FlutterComm import com.instructure.student.util.AppManager +import com.instructure.student.util.BaseAppManager import io.flutter.embedding.android.FlutterFragment import io.flutter.embedding.android.FlutterView import io.flutter.embedding.android.RenderMode @@ -36,7 +37,7 @@ class FlutterCalendarFragment : FlutterFragment() { var calendarScreenChannel = CalendarScreenChannel() var hidden: Boolean = false - override fun provideFlutterEngine(context: Context): FlutterEngine? = AppManager.flutterEngine + override fun provideFlutterEngine(context: Context): FlutterEngine? = BaseAppManager.flutterEngine // Use texture mode instead of surface mode so the FlutterView doesn't render on top of the nav drawer and a11y borders override fun getRenderMode() = RenderMode.texture @@ -88,7 +89,7 @@ class FlutterCalendarFragment : FlutterFragment() { return flutterViewField.get(delegate) as FlutterView } - if (resuming) getFlutterView().attachToFlutterEngine(AppManager.flutterEngine) + if (resuming) getFlutterView().attachToFlutterEngine(BaseAppManager.flutterEngine) val lifecycle1 = delegate::class.java.getDeclaredMethod(if (resuming) "onStart" else "onPause") lifecycle1.isAccessible = true @@ -123,7 +124,7 @@ class FlutterCalendarFragment : FlutterFragment() { class CalendarScreenChannel { val channelId: String = UUID.randomUUID().toString() - private val channel = MethodChannel(AppManager.flutterEngine.dartExecutor.binaryMessenger, channelId) + private val channel = MethodChannel(BaseAppManager.flutterEngine.dartExecutor.binaryMessenger, channelId) var onRouteToItem: ((item: PlannerItem) -> Unit)? = null diff --git a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt index 9b57f19a1e..6a4314cfad 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt @@ -355,13 +355,11 @@ class InboxComposeMessageFragment : ParentFragment() { private fun sendMessage(selectedRecipients: List, message: String) { - // Encode the message here, tell the api not to encode it - val formattedMessage = URLEncoder.encode(message, "UTF-8") sendCall = tryWeave { val attachmentIds = attachments.map { it.id }.toLongArray() val recipientIds = selectedRecipients.mapNotNull { it.stringId } val conversation = awaitApi { - InboxManager.addMessage(conversation?.id ?: 0, formattedMessage, recipientIds, includedMessageIds, attachmentIds, conversation?.contextCode, it) + InboxManager.addMessage(conversation?.id ?: 0, message, recipientIds, includedMessageIds, attachmentIds, conversation?.contextCode, it) } messageSuccess(conversation) } catch { @@ -372,13 +370,11 @@ class InboxComposeMessageFragment : ParentFragment() { private fun createConversation(selectedRecipients: List, message: String, subject: String, contextId: String, isBulk: Boolean) { sendCall?.cancel() - val formattedMessage = URLEncoder.encode(message, "UTF-8") - val formattedSubject = URLEncoder.encode(subject, "UTF-8") sendCall = tryWeave { val attachmentIds = attachments.map { it.id }.toLongArray() val recipientIds = selectedRecipients.mapNotNull { it.stringId } val conversation = awaitApi> { - InboxManager.createConversation(recipientIds, formattedMessage, formattedSubject, contextId, attachmentIds, isBulk, it) + InboxManager.createConversation(recipientIds, message, subject, contextId, attachmentIds, isBulk, it) }.first() messageSuccess(conversation) } catch { error -> 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 096df4382f..93aac69450 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 @@ -234,7 +234,7 @@ class NotificationListFragment : ParentFragment(), Bookmarkable { } COLLABORATION -> UnsupportedTabFragment.makeRoute(canvasContext, Tab.COLLABORATIONS_ID) CONFERENCE -> ConferenceListFragment.makeRoute(canvasContext) - else -> UnsupportedFeatureFragment.makeRoute(canvasContext, streamItem.type, streamItem.url ?: streamItem.htmlUrl) + else -> UnsupportedFeatureFragment.makeRoute(canvasContext, featureName = streamItem.type, url = streamItem.url ?: streamItem.htmlUrl) } if (route != null) RouteMatcher.route(context, route) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/UnsupportedFeatureFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/UnsupportedFeatureFragment.kt index c8c93f165d..a32fddca96 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/UnsupportedFeatureFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/UnsupportedFeatureFragment.kt @@ -32,12 +32,13 @@ open class UnsupportedFeatureFragment : ParentFragment() { private var canvasContext: CanvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) private var featureName by NullableStringArg(key = Const.FEATURE_NAME) + private var unsupportedDescription by NullableStringArg(key = Const.UNSUPPORTED_DESCRIPTION) private var url by NullableStringArg(key = Const.URL) override fun title(): String = getString(R.string.unsupported) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater?.inflate(R.layout.fragment_unsupported_feature, container, false) + return inflater.inflate(R.layout.fragment_unsupported_feature, container, false) } override fun applyTheme() { @@ -49,10 +50,14 @@ open class UnsupportedFeatureFragment : ParentFragment() { private fun initViews() { // Set the text - if (featureName != null) { - featureText.text = String.format(getString(R.string.isNotSupportedFeature), featureName) - } else { - featureText.text = getString(R.string.isNotSupported) + when { + unsupportedDescription != null -> featureText.text = unsupportedDescription + featureName != null -> featureText.text = String.format(getString(R.string.isNotSupportedFeature), featureName) + else -> featureText.text = getString(R.string.isNotSupported) + } + + if (url.isNullOrEmpty()) { + openInBrowser.setGone() } openInBrowser.setOnClickListener { @@ -72,10 +77,11 @@ open class UnsupportedFeatureFragment : ParentFragment() { companion object { - fun makeRoute(canvasContext: CanvasContext, title: String, url: String? = null): Route { + fun makeRoute(canvasContext: CanvasContext, featureName: String? = null, unsupportedDescription: String? = null, url: String? = null): Route { val bundle = Bundle().apply { putParcelable(Const.CANVAS_CONTEXT, canvasContext) - putString(Const.FEATURE_NAME, title) + putString(Const.FEATURE_NAME, featureName) + putString(Const.UNSUPPORTED_DESCRIPTION, unsupportedDescription) putString(Const.URL, url) } return Route(UnsupportedFeatureFragment::class.java, canvasContext, bundle) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsEffectHandler.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsEffectHandler.kt index 846e938216..f92d6fcbc5 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsEffectHandler.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/AssignmentDetailsEffectHandler.kt @@ -129,6 +129,7 @@ class AssignmentDetailsEffectHandler(val context: Context, val assignmentId: Lon Assignment.SubmissionType.ONLINE_UPLOAD -> view?.showFileUploadView(effect.assignment) Assignment.SubmissionType.ONLINE_TEXT_ENTRY -> view?.showOnlineTextEntryView(effect.assignment.id, effect.assignment.name) Assignment.SubmissionType.ONLINE_URL -> view?.showOnlineUrlEntryView(effect.assignment.id, effect.assignment.name, effect.course) + Assignment.SubmissionType.STUDENT_ANNOTATION -> view?.showStudentAnnotationView(effect.assignment.htmlUrl ?: "") Assignment.SubmissionType.EXTERNAL_TOOL, Assignment.SubmissionType.BASIC_LTI_LAUNCH -> view?.showLTIView(effect.course, effect.assignment.name ?: "", effect.ltiTool) else -> view?.showMediaRecordingView(effect.assignment) // Assignment.SubmissionType.MEDIA_RECORDING } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/SubmissionUtils.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/SubmissionUtils.kt index 0744fbdd3e..4477c4f70a 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/SubmissionUtils.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/SubmissionUtils.kt @@ -126,6 +126,7 @@ fun getSubmissionTypesVisibilities(assignment: Assignment, isStudioEnabled: Bool Assignment.SubmissionType.ONLINE_TEXT_ENTRY -> visibilities.textEntry = true Assignment.SubmissionType.ONLINE_URL -> visibilities.urlEntry = true Assignment.SubmissionType.MEDIA_RECORDING -> visibilities.mediaRecording = true + Assignment.SubmissionType.STUDENT_ANNOTATION -> visibilities.studentAnnotation = true } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsModels.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsModels.kt index 51899c51c3..9bfdb00661 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsModels.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsModels.kt @@ -89,4 +89,5 @@ sealed class SubmissionDetailsContentType { data class UrlContent(val url: String, val previewUrl: String?) : SubmissionDetailsContentType() data class DiscussionContent(val previewUrl: String?) : SubmissionDetailsContentType() object LockedContent : SubmissionDetailsContentType() + object StudentAnnotationContent : SubmissionDetailsContentType() } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsUpdate.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsUpdate.kt index 06569b44df..1fb6195f2b 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsUpdate.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/SubmissionDetailsUpdate.kt @@ -199,6 +199,8 @@ class SubmissionDetailsUpdate : UpdateInit SubmissionDetailsContentType.DiscussionContent(submission.previewUrl) + + Assignment.SubmissionType.STUDENT_ANNOTATION -> SubmissionDetailsContentType.StudentAnnotationContent else -> SubmissionDetailsContentType.UnsupportedContent(assignment?.id ?: -1) } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/SubmissionDetailsEmptyContentEffectHandler.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/SubmissionDetailsEmptyContentEffectHandler.kt index 975ab6bba0..6ac8123bae 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/SubmissionDetailsEmptyContentEffectHandler.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/SubmissionDetailsEmptyContentEffectHandler.kt @@ -67,6 +67,7 @@ class SubmissionDetailsEmptyContentEffectHandler(val context: Context, val assig Assignment.SubmissionType.ONLINE_UPLOAD -> view?.showFileUploadView(effect.assignment) Assignment.SubmissionType.ONLINE_TEXT_ENTRY -> view?.showOnlineTextEntryView(effect.assignment.id, effect.assignment.name) Assignment.SubmissionType.ONLINE_URL -> view?.showOnlineUrlEntryView(effect.assignment.id, effect.assignment.name, effect.course) + Assignment.SubmissionType.STUDENT_ANNOTATION -> view?.showStudentAnnotationView(effect.assignment.htmlUrl ?: "") Assignment.SubmissionType.EXTERNAL_TOOL, Assignment.SubmissionType.BASIC_LTI_LAUNCH -> view?.showLTIView(effect.course, title = effect.assignment.name ?: "", ltiTool = effect.ltiTool) else -> view?.showMediaRecordingView() // e.g. Assignment.SubmissionType.MEDIA_RECORDING } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/ui/SubmissionDetailsEmptyContentView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/ui/SubmissionDetailsEmptyContentView.kt index 3e289ac83a..85cccebcea 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/ui/SubmissionDetailsEmptyContentView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/emptySubmission/ui/SubmissionDetailsEmptyContentView.kt @@ -104,6 +104,9 @@ class SubmissionDetailsEmptyContentView( // The LTI info shouldn't be null if we are showing the Studio upload option showStudioUploadView(assignment, ltiToolUrl!!, ltiToolName!!) } + setupDialogRow(dialog, dialog.submissionEntryStudentAnnotation, visibilities.studentAnnotation) { + showStudentAnnotationView(assignment.htmlUrl ?: "") + } } dialog.show() } @@ -217,4 +220,10 @@ class SubmissionDetailsEmptyContentView( // Not run on main thread of fragment host by default, so force it to run on UI thread (context as Activity).runOnUiThread { (context as Activity).onBackPressed() } } + + fun showStudentAnnotationView(assignmentUrl: String) { + logEvent(AnalyticsEventConstants.SUBMIT_STUDENT_ANNOTATION_SELECTED) + RouteMatcher.route(context, + UnsupportedFeatureFragment.makeRoute(canvasContext, unsupportedDescription = context.getString(R.string.studentAnnotationUnsupportedDescription), url = assignmentUrl)) + } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/ui/SubmissionDetailsView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/ui/SubmissionDetailsView.kt index 5eb9f73bec..cf5c6499d5 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/ui/SubmissionDetailsView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/ui/SubmissionDetailsView.kt @@ -41,8 +41,8 @@ import com.instructure.pandautils.activities.BaseViewMediaActivity import com.instructure.pandautils.utils.* import com.instructure.pandautils.views.RecordingMediaType import com.instructure.student.R -import com.instructure.student.fragment.ViewUnsupportedFileFragment import com.instructure.student.fragment.ViewImageFragment +import com.instructure.student.fragment.ViewUnsupportedFileFragment import com.instructure.student.mobius.assignmentDetails.submissionDetails.SubmissionDetailsContentType import com.instructure.student.mobius.assignmentDetails.submissionDetails.SubmissionDetailsEvent import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.* @@ -95,6 +95,10 @@ class SubmissionDetailsView( drawerViewPager.adapter = drawerPagerAdapter configureDrawerTabLayout() configureSlidingPanelHeight() + + if (isAccessibilityEnabled(context)) { + slidingUpPanelLayout?.anchorPoint = 1.0f + } } private fun configureDrawerTabLayout() { @@ -287,6 +291,7 @@ class SubmissionDetailsView( SubmissionDetailsContentType.NoneContent -> SubmissionMessageFragment.newInstance(title = R.string.noOnlineSubmissions, subtitle = R.string.noneContentMessage) SubmissionDetailsContentType.OnPaperContent -> SubmissionMessageFragment.newInstance(title = R.string.noOnlineSubmissions, subtitle = R.string.onPaperContentMessage) SubmissionDetailsContentType.LockedContent -> SubmissionMessageFragment.newInstance(title = R.string.submissionDetailsAssignmentLocked, subtitle = R.string.could_not_route_locked) + SubmissionDetailsContentType.StudentAnnotationContent -> SubmissionMessageFragment.newInstance(title = R.string.unsupportedSubmissionType, message = R.string.studentAnnotationUnsupportedMessage) is SubmissionDetailsContentType.UnsupportedContent -> { // Users shouldn't get here, but we'll handle the case and send up some analytics if they do val bundle = Bundle().apply { diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsFragment.kt index d182c82af2..c75a3e7188 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsFragment.kt @@ -58,7 +58,7 @@ class AssignmentDetailsFragment : override fun makeUpdate() = AssignmentDetailsUpdate() override fun makeView(inflater: LayoutInflater, parent: ViewGroup) = - AssignmentDetailsView(canvasContext, isAccessibilityEnabled(), inflater, parent) + AssignmentDetailsView(canvasContext, isAccessibilityEnabled(requireContext()), inflater, parent) override fun makePresenter() = AssignmentDetailsPresenter @@ -66,11 +66,6 @@ class AssignmentDetailsFragment : override fun getExternalEventSources() = listOf(AssignmentDetailsEventBusSource()) - private fun isAccessibilityEnabled(): Boolean { - val am: AccessibilityManager? = requireContext().getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager? - return am?.isEnabled ?: false && am?.isTouchExplorationEnabled ?: false - } - companion object { const val VIDEO_REQUEST_CODE = 45519 const val CHOOSE_MEDIA_REQUEST_CODE = 45520 diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt index c6d26b3481..554e06d08a 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt @@ -97,8 +97,10 @@ class AssignmentDetailsView( constraintSet.clone(constraintParent) constraintSet.clear(submitButton.id, ConstraintSet.BOTTOM) constraintSet.clear(swipeRefreshLayout.id, ConstraintSet.TOP) + constraintSet.clear(swipeRefreshLayout.id, ConstraintSet.BOTTOM) constraintSet.connect(submitButton.id, ConstraintSet.TOP, toolbar.id, ConstraintSet.BOTTOM) constraintSet.connect(swipeRefreshLayout.id, ConstraintSet.TOP, submitButton.id, ConstraintSet.BOTTOM) + constraintSet.connect(swipeRefreshLayout.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) constraintSet.applyTo(constraintParent) } } @@ -275,6 +277,9 @@ class AssignmentDetailsView( // The LTI info shouldn't be null if we are showing the Studio upload option showStudioUploadView(assignment, ltiToolUrl!!, ltiToolName!!) } + setupDialogRow(dialog, dialog.submissionEntryStudentAnnotation, visibilities.studentAnnotation) { + showStudentAnnotationView(assignment.htmlUrl ?: "") + } } dialog.show() } @@ -396,6 +401,12 @@ class AssignmentDetailsView( RouteMatcher.route(context, StudioWebViewFragment.makeRoute(canvasContext, ltiUrl, studioLtiToolName, true, assignment)) } + fun showStudentAnnotationView(assignmentUrl: String) { + logEvent(AnalyticsEventConstants.SUBMIT_STUDENT_ANNOTATION_SELECTED) + RouteMatcher.route(context, + UnsupportedFeatureFragment.makeRoute(canvasContext, unsupportedDescription = context.getString(R.string.studentAnnotationUnsupportedDescription), url = assignmentUrl)) + } + fun showQuizOrDiscussionView(url: String) { if (!RouteMatcher.canRouteInternally(context, url, ApiPrefs.domain, true)) { val intent = Intent(context, InternalWebViewActivity::class.java) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsViewState.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsViewState.kt index b6ac4de1cf..9c9984b8fa 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsViewState.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsViewState.kt @@ -79,7 +79,8 @@ data class SubmissionTypesVisibilities( var urlEntry: Boolean = false, var fileUpload: Boolean = false, var mediaRecording: Boolean = false, - var studioUpload: Boolean = false + var studioUpload: Boolean = false, + var studentAnnotation: Boolean = false ) data class QuizDescriptionViewState( diff --git a/apps/student/src/main/java/com/instructure/student/mobius/elementary/ElementaryDashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/elementary/ElementaryDashboardFragment.kt index 6b32f2adeb..2304999645 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/elementary/ElementaryDashboardFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/elementary/ElementaryDashboardFragment.kt @@ -24,12 +24,10 @@ import com.google.android.material.tabs.TabLayout import com.instructure.canvasapi2.models.CanvasContext import com.instructure.interactions.router.Route import com.instructure.pandautils.features.elementary.ElementaryDashboardPagerAdapter -import com.instructure.pandautils.utils.Const -import com.instructure.pandautils.utils.ParcelableArg -import com.instructure.pandautils.utils.isTablet -import com.instructure.pandautils.utils.makeBundle +import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.fragment.ParentFragment +import com.instructure.student.util.FeatureFlagPrefs import kotlinx.android.synthetic.main.fragment_course_grid.toolbar import kotlinx.android.synthetic.main.fragment_elementary_dashboard.* @@ -49,6 +47,13 @@ class ElementaryDashboardFragment : ParentFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + if (!FeatureFlagPrefs.showInProgressK5Tabs) { + dashboardTabLayout.removeTabAt(3) + dashboardTabLayout.removeTabAt(2) + dashboardTabLayout.removeTabAt(1) + } + dashboardPager.adapter = ElementaryDashboardPagerAdapter(canvasContext, childFragmentManager) dashboardTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabReselected(tab: TabLayout.Tab?) = Unit @@ -64,6 +69,13 @@ class ElementaryDashboardFragment : ParentFragment() { }) } + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (!hidden) { + (dashboardPager?.adapter as? ElementaryDashboardPagerAdapter)?.refreshHomeroomAssignments() + } + } + companion object { fun newInstance(route: Route) = ElementaryDashboardFragment().apply { diff --git a/apps/student/src/main/java/com/instructure/student/mobius/elementary/StudentHomeroomRouter.kt b/apps/student/src/main/java/com/instructure/student/mobius/elementary/StudentHomeroomRouter.kt index 53d73d0ac4..205df4e6f0 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/elementary/StudentHomeroomRouter.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/elementary/StudentHomeroomRouter.kt @@ -18,9 +18,15 @@ package com.instructure.student.mobius.elementary import androidx.fragment.app.FragmentActivity import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.features.elementary.homeroom.HomeroomRouter +import com.instructure.student.flutterChannels.FlutterComm import com.instructure.student.fragment.AnnouncementListFragment +import com.instructure.student.fragment.AssignmentListFragment +import com.instructure.student.fragment.CourseBrowserFragment +import com.instructure.student.fragment.DiscussionDetailsFragment import com.instructure.student.router.RouteMatcher class StudentHomeroomRouter(private val activity: FragmentActivity) : HomeroomRouter { @@ -41,4 +47,20 @@ class StudentHomeroomRouter(private val activity: FragmentActivity) : HomeroomRo val route = AnnouncementListFragment.makeRoute(canvasContext) RouteMatcher.route(activity, route) } + + override fun openCourse(course: Course) { + RouteMatcher.route(activity, CourseBrowserFragment.makeRoute(course)) + } + + override fun openAssignments(course: Course) { + RouteMatcher.route(activity, AssignmentListFragment.makeRoute(course)) + } + + override fun openAnnouncementDetails(course: Course, announcement: DiscussionTopicHeader) { + RouteMatcher.route(activity, DiscussionDetailsFragment.makeRoute(course, announcement)) + } + + override fun updateColors() { + FlutterComm.sendUpdatedTheme() + } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt index c63307d4b3..3f2efbc00e 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt @@ -34,11 +34,11 @@ class ElementaryNavigationBehavior() : NavigationBehavior { override val homeFragmentClass: Class = ElementaryDashboardFragment::class.java - override val visibleNavigationMenuItems: Set = emptySet() + override val visibleNavigationMenuItems: Set = setOf(NavigationMenuItem.FILES, NavigationMenuItem.SETTINGS) override val visibleOptionsMenuItems: Set = emptySet() - override val visibleAccountMenuItems: Set = setOf(AccountMenuItem.ACCOUNT, AccountMenuItem.HELP, AccountMenuItem.LOGOUT) + override val visibleAccountMenuItems: Set = setOf(AccountMenuItem.HELP, AccountMenuItem.CHANGE_USER, AccountMenuItem.LOGOUT) override val shouldOverrideFont: Boolean get() = true diff --git a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt index 9eb5f5c4ea..d256245e6f 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt @@ -49,5 +49,5 @@ enum class OptionsMenuItem { } enum class AccountMenuItem { - ACCOUNT, HELP, CHANGE_USER, LOGOUT + HELP, CHANGE_USER, LOGOUT } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/util/AppManager.kt b/apps/student/src/main/java/com/instructure/student/util/AppManager.kt index e6f80535b4..549c7cfde6 100644 --- a/apps/student/src/main/java/com/instructure/student/util/AppManager.kt +++ b/apps/student/src/main/java/com/instructure/student/util/AppManager.kt @@ -50,181 +50,17 @@ import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint import javax.inject.Inject @HiltAndroidApp -class AppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling { +class AppManager : BaseAppManager() { @Inject lateinit var typefaceBehavior: TypefaceBehavior - // To enable debug logging use: adb shell setprop log.tag.GAv4 DEBUG - private val defaultTracker: Tracker by lazy { - val analytics = GoogleAnalytics.getInstance(this) - analytics.newTracker(R.xml.analytics) - } - override fun onCreate() { - if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) { - // Skip app initialization. - return - } super.onCreate() - - // Call it superstition, but I don't trust BuildConfig flags to be set correctly - // in library builds. IS_TESTING, for example, does not percolate down to libraries - // correctly. So I'm reading/setting these user properties here instead of canvasapi2/AppManager. - Analytics.setUserProperty(USER_PROPERTY_BUILD_TYPE, if(BuildConfig.DEBUG) "debug" else "release") - Analytics.setUserProperty(USER_PROPERTY_OS_VERSION, Build.VERSION.SDK_INT.toString()) - - // Hold off on initializing this until we emit the user properties. - RemoteConfigUtils.initialize() - - initPSPDFKit() - - if (BuildConfig.DEBUG) { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) - } else { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) - } - MasqueradeHelper.masqueradeLogoutTask = Runnable { StudentLogoutTask(LogoutTask.Type.LOGOUT, typefaceBehavior = typefaceBehavior).execute() } - - ColorKeeper.defaultColor = ContextCompat.getColor(this, R.color.defaultPrimary) - - // There appears to be a bug when the user is installing/updating the android webview stuff. - // http://code.google.com/p/android/issues/detail?id=175124 - try { - WebView.setWebContentsDebuggingEnabled(true) - } catch (e: Exception) { - FirebaseCrashlytics.getInstance().log("Exception trying to setWebContentsDebuggingEnabled") - } - - PageViewUploadService.schedule(this, StudentPageViewService::class.java) - - initFlutterEngine() - } - - private fun initFlutterEngine() { - flutterEngine = FlutterEngine(this) - - FlutterComm.init(flutterEngine, applicationContext) - - // Execute the 'main' entrypoint - flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint.createDefault()) - - // Cache the FlutterEngine - FlutterEngineCache.getInstance().put(FLUTTER_ENGINE_ID, flutterEngine) - } - - override fun onCanvasTokenRefreshed() = FlutterComm.sendUpdatedLogin() - - override fun trackButtonPressed(buttonName: String?, buttonValue: Long?) { - if (buttonName == null) return - - if (buttonValue == null) { - defaultTracker.send( - HitBuilders.EventBuilder() - .setCategory("UI Actions") - .setAction("Button Pressed") - .setLabel(buttonName) - .build() - ) - } else { - defaultTracker.send( - HitBuilders.EventBuilder() - .setCategory("UI Actions") - .setAction("Button Pressed") - .setLabel(buttonName) - .setValue(buttonValue) - .build() - ) - } - } - - override fun trackScreen(screenName: String?) { - if (screenName == null) return - - val tracker = defaultTracker - tracker.setScreenName(screenName) - tracker.send(HitBuilders.ScreenViewBuilder().build()) - } - - override fun trackEnrollment(enrollmentType: String?) { - if (enrollmentType == null) return - - defaultTracker.send( - HitBuilders.AppViewBuilder() - .setCustomDimension(1, enrollmentType) - .build() - ) - } - - override fun trackDomain(domain: String?) { - if (domain == null) return - - defaultTracker.send( - HitBuilders.AppViewBuilder() - .setCustomDimension(2, domain) - .build() - ) - } - - override fun trackEvent(category: String?, action: String?, label: String?, value: Long) { - if (category == null || action == null || label == null) return - - val tracker = defaultTracker - tracker.send( - HitBuilders.EventBuilder() - .setCategory(category) - .setAction(action) - .setLabel(label) - .setValue(value) - .build() - ) - } - - override fun trackUIEvent(action: String?, label: String?, value: Long) { - if (action == null || label == null) return - - defaultTracker.send( - HitBuilders.EventBuilder() - .setAction(action) - .setLabel(label) - .setValue(value) - .build() - ) - } - - override fun trackTiming(category: String?, name: String?, label: String?, duration: Long) { - if (category == null || name == null || label == null) return - - val tracker = defaultTracker - tracker.send( - HitBuilders.TimingBuilder() - .setCategory(category) - .setLabel(label) - .setVariable(name) - .setValue(duration) - .build() - ) - } - - private fun initPSPDFKit() { - try { - PSPDFKit.initialize(this, BuildConfig.PSPDFKIT_LICENSE_KEY) - } catch (e: PSPDFKitInitializationFailedException) { - Logger.e("Current device is not compatible with PSPDFKIT!") - } catch (e: InvalidPSPDFKitLicenseException) { - Logger.e("Invalid or Trial PSPDFKIT License!") - } } override fun performLogoutOnAuthError() { StudentLogoutTask(LogoutTask.Type.LOGOUT, typefaceBehavior = typefaceBehavior).execute() } - - companion object { - private const val FLUTTER_ENGINE_ID = "flutter_engine_embed" - - lateinit var flutterEngine: FlutterEngine - } - } diff --git a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt new file mode 100644 index 0000000000..655aceeebf --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.instructure.student.util + +import android.os.Build +import android.webkit.WebView +import androidx.core.content.ContextCompat +import com.google.android.gms.analytics.GoogleAnalytics +import com.google.android.gms.analytics.HitBuilders +import com.google.android.gms.analytics.Tracker +import com.google.android.play.core.missingsplits.MissingSplitsManagerFactory +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.utils.Analytics +import com.instructure.canvasapi2.utils.pageview.PageViewUploadService +import com.instructure.loginapi.login.tasks.LogoutTask +import com.instructure.pandautils.typeface.TypefaceBehavior +import com.instructure.pandautils.utils.ColorKeeper +import com.instructure.student.BuildConfig +import com.instructure.student.R +import com.instructure.student.flutterChannels.FlutterComm +import com.instructure.student.service.StudentPageViewService +import com.instructure.student.tasks.StudentLogoutTask +import com.pspdfkit.PSPDFKit +import com.pspdfkit.exceptions.InvalidPSPDFKitLicenseException +import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException +import dagger.hilt.EntryPoint +import dagger.hilt.EntryPoints +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.embedding.engine.dart.DartExecutor +import javax.inject.Inject + +open class BaseAppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling { + + // To enable debug logging use: adb shell setprop log.tag.GAv4 DEBUG + private val defaultTracker: Tracker by lazy { + val analytics = GoogleAnalytics.getInstance(this) + analytics.newTracker(R.xml.analytics) + } + + override fun onCreate() { + if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) { + // Skip app initialization. + return + } + super.onCreate() + + // Call it superstition, but I don't trust BuildConfig flags to be set correctly + // in library builds. IS_TESTING, for example, does not percolate down to libraries + // correctly. So I'm reading/setting these user properties here instead of canvasapi2/AppManager. + Analytics.setUserProperty(AnalyticsEventConstants.USER_PROPERTY_BUILD_TYPE, if(BuildConfig.DEBUG) "debug" else "release") + Analytics.setUserProperty(AnalyticsEventConstants.USER_PROPERTY_OS_VERSION, Build.VERSION.SDK_INT.toString()) + + // Hold off on initializing this until we emit the user properties. + RemoteConfigUtils.initialize() + + initPSPDFKit() + + if (BuildConfig.DEBUG) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) + } else { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) + } + + ColorKeeper.defaultColor = ContextCompat.getColor(this, R.color.defaultPrimary) + + // There appears to be a bug when the user is installing/updating the android webview stuff. + // http://code.google.com/p/android/issues/detail?id=175124 + try { + WebView.setWebContentsDebuggingEnabled(true) + } catch (e: Exception) { + FirebaseCrashlytics.getInstance().log("Exception trying to setWebContentsDebuggingEnabled") + } + + PageViewUploadService.schedule(this, StudentPageViewService::class.java) + + initFlutterEngine() + } + + private fun initFlutterEngine() { + flutterEngine = FlutterEngine(this) + + FlutterComm.init(flutterEngine, applicationContext) + + // Execute the 'main' entrypoint + flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()) + + // Cache the FlutterEngine + FlutterEngineCache.getInstance().put(FLUTTER_ENGINE_ID, flutterEngine) + } + + override fun onCanvasTokenRefreshed() = FlutterComm.sendUpdatedLogin() + + override fun trackButtonPressed(buttonName: String?, buttonValue: Long?) { + if (buttonName == null) return + + if (buttonValue == null) { + defaultTracker.send( + HitBuilders.EventBuilder() + .setCategory("UI Actions") + .setAction("Button Pressed") + .setLabel(buttonName) + .build() + ) + } else { + defaultTracker.send( + HitBuilders.EventBuilder() + .setCategory("UI Actions") + .setAction("Button Pressed") + .setLabel(buttonName) + .setValue(buttonValue) + .build() + ) + } + } + + override fun trackScreen(screenName: String?) { + if (screenName == null) return + + val tracker = defaultTracker + tracker.setScreenName(screenName) + tracker.send(HitBuilders.ScreenViewBuilder().build()) + } + + override fun trackEnrollment(enrollmentType: String?) { + if (enrollmentType == null) return + + defaultTracker.send( + HitBuilders.AppViewBuilder() + .setCustomDimension(1, enrollmentType) + .build() + ) + } + + override fun trackDomain(domain: String?) { + if (domain == null) return + + defaultTracker.send( + HitBuilders.AppViewBuilder() + .setCustomDimension(2, domain) + .build() + ) + } + + override fun trackEvent(category: String?, action: String?, label: String?, value: Long) { + if (category == null || action == null || label == null) return + + val tracker = defaultTracker + tracker.send( + HitBuilders.EventBuilder() + .setCategory(category) + .setAction(action) + .setLabel(label) + .setValue(value) + .build() + ) + } + + override fun trackUIEvent(action: String?, label: String?, value: Long) { + if (action == null || label == null) return + + defaultTracker.send( + HitBuilders.EventBuilder() + .setAction(action) + .setLabel(label) + .setValue(value) + .build() + ) + } + + override fun trackTiming(category: String?, name: String?, label: String?, duration: Long) { + if (category == null || name == null || label == null) return + + val tracker = defaultTracker + tracker.send( + HitBuilders.TimingBuilder() + .setCategory(category) + .setLabel(label) + .setVariable(name) + .setValue(duration) + .build() + ) + } + + private fun initPSPDFKit() { + try { + PSPDFKit.initialize(this, BuildConfig.PSPDFKIT_LICENSE_KEY) + } catch (e: PSPDFKitInitializationFailedException) { + Logger.e("Current device is not compatible with PSPDFKIT!") + } catch (e: InvalidPSPDFKitLicenseException) { + Logger.e("Invalid or Trial PSPDFKIT License!") + } + } + + override fun performLogoutOnAuthError() = Unit + + companion object { + private const val FLUTTER_ENGINE_ID = "flutter_engine_embed" + + lateinit var flutterEngine: FlutterEngine + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/util/FeatureFlagPrefs.kt b/apps/student/src/main/java/com/instructure/student/util/FeatureFlagPrefs.kt index 0074090c23..cecc7965d6 100644 --- a/apps/student/src/main/java/com/instructure/student/util/FeatureFlagPrefs.kt +++ b/apps/student/src/main/java/com/instructure/student/util/FeatureFlagPrefs.kt @@ -15,7 +15,10 @@ */ package com.instructure.student.util +import com.instructure.canvasapi2.utils.FeatureFlagPref import com.instructure.canvasapi2.utils.PrefManager object FeatureFlagPrefs : PrefManager("feature_flags") { + + var showInProgressK5Tabs by FeatureFlagPref("Show K5 in progress tabs") } diff --git a/apps/student/src/main/res/layout-sw720dp/fragment_elementary_dashboard.xml b/apps/student/src/main/res/layout-sw720dp/fragment_elementary_dashboard.xml index 2f8ff4e704..26b1b51ce8 100644 --- a/apps/student/src/main/res/layout-sw720dp/fragment_elementary_dashboard.xml +++ b/apps/student/src/main/res/layout-sw720dp/fragment_elementary_dashboard.xml @@ -17,6 +17,7 @@ diff --git a/apps/student/src/main/res/layout/dialog_submission_picker.xml b/apps/student/src/main/res/layout/dialog_submission_picker.xml index 159b947a8a..0e924df377 100644 --- a/apps/student/src/main/res/layout/dialog_submission_picker.xml +++ b/apps/student/src/main/res/layout/dialog_submission_picker.xml @@ -121,4 +121,24 @@ + + + + + + + + diff --git a/apps/student/src/main/res/layout/fragment_application_settings.xml b/apps/student/src/main/res/layout/fragment_application_settings.xml index d60114002d..4578a5b57d 100644 --- a/apps/student/src/main/res/layout/fragment_application_settings.xml +++ b/apps/student/src/main/res/layout/fragment_application_settings.xml @@ -48,6 +48,54 @@ android:animateLayoutChanges="true" android:orientation="vertical"> + + + + + + + + + + diff --git a/apps/student/src/main/res/layout/fragment_submission_message.xml b/apps/student/src/main/res/layout/fragment_submission_message.xml index 9f25c75302..17fd3d28a5 100644 --- a/apps/student/src/main/res/layout/fragment_submission_message.xml +++ b/apps/student/src/main/res/layout/fragment_submission_message.xml @@ -43,6 +43,7 @@ android:textColor="#2D3B45" android:textSize="24sp" android:visibility="gone" + android:gravity="center_horizontal" app:layout_constraintBottom_toTopOf="@+id/subtitleTextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/apps/student/src/main/res/layout/fragment_unsupported_feature.xml b/apps/student/src/main/res/layout/fragment_unsupported_feature.xml index 4ec7c9a460..54cce10f9d 100644 --- a/apps/student/src/main/res/layout/fragment_unsupported_feature.xml +++ b/apps/student/src/main/res/layout/fragment_unsupported_feature.xml @@ -42,6 +42,7 @@ android:layout_marginTop="32dp" android:layout_marginEnd="24dp" android:layout_marginStart="24dp" + android:gravity="center_horizontal" android:layout_gravity="center_horizontal"/>