diff --git a/apps/flutter_parent/lib/l10n/res/intl_sv.arb b/apps/flutter_parent/lib/l10n/res/intl_sv.arb
index cf3e28412c..4d3578f905 100644
--- a/apps/flutter_parent/lib/l10n/res/intl_sv.arb
+++ b/apps/flutter_parent/lib/l10n/res/intl_sv.arb
@@ -244,7 +244,7 @@
"type": "text",
"placeholders": {}
},
- "domainSearchHelpBody": "Försök med att söka efter namnet på skolan eller distrikten du vill ansluta till, t.ex. “Allmänna skolan” eller “Skolor i Skåne”. Du kan även ange en Canvas-domän direkt, t.ex. “smith.instructure.com.”\n\nMer information om hur du kan hitta din institutions Canvas-konto finns på {canvasGuides} eller kontakta {canvasSupport} eller din skola för att få hjälp.",
+ "domainSearchHelpBody": "Försök med att söka efter namnet på skolan eller distrikten du vill ansluta till, t.ex. “Allmänna skolan” eller “Skolor i Skåne”. Du kan även ange en Canvas-domän direkt, t.ex. “smith.instructure.com.”\n\nMer information om hur du kan hitta din lärosätes Canvas-konto finns på {canvasGuides} eller kontakta {canvasSupport} eller din skola för att få hjälp.",
"@domainSearchHelpBody": {
"description": "The body text shown in the help dialog on the domain search screen",
"type": "text",
@@ -511,7 +511,7 @@
"howMany": {}
}
},
- "Download": "Ladda ned",
+ "Download": "Ladda ner",
"@Download": {
"description": "Label for the button that will begin downloading a file",
"type": "text",
@@ -965,7 +965,7 @@
"type": "text",
"placeholders": {}
},
- "Institution Announcement": "Institutionsmeddelande",
+ "Institution Announcement": "Meddelande från lärosätet",
"@Institution Announcement": {
"description": "Title for alerts when there is an institution announcement",
"type": "text",
@@ -1081,7 +1081,7 @@
"type": "text",
"placeholders": {}
},
- "Incomplete": "ofullständig",
+ "Incomplete": "Inte färdig",
"@Incomplete": {
"description": "Grading status for an assignment marked as incomplete",
"type": "text",
@@ -1154,7 +1154,7 @@
"type": "text",
"placeholders": {}
},
- "Institution Announcements": "Institutionsannonseringar",
+ "Institution Announcements": "Meddelande från lärosätet",
"@Institution Announcements": {
"type": "text",
"placeholders": {}
@@ -2025,7 +2025,7 @@
"type": "text",
"placeholders": {}
},
- "Interactions on this page are limited by your institution.": "Interaktioner på den här sidan har begränsats av din institution.",
+ "Interactions on this page are limited by your institution.": "Interaktioner på den här sidan har begränsats av ditt lärosäte.",
"@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",
@@ -2128,7 +2128,7 @@
"type": "text",
"placeholders": {}
},
- "We are unable to display this link, it may belong to an institution you currently aren't logged in to.": "Det går inte att visa den här länken. Den kan tillhöra en institution du för närvarande inte är inloggad på.",
+ "We are unable to display this link, it may belong to an institution you currently aren't logged in to.": "Det går inte att visa den här länken. Den kan tillhöra ett lärosäte du för närvarande inte är inloggad på.",
"@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",
diff --git a/apps/flutter_parent/lib/l10n/res/intl_sv_instk12.arb b/apps/flutter_parent/lib/l10n/res/intl_sv_instk12.arb
index 881ba02e32..2733f68557 100644
--- a/apps/flutter_parent/lib/l10n/res/intl_sv_instk12.arb
+++ b/apps/flutter_parent/lib/l10n/res/intl_sv_instk12.arb
@@ -511,7 +511,7 @@
"howMany": {}
}
},
- "Download": "Ladda ned",
+ "Download": "Ladda ner",
"@Download": {
"description": "Label for the button that will begin downloading a file",
"type": "text",
diff --git a/apps/student/build.gradle b/apps/student/build.gradle
index 966a9784d0..eb7d68d8d1 100644
--- a/apps/student/build.gradle
+++ b/apps/student/build.gradle
@@ -26,7 +26,7 @@ apply from: '../../gradle/coverage.gradle'
apply plugin: 'com.squareup.sqldelight'
apply plugin: 'dagger.hilt.android.plugin'
-def updatePriority = 0
+def updatePriority = 2
def coverageEnabled = project.hasProperty('coverage')
if (coverageEnabled) {
@@ -59,8 +59,8 @@ android {
applicationId "com.instructure.candroid"
minSdkVersion Versions.MIN_SDK
targetSdkVersion Versions.TARGET_SDK
- versionCode = 228
- versionName = '6.12.0'
+ versionCode = 230
+ versionName = '6.14.0'
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
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
index dab48f2c11..1e82f452bb 100644
--- 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
@@ -89,7 +89,6 @@ class ElementaryDashboardInteractionTest : StudentTest() {
// 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,
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt
new file mode 100644
index 0000000000..4a384575d3
--- /dev/null
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.containsTextCaseInsensitive
+import com.instructure.canvas.espresso.mockCanvas.MockCanvas
+import com.instructure.canvas.espresso.mockCanvas.addCourseWithEnrollment
+import com.instructure.canvas.espresso.mockCanvas.init
+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 GradesInteractionTest : StudentTest() {
+
+ override fun displaysPageObjects() = Unit
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testShowGrades() {
+ val data = createMockData(courseCount = 3)
+ goToGrades(data)
+
+ gradesPage.assertPageObjects()
+
+ data.courses.forEach {
+ gradesPage.assertCourseShownWithGrades(it.value.name, "B+")
+ }
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testRefresh() {
+ val data = createMockData(courseCount = 3)
+ goToGrades(data)
+
+ gradesPage.assertPageObjects()
+
+ data.courses.forEach {
+ gradesPage.assertCourseShownWithGrades(it.value.name, "B+")
+ }
+
+ val newCourse = data.addCourseWithEnrollment(data.students[0], Enrollment.EnrollmentType.Student, 50.0)
+
+ gradesPage.refresh()
+
+ gradesPage.assertCourseShownWithGrades(newCourse.name, "50%")
+ }
+
+ @Test
+ @TestMetaData(Priority.P2, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testEmptyView() {
+ val data = createMockData(homeroomCourseCount = 1)
+ goToGrades(data)
+
+ gradesPage.assertEmptyViewVisible()
+ gradesPage.assertRecyclerViewNotVisible()
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOpenCourseGrades() {
+ val data = createMockData(courseCount = 3)
+ goToGrades(data)
+
+ val course = data.courses.values.first()
+
+ gradesPage.clickGradeRow(course.name)
+ courseGradesPage.assertPageObjects()
+ courseGradesPage.assertTotalGrade(containsTextCaseInsensitive("B+"))
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testChangeGradingPeriod() {
+ val data = createMockData(courseCount = 3, withGradingPeriods = true)
+ goToGrades(data)
+
+ gradesPage.assertSelectedGradingPeriod(gradesPage.getStringFromResource(R.string.currentGradingPeriod))
+ gradesPage.clickGradingPeriodSelector()
+
+ val gradingPeriod = data.courseGradingPeriods.values.first().first()
+ gradesPage.selectGradingPeriod(gradingPeriod.title!!)
+ gradesPage.assertSelectedGradingPeriod(gradingPeriod.title!!)
+ }
+
+ private fun createMockData(
+ courseCount: Int = 0,
+ withGradingPeriods: Boolean = false,
+ homeroomCourseCount: 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")
+
+ return MockCanvas.init(
+ studentCount = 1,
+ courseCount = courseCount,
+ withGradingPeriods = withGradingPeriods,
+ homeroomCourseCount = homeroomCourseCount)
+ }
+
+ private fun goToGrades(data: MockCanvas) {
+ val student = data.students[0]
+ val token = data.tokenFor(student)!!
+ tokenLoginElementary(data.domain, token, student)
+ elementaryDashboardPage.waitForRender()
+ elementaryDashboardPage.selectGradesTab()
+ }
+}
\ No newline at end of file
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
index 4f3160188b..117e1276aa 100644
--- 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
@@ -205,7 +205,7 @@ class HomeroomInteractionTest : StudentTest() {
homeroomPage.assertPageObjects()
- homeroomPage.openCourseAnnouncment(courseAnnouncement.title!!)
+ homeroomPage.openCourseAnnouncement(courseAnnouncement.title!!)
discussionDetailsPage.assertPageObjects()
discussionDetailsPage.assertTitleText(courseAnnouncement.title!!)
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt
new file mode 100644
index 0000000000..2d6d90176b
--- /dev/null
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt
@@ -0,0 +1,266 @@
+/*
+ * 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.*
+import com.instructure.canvasapi2.models.Enrollment
+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 ResourcesInteractionTest : StudentTest() {
+
+ override fun displaysPageObjects() = Unit
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testImportantLinksAndActionItemsShowUpInResourcesScreen() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2)
+
+ val homeroomCourse = data.courses.values.first { it.homeroomCourse }
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "Important links content")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ goToResources(data)
+
+ resourcesPage.assertPageObjects()
+ resourcesPage.assertImportantLinksDisplayed(courseWithSyllabus.syllabusBody!!)
+
+ resourcesPage.assertStudentApplicationsHeaderDisplayed()
+ resourcesPage.assertLtiToolDisplayed("Google Drive")
+ resourcesPage.assertLtiToolDisplayed("Media Gallery")
+
+ val teacher = data.teachers[0]
+ resourcesPage.assertStaffInfoHeaderDisplayed()
+ resourcesPage.assertStaffDisplayed(teacher.shortName!!)
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testImportantLinksForTwoCourses() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2)
+
+ val homeroomCourse = data.courses.values.first { it.homeroomCourse }
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "Important links content")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val homeroomCourse2 = data.addCourseWithEnrollment(data.students[0], Enrollment.EnrollmentType.Student, isHomeroom = true)
+ data.addEnrollment(data.teachers[0], homeroomCourse, Enrollment.EnrollmentType.Teacher)
+
+ val courseWithSyllabus2 = homeroomCourse2.copy(syllabusBody = "Important links 2")
+ data.courses[homeroomCourse2.id] = courseWithSyllabus2
+
+ goToResources(data)
+
+ resourcesPage.assertPageObjects()
+
+ // We only assert the course names, because can't differentiate between the two WebViews.
+ resourcesPage.assertCourseNameDisplayed(courseWithSyllabus.name)
+ resourcesPage.assertCourseNameDisplayed(courseWithSyllabus2.name)
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOnlyActionItemsShowIfSyllabusIsEmpty() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2)
+
+ val homeroomCourse = data.courses.values.first { it.homeroomCourse }
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ goToResources(data)
+
+ resourcesPage.assertImportantLinksNotDisplayed()
+
+ resourcesPage.assertStudentApplicationsHeaderDisplayed()
+ resourcesPage.assertLtiToolDisplayed("Google Drive")
+ resourcesPage.assertLtiToolDisplayed("Media Gallery")
+
+ val teacher = data.teachers[0]
+ resourcesPage.assertStaffInfoHeaderDisplayed()
+ resourcesPage.assertStaffDisplayed(teacher.shortName!!)
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOnlyLtiToolsShowIfNoHomeroomCourse() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2, homeroomCourseCount = 0)
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ goToResources(data)
+
+ resourcesPage.assertImportantLinksNotDisplayed()
+
+ resourcesPage.assertStudentApplicationsHeaderDisplayed()
+ resourcesPage.assertLtiToolDisplayed("Google Drive")
+ resourcesPage.assertLtiToolDisplayed("Media Gallery")
+
+ resourcesPage.assertStaffInfoNotDisplayed()
+ }
+
+ @Test
+ @TestMetaData(Priority.P2, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testEmptyState() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2, homeroomCourseCount = 0)
+
+ goToResources(data)
+
+ resourcesPage.assertImportantLinksNotDisplayed()
+ resourcesPage.assertStudentApplicationsNotDisplayed()
+ resourcesPage.assertStaffInfoNotDisplayed()
+ resourcesPage.assertEmptyViewDisplayed()
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testRefresh() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2, homeroomCourseCount = 0)
+
+ goToResources(data)
+
+ resourcesPage.assertEmptyViewDisplayed()
+
+ val homeroomCourse = data.addCourseWithEnrollment(data.students[0], Enrollment.EnrollmentType.Student, isHomeroom = true)
+ data.addEnrollment(data.teachers[0], homeroomCourse, Enrollment.EnrollmentType.Teacher)
+
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "Important links content")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ resourcesPage.refresh()
+
+ resourcesPage.assertPageObjects()
+ resourcesPage.assertImportantLinksDisplayed(courseWithSyllabus.syllabusBody!!)
+
+ resourcesPage.assertStudentApplicationsHeaderDisplayed()
+ resourcesPage.assertLtiToolDisplayed("Google Drive")
+ resourcesPage.assertLtiToolDisplayed("Media Gallery")
+
+ val teacher = data.teachers[0]
+ resourcesPage.assertStaffInfoHeaderDisplayed()
+ resourcesPage.assertStaffDisplayed(teacher.shortName!!)
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOpenLtiToolShowsCourseSelector() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2)
+
+ val homeroomCourse = data.courses.values.first { it.homeroomCourse }
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "Important links content")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ goToResources(data)
+
+ resourcesPage.openLtiApp("Google Drive")
+ nonHomeroomCourses.forEach {
+ resourcesPage.assertCourseShown(it.name)
+ }
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOpenComposeMessageScreen() {
+ val data = createMockDataWithHomeroomCourse(courseCount = 2)
+
+ val homeroomCourse = data.courses.values.first { it.homeroomCourse }
+ val courseWithSyllabus = homeroomCourse.copy(syllabusBody = "Important links content")
+ data.courses[homeroomCourse.id] = courseWithSyllabus
+
+ val nonHomeroomCourses = data.courses.values.filter { !it.homeroomCourse }
+ nonHomeroomCourses.forEach {
+ data.addLTITool("Google Drive", "http://google.com", it, 1234L)
+ data.addLTITool("Media Gallery", "http://instructure.com", it, 12345L)
+ }
+
+ goToResources(data)
+ resourcesPage.openComposeMessage(data.teachers[0].shortName!!)
+
+ newMessagePage.assertToolbarTitleNewMessage()
+ newMessagePage.assertCourseSelectorNotShown()
+ newMessagePage.assertRecipientsNotShown()
+ newMessagePage.assertSendIndividualMessagesNotShown()
+ newMessagePage.assertSubjectViewShown()
+ newMessagePage.assertMessageViewShown()
+ }
+
+ 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")
+
+ return MockCanvas.init(
+ studentCount = 1,
+ teacherCount = 1,
+ courseCount = courseCount,
+ pastCourseCount = pastCourseCount,
+ favoriteCourseCount = favoriteCourseCount,
+ accountNotificationCount = announcementCount,
+ homeroomCourseCount = homeroomCourseCount)
+ }
+
+ private fun goToResources(data: MockCanvas) {
+ val student = data.students[0]
+ val token = data.tokenFor(student)!!
+ tokenLoginElementary(data.domain, token, student)
+ elementaryDashboardPage.waitForRender()
+ elementaryDashboardPage.selectResourcesTab()
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt
new file mode 100644
index 0000000000..ac411ea948
--- /dev/null
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt
@@ -0,0 +1,294 @@
+/*
+ * 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.addAssignment
+import com.instructure.canvas.espresso.mockCanvas.addTodo
+import com.instructure.canvas.espresso.mockCanvas.init
+import com.instructure.canvasapi2.models.Assignment
+import com.instructure.canvasapi2.utils.RemoteConfigParam
+import com.instructure.canvasapi2.utils.RemoteConfigPrefs
+import com.instructure.canvasapi2.utils.toApiString
+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.pandautils.utils.date.DateTimeProvider
+import com.instructure.student.R
+import com.instructure.student.ui.utils.FakeDateTimeProvider
+import com.instructure.student.ui.utils.StudentTest
+import com.instructure.student.ui.utils.tokenLoginElementary
+import dagger.hilt.android.testing.HiltAndroidTest
+import org.junit.Before
+import org.junit.Test
+import java.util.*
+import javax.inject.Inject
+
+@HiltAndroidTest
+class ScheduleInteractionTest : StudentTest() {
+
+ @Inject
+ lateinit var dateTimeProvider: DateTimeProvider
+
+ override fun displaysPageObjects() = Unit
+
+ @Before
+ fun setUp() {
+ if (!this::dateTimeProvider.isInitialized) {
+ hiltRule.inject()
+ }
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testShowCorrectHeaderItems() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+ goToSchedule(data)
+
+ schedulePage.assertPageObjects()
+ schedulePage.assertDayHeaderShown("August 08", "Sunday", 0)
+ schedulePage.assertDayHeaderShown("August 09", "Monday", 2)
+ schedulePage.assertNoScheduleItemDisplayed()
+
+ schedulePage.assertDayHeaderShown("August 10", schedulePage.getStringFromResource(R.string.yesterday), 4)
+ schedulePage.assertDayHeaderShown("August 11", schedulePage.getStringFromResource(R.string.today), 6)
+ schedulePage.assertNoScheduleItemDisplayed()
+
+ schedulePage.assertDayHeaderShown("August 12", schedulePage.getStringFromResource(R.string.tomorrow), 8)
+ schedulePage.assertDayHeaderShown("August 13", "Friday", 10)
+ schedulePage.assertNoScheduleItemDisplayed()
+
+ schedulePage.assertDayHeaderShown("August 14", "Saturday", 12)
+ schedulePage.assertNoScheduleItemDisplayed()
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testShowScheduledAssignments() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val courses = data.courses.values.filter { !it.homeroomCourse }
+ courses[0].name = "Course 1"
+
+ val currentDate = dateTimeProvider.getCalendar().time.toApiString()
+ val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate, name = "Assignment 1")
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(10)
+ schedulePage.assertCourseHeaderDisplayed(courses[0].name)
+ schedulePage.assertScheduleItemDisplayed(assignment1.name!!)
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testShowMissingAssignments() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val courses = data.courses.values.filter { !it.homeroomCourse }
+
+ val currentDate = dateTimeProvider.getCalendar().time.toApiString()
+ val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate)
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(12)
+ schedulePage.assertMissingItemDisplayed(assignment1.name!!, courses[0].name, "10 pts")
+ }
+
+ @Test
+ @TestMetaData(Priority.P0, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testShowToDoEvents() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val todo = data.addTodo("To Do event", data.students[0].id, date = dateTimeProvider.getCalendar().time)
+ val todo2 = data.addTodo("Calendar event", data.students[0].id, date = dateTimeProvider.getCalendar().time)
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(8)
+ schedulePage.assertCourseHeaderDisplayed(schedulePage.getStringFromResource(R.string.schedule_todo_title))
+ schedulePage.assertScheduleItemDisplayed(todo.plannable.title)
+ schedulePage.assertScheduleItemDisplayed(todo2.plannable.title)
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testRefresh() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val courses = data.courses.values.filter { !it.homeroomCourse }
+
+ goToSchedule(data)
+
+ // Check that we don't have any elements initially
+ schedulePage.assertNoScheduleItemDisplayed()
+ schedulePage.scrollToPosition(8)
+ schedulePage.assertNoScheduleItemDisplayed()
+
+ val currentDate = dateTimeProvider.getCalendar().time.toApiString()
+ val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate)
+ val assignment2 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate)
+
+ schedulePage.scrollToPosition(0)
+ schedulePage.refresh()
+
+ // Check that refresh was successful
+ schedulePage.scrollToPosition(7)
+ schedulePage.assertCourseHeaderDisplayed(courses[0].name)
+ schedulePage.assertScheduleItemDisplayed(assignment1.name!!)
+ schedulePage.assertScheduleItemDisplayed(assignment2.name!!)
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testGoBack2Weeks() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ goToSchedule(data)
+
+ schedulePage.assertDayHeaderShown("August 08", "Sunday", 0)
+ schedulePage.assertDayHeaderShown("August 09", "Monday", 2)
+
+ schedulePage.previousWeekButtonClick()
+ schedulePage.swipeRight()
+
+ schedulePage.assertDayHeaderShown("July 25", "Sunday", 0, recyclerViewMatcherText = "July 25")
+ schedulePage.assertDayHeaderShown("July 26", "Monday", 2, recyclerViewMatcherText = "July 25")
+ schedulePage.assertDayHeaderShown("July 27", "Tuesday", 4, recyclerViewMatcherText = "July 26")
+ schedulePage.assertDayHeaderShown("July 28", "Wednesday", 6, recyclerViewMatcherText = "July 27")
+ schedulePage.assertDayHeaderShown("July 29", "Thursday", 8, recyclerViewMatcherText = "July 28")
+ schedulePage.assertDayHeaderShown("July 30", "Friday", 10, recyclerViewMatcherText = "July 29")
+ schedulePage.assertDayHeaderShown("July 31", "Saturday", 12, recyclerViewMatcherText = "July 30")
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testGoForward2Weeks() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ goToSchedule(data)
+
+ schedulePage.assertDayHeaderShown("August 08", "Sunday", 0)
+ schedulePage.assertDayHeaderShown("August 09", "Monday", 2)
+
+ schedulePage.nextWeekButtonClick()
+ schedulePage.swipeLeft()
+
+ schedulePage.assertDayHeaderShown("August 22", "Sunday", 0, recyclerViewMatcherText = "August 22")
+ schedulePage.assertDayHeaderShown("August 23", "Monday", 2, recyclerViewMatcherText = "August 22")
+ schedulePage.assertDayHeaderShown("August 24", "Tuesday", 4, recyclerViewMatcherText = "August 23")
+ schedulePage.assertDayHeaderShown("August 25", "Wednesday", 6, recyclerViewMatcherText = "August 24")
+ schedulePage.assertDayHeaderShown("August 26", "Thursday", 8, recyclerViewMatcherText = "August 25")
+ schedulePage.assertDayHeaderShown("August 27", "Friday", 10, recyclerViewMatcherText = "August 26")
+ schedulePage.assertDayHeaderShown("August 28", "Saturday", 12, recyclerViewMatcherText = "August 27")
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOpenAssignment() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val courses = data.courses.values.filter { !it.homeroomCourse }
+ courses[0].name = "Course 1"
+
+ val currentDate = dateTimeProvider.getCalendar().time.toApiString()
+ val assignment = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate, name = "Assignment 1")
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(9)
+ schedulePage.clickScheduleItem(assignment.name!!)
+
+ assignmentDetailsPage.assertPageObjects()
+ assignmentDetailsPage.verifyAssignmentDetails(assignment)
+ }
+
+ @Test
+ @TestMetaData(Priority.P1, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testOpenCourse() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ val courses = data.courses.values.filter { !it.homeroomCourse }
+
+ val currentDate = dateTimeProvider.getCalendar().time.toApiString()
+ data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate)
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(8)
+ schedulePage.clickCourseHeader(courses[0].name)
+
+ courseBrowserPage.assertPageObjects()
+ courseBrowserPage.assertTitleCorrect(courses[0])
+ }
+
+ @Test
+ @TestMetaData(Priority.P2, FeatureCategory.K5_DASHBOARD, TestCategory.INTERACTION)
+ fun testMarkAsDone() {
+ setDate(2021, Calendar.AUGUST, 11)
+ val data = createMockData(courseCount = 1)
+
+ data.addTodo("To Do event", data.students[0].id, date = dateTimeProvider.getCalendar().time)
+
+ goToSchedule(data)
+ schedulePage.scrollToPosition(8)
+
+ schedulePage.assertMarkedAsDoneNotShown()
+
+ schedulePage.clickDoneCheckbox()
+ schedulePage.assertMarkedAsDoneShown()
+ }
+
+ private fun createMockData(
+ courseCount: Int = 0,
+ withGradingPeriods: Boolean = false,
+ homeroomCourseCount: 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")
+
+ return MockCanvas.init(
+ studentCount = 1,
+ courseCount = courseCount,
+ withGradingPeriods = withGradingPeriods,
+ homeroomCourseCount = homeroomCourseCount)
+ }
+
+ private fun goToSchedule(data: MockCanvas) {
+ val student = data.students[0]
+ val token = data.tokenFor(student)!!
+ tokenLoginElementary(data.domain, token, student)
+ elementaryDashboardPage.waitForRender()
+ elementaryDashboardPage.selectScheduleTab()
+ }
+
+ private fun setDate(year: Int, month: Int, dayOfMonth: Int) {
+ val cal = Calendar.getInstance()
+ cal.set(Calendar.YEAR, year)
+ cal.set(Calendar.MONTH, month)
+ cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)
+ (dateTimeProvider as FakeDateTimeProvider).fakeTimeInMillis = cal.timeInMillis
+ }
+}
\ No newline at end of file
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 0034c69b61..9fd5f912e7 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
@@ -115,6 +115,7 @@ class SettingsInteractionTest : StudentTest() {
fun testPairObserver_refreshCode() {
setUpAndSignIn()
+ ApiPrefs.canGeneratePairingCode = true
dashboardPage.launchSettingsPage()
settingsPage.launchPairObserverPage()
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentListPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentListPage.kt
index 405413750c..10461bb1be 100644
--- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentListPage.kt
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentListPage.kt
@@ -17,7 +17,6 @@
package com.instructure.student.ui.pages
import android.view.View
-import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
@@ -28,6 +27,8 @@ import com.instructure.dataseeding.model.AssignmentApiModel
import com.instructure.dataseeding.model.QuizApiModel
import com.instructure.espresso.*
import com.instructure.espresso.page.BasePage
+import com.instructure.espresso.page.onView
+import com.instructure.espresso.page.waitForView
import com.instructure.espresso.page.waitForViewWithText
import com.instructure.student.R
import org.hamcrest.Matcher
@@ -74,7 +75,7 @@ class AssignmentListPage : BasePage(pageResId = R.id.assignmentListPage) {
private fun assertHasAssignmentCommon(assignmentName: String, assignmentDueAt: String?, expectedGrade: String? = null) {
waitForMatcherWithRefreshes(withText(assignmentName))
- waitForViewWithText(assignmentName).assertDisplayed()
+ waitForView(allOf(withText(assignmentName), isDescendantOfA(withId(R.id.assignmentListPage)))).assertDisplayed()
// Check that either the assignment due date is present, or "No Due Date" is displayed
if(assignmentDueAt != null) {
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt
index 576ad03129..f13e38b928 100644
--- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GradesPage.kt
@@ -16,8 +16,55 @@
*/
package com.instructure.student.ui.pages
-import com.instructure.espresso.page.BasePage
+import com.instructure.espresso.*
+import com.instructure.espresso.page.*
import com.instructure.student.R
class GradesPage : BasePage(R.id.gradesPage) {
+
+ private val swipeRefreshLayout by OnViewWithId(R.id.gradesRefreshLayout)
+ private val gradesRecyclerView by OnViewWithId(R.id.gradesRecyclerView)
+ private val emptyView by OnViewWithId(R.id.gradesEmptyView, autoAssert = false)
+
+ fun assertCourseShownWithGrades(courseName: String, grade: String) {
+ val courseNameMatcher = withId(R.id.gradesCourseNameText) + withText(courseName)
+ val gradeMatcher = withId(R.id.scoreText) + withText(grade)
+
+ onView(withId(R.id.gradeRow) + withDescendant(courseNameMatcher) + withDescendant(gradeMatcher))
+ .scrollTo()
+ .assertDisplayed()
+ }
+
+ fun refresh() {
+ swipeRefreshLayout.swipeDown()
+ }
+
+ fun assertEmptyViewVisible() {
+ emptyView.assertDisplayed()
+ }
+
+ fun assertRecyclerViewNotVisible() {
+ gradesRecyclerView.assertNotDisplayed()
+ }
+
+ fun clickGradeRow(courseName: String) {
+ onView(withId(R.id.gradesCourseNameText) + withText(courseName))
+ .scrollTo()
+ .click()
+ }
+
+ fun clickGradingPeriodSelector() {
+ onView(withId(R.id.gradingPeriodSelector))
+ .click()
+ }
+
+ fun selectGradingPeriod(gradingPeriodName: String) {
+ onView(withText(gradingPeriodName))
+ .click()
+ }
+
+ fun assertSelectedGradingPeriod(gradingPeriodName: String) {
+ onView(withId(R.id.gradingPeriodSelector) + withText(gradingPeriodName))
+ .assertDisplayed()
+ }
}
\ 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
index b9abb82543..437f780447 100644
--- 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
@@ -97,9 +97,9 @@ class HomeroomPage : BasePage(R.id.homeroomPage) {
.click()
}
- fun openCourseAnnouncment(announcementText: String) {
+ fun openCourseAnnouncement(announcementText: String) {
+ swipeRefreshLayout.swipeUp()
onView(withId(R.id.announcementText) + withText(announcementText))
- .scrollTo()
.click()
}
@@ -116,8 +116,8 @@ class HomeroomPage : BasePage(R.id.homeroomPage) {
}
fun openAssignments(todoText: String) {
+ swipeRefreshLayout.swipeUp()
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/NewMessagePage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NewMessagePage.kt
index 6819469c76..7d146add29 100644
--- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NewMessagePage.kt
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NewMessagePage.kt
@@ -22,23 +22,19 @@ import androidx.test.espresso.Espresso
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.action.ViewActions.click
-import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.*
-import com.instructure.canvas.espresso.withCustomConstraints
import com.instructure.canvasapi2.models.Course
import com.instructure.canvasapi2.models.User
import com.instructure.dataseeding.model.CanvasUserApiModel
import com.instructure.dataseeding.model.CourseApiModel
import com.instructure.dataseeding.model.GroupApiModel
import com.instructure.espresso.*
-import com.instructure.espresso.page.BasePage
-import com.instructure.espresso.page.onView
-import com.instructure.espresso.page.onViewWithId
+import com.instructure.espresso.page.*
import com.instructure.student.R
import junit.framework.Assert.assertTrue
-import org.hamcrest.Matchers.*
+import org.hamcrest.Matchers.allOf
class NewMessagePage : BasePage() {
@@ -172,6 +168,30 @@ class NewMessagePage : BasePage() {
setMessage(message)
Espresso.closeSoftKeyboard()
}
+
+ fun assertToolbarTitleNewMessage() {
+ onView(withId(R.id.toolbar) + withDescendant(withText(R.string.newMessage))).assertDisplayed()
+ }
+
+ fun assertCourseSelectorNotShown() {
+ coursesSpinner.assertNotDisplayed()
+ }
+
+ fun assertRecipientsNotShown() {
+ onViewWithId(R.id.recipientWrapper).assertNotDisplayed()
+ }
+
+ fun assertSendIndividualMessagesNotShown() {
+ sendIndividualMessageSwitch.assertNotDisplayed()
+ }
+
+ fun assertSubjectViewShown() {
+ onViewWithId(R.id.editSubject).assertDisplayed()
+ }
+
+ fun assertMessageViewShown() {
+ onViewWithId(R.id.message)
+ }
}
/** Custom ViewAssertion to make sure that a TextBox is empty */
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
index b6e52f050d..302f348b35 100644
--- 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
@@ -16,8 +16,84 @@
*/
package com.instructure.student.ui.pages
-import com.instructure.espresso.page.BasePage
+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 ResourcesPage : BasePage(R.id.resourcesPage) {
+
+ private val swipeRefreshLayout by OnViewWithId(R.id.resourcesSwipeRefreshLayout)
+ private val importantLinksTitle by OnViewWithId(R.id.importantLinksTitle, autoAssert = false)
+ private val importantLinksContainer by OnViewWithId(R.id.importantLinksContainer)
+ private val coursesRecyclerView by OnViewWithId(R.id.actionItemsRecyclerView)
+
+ fun assertImportantLinksDisplayed(content: String) {
+ importantLinksTitle.assertDisplayed()
+ Web.onWebView()
+ .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "html"))
+ .check(WebViewAssertions.webMatches(DriverAtoms.getText(), Matchers.comparesEqualTo(content)))
+ }
+
+ fun assertCourseNameDisplayed(courseName: String) {
+ onView(withId(R.id.importantLinksCourseName) + withText(courseName)).assertDisplayed()
+ }
+
+ fun assertStudentApplicationsHeaderDisplayed() {
+ onView(withText(R.string.studentApplications)).assertDisplayed()
+ }
+
+ fun assertLtiToolDisplayed(name: String) {
+ onView(withId(R.id.ltiAppCardView) + withDescendant(withText(name))).assertDisplayed()
+ }
+
+ fun assertStaffInfoHeaderDisplayed() {
+ onView(withText(R.string.staffContactInfo)).assertDisplayed()
+ }
+
+ fun assertStaffDisplayed(name: String) {
+ onView(withId(R.id.contactInfoLayout) + withDescendant(withText(name))).assertDisplayed()
+ }
+
+ fun assertImportantLinksNotDisplayed() {
+ importantLinksTitle.assertNotDisplayed()
+ importantLinksContainer.check(ViewAssertions.matches(ViewMatchers.hasChildCount(0)))
+ }
+
+ fun assertStaffInfoNotDisplayed() {
+ onView(withText(R.string.staffContactInfo)).check(ViewAssertions.doesNotExist())
+ onView(withId(R.id.contactInfoLayout)).check(ViewAssertions.doesNotExist())
+ }
+
+ fun assertStudentApplicationsNotDisplayed() {
+ onView(withText(R.string.studentApplications)).check(ViewAssertions.doesNotExist())
+ onView(withId(R.id.ltiAppCardView)).check(ViewAssertions.doesNotExist())
+ }
+
+ fun assertEmptyViewDisplayed() {
+ onViewWithId(R.id.resourcesEmptyView).assertDisplayed()
+ onViewWithText(R.string.resourcesEmptyMessage).assertDisplayed()
+ }
+
+ fun refresh() {
+ swipeRefreshLayout.swipeDown()
+ }
+
+ fun openLtiApp(name: String) {
+ onView(withId(R.id.ltiAppCardView) + withDescendant(withText(name))).click()
+ }
+
+ fun assertCourseShown(courseName: String) {
+ onView(withText(courseName))
+ }
+
+ fun openComposeMessage(teacherName: String) {
+ onView(withId(R.id.contactInfoLayout) + withDescendant(withText(teacherName))).click()
+ }
}
\ 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
index 2f1d0f4f60..f56c4b93e4 100644
--- 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
@@ -16,8 +16,97 @@
*/
package com.instructure.student.ui.pages
-import com.instructure.espresso.page.BasePage
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.contrib.RecyclerViewActions
+import com.instructure.espresso.*
+import com.instructure.espresso.page.*
+import com.instructure.pandautils.binding.BindableViewHolder
import com.instructure.student.R
class SchedulePage : BasePage(R.id.schedulePage) {
+
+ private val pager by OnViewWithId(R.id.schedulePager)
+ private val previousWeekButton by OnViewWithId(R.id.previousWeekButton)
+ private val nextWeekButton by OnViewWithId(R.id.nextWeekButton)
+ private val recyclerView by OnViewWithId(R.id.scheduleRecyclerView)
+ private val swipeRefreshLayout by OnViewWithId(R.id.scheduleSwipeRefreshLayout)
+
+ fun assertDayHeaderShown(dateText: String, dayText: String, position: Int, recyclerViewMatcherText: String? = null) {
+ val dateTextMatcher = withId(R.id.dateText) + withText(dateText)
+ val dayTextMatcher = withId(R.id.dayText) + withText(dayText)
+
+ val todayHeaderMatcher = withId(R.id.scheduleHeaderLayout) + withDescendant(dateTextMatcher) + withDescendant(dayTextMatcher)
+ if (recyclerViewMatcherText != null) {
+ val recyclerViewInteraction = onView(withId(R.id.scheduleRecyclerView) + withDescendant(withText(recyclerViewMatcherText)))
+ recyclerViewInteraction.perform(RecyclerViewActions.scrollToPosition(position))
+ } else {
+ recyclerView.perform(RecyclerViewActions.scrollToPosition(position))
+ }
+ waitForView(todayHeaderMatcher).assertDisplayed()
+ }
+
+ fun assertNoScheduleItemDisplayed() {
+ onView(withId(R.id.scheduleCourseItemLayout)).check(ViewAssertions.doesNotExist())
+ }
+
+ fun scrollToPosition(position: Int) {
+ recyclerView.perform(RecyclerViewActions.scrollToPosition(position))
+ }
+
+ fun assertCourseHeaderDisplayed(courseName: String) {
+ onView(withId(R.id.scheduleCourseHeaderText) + withText(courseName)).assertDisplayed()
+ }
+
+ fun assertScheduleItemDisplayed(scheduleItemName: String) {
+ onView(withAncestor(R.id.plannerItems) + withText(scheduleItemName)).assertDisplayed()
+ }
+
+ fun assertMissingItemDisplayed(itemName: String, courseName: String, pointsPossible: String) {
+ val titleMatcher = withId(R.id.title) + withText(itemName)
+ val courseNameMatcher = withId(R.id.courseName) + withText(courseName)
+ val pointsPossibleMatcher = withId(R.id.points) + withText(pointsPossible)
+
+ onView(withId(R.id.missingItemLayout) + withDescendant(titleMatcher) + withDescendant(courseNameMatcher) + withDescendant(pointsPossibleMatcher))
+ .assertDisplayed()
+ }
+
+ fun refresh() {
+ swipeRefreshLayout.swipeDown()
+ }
+
+ fun previousWeekButtonClick() {
+ previousWeekButton.click()
+ }
+
+ fun swipeRight() {
+ pager.swipeRight()
+ }
+
+ fun nextWeekButtonClick() {
+ nextWeekButton.click()
+ }
+
+ fun swipeLeft() {
+ pager.swipeLeft()
+ }
+
+ fun clickCourseHeader(courseName: String) {
+ onView(withId(R.id.scheduleCourseHeaderText) + withText(courseName)).click()
+ }
+
+ fun clickScheduleItem(name: String) {
+ onView(withAncestor(R.id.plannerItems) + withText(name)).click()
+ }
+
+ fun clickDoneCheckbox() {
+ onView(withId(R.id.checkbox)).click()
+ }
+
+ fun assertMarkedAsDoneShown() {
+ onViewWithText(R.string.schedule_marked_as_done).assertDisplayed()
+ }
+
+ fun assertMarkedAsDoneNotShown() {
+ onViewWithText(R.string.schedule_marked_as_done).assertNotDisplayed()
+ }
}
\ No newline at end of file
diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/TestDateTimeModule.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/TestDateTimeModule.kt
new file mode 100644
index 0000000000..7e18e00d08
--- /dev/null
+++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/TestDateTimeModule.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.utils
+
+import com.instructure.pandautils.di.DateTimeModule
+import com.instructure.pandautils.utils.date.DateTimeProvider
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.components.SingletonComponent
+import dagger.hilt.testing.TestInstallIn
+import java.util.*
+import javax.inject.Singleton
+
+@Module
+@TestInstallIn(components = [SingletonComponent::class], replaces = [DateTimeModule::class])
+class TestDateTimeModule {
+
+ @Provides
+ @Singleton
+ fun provideDateTimeProvider(): DateTimeProvider {
+ return FakeDateTimeProvider()
+ }
+}
+
+class FakeDateTimeProvider : DateTimeProvider {
+
+ var fakeTimeInMillis: Long = Calendar.getInstance().timeInMillis
+
+ override fun getCalendar(): Calendar {
+ return Calendar.getInstance().apply {
+ timeInMillis = fakeTimeInMillis
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt
index 41a934c58a..d39e0c2b4b 100644
--- a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt
+++ b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt
@@ -75,6 +75,10 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI
}
}
+ val termsOfService = awaitApi { UserManager.getTermsOfService(it, true) }
+ ApiPrefs.canGeneratePairingCode = termsOfService.selfRegistrationType == SelfRegistration.ALL
+ || termsOfService.selfRegistrationType == SelfRegistration.OBSERVER
+
// Grab colors
if (ColorKeeper.hasPreviouslySynced) {
UserManager.getColors(userColorsCallback, true)
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 d0fc9b66cd..b5264e3f96 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
@@ -99,8 +99,10 @@ import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
+import java.util.*
import javax.inject.Inject
+private const val BOTTOM_NAV_SCREEN = "bottomNavScreen"
@AndroidEntryPoint
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
@@ -126,6 +128,8 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
private var mDrawerToggle: ActionBarDrawerToggle? = null
private var colorOverlayJob: Job? = null
+ private val bottomNavScreensStack: Deque = ArrayDeque()
+
override fun contentResId(): Int = R.layout.activity_navigation
private val isDrawerOpen: Boolean
@@ -194,7 +198,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
/* Update nav bar visibility to show for specific 'root' fragments. Also show the nav bar when there is
only one fragment on the backstack, which commonly occurs with non-root fragments when routing
from external sources. */
- val visible = it::class.java in navigationBehavior.bottomNavBarFragments || supportFragmentManager.backStackEntryCount <= 1
+ val visible = isBottomNavFragment(it) || supportFragmentManager.backStackEntryCount <= 1
bottomBar.setVisible(visible)
bottomBarDivider.setVisible(visible)
}
@@ -225,6 +229,8 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
MasqueradeHelper.startMasquerading(masqueradingUserId, ApiPrefs.domain, NavigationActivity::class.java)
}
+ bottomBar.inflateMenu(navigationBehavior.bottomBarMenu)
+
supportFragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
if (savedInstanceState == null) {
@@ -314,8 +320,8 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
override fun loadLandingPage(clearBackStack: Boolean) {
if (clearBackStack) clearBackStack(navigationBehavior.homeFragmentClass)
- val homeRoute = navigationBehavior.createHomeFragmentRoute(ApiPrefs.user)
- addFragment(navigationBehavior.createHomeFragment(homeRoute), homeRoute)
+ selectBottomNavFragment(navigationBehavior.homeFragmentClass)
+ bottomNavScreensStack.clear()
if (intent.extras?.containsKey(AppShortcutManager.APP_SHORTCUT_PLACEMENT) == true) {
// Launch to the app shortcut placement
@@ -329,26 +335,15 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
val route = BookmarksFragment.makeRoute(ApiPrefs.user)
addFragment(BookmarksFragment.newInstance(route) { RouteMatcher.routeUrl(this, it.url!!) }, route)
}
- AppShortcutManager.APP_SHORTCUT_CALENDAR -> {
- val route = CalendarFragment.makeRoute()
- addFragment(CalendarFragment.newInstance(route), route)
- }
- AppShortcutManager.APP_SHORTCUT_TODO -> {
- val route = ToDoListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(ToDoListFragment.newInstance(route), route)
- }
- AppShortcutManager.APP_SHORTCUT_NOTIFICATIONS -> {
- val route = NotificationListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(NotificationListFragment.newInstance(route), route)
- }
+ AppShortcutManager.APP_SHORTCUT_CALENDAR -> selectBottomNavFragment(CalendarFragment::class.java)
+ AppShortcutManager.APP_SHORTCUT_TODO -> selectBottomNavFragment(ToDoListFragment::class.java)
+ AppShortcutManager.APP_SHORTCUT_NOTIFICATIONS -> selectBottomNavFragment(NotificationListFragment::class.java)
AppShortcutManager.APP_SHORTCUT_INBOX -> {
if (ApiPrefs.isStudentView) {
// Inbox not available in Student View
- val route = NothingToSeeHereFragment.makeRoute()
- addFragment(NothingToSeeHereFragment.newInstance(), route)
+ selectBottomNavFragment(NothingToSeeHereFragment::class.java)
} else {
- val route = InboxFragment.makeRoute()
- addFragment(InboxFragment.newInstance(route), route)
+ selectBottomNavFragment(InboxFragment::class.java)
}
}
}
@@ -451,10 +446,14 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
Logger.e("Error getting version: " + e)
}
- toolbar.setNavigationIcon(R.drawable.ic_hamburger)
- toolbar.navigationContentDescription = getString(R.string.navigation_drawer_open)
- toolbar.setNavigationOnClickListener {
- openNavigationDrawer()
+ if (isBottomNavFragment(fragment)) {
+ toolbar.setNavigationIcon(R.drawable.ic_hamburger)
+ toolbar.navigationContentDescription = getString(R.string.navigation_drawer_open)
+ toolbar.setNavigationOnClickListener {
+ openNavigationDrawer()
+ }
+ } else {
+ toolbar.setupAsBackButton(fragment)
}
drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START)
@@ -545,24 +544,15 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
private val bottomBarItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item: MenuItem ->
when (item.itemId) {
- R.id.bottomNavigationHome -> handleRoute(Route(navigationBehavior.homeFragmentClass, ApiPrefs.user))
- R.id.bottomNavigationCalendar -> handleRoute(CalendarFragment.makeRoute())
- R.id.bottomNavigationToDo -> {
- val route = ToDoListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(ToDoListFragment.newInstance(route), route)
- }
- R.id.bottomNavigationNotifications ->{
- val route = NotificationListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(NotificationListFragment.newInstance(route), route)
- }
+ R.id.bottomNavigationHome -> selectBottomNavFragment(navigationBehavior.homeFragmentClass)
+ R.id.bottomNavigationCalendar -> selectBottomNavFragment(CalendarFragment::class.java)
+ R.id.bottomNavigationToDo -> selectBottomNavFragment(ToDoListFragment::class.java)
+ R.id.bottomNavigationNotifications -> selectBottomNavFragment(NotificationListFragment::class.java)
R.id.bottomNavigationInbox -> {
if (ApiPrefs.isStudentView) {
- // Inbox not available in Student View
- val route = NothingToSeeHereFragment.makeRoute()
- addFragment(NothingToSeeHereFragment.newInstance(), route)
+ selectBottomNavFragment(NothingToSeeHereFragment::class.java)
} else {
- val route = InboxFragment.makeRoute()
- addFragment(InboxFragment.newInstance(route), route)
+ selectBottomNavFragment(InboxFragment::class.java)
}
}
}
@@ -586,24 +576,15 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
if(!abortReselect) {
when (item.itemId) {
- R.id.bottomNavigationHome -> handleRoute(Route(navigationBehavior.homeFragmentClass, ApiPrefs.user))
- R.id.bottomNavigationCalendar -> handleRoute(CalendarFragment.makeRoute())
- R.id.bottomNavigationToDo -> {
- val route = ToDoListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(ToDoListFragment.newInstance(route), route)
- }
- R.id.bottomNavigationNotifications -> {
- val route = NotificationListFragment.makeRoute(ApiPrefs.user!!)
- addFragment(NotificationListFragment.newInstance(route), route)
- }
+ R.id.bottomNavigationHome -> selectBottomNavFragment(navigationBehavior.homeFragmentClass)
+ R.id.bottomNavigationCalendar -> selectBottomNavFragment(CalendarFragment::class.java)
+ R.id.bottomNavigationToDo -> selectBottomNavFragment(ToDoListFragment::class.java)
+ R.id.bottomNavigationNotifications -> selectBottomNavFragment(NotificationListFragment::class.java)
R.id.bottomNavigationInbox -> {
if (ApiPrefs.isStudentView) {
- // Inbox not available in Student View
- val route = NothingToSeeHereFragment.makeRoute()
- addFragment(NothingToSeeHereFragment.newInstance(), route)
+ selectBottomNavFragment(NothingToSeeHereFragment::class.java)
} else {
- val route = InboxFragment.makeRoute()
- addFragment(InboxFragment.newInstance(route), route)
+ selectBottomNavFragment(InboxFragment::class.java)
}
}
}
@@ -679,19 +660,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
}
addBookmark()
return true
- } else if (item.itemId == android.R.id.home) {
- //if we hit the x while we're on a detail fragment, we always want to close the top fragment
- //and not have it trigger an actual "back press"
- val topFragment = topFragment
- if (supportFragmentManager.backStackEntryCount > 0) {
- if (topFragment != null) {
- supportFragmentManager.beginTransaction().remove(topFragment).commit()
- }
- super.onBackPressed()
- } else if (topFragment == null) {
- super.onBackPressed()
- }
- return true
}
return super.onOptionsItemSelected(item)
@@ -787,31 +755,72 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
}
private fun addFragment(fragment: Fragment?, route: Route) {
+ if (RouteType.DIALOG == route.routeType && fragment is DialogFragment && isTablet) {
+ val ft = supportFragmentManager.beginTransaction()
+ ft.addToBackStack(fragment::class.java.name)
+ fragment.show(ft, fragment::class.java.name)
+ } else {
+ if (fragment != null && fragment::class.java.name in getBottomNavFragmentNames() && isBottomNavFragment(currentFragment)) {
+ selectBottomNavFragment(fragment::class.java)
+ } else {
+ addFullScreenFragment(fragment)
+ }
+ }
+ }
+
+ private fun selectBottomNavFragment(fragmentClass: Class) {
+ val selectedFragment = supportFragmentManager.findFragmentByTag(fragmentClass.name)
+
+ if (selectedFragment == null) {
+ val fragment = createBottomNavFragment(fragmentClass.name)
+ val newArguments = if (fragment?.arguments != null) fragment.requireArguments() else Bundle()
+ newArguments.putBoolean(BOTTOM_NAV_SCREEN, true)
+ fragment?.arguments = newArguments
+ addFullScreenFragment(fragment)
+ } else {
+ showHiddenFragment(selectedFragment)
+ }
+
+ bottomNavScreensStack.remove(fragmentClass.name)
+ bottomNavScreensStack.push(fragmentClass.name)
+ }
+
+ private fun addFullScreenFragment(fragment: Fragment?) {
if (fragment == null) {
- Logger.e("NavigationActivity:addFragment() - Could not route null Fragment.")
+ Logger.e("NavigationActivity:addFullScreenFragment() - Could not route null Fragment.")
return
}
val ft = supportFragmentManager.beginTransaction()
+ ft.setCustomAnimations(R.anim.fade_in_quick, R.anim.fade_out_quick)
+ currentFragment?.let { ft.hide(it) }
+ ft.add(R.id.fullscreen, fragment, fragment::class.java.name)
+ ft.addToBackStack(fragment::class.java.name)
+ ft.commitAllowingStateLoss()
+ }
- if (RouteType.DIALOG == route.routeType && fragment is DialogFragment && isTablet) {
- ft.addToBackStack(fragment::class.java.name)
- fragment.show(ft, fragment::class.java.name)
- } else {
- ft.setCustomAnimations(R.anim.fade_in_quick, R.anim.fade_out_quick)
- currentFragment?.let { ft.hide(it) }
- ft.add(R.id.fullscreen, fragment, fragment::class.java.name)
- ft.addToBackStack(fragment::class.java.name)
- ft.commitAllowingStateLoss()
+ private fun showHiddenFragment(fragment: Fragment) {
+ val ft = supportFragmentManager.beginTransaction()
+ ft.setCustomAnimations(R.anim.fade_in_quick, R.anim.fade_out_quick)
+ val bottomBarFragments = getBottomBarFragments(fragment::class.java.name)
+ bottomBarFragments.forEach {
+ ft.hide(it)
}
+ ft.show(fragment)
+ ft.commitAllowingStateLoss()
}
+ private fun getBottomBarFragments(selectedFragmentName: String): List {
+ return getBottomNavFragmentNames()
+ .filter { it != selectedFragmentName }
+ .mapNotNull { supportFragmentManager.findFragmentByTag(it) }
+ }
//endregion
//region Back Stack
override fun onBackPressed() {
- if(isDrawerOpen) {
+ if (isDrawerOpen) {
closeNavigationDrawer()
return
}
@@ -825,19 +834,48 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
val topFragment = topFragment
if (topFragment is ParentFragment) {
if (!topFragment.handleBackPressed()) {
- super.onBackPressed()
+ if (isBottomNavFragment(topFragment)) {
+ handleBottomNavBackStack()
+ } else {
+ super.onBackPressed()
+ }
}
} else {
super.onBackPressed()
}
}
+ private fun handleBottomNavBackStack() {
+ if (bottomNavScreensStack.size == 0) {
+ finish()
+ } else if (bottomNavScreensStack.size == 1) {
+ bottomNavScreensStack.pop()
+ val previousFragment = supportFragmentManager.findFragmentByTag(navigationBehavior.homeFragmentClass.name)
+ if (previousFragment != null) {
+ showHiddenFragment(previousFragment)
+ applyCurrentFragmentTheme()
+ }
+ } else {
+ bottomNavScreensStack.pop()
+ val previousFragmentName = bottomNavScreensStack.peek()
+ val previousFragment = supportFragmentManager.findFragmentByTag(previousFragmentName)
+ if (previousFragment != null) {
+ showHiddenFragment(previousFragment)
+ applyCurrentFragmentTheme()
+ }
+ }
+ }
+
override val topFragment: Fragment?
get() {
val stackSize = supportFragmentManager.backStackEntryCount
if (stackSize > 0) {
- val fragmentTag = supportFragmentManager.getBackStackEntryAt(stackSize - 1).name
- return supportFragmentManager.findFragmentByTag(fragmentTag)
+ val backStackEntryName = supportFragmentManager.getBackStackEntryAt(stackSize - 1).name
+ return if (backStackEntryName in getBottomNavFragmentNames()) {
+ currentFragment
+ } else {
+ supportFragmentManager.findFragmentByTag(backStackEntryName)
+ }
}
return null
}
@@ -852,7 +890,20 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
return null
}
- override val currentFragment: Fragment? get() = supportFragmentManager.findFragmentById(R.id.fullscreen)
+ override val currentFragment: Fragment?
+ get() {
+ val fragment = supportFragmentManager.findFragmentById(R.id.fullscreen)
+ return if (fragment != null && isBottomNavFragment(fragment)) {
+ val currentFragmentName = bottomNavScreensStack.peek() ?: navigationBehavior.homeFragmentClass.name
+ supportFragmentManager.findFragmentByTag(currentFragmentName)
+ } else {
+ fragment
+ }
+ }
+
+ private fun isBottomNavFragment(fragment: Fragment?) = fragment?.arguments?.getBoolean(BOTTOM_NAV_SCREEN) == true
+
+ private fun getBottomNavFragmentNames() = navigationBehavior.bottomNavBarFragments.map { it.name }
private fun clearBackStack(cls: Class<*>?) {
val fragment = topFragment
@@ -942,16 +993,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
gauge.tag = gaugeLaunchDefinition
}
- override fun updateCalendarStartDay() {
- //Restarts the CalendarListViewFragment to update the changed start day of the week
- val fragment = supportFragmentManager.findFragmentByTag(CalendarFragment::class.java.name) as? ParentFragment
- if (fragment != null) {
- supportFragmentManager.beginTransaction().remove(fragment).commit()
- }
- val route = CalendarFragment.makeRoute()
- addFragment(CalendarFragment.newInstance(route), route)
- }
-
override fun addBookmark() {
val dialog = BookmarkCreationDialog.newInstance(this, topFragment, peekingFragment)
dialog?.show(supportFragmentManager, BookmarkCreationDialog::class.java.simpleName)
@@ -1045,6 +1086,33 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.
toast(R.string.errorOccurred)
}
+ private fun createBottomNavFragment(name: String?): ParentFragment? {
+ return when (name) {
+ navigationBehavior.homeFragmentClass.name -> {
+ val route = navigationBehavior.createHomeFragmentRoute(ApiPrefs.user)
+ navigationBehavior.createHomeFragment(route)
+ }
+ CalendarFragment::class.java.name -> {
+ val route = CalendarFragment.makeRoute()
+ CalendarFragment.newInstance(route)
+ }
+ ToDoListFragment::class.java.name -> {
+ val route = ToDoListFragment.makeRoute(ApiPrefs.user!!)
+ ToDoListFragment.newInstance(route)
+ }
+ NotificationListFragment::class.java.name -> {
+ val route = NotificationListFragment.makeRoute(ApiPrefs.user!!)
+ NotificationListFragment.newInstance(route)
+ }
+ InboxFragment::class.java.name -> {
+ val route = InboxFragment.makeRoute()
+ InboxFragment.newInstance(route)
+ }
+ NothingToSeeHereFragment::class.java.name -> NothingToSeeHereFragment.newInstance()
+ else -> null
+ }
+ }
+
companion object {
fun createIntent(context: Context, route: Route): Intent {
return Intent(context, NavigationActivity::class.java).apply { putExtra(Route.ROUTE, route) }
diff --git a/apps/student/src/main/java/com/instructure/student/activity/ShareFileUploadActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/ShareFileUploadActivity.kt
index 5c6c6efc8e..10c7871031 100644
--- a/apps/student/src/main/java/com/instructure/student/activity/ShareFileUploadActivity.kt
+++ b/apps/student/src/main/java/com/instructure/student/activity/ShareFileUploadActivity.kt
@@ -132,7 +132,6 @@ class ShareFileUploadActivity : AppCompatActivity(), ShareFileDestinationDialog.
private fun getCourses() {
loadCoursesJob = tryWeave {
val courses = awaitApi> { CourseManager.getCourses(true, it) }
- .filter { it.isNotDeleted() }
if (courses.isNotEmpty()) {
this@ShareFileUploadActivity.courses = ArrayList(courses)
if (uploadFileSourceFragment == null) showDestinationDialog()
diff --git a/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt
index 5543220bf3..a57e35d8c8 100644
--- a/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt
+++ b/apps/student/src/main/java/com/instructure/student/adapter/NotificationListRecyclerAdapter.kt
@@ -34,7 +34,6 @@ import com.instructure.canvasapi2.utils.ApiPrefs.user
import com.instructure.canvasapi2.utils.ApiType
import com.instructure.canvasapi2.utils.DateHelper
import com.instructure.canvasapi2.utils.LinkHeaders
-import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.pandarecycler.util.GroupSortedList.GroupComparatorCallback
import com.instructure.pandarecycler.util.GroupSortedList.ItemComparatorCallback
import com.instructure.pandarecycler.util.Types
@@ -157,8 +156,7 @@ class NotificationListRecyclerAdapter(
coursesCallback = object : StatusCallback>() {
override fun onResponse(response: Response>, linkHeaders: LinkHeaders, type: ApiType) {
- val courses = response.body()?.filter { it.isNotDeleted() }
- courseMap = createCourseMap(courses)
+ courseMap = createCourseMap(response.body())
populateActivityStreamAdapter()
}
}
diff --git a/apps/student/src/main/java/com/instructure/student/adapter/TodoListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/TodoListRecyclerAdapter.kt
index 2c099903fb..f787c56283 100644
--- a/apps/student/src/main/java/com/instructure/student/adapter/TodoListRecyclerAdapter.kt
+++ b/apps/student/src/main/java/com/instructure/student/adapter/TodoListRecyclerAdapter.kt
@@ -225,7 +225,7 @@ open class TodoListRecyclerAdapter : ExpandableRecyclerAdapter>, linkHeaders: LinkHeaders, type: ApiType) {
val body = response.body() ?: return
val filteredCourses = body.filter {
- !it.accessRestrictedByDate && !it.isInvited() && it.isNotDeleted() && (when (filterMode) {
+ !it.accessRestrictedByDate && !it.isInvited() && (when (filterMode) {
is FavoritedCourses -> it.isFavorite
else -> true
})
diff --git a/apps/student/src/main/java/com/instructure/student/di/FragmentModule.kt b/apps/student/src/main/java/com/instructure/student/di/FragmentModule.kt
new file mode 100644
index 0000000000..e5acd2200a
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/di/FragmentModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.di
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.pandautils.navigation.WebViewRouter
+import com.instructure.student.navigation.StudentWebViewRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+/**
+ * Module for various common Fragment scope dependencies that are used in different Fragments.
+ */
+@Module
+@InstallIn(FragmentComponent::class)
+class FragmentModule {
+
+ @Provides
+ fun provideWebViewRouter(activity: FragmentActivity): WebViewRouter {
+ return StudentWebViewRouter(activity)
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/di/elementary/GradesModule.kt b/apps/student/src/main/java/com/instructure/student/di/elementary/GradesModule.kt
new file mode 100644
index 0000000000..466bcf9e9f
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/di/elementary/GradesModule.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.di.elementary
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.pandautils.features.elementary.grades.GradesRouter
+import com.instructure.student.mobius.elementary.grades.StudentGradesRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class GradesModule {
+
+ @Provides
+ fun provideGradesRouter(activity: FragmentActivity): GradesRouter {
+ return StudentGradesRouter(activity)
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt b/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt
index 9f97d2e0a0..a8147a721a 100644
--- a/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt
+++ b/apps/student/src/main/java/com/instructure/student/di/elementary/HomeroomModule.kt
@@ -18,7 +18,7 @@ package com.instructure.student.di.elementary
import androidx.fragment.app.FragmentActivity
import com.instructure.pandautils.features.elementary.homeroom.HomeroomRouter
-import com.instructure.student.mobius.elementary.StudentHomeroomRouter
+import com.instructure.student.mobius.elementary.homeroom.StudentHomeroomRouter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/apps/student/src/main/java/com/instructure/student/di/elementary/ResourcesModule.kt b/apps/student/src/main/java/com/instructure/student/di/elementary/ResourcesModule.kt
new file mode 100644
index 0000000000..fc7e62e8a3
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/di/elementary/ResourcesModule.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.di.elementary
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesRouter
+import com.instructure.student.mobius.elementary.resources.StudentResourcesRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class ResourcesModule {
+
+ @Provides
+ fun provideResourcesRouter(activity: FragmentActivity): ResourcesRouter {
+ return StudentResourcesRouter(activity)
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/di/elementary/schedule/ScheduleModule.kt b/apps/student/src/main/java/com/instructure/student/di/elementary/schedule/ScheduleModule.kt
new file mode 100644
index 0000000000..2928c63b47
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/di/elementary/schedule/ScheduleModule.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.di.elementary.schedule
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.pandautils.features.elementary.schedule.ScheduleRouter
+import com.instructure.student.mobius.elementary.schedule.StudentScheduleRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class ScheduleModule {
+
+ @Provides
+ fun provideScheduleRouter(activity: FragmentActivity): ScheduleRouter {
+ return StudentScheduleRouter(activity)
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/dialog/CanvasContextListDialog.kt b/apps/student/src/main/java/com/instructure/student/dialog/CanvasContextListDialog.kt
index e5f3d26215..4f059177aa 100644
--- a/apps/student/src/main/java/com/instructure/student/dialog/CanvasContextListDialog.kt
+++ b/apps/student/src/main/java/com/instructure/student/dialog/CanvasContextListDialog.kt
@@ -95,7 +95,7 @@ class CanvasContextListDialog : AppCompatDialogFragment() {
{ CourseManager.getCourses(forceNetwork, it) },
{ GroupManager.getFavoriteGroups(it, forceNetwork) }
)
- val validCourses = courses.filter { it.isFavorite && it.isValidTerm() && it.hasActiveEnrollment() && it.isNotDeleted() }
+ val validCourses = courses.filter { it.isFavorite && it.isValidTerm() && it.hasActiveEnrollment() }
val courseMap = validCourses.associateBy { it.id }
val validGroups = groups.filter { it.courseId == 0L || courseMap[it.courseId] != null }
updateCanvasContexts(validCourses, validGroups)
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 936b4b7dc0..b23cadb342 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
@@ -83,12 +83,14 @@ class ApplicationSettingsFragment : ParentFragment() {
legal.onClick { LegalDialogStyled().show(requireFragmentManager(), LegalDialogStyled.TAG) }
pinAndFingerprint.setGone() // TODO: Wire up once implemented
- pairObserver.setVisible()
- pairObserver.onClick {
- if (APIHelper.hasNetworkConnection()) {
- addFragment(PairObserverFragment.newInstance())
- } else {
- NoInternetConnectionDialog.show(requireFragmentManager())
+ if (ApiPrefs.canGeneratePairingCode == true) {
+ pairObserver.setVisible()
+ pairObserver.onClick {
+ if (APIHelper.hasNetworkConnection()) {
+ addFragment(PairObserverFragment.newInstance())
+ } else {
+ NoInternetConnectionDialog.show(requireFragmentManager())
+ }
}
}
diff --git a/apps/student/src/main/java/com/instructure/student/fragment/CalendarFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/CalendarFragment.kt
index b0ec29293b..ee0ece3622 100644
--- a/apps/student/src/main/java/com/instructure/student/fragment/CalendarFragment.kt
+++ b/apps/student/src/main/java/com/instructure/student/fragment/CalendarFragment.kt
@@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.instructure.canvasapi2.models.PlannableType
import com.instructure.canvasapi2.models.PlannerItem
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.pageview.PageView
@@ -78,13 +79,13 @@ class CalendarFragment : ParentFragment() {
private fun routeToItem(item: PlannerItem) {
val route: Route? = when (item.plannableType) {
- "assignment" -> {
+ PlannableType.ASSIGNMENT -> {
AssignmentDetailsFragment.makeRoute(item.canvasContext, item.plannable.id)
}
- "discussion_topic" -> {
+ PlannableType.DISCUSSION_TOPIC -> {
DiscussionDetailsFragment.makeRoute(item.canvasContext, item.plannable.id, title = item.plannable.title)
}
- "quiz" -> {
+ PlannableType.QUIZ -> {
if (item.plannable.assignmentId != null) {
// This is a quiz assignment, go to the assignment page
AssignmentDetailsFragment.makeRoute(item.canvasContext, item.plannable.assignmentId!!)
@@ -96,7 +97,7 @@ class CalendarFragment : ParentFragment() {
} else null
}
}
- "calendar_event" -> {
+ PlannableType.CALENDAR_EVENT -> {
CalendarEventFragment.makeRoute(item.canvasContext, item.plannable.id)
}
else -> {
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 3468d97569..f6953342f8 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
@@ -109,7 +109,9 @@ class FlutterCalendarFragment : FlutterFragment() {
// Perform onBackPressed on the FlutterFragment, which will attempt to pop the current route and update
// the 'shouldPop' value for future use.
- onBackPressed()
+ if (!shouldPop) {
+ onBackPressed()
+ }
// If 'shouldPop' was true it means we just popped a CalendarScreen in Flutter and that we should also
// allow this fragment to be popped
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 6a4314cfad..515e5660a4 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
@@ -60,6 +60,7 @@ class InboxComposeMessageFragment : ParentFragment() {
private val includedMessageIds by LongArrayArg(key = Const.MESSAGE)
private val isReply by BooleanArg(key = IS_REPLY)
private val currentMessage by NullableParcelableArg(key = Const.MESSAGE_TO_USER)
+ private val homeroomMessage by BooleanArg(key = HOMEROOM_MESSAGE)
private var selectedContext by NullableParcelableArg(key = Const.CANVAS_CONTEXT)
private val isNewMessage by lazy { conversation == null }
@@ -203,6 +204,16 @@ class InboxComposeMessageFragment : ParentFragment() {
// Get courses and groups if this is a new compose message
if (isNewMessage) getAllCoursesAndGroups()
+
+ if (homeroomMessage) {
+ hideFieldsForHomeroomMessage()
+ }
+ }
+
+ private fun hideFieldsForHomeroomMessage() {
+ spinnerWrapper.setGone()
+ recipientWrapper.setGone()
+ sendIndividualMessageWrapper.setGone()
}
private fun getAllCoursesAndGroups() {
@@ -244,8 +255,10 @@ class InboxComposeMessageFragment : ParentFragment() {
}
private fun courseWasSelected() {
- recipientWrapper.visibility = View.VISIBLE
- contactsImageButton.visibility = View.VISIBLE
+ if (!homeroomMessage) {
+ recipientWrapper.visibility = View.VISIBLE
+ contactsImageButton.visibility = View.VISIBLE
+ }
requireActivity().invalidateOptionsMenu()
chips.canvasContext = selectedContext
}
@@ -442,6 +455,7 @@ class InboxComposeMessageFragment : ParentFragment() {
private const val IS_REPLY = "is_reply"
private const val PARTICIPANTS = "participants"
+ private const val HOMEROOM_MESSAGE = "homeroom_message"
fun makeRoute(
isReply: Boolean,
@@ -464,11 +478,13 @@ class InboxComposeMessageFragment : ParentFragment() {
fun makeRoute(
canvasContext: CanvasContext,
- participants: ArrayList
+ participants: ArrayList,
+ homeroomMessage: Boolean = false
): Route {
val bundle = Bundle().apply {
putParcelableArrayList(PARTICIPANTS, participants)
putParcelable(Const.CANVAS_CONTEXT, canvasContext)
+ putBoolean(HOMEROOM_MESSAGE, homeroomMessage)
}
return Route(InboxComposeMessageFragment::class.java, canvasContext, bundle)
}
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 2304999645..e7844fdb00 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
@@ -20,14 +20,22 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.viewpager.widget.ViewPager
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.*
+import com.instructure.pandautils.features.elementary.grades.GradesFragment
+import com.instructure.pandautils.features.elementary.homeroom.HomeroomFragment
+import com.instructure.pandautils.features.elementary.resources.ResourcesFragment
+import com.instructure.pandautils.features.elementary.schedule.pager.SchedulePagerFragment
+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.student.R
+import com.instructure.student.databinding.FragmentElementaryDashboardBinding
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.*
@@ -35,6 +43,15 @@ class ElementaryDashboardFragment : ParentFragment() {
private val canvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT)
+ private val schedulePagerFragment = SchedulePagerFragment.newInstance()
+
+ private val fragments = listOf(
+ HomeroomFragment.newInstance(),
+ schedulePagerFragment,
+ GradesFragment.newInstance(),
+ ResourcesFragment.newInstance()
+ )
+
override fun title(): String = if (isAdded) getString(R.string.dashboard) else ""
override fun applyTheme() {
@@ -42,19 +59,24 @@ class ElementaryDashboardFragment : ParentFragment() {
navigation?.attachNavigationDrawer(this, toolbar)
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- layoutInflater.inflate(R.layout.fragment_elementary_dashboard, container, false)
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val binding = FragmentElementaryDashboardBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = this
+ binding.todayButtonVisibility = schedulePagerFragment.getTodayButtonVisibility()
+
+ binding.todayButton.setOnClickListener {
+ schedulePagerFragment.jumpToToday()
+ }
+
+ binding.dashboardPager.offscreenPageLimit = fragments.size
+
+ return binding.root
+ }
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)
+ dashboardPager.adapter = ElementaryDashboardPagerAdapter(fragments, childFragmentManager)
dashboardTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
@@ -63,9 +85,14 @@ class ElementaryDashboardFragment : ParentFragment() {
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.let {
dashboardPager.setCurrentItem(it.position, !isTablet)
+ if (it.position != fragments.indexOf(schedulePagerFragment)) {
+ todayButton.visibility = View.GONE
+ } else {
+ todayButton.visibility =
+ if (schedulePagerFragment.getTodayButtonVisibility().value == true) View.VISIBLE else View.GONE
+ }
}
}
-
})
}
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/elementary/grades/StudentGradesRouter.kt b/apps/student/src/main/java/com/instructure/student/mobius/elementary/grades/StudentGradesRouter.kt
new file mode 100644
index 0000000000..0475d8f222
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/mobius/elementary/grades/StudentGradesRouter.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.mobius.elementary.grades
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.canvasapi2.models.Course
+import com.instructure.pandautils.features.elementary.grades.GradesRouter
+import com.instructure.student.fragment.GradesListFragment
+import com.instructure.student.router.RouteMatcher
+
+class StudentGradesRouter(private val activity: FragmentActivity) : GradesRouter {
+
+ override fun openCourseGrades(course: Course) {
+ val route = GradesListFragment.makeRoute(course)
+ RouteMatcher.route(activity, route)
+ }
+}
\ No newline at end of file
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/homeroom/StudentHomeroomRouter.kt
similarity index 71%
rename from apps/student/src/main/java/com/instructure/student/mobius/elementary/StudentHomeroomRouter.kt
rename to apps/student/src/main/java/com/instructure/student/mobius/elementary/homeroom/StudentHomeroomRouter.kt
index 205df4e6f0..a4e73ebc8f 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/homeroom/StudentHomeroomRouter.kt
@@ -14,7 +14,7 @@
* along with this program. If not, see .
*
*/
-package com.instructure.student.mobius.elementary
+package com.instructure.student.mobius.elementary.homeroom
import androidx.fragment.app.FragmentActivity
import com.instructure.canvasapi2.models.CanvasContext
@@ -23,26 +23,12 @@ 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.fragment.*
+import com.instructure.student.mobius.assignmentDetails.ui.AssignmentDetailsFragment
import com.instructure.student.router.RouteMatcher
class StudentHomeroomRouter(private val activity: FragmentActivity) : HomeroomRouter {
- override fun canRouteInternally(url: String): Boolean {
- return RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = false, allowUnsupported = false)
- }
-
- override fun routeInternally(url: String) {
- RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = true, allowUnsupported = false)
- }
-
- override fun openMedia(url: String) {
- RouteMatcher.openMedia(activity, url)
- }
-
override fun openAnnouncements(canvasContext: CanvasContext) {
val route = AnnouncementListFragment.makeRoute(canvasContext)
RouteMatcher.route(activity, route)
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/elementary/resources/StudentResourcesRouter.kt b/apps/student/src/main/java/com/instructure/student/mobius/elementary/resources/StudentResourcesRouter.kt
new file mode 100644
index 0000000000..c6c5ceff05
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/mobius/elementary/resources/StudentResourcesRouter.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.mobius.elementary.resources
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.canvasapi2.models.*
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesRouter
+import com.instructure.student.fragment.InboxComposeMessageFragment
+import com.instructure.student.fragment.LtiLaunchFragment
+import com.instructure.student.router.RouteMatcher
+
+class StudentResourcesRouter(private val activity: FragmentActivity) : ResourcesRouter {
+
+ override fun openLti(ltiTool: LTITool) {
+ val course = Course(id = ltiTool.contextId ?: 0, name = ltiTool.contextName ?: "")
+ val route = LtiLaunchFragment.makeRoute(
+ course,
+ ltiTool.url ?: ltiTool.courseNavigation?.url ?: "",
+ ltiTool.courseNavigation?.text ?: ltiTool.name ?: "",
+ sessionLessLaunch = true,
+ isAssignmentLTI = false,
+ ltiTool = ltiTool)
+ RouteMatcher.route(activity, route)
+ }
+
+ override fun openComposeMessage(user: User) {
+ val recipient = Recipient.from(user)
+ val context = Course(id = user.enrollments[0].courseId, homeroomCourse = true)
+ val route = InboxComposeMessageFragment.makeRoute(context, arrayListOf(recipient), homeroomMessage = true)
+ RouteMatcher.route(activity, route)
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/elementary/schedule/StudentScheduleRouter.kt b/apps/student/src/main/java/com/instructure/student/mobius/elementary/schedule/StudentScheduleRouter.kt
new file mode 100644
index 0000000000..580c9ec153
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/mobius/elementary/schedule/StudentScheduleRouter.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.mobius.elementary.schedule
+
+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.pandautils.features.elementary.schedule.ScheduleRouter
+import com.instructure.student.fragment.BasicQuizViewFragment
+import com.instructure.student.fragment.CalendarEventFragment
+import com.instructure.student.fragment.CourseBrowserFragment
+import com.instructure.student.fragment.DiscussionDetailsFragment
+import com.instructure.student.mobius.assignmentDetails.ui.AssignmentDetailsFragment
+import com.instructure.student.router.RouteMatcher
+
+class StudentScheduleRouter(private val activity: FragmentActivity) : ScheduleRouter {
+
+ override fun openAssignment(canvasContext: CanvasContext, assignmentId: Long) {
+ RouteMatcher.route(activity, AssignmentDetailsFragment.makeRoute(canvasContext, assignmentId))
+ }
+
+ override fun openCalendarEvent(canvasContext: CanvasContext, scheduleItemId: Long) {
+ RouteMatcher.route(activity, CalendarEventFragment.makeRoute(canvasContext, scheduleItemId))
+ }
+
+ override fun openAnnouncementDetails(course: Course, announcement: DiscussionTopicHeader) {
+ RouteMatcher.route(activity, DiscussionDetailsFragment.makeRoute(course, announcement))
+ }
+
+ override fun openQuiz(canvasContext: CanvasContext, htmlUrl: String) {
+ RouteMatcher.route(activity, BasicQuizViewFragment.makeRoute(canvasContext, htmlUrl))
+ }
+
+ override fun openDiscussion(canvasContext: CanvasContext, discussionId: Long, discussionTitle: String) {
+ RouteMatcher.route(
+ activity,
+ DiscussionDetailsFragment.makeRoute(canvasContext, discussionId, title = discussionTitle)
+ )
+ }
+
+ override fun openCourse(course: Course) {
+ RouteMatcher.route(activity, CourseBrowserFragment.makeRoute(course))
+ }
+}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt
index 759c5d8e82..9229b7757e 100644
--- a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt
+++ b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt
@@ -19,6 +19,7 @@ package com.instructure.student.navigation
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.interactions.router.Route
+import com.instructure.student.R
import com.instructure.student.fragment.*
class DefaultNavigationBehavior() : NavigationBehavior {
@@ -42,6 +43,8 @@ class DefaultNavigationBehavior() : NavigationBehavior {
override val shouldOverrideFont: Boolean
get() = false
+ override val bottomBarMenu: Int = R.menu.bottom_bar_menu
+
override fun createHomeFragmentRoute(canvasContext: CanvasContext?): Route {
return DashboardFragment.makeRoute(ApiPrefs.user)
}
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 3f2efbc00e..8e3291174c 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
@@ -19,6 +19,7 @@ package com.instructure.student.navigation
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.interactions.router.Route
+import com.instructure.student.R
import com.instructure.student.fragment.*
import com.instructure.student.mobius.elementary.ElementaryDashboardFragment
@@ -43,6 +44,8 @@ class ElementaryNavigationBehavior() : NavigationBehavior {
override val shouldOverrideFont: Boolean
get() = true
+ override val bottomBarMenu: Int = R.menu.bottom_bar_menu_elementary
+
override fun createHomeFragmentRoute(canvasContext: CanvasContext?): Route {
return ElementaryDashboardFragment.makeRoute(ApiPrefs.user)
}
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 d256245e6f..1c84ada09c 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
@@ -16,6 +16,7 @@
*/
package com.instructure.student.navigation
+import androidx.annotation.MenuRes
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.interactions.router.Route
import com.instructure.student.fragment.ParentFragment
@@ -35,6 +36,9 @@ interface NavigationBehavior {
val shouldOverrideFont: Boolean
+ @get:MenuRes
+ val bottomBarMenu: Int
+
fun createHomeFragmentRoute(canvasContext: CanvasContext?): Route
fun createHomeFragment(route: Route): ParentFragment
diff --git a/apps/student/src/main/java/com/instructure/student/navigation/StudentWebViewRouter.kt b/apps/student/src/main/java/com/instructure/student/navigation/StudentWebViewRouter.kt
new file mode 100644
index 0000000000..634e3baa55
--- /dev/null
+++ b/apps/student/src/main/java/com/instructure/student/navigation/StudentWebViewRouter.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.navigation
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.canvasapi2.utils.ApiPrefs
+import com.instructure.pandautils.navigation.WebViewRouter
+import com.instructure.student.router.RouteMatcher
+
+class StudentWebViewRouter(val activity: FragmentActivity) : WebViewRouter {
+
+ override fun canRouteInternally(url: String): Boolean {
+ return RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = false, allowUnsupported = false)
+ }
+
+ override fun routeInternally(url: String) {
+ RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = true, allowUnsupported = false)
+ }
+
+ override fun openMedia(url: String) {
+ RouteMatcher.openMedia(activity, url)
+ }
+}
\ 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 cecc7965d6..251ba56c6a 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,10 +15,8 @@
*/
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 26b1b51ce8..3c596cc38e 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
@@ -14,91 +14,108 @@
~ along with this program. If not, see .
~
-->
-
+ xmlns:tools="http://schemas.android.com/tools">
-
+
-
+
-
+
+
-
+
-
+ android:layout_height="?android:attr/actionBarSize"
+ android:background="@color/defaultPrimary"
+ android:elevation="6dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:theme="@style/ToolBarStyle">
-
+
+
+
+ android:background="@color/white"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/toolbar"
+ app:tabContentStart="8dp"
+ app:tabIconTint="@color/tab_layout_icon_tint"
+ app:tabIndicator="@drawable/tab_bar_indicator"
+ app:tabIndicatorColor="@color/blueAnnotation"
+ app:tabIndicatorFullWidth="false"
+ app:tabIndicatorHeight="3dp"
+ app:tabInlineLabel="true"
+ app:tabMode="scrollable"
+ app:tabPaddingEnd="12dp"
+ app:tabPaddingStart="8dp"
+ app:tabSelectedTextColor="@color/blueAnnotation"
+ app:tabTextAppearance="@android:style/TextAppearance.Widget.TabWidget"
+ app:tabTextColor="@color/defaultTextDark">
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/apps/student/src/main/res/layout/activity_navigation.xml b/apps/student/src/main/res/layout/activity_navigation.xml
index d244d0f333..6423d17983 100644
--- a/apps/student/src/main/res/layout/activity_navigation.xml
+++ b/apps/student/src/main/res/layout/activity_navigation.xml
@@ -58,8 +58,7 @@
android:layout_height="wrap_content"
android:background="@color/white"
app:elevation="0dp"
- app:labelVisibilityMode="labeled"
- app:menu="@menu/bottom_bar_menu" />
+ app:labelVisibilityMode="labeled" />
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 4578a5b57d..9e3885f29a 100644
--- a/apps/student/src/main/res/layout/fragment_application_settings.xml
+++ b/apps/student/src/main/res/layout/fragment_application_settings.xml
@@ -74,7 +74,7 @@
android:layout_height="wrap_content"
android:labelFor="@+id/toggle"
android:textSize="16sp"
- android:text="@string/settingsElementaryView"
+ android:text="@string/settingsHomeroomView"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@id/elementaryViewDescription"
app:layout_constraintStart_toStartOf="parent"
diff --git a/apps/student/src/main/res/layout/fragment_elementary_dashboard.xml b/apps/student/src/main/res/layout/fragment_elementary_dashboard.xml
index 20fc5e183c..bd1330b2da 100644
--- a/apps/student/src/main/res/layout/fragment_elementary_dashboard.xml
+++ b/apps/student/src/main/res/layout/fragment_elementary_dashboard.xml
@@ -14,80 +14,106 @@
~ along with this program. If not, see .
~
-->
-
+ xmlns:tools="http://schemas.android.com/tools">
-
+
-
+
-
+
+
-
+
-
+ android:layout_height="?android:attr/actionBarSize"
+ android:background="@color/defaultPrimary"
+ android:elevation="6dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:theme="@style/ToolBarStyle">
-
+
+
+
+ android:background="@color/white"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/toolbar"
+ app:tabContentStart="16dp"
+ app:tabIconTint="@color/tab_layout_icon_tint"
+ app:tabIndicator="@drawable/tab_bar_indicator"
+ app:tabIndicatorColor="@color/blueAnnotation"
+ app:tabIndicatorFullWidth="false"
+ app:tabIndicatorHeight="3dp"
+ app:tabInlineLabel="true"
+ app:tabMode="scrollable"
+ app:tabPaddingEnd="12dp"
+ app:tabPaddingStart="8dp"
+ app:tabSelectedTextColor="@color/blueAnnotation"
+ app:tabTextAppearance="@android:style/TextAppearance.Widget.TabWidget"
+ app:tabTextColor="@color/defaultTextDark">
-
+
-
+
-
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/apps/student/src/main/res/menu/bottom_bar_menu_elementary.xml b/apps/student/src/main/res/menu/bottom_bar_menu_elementary.xml
new file mode 100644
index 0000000000..d4885b30ed
--- /dev/null
+++ b/apps/student/src/main/res/menu/bottom_bar_menu_elementary.xml
@@ -0,0 +1,62 @@
+
+
+
diff --git a/apps/student/src/main/res/values/styles.xml b/apps/student/src/main/res/values/styles.xml
index 8534714d6e..28460ec092 100644
--- a/apps/student/src/main/res/values/styles.xml
+++ b/apps/student/src/main/res/values/styles.xml
@@ -240,11 +240,6 @@
@color/textLightGray
-
-
-
-
diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle
index 37c6e6fbd8..19face607e 100644
--- a/apps/teacher/build.gradle
+++ b/apps/teacher/build.gradle
@@ -25,7 +25,7 @@ apply plugin: 'com.google.firebase.crashlytics'
apply from: '../../gradle/coverage.gradle'
apply plugin: 'dagger.hilt.android.plugin'
-def updatePriority = 0
+def updatePriority = 2
def coverageEnabled = project.hasProperty('coverage')
if (coverageEnabled) {
@@ -43,8 +43,8 @@ android {
defaultConfig {
minSdkVersion Versions.MIN_SDK
targetSdkVersion Versions.TARGET_SDK
- versionCode = 42
- versionName = '1.14.0'
+ versionCode = 43
+ versionName = '1.14.1'
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner'
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/FragmentModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/FragmentModule.kt
new file mode 100644
index 0000000000..8c9b3a40f5
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/di/FragmentModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.teacher.di
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.pandautils.navigation.WebViewRouter
+import com.instructure.teacher.navigation.TeacherWebViewRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+/**
+ * Module for various common Fragment scope dependencies that are used in different Fragments.
+ */
+@Module
+@InstallIn(FragmentComponent::class)
+class FragmentModule {
+
+ @Provides
+ fun provideWebViewRouter(activity: FragmentActivity): WebViewRouter {
+ return TeacherWebViewRouter(activity)
+ }
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/GradesModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/GradesModule.kt
new file mode 100644
index 0000000000..300d1b9a0f
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/GradesModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.teacher.di.elementary
+
+import com.instructure.pandautils.features.elementary.grades.GradesRouter
+import com.instructure.teacher.features.elementary.grades.TeacherGradesRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class GradesModule {
+
+ @Provides
+ fun provideGradesRouter(): GradesRouter {
+ return TeacherGradesRouter()
+ }
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/HomeroomModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/HomeroomModule.kt
index 35bdddafc5..c77040569e 100644
--- a/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/HomeroomModule.kt
+++ b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/HomeroomModule.kt
@@ -16,9 +16,8 @@
*/
package com.instructure.teacher.di.elementary
-import androidx.fragment.app.FragmentActivity
import com.instructure.pandautils.features.elementary.homeroom.HomeroomRouter
-import com.instructure.teacher.features.elementary.TeacherHomeroomRouter
+import com.instructure.teacher.features.elementary.homeroom.TeacherHomeroomRouter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/ResourcesModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/ResourcesModule.kt
new file mode 100644
index 0000000000..183472a02b
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/ResourcesModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.teacher.di.elementary
+
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesRouter
+import com.instructure.teacher.features.elementary.resources.TeacherResourcesRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class ResourcesModule {
+
+ @Provides
+ fun provideResourcesRouter(): ResourcesRouter {
+ return TeacherResourcesRouter()
+ }
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/schedule/ScheduleModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/schedule/ScheduleModule.kt
new file mode 100644
index 0000000000..65a11f63c9
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/di/elementary/schedule/ScheduleModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.teacher.di.elementary.schedule
+
+import com.instructure.pandautils.features.elementary.schedule.ScheduleRouter
+import com.instructure.teacher.features.elementary.TeacherScheduleRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.FragmentComponent
+
+@Module
+@InstallIn(FragmentComponent::class)
+class ScheduleModule {
+
+ @Provides
+ fun provideScheduleRouter(): ScheduleRouter {
+ return TeacherScheduleRouter()
+ }
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherScheduleRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherScheduleRouter.kt
new file mode 100644
index 0000000000..872fdff366
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherScheduleRouter.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.teacher.features.elementary
+
+import com.instructure.canvasapi2.models.CanvasContext
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.DiscussionTopicHeader
+import com.instructure.pandautils.features.elementary.schedule.ScheduleRouter
+
+class TeacherScheduleRouter : ScheduleRouter {
+ override fun openAssignment(canvasContext: CanvasContext, assignmentId: Long) = Unit
+
+ override fun openCalendarEvent(canvasContext: CanvasContext, scheduleItemId: Long) = Unit
+
+ override fun openAnnouncementDetails(course: Course, announcement: DiscussionTopicHeader) = Unit
+
+ override fun openQuiz(canvasContext: CanvasContext, htmlUrl: String) = Unit
+
+ override fun openDiscussion(canvasContext: CanvasContext, discussionId: Long, discussionTitle: String) = Unit
+
+ override fun openCourse(course: Course) = Unit
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/grades/TeacherGradesRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/grades/TeacherGradesRouter.kt
new file mode 100644
index 0000000000..110c12d5de
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/grades/TeacherGradesRouter.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.teacher.features.elementary.grades
+
+import com.instructure.canvasapi2.models.Course
+import com.instructure.pandautils.features.elementary.grades.GradesRouter
+
+class TeacherGradesRouter : GradesRouter {
+ override fun openCourseGrades(course: Course) = Unit
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherHomeroomRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/homeroom/TeacherHomeroomRouter.kt
similarity index 85%
rename from apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherHomeroomRouter.kt
rename to apps/teacher/src/main/java/com/instructure/teacher/features/elementary/homeroom/TeacherHomeroomRouter.kt
index 24fcf77bba..0b065b75bb 100644
--- a/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/TeacherHomeroomRouter.kt
+++ b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/homeroom/TeacherHomeroomRouter.kt
@@ -14,7 +14,7 @@
* along with this program. If not, see .
*
*/
-package com.instructure.teacher.features.elementary
+package com.instructure.teacher.features.elementary.homeroom
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.Course
@@ -22,11 +22,6 @@ import com.instructure.canvasapi2.models.DiscussionTopicHeader
import com.instructure.pandautils.features.elementary.homeroom.HomeroomRouter
class TeacherHomeroomRouter : HomeroomRouter {
- override fun canRouteInternally(url: String): Boolean = false
-
- override fun routeInternally(url: String) = Unit
-
- override fun openMedia(url: String) = Unit
override fun openAnnouncements(canvasContext: CanvasContext) = Unit
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/resources/TeacherResourcesRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/resources/TeacherResourcesRouter.kt
new file mode 100644
index 0000000000..281a7bf0aa
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/features/elementary/resources/TeacherResourcesRouter.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.teacher.features.elementary.resources
+
+import com.instructure.canvasapi2.models.LTITool
+import com.instructure.canvasapi2.models.User
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesRouter
+
+class TeacherResourcesRouter : ResourcesRouter {
+
+ override fun openLti(ltiTool: LTITool) = Unit
+
+ override fun openComposeMessage(user: User) = Unit
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt
index 47cecb3246..451a908904 100644
--- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt
+++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt
@@ -461,7 +461,11 @@ class EditAssignmentDetailsFragment : BaseFragment() {
} catch (e: Throwable) {
saveButton?.setVisible()
savingProgressBar.setGone()
- toast(R.string.error_saving_assignment)
+ if (mAssignment.inClosedGradingPeriod) {
+ toast(R.string.error_saving_assignment_closed_grading_period)
+ } else {
+ toast(R.string.error_saving_assignment)
+ }
}
}
}
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/navigation/TeacherWebViewRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/navigation/TeacherWebViewRouter.kt
new file mode 100644
index 0000000000..1632ce8fcb
--- /dev/null
+++ b/apps/teacher/src/main/java/com/instructure/teacher/navigation/TeacherWebViewRouter.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.teacher.navigation
+
+import androidx.fragment.app.FragmentActivity
+import com.instructure.canvasapi2.utils.ApiPrefs
+import com.instructure.pandautils.navigation.WebViewRouter
+import com.instructure.teacher.router.RouteMatcher
+
+class TeacherWebViewRouter(val activity: FragmentActivity) : WebViewRouter {
+
+ override fun canRouteInternally(url: String): Boolean {
+ return RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = false)
+ }
+
+ override fun routeInternally(url: String) {
+ RouteMatcher.canRouteInternally(activity, url, ApiPrefs.domain, routeIfPossible = true)
+ }
+
+ override fun openMedia(url: String) {
+ RouteMatcher.openMedia(activity, url)
+ }
+}
\ No newline at end of file
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/grade_slider/SpeedGraderSlider.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/grade_slider/SpeedGraderSlider.kt
index 6e45e44609..2ae9f95e2c 100644
--- a/apps/teacher/src/main/java/com/instructure/teacher/view/grade_slider/SpeedGraderSlider.kt
+++ b/apps/teacher/src/main/java/com/instructure/teacher/view/grade_slider/SpeedGraderSlider.kt
@@ -36,7 +36,7 @@ import kotlin.math.roundToInt
import kotlin.properties.Delegates
class SpeedGraderSlider @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
var onGradeChanged: (String, Boolean) -> Unit by Delegates.notNull()
@@ -44,6 +44,7 @@ class SpeedGraderSlider @JvmOverloads constructor(
private lateinit var assignment: Assignment
private var submission: Submission? = null
private lateinit var assignee: Assignee
+ private var maxGradeValue: Int = 0
private var isExcused: Boolean = false
private var notGraded: Boolean = false
@@ -68,7 +69,25 @@ class SpeedGraderSlider @JvmOverloads constructor(
accessibleTouchTarget()
}
- slider.max = 0
+ minGrade.apply {
+ setOnClickListener {
+ updateGrade(0)
+ notGraded = false
+ isExcused = false
+ }
+ accessibleTouchTarget()
+ }
+
+ maxGrade.apply {
+ setOnClickListener {
+ updateGrade(maxGradeValue)
+ notGraded = false
+ isExcused = false
+ }
+ accessibleTouchTarget()
+ }
+
+ slider.max = maxGradeValue
}
fun setData(assignment: Assignment, submission: Submission?, assignee: Assignee) {
@@ -111,17 +130,26 @@ class SpeedGraderSlider @JvmOverloads constructor(
pointsPossibleView.setGone()
}
slider.progress = this.submission?.score?.div(this.assignment.pointsPossible)?.times(100)?.toInt()
- ?: 0
+ ?: 0
minGrade.text = "0%"
}
+ maxGradeValue = slider.max
+
slider.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (!isExcused && !notGraded) {
if (assignment.gradingType?.let { Assignment.getGradingTypeFromAPIString(it) } == Assignment.GradingType.PERCENT) {
- EventBus.getDefault().post(ShowSliderGradeEvent(seekBar, this@SpeedGraderSlider.assignee.id, "$progress%"))
+ EventBus.getDefault()
+ .post(ShowSliderGradeEvent(seekBar, this@SpeedGraderSlider.assignee.id, "$progress%"))
} else {
- EventBus.getDefault().post(ShowSliderGradeEvent(seekBar, this@SpeedGraderSlider.assignee.id, progress.toString()))
+ EventBus.getDefault().post(
+ ShowSliderGradeEvent(
+ seekBar,
+ this@SpeedGraderSlider.assignee.id,
+ progress.toString()
+ )
+ )
}
}
@@ -188,7 +216,8 @@ class SpeedGraderSlider @JvmOverloads constructor(
val label: String
if (assignment.gradingType?.let { Assignment.getGradingTypeFromAPIString(it) } == Assignment.GradingType.POINTS) {
- anchorRect.left = anchorRect.left + slider.paddingLeft + (this.assignment.pointsPossible.toInt() * stepWidth).roundToInt()
+ anchorRect.left =
+ anchorRect.left + slider.paddingLeft + (this.assignment.pointsPossible.toInt() * stepWidth).roundToInt()
label = NumberHelper.formatDecimal(this.assignment.pointsPossible, 0, true)
} else {
anchorRect.left = anchorRect.left + slider.paddingLeft + width / 2
diff --git a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml
index 2583a6ee5d..eb7a5025ae 100644
--- a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml
+++ b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml
@@ -355,7 +355,7 @@
Låt eleven slippa göraLåt gruppen slippa göraFullständig
- Ofullständig
+ Inte färdigEj bedömdUrsäktad%s %s
@@ -822,7 +822,7 @@
Dölj knappar för Skapa fil och Skapa mappAvpubliceraBegränsad åtkomst
- Begränsad
+ BegränsatDold, filerna inuti kommer att vara tillgängliga via länkar.Endast tillgänglig för elever med länk. Ej tillgänglig i elevfiler.Schemalägg elevens tillgänglighet
diff --git a/apps/teacher/src/main/res/values-sv/strings.xml b/apps/teacher/src/main/res/values-sv/strings.xml
index c5caf66da6..bd1a7dab53 100644
--- a/apps/teacher/src/main/res/values-sv/strings.xml
+++ b/apps/teacher/src/main/res/values-sv/strings.xml
@@ -354,7 +354,7 @@
Låt studenten slippa göraLåt gruppen slippa göraFärdig
- ofullständig
+ Inte färdigEj bedömdUrsäktad%s %s
@@ -391,7 +391,7 @@
Inte inskickade, %s av %sVisa omdöme som…Procent
- Färdig/ofullständig
+ Färdig/Inte färdigPoängGPA-skala
@@ -821,7 +821,7 @@
Dölj knappar för Skapa fil och Skapa mappAvpubliceraBegränsad åtkomst
- Begränsad
+ BegränsatDold, filerna inuti kommer att vara tillgängliga via länkar.Endast tillgänglig för studenter med länk. Ej tillgänglig i studentfiler.Schemalägg studentens tillgänglighet
diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt
index 960e0e0592..cad597cbe6 100644
--- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt
+++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt
@@ -20,7 +20,6 @@ package com.instructure.canvas.espresso.mockCanvas
import android.util.Log
import com.github.javafaker.Faker
-import com.instructure.canvas.espresso.mockCanvas.MockCanvas.Companion.data
import com.instructure.canvas.espresso.mockCanvas.utils.Randomizer
import com.instructure.canvasapi2.models.*
import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation
@@ -150,12 +149,15 @@ class MockCanvas {
val assignmentGroups = mutableMapOf>()
/** Map of assignment ID to assignment */
- val assignments = mutableMapOf()
+ val assignments = mutableMapOf()
+
+ /** Map of todos */
+ val todos = mutableListOf()
/** Map of assignment ID to a list of submissions */
val submissions = mutableMapOf>()
- var ltiTool: LTITool? = null
+ val ltiTools = mutableListOf()
/** Map of course ids to discussion topic headers */
val courseDiscussionTopicHeaders = mutableMapOf>()
@@ -448,15 +450,19 @@ fun MockCanvas.updateUserEnrollments() {
}
}
-fun MockCanvas.addCourseWithEnrollment(user: User, enrollmentType: Enrollment.EnrollmentType) {
- val course = addCourse()
+fun MockCanvas.addCourseWithEnrollment(user: User, enrollmentType: Enrollment.EnrollmentType, score: Double = 0.0, grade: String = "", isHomeroom: Boolean = false): Course {
+ val course = addCourse(isHomeroom = isHomeroom)
addEnrollment(
user = user,
course = course,
type = enrollmentType,
- courseSectionId = if(course.sections.count() > 0) course.sections.get(0).id else 0
+ courseSectionId = if(course.sections.count() > 0) course.sections.get(0).id else 0,
+ currentScore = score,
+ currentGrade = grade
)
+
+ return course
}
/** Creates a new Course and adds it to MockCanvas */
@@ -471,6 +477,15 @@ fun MockCanvas.addCourse(
): Course {
val randomCourseName = Randomizer.randomCourseName()
val endAt = if (concluded) OffsetDateTime.now().minusWeeks(1).toApiString() else null
+
+ val gradingPeriodList = if (withGradingPeriod) {
+ val gradingPeriodId = this.newItemId()
+ val gradingPeriod = GradingPeriod(gradingPeriodId, "Grading Period $gradingPeriodId")
+ addGradingPeriod(id, gradingPeriod)
+ } else {
+ emptyList()
+ }
+
val course = Course(
id = id,
name = randomCourseName,
@@ -481,7 +496,8 @@ fun MockCanvas.addCourse(
isFavorite = isFavorite,
sections = if (section != null) listOf(section) else listOf(),
isPublic = isPublic,
- homeroomCourse = isHomeroom
+ homeroomCourse = isHomeroom,
+ gradingPeriods = gradingPeriodList
)
courses += course.id to course
@@ -490,22 +506,17 @@ fun MockCanvas.addCourse(
val quizzesTab = Tab(position = 1, label = "Quizzes", visibility = "public", tabId = Tab.QUIZZES_ID)
courseTabs += course.id to mutableListOf(assignmentsTab, quizzesTab)
- if(withGradingPeriod) {
- val id = this.newItemId()
- val gradingPeriod = GradingPeriod(id, "Grading Period $id")
- addGradingPeriod(course.id, gradingPeriod)
- }
-
return course
}
-fun MockCanvas.addGradingPeriod(courseId : Long, gradingPeriod: GradingPeriod) {
+fun MockCanvas.addGradingPeriod(courseId : Long, gradingPeriod: GradingPeriod): List {
var gpList = courseGradingPeriods[courseId]
if(gpList == null) {
gpList = mutableListOf()
courseGradingPeriods[courseId] = gpList
}
gpList.add(gradingPeriod)
+ return gpList
}
/** Adds the provided permissions to the course */
@@ -969,10 +980,10 @@ fun MockCanvas.addSubmissionForAssignment(
return userRootSubmission
}
-fun MockCanvas.addLTITool(name: String, url: String): LTITool {
- val ltiTool = LTITool(id = 123L, name = name, url = url)
+fun MockCanvas.addLTITool(name: String, url: String, course: Course, id: Long = newItemId()): LTITool {
+ val ltiTool = LTITool(id = id, name = name, url = url, contextId = course.id, contextName = course.name)
- this.ltiTool = ltiTool
+ this.ltiTools.add(ltiTool)
return ltiTool
}
@@ -993,23 +1004,27 @@ fun MockCanvas.addTerm(name: String = Randomizer.randomEnrollmentTitle()): Term
/** Creates a new Enrollment and adds it to MockCanvas */
fun MockCanvas.addEnrollment(
- user: User,
- course: Course,
- type: Enrollment.EnrollmentType,
- observedUser: User? = null,
- courseSectionId: Long = 0
+ user: User,
+ course: Course,
+ type: Enrollment.EnrollmentType,
+ observedUser: User? = null,
+ courseSectionId: Long = 0,
+ currentScore: Double = 88.1,
+ currentGrade: String = "B+"
): Enrollment {
val enrollment = Enrollment(
- id = enrollments.size + 1L,
- role = type,
- type = type,
- courseId = course.id,
- enrollmentState = "active",
- userId = user.id,
- observedUser = observedUser,
- grades = Grades(currentScore = 88.1, currentGrade = "B+"),
- courseSectionId = courseSectionId,
- user = user
+ id = enrollments.size + 1L,
+ role = type,
+ type = type,
+ courseId = course.id,
+ enrollmentState = "active",
+ userId = user.id,
+ observedUser = observedUser,
+ grades = Grades(currentScore = currentScore, currentGrade = currentGrade),
+ courseSectionId = courseSectionId,
+ user = user,
+ computedCurrentScore = currentScore,
+ computedCurrentGrade = currentGrade
)
enrollments += enrollment.id to enrollment
course.enrollments?.add(enrollment) // You won't see grades in the dashboard unless the course has enrollments
@@ -1931,7 +1946,7 @@ fun MockCanvas.addSubmissionStreamItem(
// Record the StreamItem
var list = streamItems[user.id]
- if(list == null) {
+ if (list == null) {
list = mutableListOf()
streamItems[user.id] = list
}
@@ -1939,4 +1954,23 @@ fun MockCanvas.addSubmissionStreamItem(
// Return the StreamItem
return item
+}
+
+fun MockCanvas.addTodo(name: String, userId: Long, courseId: Long? = null, date: Date? = null): PlannerItem {
+ val todo = PlannerItem(
+ courseId,
+ null,
+ userId,
+ null,
+ null,
+ PlannableType.TODO,
+ Plannable(newItemId(), name, courseId, null, userId, null, date, null, date.toApiString()),
+ date ?: Date(),
+ null,
+ null,
+ null
+ )
+
+ todos.add(todo)
+ return todo
}
\ No newline at end of file
diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt
index 508fe95d8c..0d0141b762 100644
--- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt
+++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt
@@ -20,10 +20,7 @@ import android.util.Log
import com.instructure.canvas.espresso.mockCanvas.Endpoint
import com.instructure.canvas.espresso.mockCanvas.endpoint
import com.instructure.canvas.espresso.mockCanvas.utils.*
-import com.instructure.canvasapi2.models.QuizSubmissionQuestion
-import com.instructure.canvasapi2.models.QuizSubmissionQuestionResponse
-import com.instructure.canvasapi2.models.ScheduleItem
-import com.instructure.canvasapi2.models.Tab
+import com.instructure.canvasapi2.models.*
import okio.Buffer
/**
@@ -122,6 +119,27 @@ object ApiEndpoint : Endpoint(
request.successResponse(result)
}
}
+ ),
+ Segment("external_tools") to Endpoint(
+ Segment("visible_course_nav_tools") to Endpoint {
+ GET {
+ val contextCodes = request.url().queryParameterValues("context_codes[]")
+ val courseIds = contextCodes.map { it.substringAfter("_").toLong() }
+
+ val ltiToolsForCourse = data.ltiTools.filter {
+ courseIds.contains(it.contextId ?: 0)
+ }
+ request.successResponse(ltiToolsForCourse)
+ }
+ }
+ ),
+ Segment("planner") to Endpoint(
+ Segment("overrides") to Endpoint {
+ POST {
+ val plannerOverride = getJsonFromRequestBody(request.body())
+ request.successResponse(plannerOverride!!)
+ }
+ }
)
)
diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ExternalToolsEndpoints.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ExternalToolsEndpoints.kt
index c511f03b73..7529f99f3e 100644
--- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ExternalToolsEndpoints.kt
+++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ExternalToolsEndpoints.kt
@@ -23,8 +23,10 @@ import com.instructure.canvas.espresso.mockCanvas.utils.unauthorizedResponse
object ExternalToolsEndpoint : Endpoint(
response = {
GET { // Only currently used for AssignmentDetailsInteractionTest.testQuizzesNext, which is commented out
- if(data.ltiTool != null) {
- request.successResponse(data.ltiTool!!)
+ val course = data.courses[pathVars.courseId]!!
+ val result = data.ltiTools.filter { it.contextId == course.id }
+ if(!result.isNullOrEmpty()) {
+ request.successResponse(result)
} else {
request.unauthorizedResponse()
}
diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/UserEndpoints.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/UserEndpoints.kt
index a4c1f243f1..d69c60f645 100644
--- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/UserEndpoints.kt
+++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/UserEndpoints.kt
@@ -19,14 +19,7 @@ package com.instructure.canvas.espresso.mockCanvas.endpoints
import com.instructure.canvas.espresso.mockCanvas.Endpoint
import com.instructure.canvas.espresso.mockCanvas.addFileToFolder
import com.instructure.canvas.espresso.mockCanvas.endpoint
-import com.instructure.canvas.espresso.mockCanvas.utils.LongId
-import com.instructure.canvas.espresso.mockCanvas.utils.PathVars
-import com.instructure.canvas.espresso.mockCanvas.utils.Segment
-import com.instructure.canvas.espresso.mockCanvas.utils.UserId
-import com.instructure.canvas.espresso.mockCanvas.utils.successPaginatedResponse
-import com.instructure.canvas.espresso.mockCanvas.utils.successResponse
-import com.instructure.canvas.espresso.mockCanvas.utils.unauthorizedResponse
-import com.instructure.canvas.espresso.mockCanvas.utils.user
+import com.instructure.canvas.espresso.mockCanvas.utils.*
import com.instructure.canvasapi2.models.*
import com.instructure.canvasapi2.utils.pageview.PandataInfo
import okio.Buffer
@@ -73,14 +66,19 @@ object UserEndpoint : Endpoint(
val userId = pathVars.userId
val userCourseIds = data.enrollments.values.filter {it.userId == userId}.map {it -> it.courseId}
+ val todos = data.todos.filter { it.userId == userId }
+
// Gather our assignments
// Currently we assume all the assignments are due today
val plannerItemsList = data.assignments.values
.filter { userCourseIds.contains(it.courseId) }
.map {
- val plannable = Plannable(it.id, it.name ?: "", it.courseId, null, userId, null, null, it.id)
- PlannerItem(it.courseId, null, userId, null, null, "assignment", plannable, Date(), null, SubmissionState())
+ val plannableDate = it.dueDate ?: Date()
+ val plannable = Plannable(it.id, it.name
+ ?: "", it.courseId, null, userId, null, it.dueDate, it.id, null)
+ PlannerItem(it.courseId, null, userId, null, null, PlannableType.ASSIGNMENT, plannable, plannableDate, null, SubmissionState(), false)
}
+ .plus(todos)
request.successResponse(plannerItemsList)
}
diff --git a/automation/espresso/src/main/kotlin/com/instructure/espresso/NestedScrollViewExtension.kt b/automation/espresso/src/main/kotlin/com/instructure/espresso/NestedScrollViewExtension.kt
index fdd45069e8..89a162df2d 100644
--- a/automation/espresso/src/main/kotlin/com/instructure/espresso/NestedScrollViewExtension.kt
+++ b/automation/espresso/src/main/kotlin/com/instructure/espresso/NestedScrollViewExtension.kt
@@ -21,6 +21,7 @@ import android.widget.HorizontalScrollView
import android.widget.ListView
import android.widget.ScrollView
import androidx.core.widget.NestedScrollView
+import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
@@ -33,6 +34,7 @@ class NestedScrollViewExtension(scrolltoAction: ViewAction = ViewActions.scrollT
ViewMatchers.isDescendantOfA(Matchers.anyOf(ViewMatchers.isAssignableFrom(NestedScrollView::class.java),
ViewMatchers.isAssignableFrom(ScrollView::class.java),
ViewMatchers.isAssignableFrom(HorizontalScrollView::class.java),
+ ViewMatchers.isAssignableFrom(RecyclerView::class.java),
ViewMatchers.isAssignableFrom(ListView::class.java))))
}
}
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/SubmissionStateTypeAdapter.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/SubmissionStateTypeAdapter.kt
index f16ffef97f..aebfa80881 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/SubmissionStateTypeAdapter.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/SubmissionStateTypeAdapter.kt
@@ -28,7 +28,13 @@ class SubmissionStateTypeAdapter : JsonDeserializer {
if (json?.isJsonObject == true) {
val submitted = json.asJsonObject?.get("submitted")?.asBoolean ?: false
val missing = json.asJsonObject?.get("missing")?.asBoolean ?: false
- return SubmissionState(submitted, missing)
+ val late = json.asJsonObject?.get("late")?.asBoolean ?: false
+ val excused = json.asJsonObject?.get("excused")?.asBoolean ?: false
+ val graded = json.asJsonObject?.get("graded")?.asBoolean ?: false
+ val needsGrading = json.asJsonObject?.get("needs_grading")?.asBoolean ?: false
+ val withFeedback = json.asJsonObject?.get("with_feedback")?.asBoolean ?: false
+ val redoRequest = json.asJsonObject?.get("redo_request")?.asBoolean ?: false
+ return SubmissionState(submitted, missing, late, excused, graded, needsGrading, withFeedback, redoRequest)
} else {
return null
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/CourseAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/CourseAPI.kt
index a245a692a1..19341539c4 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/CourseAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/CourseAPI.kt
@@ -38,7 +38,7 @@ object CourseAPI {
@get:GET("dashboard/dashboard_cards")
val dashboardCourses: Call>
- @get:GET("courses?include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=current_and_concluded")
+ @get:GET("courses?include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=completed&state[]=available")
val firstPageCourses: Call>
@get:GET("courses?include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=current_and_concluded")
@@ -47,6 +47,9 @@ object CourseAPI {
@get:GET("courses?include[]=term&include[]=syllabus_body&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=completed&state[]=available&include[]=observed_users")
val firstPageCoursesWithSyllabus: Call>
+ @get:GET("courses?include[]=term&include[]=syllabus_body&include[]=license&include[]=is_public&include[]=permissions&enrollment_state=active")
+ val firstPageCoursesWithSyllabusWithActiveEnrollment: Call>
+
@get:GET("courses?include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=completed&state[]=available&state[]=unpublished")
val firstPageCoursesTeacher: Call>
@@ -104,6 +107,9 @@ object CourseAPI {
@GET("courses/{courseId}/rubrics/{rubricId}")
fun getRubricSettings(@Path("courseId") courseId: Long, @Path("rubricId") rubricId: Long): Call
+
+ @GET("courses?include[]=total_scores&include[]=current_grading_period_scores&include[]=grading_periods&include[]=course_image&enrollment_state=active")
+ fun getFirstPageCoursesWithGrades(): Call>
}
@Throws(IOException::class)
@@ -158,6 +164,10 @@ object CourseAPI {
callback.addCall(adapter.build(CoursesInterface::class.java, params).firstPageCoursesWithSyllabus).enqueue(callback)
}
+ fun getFirstPageCoursesWithSyllabusWithActiveEnrollment(adapter: RestBuilder, callback: StatusCallback>, params: RestParams) {
+ callback.addCall(adapter.build(CoursesInterface::class.java, params).firstPageCoursesWithSyllabusWithActiveEnrollment).enqueue(callback)
+ }
+
fun getFirstPageCoursesTeacher(adapter: RestBuilder, callback: StatusCallback>, params: RestParams) {
callback.addCall(adapter.build(CoursesInterface::class.java, params).firstPageCoursesTeacher).enqueue(callback)
}
@@ -241,4 +251,8 @@ object CourseAPI {
fun getRubricSettings(courseId: Long, rubricId: Long, adapter: RestBuilder, callback: StatusCallback, params: RestParams) {
callback.addCall(adapter.build(CoursesInterface::class.java, params).getRubricSettings(courseId, rubricId)).enqueue(callback)
}
+
+ fun getFirstPageCoursesWithGrades(adapter: RestBuilder, callback: StatusCallback>, params: RestParams) {
+ callback.addCall(adapter.build(CoursesInterface::class.java, params).getFirstPageCoursesWithGrades()).enqueue(callback)
+ }
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/EnrollmentAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/EnrollmentAPI.kt
index 2b36f951b2..dd684ab2c1 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/EnrollmentAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/EnrollmentAPI.kt
@@ -62,6 +62,9 @@ object EnrollmentAPI {
@POST("courses/{courseId}/enrollments/{enrollmentId}/{action}")
fun handleInvite(@Path("courseId") courseId: Long, @Path("enrollmentId") enrollmentId: Long, @Path("action") action: String): Call
+
+ @GET("users/self/enrollments?state[]=active&type[]=StudentEnrollment")
+ fun getFirstPageEnrollmentsForGradingPeriod(@Query("grading_period_id") gradingPeriodId: Long): Call>
}
fun getFirstPageEnrollmentsForCourse(
@@ -117,4 +120,16 @@ object EnrollmentAPI {
fun handleInvite(courseId: Long, enrollmentId: Long, acceptInvite: Boolean, adapter: RestBuilder, params: RestParams, callback: StatusCallback) {
callback.addCall(adapter.build(EnrollmentInterface::class.java, params).handleInvite(courseId, enrollmentId, if (acceptInvite) "accept" else "reject")).enqueue(callback)
}
+
+ fun getEnrollmentsForGradingPeriod(
+ gradingPeriodId: Long,
+ adapter: RestBuilder,
+ params: RestParams,
+ callback: StatusCallback>) {
+ if (StatusCallback.isFirstPage(callback.linkHeaders)) {
+ callback.addCall(adapter.build(EnrollmentInterface::class.java, params).getFirstPageEnrollmentsForGradingPeriod(gradingPeriodId)).enqueue(callback)
+ } else if (callback.linkHeaders != null && StatusCallback.moreCallsExist(callback.linkHeaders)) {
+ callback.addCall(adapter.build(EnrollmentInterface::class.java, params).getNextPage(callback.linkHeaders!!.nextUrl!!)).enqueue(callback)
+ }
+ }
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ExternalToolAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ExternalToolAPI.kt
index 6a007c9a44..31ef290017 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ExternalToolAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ExternalToolAPI.kt
@@ -25,6 +25,7 @@ import com.instructure.canvasapi2.models.LTITool
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
+import retrofit2.http.Query
import retrofit2.http.Url
@@ -35,6 +36,9 @@ internal object ExternalToolAPI {
@GET("{contextId}/external_tools?include_parents=true")
fun getExternalToolsForCanvasContext(@Path("contextId") contextId: Long): Call>
+ @GET("external_tools/visible_course_nav_tools")
+ fun getExternalToolsForCourses(@Query("context_codes[]", encoded = true) contextCodes: List): Call>
+
@GET
fun getLtiFromUrl(@Url url: String): Call
}
@@ -47,6 +51,14 @@ internal object ExternalToolAPI {
callback.addCall(adapter.build(ExternalToolInterface::class.java, params).getExternalToolsForCanvasContext(canvasContextId)).enqueue(callback)
}
+ fun getExternalToolsForCourses(
+ canvasContextIds: List,
+ adapter: RestBuilder,
+ params: RestParams,
+ callback: StatusCallback>) {
+ callback.addCall(adapter.build(ExternalToolInterface::class.java, params).getExternalToolsForCourses(canvasContextIds)).enqueue(callback)
+ }
+
fun getLtiFromUrlSynchronous(url: String, adapter: RestBuilder, params: RestParams): LTITool? {
try {
val response = adapter.build(ExternalToolInterface::class.java, params).getLtiFromUrl(url).execute()
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/PlannerAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/PlannerAPI.kt
index 4379a65ab1..0e7252cda0 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/PlannerAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/PlannerAPI.kt
@@ -6,10 +6,10 @@ import com.instructure.canvasapi2.builders.RestParams
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.DiscussionTopicHeader
import com.instructure.canvasapi2.models.PlannerItem
+import com.instructure.canvasapi2.models.PlannerOverride
+import com.instructure.canvasapi2.utils.weave.apiAsync
import retrofit2.Call
-import retrofit2.http.GET
-import retrofit2.http.Path
-import retrofit2.http.Query
+import retrofit2.http.*
object PlannerAPI {
@@ -18,9 +18,24 @@ object PlannerAPI {
@GET("users/self/planner/items")
fun getPlannerItems(@Query("start_date") startDate: String?,
@Query("end_date") endDate: String?): Call>
+
+ @POST("planner/overrides")
+ fun createPlannerOverride(@Body plannerOverride: PlannerOverride): Call
+
+ @FormUrlEncoded
+ @PUT("planner/overrides/{overrideId}")
+ fun updatePlannerOverride(@Path("overrideId") plannerOverrideId: Long, @Field("marked_complete") complete: Boolean): Call
}
fun getPlannerItems(adapter: RestBuilder, callback: StatusCallback>, params: RestParams, startDate: String? = null, endDate: String? = null) {
callback.addCall(adapter.build(PlannerInterface::class.java, params).getPlannerItems(startDate, endDate)).enqueue(callback)
}
+
+ fun createPlannerOverride(adapter: RestBuilder, callback: StatusCallback, params: RestParams, plannerOverride: PlannerOverride) {
+ callback.addCall(adapter.build(PlannerInterface::class.java, params).createPlannerOverride(plannerOverride)).enqueue(callback)
+ }
+
+ fun updatePlannerOverride(adapter: RestBuilder, callback: StatusCallback, params: RestParams, id: Long, complete: Boolean) {
+ callback.addCall(adapter.build(PlannerInterface::class.java, params).updatePlannerOverride(id, complete)).enqueue(callback)
+ }
}
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ToDoAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ToDoAPI.kt
index 5984d94231..d5e7fa7bdb 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ToDoAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/ToDoAPI.kt
@@ -70,4 +70,8 @@ object ToDoAPI {
}
}
+ fun getCourseTodos(canvasContext: CanvasContext, adapter: RestBuilder, params: RestParams, callback: StatusCallback>) {
+ callback.addCall(adapter.build(ToDosInterface::class.java, params).getCourseTodos(canvasContext.id)).enqueue(callback)
+ }
+
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UserAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UserAPI.kt
index 8a44e9f74e..1b3e14e107 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UserAPI.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/UserAPI.kt
@@ -101,6 +101,9 @@ object UserAPI {
@GET
fun getNextPageMissingSubmissions(@Url nextUrl: String): Call>
+
+ @GET("courses/{courseId}/users?enrollment_type[]=teacher&enrollment_type[]=ta&include[]=avatar_url&include[]=bio&include[]=enrollments")
+ fun getFirstPageTeacherListForCourse(@Path("courseId") courseId: Long): Call>
}
fun getColors(adapter: RestBuilder, callback: StatusCallback, params: RestParams) {
@@ -244,4 +247,12 @@ object UserAPI {
val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
callback.addCall(adapter.build(UsersInterface::class.java, params).getNextPageMissingSubmissions(nextUrl)).enqueue(callback)
}
+
+ fun getTeacherListForCourse(adapter: RestBuilder, params: RestParams, courseId: Long, callback: StatusCallback>) {
+ if (StatusCallback.isFirstPage(callback.linkHeaders)) {
+ callback.addCall(adapter.build(UsersInterface::class.java, params).getFirstPageTeacherListForCourse(courseId)).enqueue(callback)
+ } else if (callback.linkHeaders != null && StatusCallback.moreCallsExist(callback.linkHeaders)) {
+ callback.addCall(adapter.build(UsersInterface::class.java, params).next(callback.linkHeaders!!.nextUrl!!)).enqueue(callback)
+ }
+ }
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/di/ApiModule.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/di/ApiModule.kt
index 1882630543..e7746b1e7b 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/di/ApiModule.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/di/ApiModule.kt
@@ -1,7 +1,9 @@
package com.instructure.canvasapi2.di
+import com.instructure.canvasapi2.apis.CalendarEventAPI
import com.instructure.canvasapi2.apis.HelpLinksAPI
import com.instructure.canvasapi2.apis.PlannerAPI
+import com.instructure.canvasapi2.apis.ToDoAPI
import com.instructure.canvasapi2.managers.*
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.RemoteConfigUtils
@@ -60,6 +62,21 @@ object ApiModule {
return UserManager
}
+ @Provides
+ fun provideToDoManager(): ToDoManager {
+ return ToDoManager
+ }
+
+ @Provides
+ fun provideEnrollmentManager(): EnrollmentManager {
+ return EnrollmentManager
+ }
+
+ @Provides
+ fun provideExternalToolManager(): ExternalToolManager {
+ return ExternalToolManager
+ }
+
@Provides
@Singleton
fun provideHelpLinksApi(): HelpLinksAPI {
@@ -77,4 +94,14 @@ object ApiModule {
fun providePlannerApi(): PlannerAPI {
return PlannerAPI
}
+
+ @Provides
+ fun provideAssignmentManager(): AssignmentManager {
+ return AssignmentManager
+ }
+
+ @Provides
+ fun provideCalendarEventManager(): CalendarEventManager {
+ return CalendarEventManager
+ }
}
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CalendarEventManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CalendarEventManager.kt
index ea4ecd63fb..079bde49db 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CalendarEventManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CalendarEventManager.kt
@@ -92,6 +92,8 @@ object CalendarEventManager {
CalendarEventAPI.getCalendarEvent(eventId, adapter, params, callback)
}
+ fun getCalendarEventAsync(eventId: Long, forceNetwork: Boolean) = apiAsync { getCalendarEvent(eventId, it, forceNetwork) }
+
fun deleteCalendarEvent(eventId: Long, cancelReason: String, callback: StatusCallback) {
val adapter = RestBuilder(callback)
val params = RestParams()
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CourseManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CourseManager.kt
index 29f32aef11..da232075cb 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CourseManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/CourseManager.kt
@@ -26,7 +26,6 @@ import com.instructure.canvasapi2.models.postmodels.UpdateCourseWrapper
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.DataResult
import com.instructure.canvasapi2.utils.ExhaustiveListCallback
-import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.canvasapi2.utils.weave.apiAsync
import kotlinx.coroutines.Deferred
import java.io.IOException
@@ -116,6 +115,22 @@ object CourseManager {
CourseAPI.getFirstPageCoursesWithSyllabus(adapter, depaginatedCallback, params)
}
+ fun getCoursesWithSyllabusAsyncWithActiveEnrollmentAsync(forceNetwork: Boolean) = apiAsync> { getCoursesWithSyllabusWithActiveEnrollment(forceNetwork, it) }
+
+ private fun getCoursesWithSyllabusWithActiveEnrollment(forceNetwork: Boolean, callback: StatusCallback>) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
+
+ val depaginatedCallback = object : ExhaustiveListCallback(callback) {
+ override fun getNextPage(callback: StatusCallback>, nextUrl: String, isCached: Boolean) {
+ CourseAPI.getNextPageCourses(forceNetwork, nextUrl, adapter, callback)
+ }
+ }
+
+ adapter.statusCallback = depaginatedCallback
+ CourseAPI.getFirstPageCoursesWithSyllabusWithActiveEnrollment(adapter, depaginatedCallback, params)
+ }
+
fun getCoursesTeacher(forceNetwork: Boolean, callback: StatusCallback>) {
val adapter = RestBuilder(callback)
val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
@@ -312,7 +327,7 @@ object CourseManager {
val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
val data = CourseAPI.getCoursesSynchronously(adapter, params)
- return data?.filter { it.isNotDeleted() } ?: ArrayList()
+ return data ?: ArrayList()
}
fun createCourseMap(courses: List?): Map = courses?.associateBy { it.id } ?: emptyMap()
@@ -323,4 +338,20 @@ object CourseManager {
CourseAPI.getRubricSettings(courseId, rubricId, adapter, callback, params)
}
+ fun getCoursesWithGradesAsync(forceNetwork: Boolean) = apiAsync> { getCoursesWithGrades(forceNetwork, it) }
+
+ private fun getCoursesWithGrades(forceNetwork: Boolean, callback: StatusCallback>) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(isForceReadFromNetwork = forceNetwork)
+
+ val depaginatedCallback = object : ExhaustiveListCallback(callback) {
+ override fun getNextPage(callback: StatusCallback>, nextUrl: String, isCached: Boolean) {
+ CourseAPI.getNextPageCourses(forceNetwork, nextUrl, adapter, callback)
+ }
+ }
+
+ adapter.statusCallback = depaginatedCallback
+ CourseAPI.getFirstPageCoursesWithGrades(adapter, callback, params)
+ }
+
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/EnrollmentManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/EnrollmentManager.kt
index 08de07c4e4..b03be81342 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/EnrollmentManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/EnrollmentManager.kt
@@ -100,4 +100,25 @@ object EnrollmentManager {
EnrollmentAPI.handleInvite(courseId, enrollmentId, acceptInvite, adapter, params, callback)
}
+ fun getEnrollmentsForGradingPeriodAsync(
+ gradingPeriodId: Long,
+ forceNetwork: Boolean
+ ) = apiAsync> { getEnrollmentsForGradingPeriod(gradingPeriodId, forceNetwork, it) }
+
+ private fun getEnrollmentsForGradingPeriod(
+ gradingPeriodId: Long,
+ forceNetwork: Boolean,
+ callback: StatusCallback>
+ ) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
+ val depaginatedCallback = object : ExhaustiveListCallback(callback) {
+ override fun getNextPage(callback: StatusCallback>, nextUrl: String, isCached: Boolean) {
+ EnrollmentAPI.getEnrollmentsForGradingPeriod(gradingPeriodId, adapter, params, callback)
+ }
+ }
+ adapter.statusCallback = depaginatedCallback
+ EnrollmentAPI.getEnrollmentsForGradingPeriod(gradingPeriodId, adapter, params, depaginatedCallback)
+ }
+
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ExternalToolManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ExternalToolManager.kt
index 2700df49a3..b76f090852 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ExternalToolManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ExternalToolManager.kt
@@ -45,4 +45,20 @@ object ExternalToolManager {
forceNetwork: Boolean
) = apiAsync> { getExternalToolsForCanvasContext(canvasContext, it, forceNetwork) }
+ fun getExternalToolsForCoursesAsync(ids: List, forceNetwork: Boolean) = apiAsync> { getExternalToolsForCourses(ids, it, forceNetwork) }
+
+ private fun getExternalToolsForCourses(
+ ids: List,
+ callback: StatusCallback>,
+ forceNetwork: Boolean
+ ) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(
+ isForceReadFromNetwork = forceNetwork,
+ usePerPageQueryParam = true
+ )
+
+ ExternalToolAPI.getExternalToolsForCourses(ids, adapter, params, callback)
+ }
+
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt
index b1699d15d8..8cd21e8663 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt
@@ -24,6 +24,7 @@ import com.instructure.canvasapi2.builders.RestParams
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.DiscussionTopicHeader
import com.instructure.canvasapi2.models.PlannerItem
+import com.instructure.canvasapi2.models.PlannerOverride
import com.instructure.canvasapi2.utils.weave.apiAsync
class PlannerManager(private val plannerApi: PlannerAPI) {
@@ -31,14 +32,41 @@ class PlannerManager(private val plannerApi: PlannerAPI) {
fun getPlannerItemsAsync(forceNetwork: Boolean, startDate: String? = null, endDate: String? = null) = apiAsync> { getPlannerItems(forceNetwork, it, startDate, endDate) }
private fun getPlannerItems(
- forceNetwork: Boolean,
- callback: StatusCallback>,
- startDate: String? = null,
- endDate: String? = null
+ forceNetwork: Boolean,
+ callback: StatusCallback>,
+ startDate: String? = null,
+ endDate: String? = null
) {
val adapter = RestBuilder(callback)
val params = RestParams(isForceReadFromNetwork = forceNetwork)
plannerApi.getPlannerItems(adapter, callback, params, startDate, endDate)
}
+
+ private fun createPlannerOverride(
+ forceNetwork: Boolean,
+ callback: StatusCallback,
+ plannerOverride: PlannerOverride
+ ) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(isForceReadFromNetwork = forceNetwork)
+
+ plannerApi.createPlannerOverride(adapter, callback, params, plannerOverride)
+ }
+
+ fun createPlannerOverrideAsync(forceNetwork: Boolean, plannerOverride: PlannerOverride) = apiAsync { createPlannerOverride(forceNetwork, it, plannerOverride) }
+
+ private fun updatePlannerOverride(
+ forceNetwork: Boolean,
+ callback: StatusCallback,
+ completed: Boolean,
+ overrideId: Long
+ ) {
+ val adapter = RestBuilder(callback)
+ val params = RestParams(isForceReadFromNetwork = forceNetwork)
+
+ plannerApi.updatePlannerOverride(adapter, callback, params, overrideId, completed)
+ }
+
+ fun updatePlannerOverrideAsync(forceNetwork: Boolean, completed: Boolean, overrideId: Long) = apiAsync { updatePlannerOverride(forceNetwork, it, completed, overrideId) }
}
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ToDoManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ToDoManager.kt
index 7264ee0c36..22821f40de 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ToDoManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/ToDoManager.kt
@@ -22,6 +22,7 @@ import com.instructure.canvasapi2.builders.RestBuilder
import com.instructure.canvasapi2.builders.RestParams
import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.ToDo
+import com.instructure.canvasapi2.utils.weave.apiAsync
import java.util.*
import kotlin.collections.HashSet
@@ -33,6 +34,8 @@ object ToDoManager {
ToDoAPI.getUserTodos(adapter, params, callback)
}
+ fun getUserTodosAsync(forceNetwork: Boolean) = apiAsync> { getUserTodos(it, forceNetwork) }
+
fun getUserTodosWithUngradedQuizzes(callback: StatusCallback>, forceNetwork: Boolean) {
val adapter = RestBuilder(callback)
val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
@@ -69,20 +72,28 @@ object ToDoManager {
return ToDoAPI.getCourseTodosSynchronous(canvasContext, adapter, params)
}
+ fun getCourseTodos(canvasContext: CanvasContext, forceNetwork: Boolean, callback: StatusCallback>) {
+ val adapter = RestBuilder()
+ val params = RestParams(isForceReadFromNetwork = forceNetwork)
+ ToDoAPI.getCourseTodos(canvasContext, adapter, params, callback)
+ }
+
+ fun getCourseTodosAsync(canvasContext: CanvasContext, forceNetwork: Boolean) = apiAsync> { getCourseTodos(canvasContext, forceNetwork, it) }
+
fun mergeToDoUpcoming(todoList: List?, eventList: List?): List {
val todos = todoList ?: emptyList()
var events = eventList ?: emptyList()
// Add all Assignment ids from todos
val assignmentIds =
- HashSet(todos.asSequence().filter { it.assignment != null }.map { it.assignment?.id }.toList())
+ HashSet(todos.asSequence().filter { it.assignment != null }.map { it.assignment?.id }.toList())
// If the set contains any assignment ids from Upcoming, it's a duplicate
events = events.filter { it.assignment?.id ?: -1 !in assignmentIds }
// Return combined list, sorted by date
val defaultDate = Date(0)
- return (todos + events).sortedBy{ it.comparisonDate ?: defaultDate }
+ return (todos + events).sortedBy { it.comparisonDate ?: defaultDate }
}
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UserManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UserManager.kt
index 34e23029a5..a9d1a27562 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UserManager.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/UserManager.kt
@@ -279,4 +279,18 @@ object UserManager {
UserAPI.getMissingSubmissions(forceNetwork, adapter, depaginatedCallback)
}
+ fun getTeacherListForCourseAsync(courseId: Long, forceNetwork: Boolean) = apiAsync> { getTeacherListForCourse(courseId, it, forceNetwork) }
+
+ private fun getTeacherListForCourse(courseId: Long, callback: StatusCallback>, forceNetwork: Boolean) {
+ val params = RestParams(usePerPageQueryParam = true, isForceReadFromNetwork = forceNetwork)
+ val adapter = RestBuilder(callback)
+ val depaginatedCallback = object : ExhaustiveListCallback(callback) {
+ override fun getNextPage(callback: StatusCallback>, nextUrl: String, isCached: Boolean) {
+ UserAPI.getTeacherListForCourse(adapter, params, courseId, callback)
+ }
+ }
+ adapter.statusCallback = depaginatedCallback
+ UserAPI.getTeacherListForCourse(adapter, params, courseId, depaginatedCallback)
+ }
+
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Assignment.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Assignment.kt
index 0eb75ac813..4095fa0d94 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Assignment.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Assignment.kt
@@ -103,7 +103,9 @@ data class Assignment(
val externalToolAttributes: ExternalToolAttributes? = null,
@SerializedName("planner_override")
val plannerOverride: PlannerOverride? = null,
- var isStudioEnabled: Boolean = false
+ var isStudioEnabled: Boolean = false,
+ @SerializedName("in_closed_grading_period")
+ val inClosedGradingPeriod: Boolean = false
) : CanvasModel() {
override val comparisonDate get() = dueDate
override val comparisonString get() = dueAt
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Course.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Course.kt
index d526a25dda..e7a340c092 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Course.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Course.kt
@@ -73,7 +73,9 @@ data class Course(
@SerializedName("homeroom_course")
val homeroomCourse: Boolean = false,
@SerializedName("course_color")
- val courseColor: String? = null
+ val courseColor: String? = null,
+ @SerializedName("grading_periods")
+ val gradingPeriods: List? = null
) : CanvasContext(), Comparable {
override val type: Type get() = Type.COURSE
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/LTITool.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/LTITool.kt
index 6d4a217758..ee4f2e039e 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/LTITool.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/LTITool.kt
@@ -17,15 +17,31 @@
package com.instructure.canvasapi2.models
+import android.os.Parcelable
+import com.google.gson.annotations.SerializedName
import kotlinx.android.parcel.Parcelize
@Parcelize
data class LTITool(
- override var id: Long = 0,
- var name: String? = null,
- var url: String? = null,
- var assignmentId: Long = 0L,
- var courseId: Long = 0L
+ override var id: Long = 0,
+ var name: String? = null,
+ var url: String? = null,
+ var assignmentId: Long = 0L,
+ var courseId: Long = 0L,
+ @SerializedName("course_navigation")
+ var courseNavigation: CourseNavigation? = null,
+ @SerializedName("icon_url")
+ var iconUrl: String? = null,
+ @SerializedName("context_id")
+ var contextId: Long? = null,
+ @SerializedName("context_name")
+ var contextName: String? = null
) : CanvasModel() {
override val comparisonString get() = name
}
+
+@Parcelize
+data class CourseNavigation(
+ val text: String? = null,
+ val url: String? = null
+) : Parcelable
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Plannable.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Plannable.kt
index 6f01ef1391..2d08118fa3 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Plannable.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Plannable.kt
@@ -40,5 +40,8 @@ data class Plannable(
// Used to determine if a quiz is an assignment or not
@SerializedName("assignment_id")
- val assignmentId: Long?
-) {}
+ val assignmentId: Long?,
+
+ @SerializedName("todo_date")
+ val todoDate: String?
+)
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerItem.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerItem.kt
index e7744b7aaf..8a53a647c6 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerItem.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerItem.kt
@@ -35,7 +35,7 @@ data class PlannerItem (
val contextName: String?,
@SerializedName("plannable_type")
- val plannableType: String,
+ val plannableType: PlannableType,
val plannable: Plannable,
@@ -46,7 +46,13 @@ data class PlannerItem (
val htmlUrl: String?,
@SerializedName("submissions")
- val submissionState: SubmissionState?
+ val submissionState: SubmissionState?,
+
+ @SerializedName("new_activity")
+ val newActivity: Boolean?,
+
+ @SerializedName("planner_override")
+ var plannerOverride: PlannerOverride? = null
) {
val canvasContext: CanvasContext
@@ -61,3 +67,22 @@ data class PlannerItem (
}
}
+
+enum class PlannableType {
+ @SerializedName("announcement")
+ ANNOUNCEMENT,
+ @SerializedName("assignment")
+ ASSIGNMENT,
+ @SerializedName("discussion_topic")
+ DISCUSSION_TOPIC,
+ @SerializedName("quiz")
+ QUIZ,
+ @SerializedName("wiki_page")
+ WIKI_PAGE,
+ @SerializedName("planner_note")
+ PLANNER_NOTE,
+ @SerializedName("calendar_event")
+ CALENDAR_EVENT,
+ @SerializedName("todo")
+ TODO
+}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerOverride.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerOverride.kt
index f8777273d3..9ad7e9c450 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerOverride.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/PlannerOverride.kt
@@ -22,6 +22,14 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
data class PlannerOverride(
- @SerializedName("dismissed")
- val dismissed: Boolean = false
+ @SerializedName("id")
+ val id: Long? = null,
+ @SerializedName("plannable_type")
+ val plannableType: PlannableType,
+ @SerializedName("plannable_id")
+ val plannableId: Long,
+ @SerializedName("dismissed")
+ val dismissed: Boolean = false,
+ @SerializedName("marked_complete")
+ val markedComplete: Boolean = false
) : Parcelable
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/SubmissionState.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/SubmissionState.kt
index e34a6fcc2a..3d3f5bb980 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/SubmissionState.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/SubmissionState.kt
@@ -28,5 +28,17 @@ data class SubmissionState(
@SerializedName("submitted")
val submitted: Boolean = false,
@SerializedName("missing")
- val missing: Boolean = false
+ val missing: Boolean = false,
+ @SerializedName("late")
+ val late: Boolean = false,
+ @SerializedName("excused")
+ val excused: Boolean = false,
+ @SerializedName("graded")
+ val graded: Boolean = false,
+ @SerializedName("needs_grading")
+ val needsGrading: Boolean = false,
+ @SerializedName("with_feedback")
+ val withFeedback: Boolean = false,
+ @SerializedName("redo_request")
+ val redoRequest: Boolean = false
)
\ No newline at end of file
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/TermsOfService.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/TermsOfService.kt
index 8111e98d57..29f83898d2 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/TermsOfService.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/TermsOfService.kt
@@ -28,5 +28,13 @@ data class TermsOfService(
val passive: Boolean = false,
@SerializedName("account_id")
val accountId: Long = 0,
- val content: String? = null
+ val content: String? = null,
+ @SerializedName("self_registration_type")
+ val selfRegistrationType: SelfRegistration? = null
) : Parcelable
+
+enum class SelfRegistration(val apiString: String) {
+ @SerializedName("all") ALL("all"),
+ @SerializedName("observer") OBSERVER("observer"),
+ @SerializedName("none") NONE("none"),
+}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/ApiPrefs.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/ApiPrefs.kt
index b952c5f9b6..5906538cfa 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/ApiPrefs.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/ApiPrefs.kt
@@ -86,6 +86,9 @@ object ApiPrefs : PrefManager(PREFERENCE_FILE_NAME) {
internal var masqueradeDomain by StringPref()
internal var masqueradeUser: User? by GsonPref(User::class.java, null, "masq-user")
+ // Used to determine if a student can generate a pairing code, saved during splash
+ var canGeneratePairingCode by NBooleanPref()
+
var domain: String
get() = if (isMasquerading || isStudentView) masqueradeDomain else originalDomain
set(newDomain) {
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/CanvasApiExtensions.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/CanvasApiExtensions.kt
index d0b53c6868..abc7fd1665 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/CanvasApiExtensions.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/CanvasApiExtensions.kt
@@ -23,7 +23,7 @@ import java.text.SimpleDateFormat
import java.util.*
@JvmOverloads
-fun Date?.toApiString(timeZone: TimeZone? = null): String? {
+fun Date?.toApiString(timeZone: TimeZone? = null): String {
this ?: return ""
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US)
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/DateHelper.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/DateHelper.kt
index 104f0e75b0..4451c9eca8 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/DateHelper.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/DateHelper.kt
@@ -211,9 +211,9 @@ object DateHelper {
val cal = GregorianCalendar()
cal.timeInMillis = dateTime
val genericDate = GregorianCalendar(
- cal[Calendar.YEAR],
- cal[Calendar.MONTH],
- cal[Calendar.DAY_OF_MONTH]
+ cal[Calendar.YEAR],
+ cal[Calendar.MONTH],
+ cal[Calendar.DAY_OF_MONTH]
)
return Date(genericDate.timeInMillis)
}
@@ -233,4 +233,5 @@ object DateHelper {
calendar[year, month, day, hour, minute] = second
return calendar.time
}
+
}
diff --git a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/CoursesApiPactTests.kt b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/CoursesApiPactTests.kt
index fa74d9c9f9..fcc8b5f791 100644
--- a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/CoursesApiPactTests.kt
+++ b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/pact/canvas/apis/CoursesApiPactTests.kt
@@ -102,7 +102,7 @@ class CoursesApiPactTests : ApiPactTestBase() {
//region Test grabbing all courses
//
- val allCoursesQuery = "include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=current_and_concluded"
+ val allCoursesQuery = "include[]=term&include[]=total_scores&include[]=license&include[]=is_public&include[]=needs_grading_count&include[]=permissions&include[]=favorites&include[]=current_grading_period_scores&include[]=course_image&include[]=sections&state[]=completed&state[]=available"
val allCoursesPath = "/api/v1/courses"
val allCoursesFieldInfo = listOf(
// Evidently, permissions info is *not* returned from this call, even though include[]=permissions is specified
diff --git a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/unit/CourseTest.kt b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/unit/CourseTest.kt
index bf1336cd1e..6e0cd09412 100644
--- a/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/unit/CourseTest.kt
+++ b/libs/canvas-api-2/src/test/java/com/instructure/canvasapi2/unit/CourseTest.kt
@@ -22,7 +22,6 @@ import com.instructure.canvasapi2.models.Enrollment
import com.instructure.canvasapi2.models.Section
import com.instructure.canvasapi2.models.Term
import com.instructure.canvasapi2.utils.Logger
-import com.instructure.canvasapi2.utils.isNotDeleted
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
@@ -563,22 +562,4 @@ class CourseTest {
assertTrue(course.isBetweenValidDateRange())
}
-
- @Test
- fun `Course is not deleted when workflow state is available`() {
- val course = baseCourse.copy(workflowState = Course.WorkflowState.AVAILABLE)
- assertTrue(course.isNotDeleted())
- }
-
- @Test
- fun `Course is not deleted when workflow state is completed`() {
- val course = baseCourse.copy(workflowState = Course.WorkflowState.COMPLETED)
- assertTrue(course.isNotDeleted())
- }
-
- @Test
- fun `Course is deleted when workflow state is deleted`() {
- val course = baseCourse.copy(workflowState = Course.WorkflowState.DELETED)
- assertFalse(course.isNotDeleted())
- }
}
\ No newline at end of file
diff --git a/libs/interactions/src/main/java/com/instructure/interactions/Navigation.kt b/libs/interactions/src/main/java/com/instructure/interactions/Navigation.kt
index 417b8337dc..cb4f8288ae 100644
--- a/libs/interactions/src/main/java/com/instructure/interactions/Navigation.kt
+++ b/libs/interactions/src/main/java/com/instructure/interactions/Navigation.kt
@@ -25,7 +25,6 @@ interface Navigation {
val currentFragment: Fragment?
fun popCurrentFragment()
- fun updateCalendarStartDay()
fun addBookmark()
fun canBookmark(): Boolean
diff --git a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginInitActivity.kt b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginInitActivity.kt
index f1dc4e5650..6169fcbf6e 100644
--- a/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginInitActivity.kt
+++ b/libs/login-api-2/src/main/java/com/instructure/loginapi/login/activities/BaseLoginInitActivity.kt
@@ -95,17 +95,16 @@ abstract class BaseLoginInitActivity : AppCompatActivity() {
event.getContentIfNotHandled()?.let {
if (it) {
startApp()
+ // We only want to finish here on debug builds, our login bypass for UI testing depends
+ // on a function called by this class, which then finishes the activity.
+ // See loginWithToken() in Teacher's InitLoginActivity.
+ if (!isTesting) finish()
} else {
logout()
}
}
})
}
-
- // We only want to finish here on debug builds, our login bypass for UI testing depends
- // on a function called by this class, which then finishes the activity.
- // See loginWithToken() in Teacher's InitLoginActivity.
- if (!isTesting) finish()
} else {
Handler().postDelayed({
runOnUiThread {
diff --git a/libs/pandares/src/main/res/drawable/bg_lti_app_card.xml b/libs/pandares/src/main/res/drawable/bg_lti_app_card.xml
new file mode 100644
index 0000000000..9929e30ee5
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/bg_lti_app_card.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandares/src/main/res/drawable/ic_arrow_down.xml b/libs/pandares/src/main/res/drawable/ic_arrow_down.xml
new file mode 100644
index 0000000000..c2786c4651
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_arrow_down.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/libs/pandares/src/main/res/drawable/ic_book.xml b/libs/pandares/src/main/res/drawable/ic_book.xml
new file mode 100644
index 0000000000..8a4b5180fd
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_book.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandares/src/main/res/drawable/ic_chevron_down_small.xml b/libs/pandares/src/main/res/drawable/ic_chevron_down_small.xml
new file mode 100644
index 0000000000..58a9d0e595
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_chevron_down_small.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/libs/pandares/src/main/res/drawable/ic_dashboard_grades.xml b/libs/pandares/src/main/res/drawable/ic_dashboard_grades.xml
index f98c8b0515..46b622bd18 100644
--- a/libs/pandares/src/main/res/drawable/ic_dashboard_grades.xml
+++ b/libs/pandares/src/main/res/drawable/ic_dashboard_grades.xml
@@ -1,4 +1,4 @@
-
+ android:height="25dp"
+ android:viewportWidth="24"
+ android:viewportHeight="25">
-
diff --git a/libs/pandares/src/main/res/drawable/ic_jump_to_today.xml b/libs/pandares/src/main/res/drawable/ic_jump_to_today.xml
new file mode 100644
index 0000000000..2cc4087ebd
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_jump_to_today.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandares/src/main/res/drawable/ic_mail.xml b/libs/pandares/src/main/res/drawable/ic_mail.xml
new file mode 100644
index 0000000000..8dfced5d40
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_mail.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandares/src/main/res/drawable/ic_resources.xml b/libs/pandares/src/main/res/drawable/ic_resources.xml
index abfe782df2..282ffb64df 100644
--- a/libs/pandares/src/main/res/drawable/ic_resources.xml
+++ b/libs/pandares/src/main/res/drawable/ic_resources.xml
@@ -1,4 +1,4 @@
-
+ android:height="25dp"
+ android:viewportWidth="24"
+ android:viewportHeight="25">
diff --git a/libs/pandares/src/main/res/drawable/ic_warning_red.xml b/libs/pandares/src/main/res/drawable/ic_warning_red.xml
new file mode 100644
index 0000000000..d4ce172d36
--- /dev/null
+++ b/libs/pandares/src/main/res/drawable/ic_warning_red.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandares/src/main/res/values-ar/strings.xml b/libs/pandares/src/main/res/values-ar/strings.xml
index f9772f4ebc..69f58d9a2e 100644
--- a/libs/pandares/src/main/res/values-ar/strings.xml
+++ b/libs/pandares/src/main/res/values-ar/strings.xml
@@ -1053,6 +1053,7 @@
تعذر الإقران. تأكد من أن رمز الإقران صحيح وفي إطار الحد الزمني للاستخدام.مكتملغير مكتمل
+
نشر الإعداداتنشر التقادير
@@ -1230,24 +1231,47 @@
عرض إعلانات سابقةفشل تحميل جلسة التسجيلفشل تحديث جلسة التسجيل
-
لا شيء مستحق اليوم%1$s مستحق اليوم%1$s مفقودمرحبًا!تظهر الموضوعات هنا.ليس لديك موضوعات في الوقت الحالي.
-
-
+ يتطلب إعادة تشغيل التطبيق
+ تحديد
+ تحديد فترة التقييم
+ فترة التقدير الحالية
+ فشل تحميل الدرجات
+ تعذر تحميل التقديرات لفترة التقدير
+ تعذر تحديث التقديرات
+ لا توجد تقديرات للعرض
+ لم يتم التقييم
+ تغيير فترة التقدير
+ التقديرات غير متاحة
+ جلسة التسجيل
+
+ عرض جلسة التسجيل
+ الروابط الهامة
+ تقديمات الطالب
+ معلومات الاتصال الخاصة بالعاملين
+ اختر مساقًا
+ الروابط الهامة
+ المعلم
+ المعلم المساعد
+ مواردك تظهر هنا.
+ فشل تحميل الموارد
+ فشل تحديث المواردفتح طريقة عرض بديلة أكثر سهولةالمتلقونالموضوعحدد مساقًا، %s
+
استجابات هذا الطالب مخفية لأن هذه المهمة مجهولة الاسم.التعليق التوضيحي للطالب (غير مدعوم)التعليق التوضيحي للطالب غير مدعوم حاليًا على الأجهزة المحمولة.نوع إرسال غير مدعوملا يمكن عرض الإرسال، لأن التعليق التوضيحي للطالب غير مدعوم حاليًا على الأجهزة المحمولة.
+ قمت بتمييزه بأنه تم.
diff --git a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml
index c473167d3c..851c6c2122 100644
--- a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml
+++ b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml
@@ -1176,24 +1176,47 @@
Se tidligere beskederKunne ikke indlæse HomeroomKunne ikke opdatere Homeroom
-
Intet forfalder i dag%1$s forfalder i dag%1$s manglerVelkommen!Dine emner vises her.Du har i øjeblikket ingen emner.
-
-
+ Kræver genstart af app
+ Vælg
+ Vælg vurderingsperiode
+ Nuværende vurderingsperiode
+ Kunne ikke indlæse vurderinger
+ Kunne ikke indlæse vurderinger for vurderingsperioden
+ Kunne ikke opdatere vurderinger
+ Ingen vurderinger at vise
+ Ikke bedømt
+ Skift vurderingsperiode
+ Vurderinger ikke tilgængelige
+ Klasselokale
+
+ Klasseværelsevisning
+ Vigtige links
+ Applikationer for elev
+ Kontaktoplysninger for personale
+ Vælg et fag
+ Vigtige links
+ Lærer
+ Undervisningsassistent
+ Dine ressourcer vises her.
+ Kunne ikke indlæse ressourcer
+ Kunne ikke opdatere ressourcerÅbn en mere tilgængelig alternativ visningModtagereEmneVælg et fag, %s
+
Elevens svar er skjult, fordi denne opgave er anonym.Elevers anmærkninger (ikke understøttet)Elevers anmærkninger understøttes i øjeblikket ikke på mobil.Ikke-understøttet afleveringsformAflevering kan ikke vises, fordi elevers anmærkninger i øjeblikket ikke understøttes på mobil.
+ Du har markeret det som udført.
diff --git a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml
index 80e5407956..7eb9f67485 100644
--- a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml
+++ b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml
@@ -1176,24 +1176,47 @@
View Previous AnnouncementsFailed to load HomeroomFailed to refresh Homeroom
-
Nothing Due Today%1$s due today%1$s missingWelcome!Your topics show up here.You currently have no topics.
-
-
+ Requires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load grades
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Graded
+ Change grading period
+ Grades not available
+ Homeroom
+
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Subject
+ Important Links
+ Instructor
+ Tutor
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative viewRecipientsTopicSelect a subject, %s
+
This student\'s responses are hidden because this assignment is anonymous.Student Annotation (Unsupported)Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
diff --git a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml
index 51333ed0fa..abf4ccb63d 100644
--- a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml
+++ b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml
@@ -75,8 +75,8 @@
Vis Legg til fil valgLydopptakTa opp video
-
Redigeringsprogram for tekstinnlevering
+
Skriv…Noe gikk galt ved opplasting av innlevering. Lever på nytt.
@@ -100,10 +100,10 @@
Denne oppgaven tillater ikke levering på nettDenne oppgaven tillater ikke levering på nettIngen levering på nett
-
Denne oppgaven er lenket til et eksternt verktøy for innleveringer.Åpne verktøyInnleveringstekst
+
Av %s poengAv %s poeng
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s av %2$s poengSkriv inn what-If resultat
-
Lav: %sGjennomsnitt: %sHøy: %s
+
Egendefinert resultatDet er ingen vurderingskriterier for denne oppgaven
@@ -199,7 +199,6 @@
Ingen oppgaver i denne gruppenDenne oppgaven er fritatt og blir ikke vurdert i den totale beregningenEX
-
Sorter etterTidSkriv inn
@@ -211,6 +210,7 @@
Sorter oppgaver-knapp, sorter etter typeOppgaver sortert etter tidOppgaver sortert etter type
+
Poeng\u0020Lagre
@@ -218,9 +218,9 @@
Svarene er kun synlige for dem som har postet minst ett svar.
-
Denne filen er for tiden låstRedigeringsprogram for diskusjonssvar
+
StarterSlutter
@@ -1164,6 +1164,7 @@
Fjern alle fra oversiktLegg til oversiktLegg til alle til oversikt
+
KontoHjem
@@ -1172,28 +1173,50 @@
RessurserVelkommen, %1$s!Mine emner
-
Vis tidligere beskjederKunne ikke laste inn HomeroomKunne ikke oppdatere Homeroom
-
-
Ingenting forfaller i dag%1$s forfaller i dag%1$s manglerVelkommen!Fagene dine vises her.Du har for øyeblikket ingen fag.
-
-
+ Krever omstart av appen
+ Velg
+ Velg vurderingsperiode
+ Gjeldende vurderingsperiode
+ Kunne ikke laste vurderinger
+ Kunne ikke laste inn vurderinger for vurderingsperioden
+ Kunne ikke oppdatere vurderinger
+ Ingen vurderinger å vise
+ Ikke vurdert
+ Endre vurderingsperiode
+ Vurderingerer er ikke tilgjengelig
+ Hjem
+
+ Vis hjemmerom
+ Viktige lenker
+ Elev-applikasjoner
+ Kontaktinformasjon personale
+ Velg et fag
+ Viktige lenker
+ Lærer
+ Lærerassistent
+ Ressusrene dine vises her.
+ Kunne ikke laste opp ressurser
+ Kunne ikke oppdatere ressurserÅpne en mer tilgjengelig alternativ visning
+
MottakereTittelVelg et fag, %s
+
Denne elevens svar er skjult fordi denne oppgaven er anonym.Elevmerknad (ikke støttet)Elevmerknader støttes for øyeblikket ikke på mobil.Ustøttet innleveringstypeInnlevering kan ikke vises fordi Elevmerknader for øyeblikket ikke støttes på mobil.
+ Du har merket det som fullført.
diff --git a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml
index d5334b75af..c360246ff2 100644
--- a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml
+++ b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml
@@ -75,8 +75,8 @@
Visa alternativ för Lägg till filSpela in ljudSpela in video
-
Textinlämningsredigerare
+
Skriv…Något gick fel vid uppladdning av inlämningen. Lämna in igen.
@@ -100,10 +100,10 @@
Den här uppgiften tillåter inte onlineinlämningarDen här uppgiften tillåter inte onlineinlämningarInga onlineinlämningar
-
Den här uppgiften länkar till ett externt verktyg för inlämningar.Öppna verktygInlämningstext
+
Av %s poängAv %s poäng
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s av %2$s poängAnge Vad om-resultat
-
Låg: %sMedel: %sHög: %s
+
Anpassat resultatDet finns ingen matris för den här uppgiften
@@ -127,7 +127,7 @@
Laddar upp mediefilUppladdningen av kommentaren misslyckades för %sBifoga filer till dina kommentarer genom att välja ett alternativ nedan
- Har du frågor om din uppgift?\nSkicka ett meddelande till din instruktör.
+ Har du frågor om din uppgift?\nSkicka ett meddelande till din lärare.Detta meddelande kunde inte skickas. Tryck för att försöka igen.MediauppladdningMediauppladdning – ljud
@@ -199,7 +199,6 @@
Inga uppgifter i den här gruppenDen här uppgiften är ursäktad och kommer inte räknas in i den totala uträkningenEX
-
Sortera efterTidTyp
@@ -211,6 +210,7 @@
Sortera uppgiftsknappar, sortera efter typUppgifter sorterade efter tidUppgifter sorterade efter typ
+
Poäng\u0020Spara
@@ -218,9 +218,9 @@
Svaren är endast synliga för de som har publicerat minst ett svar.
-
Den här filen är för närvarande låstDiskussionssvarsredigerare
+
BörjarSlutar
@@ -339,7 +339,7 @@
ÖppnaÖppna med en alternativ appÖppnar fil…
- Ladda ned
+ Ladda nerMeddelandebilagorBilagaBilageikon
@@ -355,7 +355,7 @@
Välj en kurs eller gruppInga meddelandenTa bort bilaga
- Ladda ned bilaga
+ Ladda ner bilagaMeddelandealternativVidarebefordraSvara alla
@@ -448,6 +448,7 @@
Sluta uppträda som användareAnvändar-IDEtt fel inträffade vid försök att uppträda som användaren.
+
ID kan inte lämnas tomtGå till quiz
@@ -604,6 +605,8 @@
ÖvningsquizBedömda enkäterEnkäter
+
+
Att göraOmdömen
@@ -649,12 +652,12 @@
ProfilinställningarKontoinställningarPIN och fingeravtryck
-
Koppla med observatörBe din förälder scanna QR-koden från Canvas Parent-appen för att parkopplas med dig. Den här koden upphör att gälla om sju dagar, eller efter en användning.Kopplingskod:\u0020ParkopplingskodfelDet gick inte att hämta en parkopplingskod. Den här funktionen stöds endast för elever.
+
Öppna Speedgraderatt göra att göra lista
@@ -725,8 +728,8 @@
SpeedGraderMätare
-
Bedömningsreglage
+
Skapa ny händelseStäng av pandorna
@@ -744,7 +747,7 @@
KursaktiviteterDiskussioner
- Konversationer
+ InkorgenSchemaläggningGrupperLarm
@@ -766,7 +769,7 @@
DiskussionsinläggLägg till i konversation
- Konversationsmeddelande
+ Meddelande i InkorgenKonversationer som skapats av digElevens mötesregistreringar
@@ -795,24 +798,25 @@
Få aviseringar när du skapar ett anslag, och när någon svarar på ditt anslag.Få aviseringar när en uppgift/inlämning har bedömts/ändrats, och när en omdömesvikt ändrats.Få aviseringar om inbjudningar till webbkonferenser, grupper, samarbeten, kamratresponsuppgifter och påminnelser.
- Endast lärare och admin. Få aviseringar när en uppgift lämnas in för första gången eller lämnas in på nytt.
- Endast lärare och admin. Få aviseringar när en sen uppgift skickas in.
+ Endast lärare och administratörer. Få aviseringar när en uppgift lämnas in för första gången eller lämnas in på nytt.
+ Endast lärare och administratörer. Få aviseringar när en sen uppgift skickas in.Få aviseringar när en kommentar görs om din inlämning.Få aviseringar när det finns ett nytt diskussionsämne i din kurs.Få aviseringar när det finns ett nytt inlägg i en diskussion som du prenumererar på.Få aviseringar när du läggs till i en konversation.Få aviseringar när du har ett nytt meddelande i din inkorg.Få aviseringar när du skapar en ny konversation.
- Endast lärare och admin. Få aviseringar när det finns en mötesregistrering.
+ Endast lärare och administratörer. Få aviseringar när det finns en mötesregistrering.Få aviseringar när det finns en ny registrering på din kalender.Få aviseringar vid avbeställning av tid.Få aviseringar när en möteslucka blir tillgänglig.Få aviseringar om nya och uppdaterade kalenderobjekt.Endast admin, väntande registrering aktiverad. Få avisering när en gruppregistrering accepteras eller nekas.
- Endast lärare och admin. Få aviseringar om kursregistreringar, genererade rapporter, exporterat innehåll, migrationsrapporter, nya kontoanvändare, och nya elevgrupper.
+ Endast lärare och administratörer. Få aviseringar om kursregistreringar, genererade rapporter, exporterat innehåll, migrationsrapporter, nya kontoanvändare, och nya elevgrupper.Få aviseringar när en konferensinspelning är klar.Obegränsade
+
Fråga%d fråga
@@ -823,6 +827,7 @@
%s poäng%s poäng
+
Tidsgräns%1$s Nya aviseringargilla-markering
@@ -994,10 +999,10 @@
Konferenser stöds ännu inte på den här mobilen.Förhandsvisningsbild för filEtt fel uppstod vid inläsning av den här PDF:en.
-
Tyvärr! Den här funktionen tillåts inte i elevens vyn.Inget att se härFunktionen saknar stöd
+
Lägg till elevAnge kopplingskoden du fått för elever.
@@ -1008,7 +1013,7 @@
Publiceringsinställningar
- Offentliggör omdömen
+ Publicera omdömenDölj omdömen%d omdöme publicerat för närvarande
@@ -1030,16 +1035,16 @@
Alla omdömen har publicerats.Publicerade omdömenDolda omdömen
- Det gick inte att offentliggör omdömen
+ Det gick inte att publicera omdömenDet gick inte att dölja omdömenBedöm före publiceringBedöm efter publiceringÅsidosättning av omdömeAktuell omdöme
-
Öppnas i Canvas ElevElevens vy
+
Vald rubrik: %sVald brödtext: %s
@@ -1111,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minut%s minuter
@@ -1131,6 +1137,7 @@
KonferensinformationInspelningarKonferens pågår...
+
Kriteriebedömning %s%s, %smer information
@@ -1157,6 +1164,7 @@
Ta bort alla från översiktenLägg till i översiktenLägga till alla i översikten
+
KontoHemrum
@@ -1165,28 +1173,50 @@
ResurserVälkommen %1$s!Mina ämnen
-
Visa tidigare meddelandenDet gick inte att läsa in fjärrundervisningsrummet.Det gick inte att uppdatera fjärrundervisningsrummet.
-
-
Inget måste lämnas in i dag%1$s förfaller i dag%1$s saknasVälkommen!Dina ämnen visas här.Du har inga ämnen för närvarande.
-
-
+ Kräver omstart av appen
+ Välj
+ Välj bedömningsperiod
+ Aktuell bedömningsperiod
+ Det gick inte att läsa in omdömen
+ Det gick inte att läsa in bedömningar för bedömningsperioden
+ Det gick inte att uppdatera bedömningar
+ Det finns inga bedömningar att visa
+ Inte bedömd
+ Ändra bedömningsperiod
+ Bedömningar är inte tillgängliga
+ Hemrum
+
+ Vy för fjärrundervisningsrum
+ Viktiga länkar
+ Elev-applikationer
+ Kontaktuppgifter för personal
+ Välj en kurs
+ Viktiga länkar
+ Lärare
+ Lärarassistent
+ Dina resurser visas här.
+ Det gick inte att läsa in resurser
+ Det gick inte att uppdatera resurserÖppna en mer tillgänglig alternativ vy
+
MottagareÄmneVälj en kurs, %s
+
Den här elevens svar är dolda eftersom uppgiften är anonym.Elevnotering (stöds inte)Elevnotering stöds för närvarande inte på mobila enheter.Inlämningstyp som inte stödsInlämningen kan inte visas eftersom elevnotering inte stöds på mobila enheter.
+ Du har markerat den som färdig.
diff --git a/libs/pandares/src/main/res/values-ca/strings.xml b/libs/pandares/src/main/res/values-ca/strings.xml
index f60268850c..1bc293502c 100644
--- a/libs/pandares/src/main/res/values-ca/strings.xml
+++ b/libs/pandares/src/main/res/values-ca/strings.xml
@@ -1176,24 +1176,47 @@
Visualitza els anuncis anteriorsNo s\'ha pogut carregar la tutoriaNo s\'ha pogut actualitzar la tutoria
-
Res venç avui%1$s venç avuiFalta %1$sUs donem la benvinguda!Les vostres matèries es mostren aquí.En aquest moment, no teniu cap matèria.
-
-
+ Heu de reiniciar l’aplicació.
+ Selecciona
+ Selecciona el període de qualificació
+ Període de qualificació actual
+ No s\'han pogut carregar les qualificacions
+ No s\'han pogut carregar les qualificacions del període de qualificació sol·licitat
+ No s\'han pogut actualitzar les qualificacions
+ No hi ha cap qualificació a mostrar
+ Sense qualificació
+ Canvia el període de qualificació
+ No hi cap qualificació disponible
+ Tutoria
+
+ Vista de la tutoria.
+ Enllaços importants
+ Sol·licituds dels estudiants
+ Informació de contacte del personal
+ Tria un curs
+ Enllaços importants
+ Professor
+ Auxiliar de professor
+ Els vostres recursos es mostren aquí.
+ No s\'han pogut carregar els recursos
+ No s\'han pogut actualitzar els recursosObriu una visualització alternativa més accessibleDestinatarisAssumpteSeleccioneu un curs, %s
+
Les respostes d\'aquest estudiant estan amagades perquè la tasca és anònima.Anotació de l’estudiant (no admesa)En aquest moment, no s\'admet l’anotació de l\'estudiant al dispositiu mòbil.Tipus de entrega no admèsNo es pot mostrar l’entrega, perquè en aquest moment no s\'admet l’anotació de l\'estudiant al dispositiu mòbil.
+ L’heu marcat com a fet.
diff --git a/libs/pandares/src/main/res/values-cy/strings.xml b/libs/pandares/src/main/res/values-cy/strings.xml
index 2dd5452e16..cba13db359 100644
--- a/libs/pandares/src/main/res/values-cy/strings.xml
+++ b/libs/pandares/src/main/res/values-cy/strings.xml
@@ -1176,24 +1176,47 @@
Gweld Cyhoeddiadau BlaenorolWedi methu llwytho HomeroomWedi methu adnewyddu Homeroom
-
Dim byd i fod i mewn heddiw%1$s i fod i mewn heddiw%1$s ar gollCroeso!Mae eich pynciau’n ymddangos yma.Does gennych chi ddim pynciau ar hyn o bryd.
-
-
+ Mae angen ailddechrau’r ap
+ Dewiswch
+ Dewis Cyfnod Graddio
+ Cyfnod Graddio Presennol
+ Wedi methu llwytho’r graddau
+ Wedi methu llwytho’r graddau ar gyfer y cyfnod graddio
+ Wedi methu adnewyddu’r graddau
+ Dim Graddau i’w dangos
+ Heb eu graddio
+ Newid cyfnod graddio
+ Graddau ddim ar gael
+ Ystafell hafan
+
+ Gwedd Homeroom
+ Dolenni Pwysig
+ Ceisiadau Myfyriwr
+ Manylion Cyswllt Staff
+ Dewis Cwrs
+ Dolenni Pwysig
+ Athro
+ Cynorthwyydd Dysgu
+ Mae eich adnoddau’n ymddangos yma.
+ Wedi methu llwytho adnoddau
+ Wedi methu adnewyddu’r adnoddauAgor gwedd arall fwy hygyrchDerbynwyrPwncDewiswch gwrs, %s
+
Mae atebion y myfyriwr hwn wedi’u cuddio gan fod yr aseiniad hwn yn ddienw.Anodiad gan Fyfyriwr (Anghydnaws)Does dim modd delio ag anodiadau gan fyfyrwyr ar ddyfeisiau symudol ar hyn o bryd.Math o Gyflwyniad AnghydnawsDoes dim modd dangos y cyflwyniad, oherwydd does dim modd delio ag anodiadau gan fyfyrwyr ar ddyfeisiau symudol ar hyn o bryd.
+ Rydych chi wedi’i farcio fel wedi’i orffen.
diff --git a/libs/pandares/src/main/res/values-da/strings.xml b/libs/pandares/src/main/res/values-da/strings.xml
index 86a652e81f..0d6821981e 100644
--- a/libs/pandares/src/main/res/values-da/strings.xml
+++ b/libs/pandares/src/main/res/values-da/strings.xml
@@ -1176,24 +1176,47 @@
Se tidligere beskederKunne ikke indlæse HomeroomKunne ikke opdatere Homeroom
-
Intet forfalder i dag%1$s forfalder i dag%1$s manglerVelkommen!Dine emner vises her.Du har i øjeblikket ingen emner.
-
-
+ Kræver genstart af app
+ Vælg
+ Vælg karakterperiode
+ Nuværende karakterperiode
+ Kunne ikke indlæse karakterer
+ Kunne ikke indlæse karakterer for karakterperioden
+ Kunne ikke opdatere karakterer
+ Ingen karakterer at vise
+ Ikke bedømt
+ Skift karakterperiode
+ Karakterer ikke tilgængelige
+ Klasselokale
+
+ Klasseværelsevisning
+ Vigtige links
+ Applikationer for studerende
+ Kontaktoplysninger for personale
+ Vælg et fag
+ Vigtige links
+ Lærer
+ Undervisningsassistent
+ Dine ressourcer vises her.
+ Kunne ikke indlæse ressourcer
+ Kunne ikke opdatere ressourcerÅbn en mere tilgængelig alternativ visningModtagereEmneVælg et fag, %s
+
Den studerendes svar er skjult, fordi denne opgave er anonym.Studerendes anmærkninger (ikke understøttet)Studerendes anmærkninger understøttes i øjeblikket ikke på mobil.Ikke-understøttet afleveringsformAflevering kan ikke vises, fordi studerendes anmærkninger i øjeblikket ikke understøttes på mobil.
+ Du har markeret det som udført.
diff --git a/libs/pandares/src/main/res/values-de/strings.xml b/libs/pandares/src/main/res/values-de/strings.xml
index ed05e5a20d..c31728f9f5 100644
--- a/libs/pandares/src/main/res/values-de/strings.xml
+++ b/libs/pandares/src/main/res/values-de/strings.xml
@@ -1176,24 +1176,47 @@
Vorherige Ankündigungen anzeigenOrganisationsstunde konnte nicht geladen werden.Aktualisierung der Organisationsstunde fehlgeschlagen
-
Heute ist nichts fällig.%1$s heute fällig%1$s fehlt/fehlenWillkommen!Ihre Fächer werden hier angezeigt.Zurzeit haben Sie keine Themen.
-
-
+ Erfordert Neustart der App
+ Auswählen
+ Benotungszeitraum auswählen
+ Aktueller Benotungszeitraums
+ Laden von Noten fehlgeschlagen
+ Noten für den Benotungszeitraum konnten nicht geladen werden.
+ Noten konnten nicht aktualisiert werden.
+ Keine Noten anzuzeigen
+ Unbenotet
+ Benotungszeitraum ändern
+ Noten nicht verfügbar
+ Organisationsstunde
+
+ Organisationsstundenansicht
+ Wichtige Links
+ Anwendungen für Studenten
+ Mitarbeiterkontakt-Info
+ Einen Kurs auswählen
+ Wichtige Links
+ Lehrer
+ Lehrassistent
+ Ihre Ressourcen werden hier angezeigt.
+ Ressourcen konnten nicht geladen werden
+ Ressourcen konnten nicht aktualisiert werdenEine leichter zugängliche, alternative Ansicht öffnenEmpfängerBetreffEinen Kurs auswählen, %s
+
Die Antworten dieses Studenten sind ausgeblendet, weil diese Aufgabe anonym ist.Studentenanmerkung (Nicht unterstützt)Studentenanmerkungen werden derzeit auf Mobilgeräten nicht unterstützt.Nicht unterstützte AbgabeartAbgabe kann nicht angezeigt werden, weil Studentenanmerkungen derzeit auf Mobilgeräten nicht unterstützt werden.
+ Sie haben es es als erledigt markiert.
diff --git a/libs/pandares/src/main/res/values-en-rAU/strings.xml b/libs/pandares/src/main/res/values-en-rAU/strings.xml
index d81f164df8..e2fdc62e38 100644
--- a/libs/pandares/src/main/res/values-en-rAU/strings.xml
+++ b/libs/pandares/src/main/res/values-en-rAU/strings.xml
@@ -1176,24 +1176,47 @@
View Previous AnnouncementsFailed to load HomeroomFailed to refresh Homeroom
-
Nothing Due Today%1$s due today%1$s missingWelcome!Your subjects show up here.You currently have no subjects.
-
-
+ Requires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load marks
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Marked
+ Change grading period
+ Grades not available
+ Homeroom
+
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Course
+ Important Links
+ Teacher
+ Teaching Assistant
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative viewRecipientsSubjectSelect a course, %s
+
This student\'s responses are hidden because this assignment is anonymous.Student Annotation (Unsupported)Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
diff --git a/libs/pandares/src/main/res/values-en-rCA/strings.xml b/libs/pandares/src/main/res/values-en-rCA/strings.xml
index 7e6db64d5a..4476d3a3a0 100644
--- a/libs/pandares/src/main/res/values-en-rCA/strings.xml
+++ b/libs/pandares/src/main/res/values-en-rCA/strings.xml
@@ -1183,6 +1183,29 @@
Welcome!Your subjects show up here.You currently have no subjects.
+ Requires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load grades
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Graded
+ Change grading period
+ Grades not available
+ Homeroom
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Course
+ Important Links
+ Teacher
+ Teaching Assistant
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative view
@@ -1196,4 +1219,5 @@
Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
diff --git a/libs/pandares/src/main/res/values-en-rCY/strings.xml b/libs/pandares/src/main/res/values-en-rCY/strings.xml
index ac9a3dd067..f0f137853f 100644
--- a/libs/pandares/src/main/res/values-en-rCY/strings.xml
+++ b/libs/pandares/src/main/res/values-en-rCY/strings.xml
@@ -1176,24 +1176,47 @@
View Previous AnnouncementsFailed to load HomeroomFailed to refresh Homeroom
-
Nothing Due Today%1$s due today%1$s missingWelcome!Your subjects show up here.You currently have no subjects.
-
-
+ Requires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load grades
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Graded
+ Change grading period
+ Grades not available
+ Homeroom
+
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Module
+ Important Links
+ Teacher
+ Teaching Assistant
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative viewRecipientsSubjectSelect a module, %s
+
This student\'s responses are hidden because this assignment is anonymous.Student Annotation (Unsupported)Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
diff --git a/libs/pandares/src/main/res/values-en-rGB/strings.xml b/libs/pandares/src/main/res/values-en-rGB/strings.xml
index 5079ad983c..1b6933e87a 100644
--- a/libs/pandares/src/main/res/values-en-rGB/strings.xml
+++ b/libs/pandares/src/main/res/values-en-rGB/strings.xml
@@ -1176,24 +1176,47 @@
View Previous AnnouncementsFailed to load HomeroomFailed to refresh Homeroom
-
Nothing Due Today%1$s due today%1$s missingWelcome!Your subjects show up here.You currently have no subjects.
-
-
+ Requires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load grades
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Graded
+ Change grading period
+ Grades not available
+ Homeroom
+
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Course
+ Important Links
+ Teacher
+ Teaching Assistant
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative viewRecipientsSubjectSelect a course, %s
+
This student\'s responses are hidden because this assignment is anonymous.Student Annotation (Unsupported)Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
diff --git a/libs/pandares/src/main/res/values-es/strings.xml b/libs/pandares/src/main/res/values-es/strings.xml
index 56a8bddee8..610c7afa98 100644
--- a/libs/pandares/src/main/res/values-es/strings.xml
+++ b/libs/pandares/src/main/res/values-es/strings.xml
@@ -75,8 +75,8 @@
Mostrar opciones de Agregar archivoGrabar audioGrabar video
-
Editor de Entrega de Texto
+
Escriba…Algo salió mal al cargar la entrega. Entregar nuevamente.
@@ -100,10 +100,10 @@
Esta tarea no permite entregas en líneaEsta tarea no permite entregas en líneaSin entregas en línea
-
Esta tarea se vincula con una herramienta externa para entregas.Abrir herramientaTexto de la entrega
+
De %s puntosDe %s pts
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s de %2$s puntosIngresar puntaje hipotético
-
Bajo: %sPromedio: %sAlto: %s
+
Puntaje personalizadoNo hay ninguna rúbrica para esta tarea
@@ -199,7 +199,6 @@
No hay tarea en este grupoEsta tarea está justificada y no será considerada en el cálculo totalEX
-
Ordenar porHoraTipo
@@ -211,6 +210,7 @@
Botón Ordenar tareas, ordenar por tipoTareas ordenadas por horaTareas ordenadas por tipo
+
Puntos\u0020Guardar
@@ -218,9 +218,9 @@
Las respuestas son visibles solamente para aquellos que han publicado al menos una respuesta.
-
Este archivo está bloqueado actualmenteEditor de Respuesta de Discusión
+
Comienza enFinaliza
@@ -448,6 +448,7 @@
Dejar de actuar como usuarioIdentificación de usuarioHubo un error al intentar actuar como usuario
+
El identificador no puede estar en blancoIr al examen
@@ -604,6 +605,8 @@
Exámenes de prácticaEncuestas calificadasEncuestas
+
+
Por hacerCalificaciones
@@ -649,12 +652,12 @@
Configuración del perfilPreferencias de la cuentaPIN y huella digital
-
Emparejamiento con un observadorPídale a su padre/madre que escanee este código QR desde la aplicación Canvas Parent para emparejarse con usted. Este código caducará dentro de siete días, o tras el primer uso.Código de emparejamiento:\u0020Ocurrió un error con el código de emparejamientoNo se pudo obtener un código de emparejamiento. Esta funcionalidad solo es compatible para los estudiantes.
+
Abrir SpeedGraderLista de cosa o cosas por hacer
@@ -725,8 +728,8 @@
SpeedGraderIndicador
-
Control deslizante de calificaciones
+
Crear nuevo eventoApagar los pandas
@@ -813,6 +816,7 @@
Reciba notificaciones cuando esté lista la grabación de una conferencia.Ilimitados
+
Pregunta%d pregunta
@@ -823,6 +827,7 @@
%s punto%s puntos
+
Límite de tiempo%1$s Nuevas notificacionesme gusta
@@ -994,10 +999,10 @@
Los teléfonos móviles aún no admiten conferencias.Imagen de vista previa del archivoSe ha producido un error al intentar cargar este PDF.
-
¡Lo sentimos! Esta funcionalidad no se permite en la vista del estudiante.No hay nada para ver aquíFuncionalidad no compatible
+
Agregar estudianteIntroduzca el código de emparejamiento de estudiante que se le proporcionó.
@@ -1036,10 +1041,10 @@
Calificación después de la publicaciónAnulación de las calificacionesCalificación actual
-
Se abre en Canvas StudentVista del estudiante
+
Cabeza seleccionada: %sCuerpo seleccionado: %s
@@ -1132,6 +1137,7 @@
Detalles de la conferenciaGrabacionesConferencia en curso
+
Valoración del criterio %s%s, %smás información
@@ -1158,6 +1164,7 @@
Eliminar todo del TableroAgregar al TableroAgregar todo al Tablero
+
CuentaAula principal
@@ -1166,28 +1173,50 @@
Recursos¡Le damos la bienvenida, %1$s!Mis temas
-
Ver anuncios previosNo se pudo cargar el Aula principal (Homeroom)No se pudo actualizar el Aula principal (Homeroom)
-
-
Nada con fecha límite el día de hoy%1$s tiene fecha límite el día de hoy%1$s faltante¡Bienvenido!Sus materias se muestran aquí.No tiene materias actualmente.
-
-
+ Requiere reiniciar la aplicación
+ Seleccionar
+ Seleccionar Período de Calificación
+ Período de Calificación Actual
+ No se pudieron cargar las calificaciones
+ No se pudieron cargar las calificaciones del período de calificación
+ No se pudieron actualizar las calificaciones
+ No hay calificaciones para mostrar
+ Sin calificar
+ Cambiar Período de calificación
+ No hay calificaciones disponibles
+ Aula principal
+
+ Vista del aula
+ Enlaces importantes
+ Solicitudes del alumno
+ Información de contacto del personal
+ Elegir un curso
+ Enlaces importantes
+ Profesor
+ Profesor asistente
+ Sus recursos se muestran aquí.
+ No se pudieron cargar los recursos
+ No se pudieron actualizar los recursosAbrir una vista alternativa más accesible
+
DestinatariosAsuntoSeleccione un curso, %s
+
Las respuestas del estudiante están ocultas porque esta tarea es anónima.Anotación del estudiante (no admitida)La anotación del estudiante actualmente no se admite en el móvil.Tipo de entrega no admitidaLa entrega no se puede mostrar porque la Anotación del estudiante actualmente no se admite en el móvil.
+ Lo marcó como terminado.
diff --git a/libs/pandares/src/main/res/values-fi/strings.xml b/libs/pandares/src/main/res/values-fi/strings.xml
index ded59a20a7..260a17bd02 100644
--- a/libs/pandares/src/main/res/values-fi/strings.xml
+++ b/libs/pandares/src/main/res/values-fi/strings.xml
@@ -75,8 +75,8 @@
Näytä Lisää tiedosto -asetuksetNauhoita ääniNauhoita video
-
Tekstin lähetyksen editori
+
Kirjoita…Jotakin meni pieleen lähetyksen päivityksessä. Lähetä uudelleen.
@@ -100,10 +100,10 @@
Tämä tehtävä ei salli verkkolähetyksiä.Tämä tehtävä ei salli verkkolähetyksiä.Ei verkkolähetyksiä
-
Tämä tehtävä linkittää ulkoisen työkalun lähetyksiin.Avaa työkaluLähetyksen testi
+
/%s pisteestä/%s pistettä
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s/%2$s pistettäSyötä Mitä jos -pistemäärä.
-
Matala: %sKeskiarvo: %sKorkea: %s
+
Mukautettu pistemäärä Tälle tehtävälle ei ole rubriikkia
@@ -199,7 +199,6 @@
Tässä ryhmässä ei ole tehtäviäTämän tehtävän annetaan mennä eikä sitä oteta huomioon koko laskelmassaEX
-
LajitteluperusteAikaTyyppi
@@ -211,6 +210,7 @@
Lajittele tehtävät -painike, lajittele tyypin perusteellaTehtävät lajiteltu ajan perusteellaTehtävät lajiteltu tyypin perusteella
+
Pisteitä\u0020Tallenna
@@ -218,9 +218,9 @@
Vastaukset ovat näkyvissä vai niille, jotka ovat lähettäneet vähintään yhden vastauksen.
-
Tämä tiedosto on parhaillaan lukittuKeskustelun lähetyksen editori
+
AlkaaPäättyy
@@ -448,6 +448,7 @@
Lopeta toimiminen käyttäjänäKäyttäjätunnusIlmeni virhe yritettäessä toimia tänä käyttäjänä.
+
Tunnus ei voi olla tyhjä.Siirry tietovisaan
@@ -604,6 +605,8 @@
HarjoittelutietovisatArvostellut kyselytKyselyt
+
+
TehtäväArvosanat
@@ -649,12 +652,12 @@
ProfiiliasetuksetTilin asetuksetPIN ja sormenjälki
-
Yhdistä havaitsijan kanssaPyydä vanhempiasi skannaamaan tämä viivakoodi Canvas Parent -sovelluksesta, jotta he saavat yhteyden sovellukseen. Tämä koodi vanhenee seitsemässä päivässä tai yhden käytön jälkeen.Parinmuodostuskoodi:\u0020Parimuodostuskoofin virheParinmuodostuskoodin nouto epäonnistui. Tämä ominaisuutta tuetaan vain opiskelijoille.
+
Avaa Speedgradermitätehdä mitä tehdä mitä tehdä -luettelo
@@ -725,8 +728,8 @@
SpeedGraderMittari
-
Arvosanojen liukuasteikko
+
Luo uusi tapahtumaKytke pandat pois päältä
@@ -813,6 +816,7 @@
Saa ilmoitus, kun konferessin äänitys on valmis.Rajoittamaton
+
Kysymys%d kysymys
@@ -823,6 +827,7 @@
%s piste%s pistettä
+
Aikaraja%1$s Uudet ilmoituksettykkäys
@@ -994,10 +999,10 @@
Konferensseja ei vielä tueta matkapuhelimessa.Tiedoston esikatselukuvaTämän PDF-tiedoston lataamisen yhteydessä ilmeni virhe.
-
Pahoittelut! Tätä ominaisuutta ei sallita opiskelijanäkymässä.Täällä ei ole mitään nähtävää.Tukematon ominaisuus
+
Lisää opiskelijaPane opiskelijan parinmuodostuskoodi, joka on toimitettu sinulle.
@@ -1036,10 +1041,10 @@
Arvosana lähetyksen jälkeenKorvaa arvosanaNykyinen arvosana
-
Avautuu Canvas StudentissaOpiskelijanäkymä
+
Valittu pää: %sValittu keho: %s
@@ -1111,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s Minuutti%s minuuttia
@@ -1131,6 +1137,7 @@
Konferenssin tiedotNauhoituksetKokous käynnissä
+
Kriteerien luokitukset %s%s, %slisätietoja
@@ -1157,6 +1164,7 @@
Poista widget koontinäytöstäLisää koontinäytölleLisää kaikki koontinäyttöön
+
TiliHomeroom
@@ -1165,28 +1173,50 @@
ResurssitTervetuloa, %1$s!Omat aiheet
-
Näytä edelliset ilmoituksetHomeroomin lataus epäonnistuiHomeroomin päivitys epäonnistui
-
-
Tänään ei eräänny mitään%1$s erääntyy tänään%1$s puuttuuTervetuloa!Aiheesi näkyvät täällä.Sinulla ei tällä hetkellä ole aiheita.
-
-
+ Vaatii sovelluksen uudelleenkäynnistyksen
+ Valitse
+ Valitse arvosanojen antojakso
+ Nykyinen arvosanojen antojakso
+ arvosanojen lataus epäonnistui
+ Arvosanojen lataus pyydetylle arvosanojen antojaksolle epäonnistui
+ Arvosanojen päivitys epäonnistui
+ Ei esitettäviä arvosanoja
+ Ei arvioida
+ Vaihda arvosanojen antojakso
+ Arvosanat eivät ole käytettävissä
+ Homeroom
+
+ Homeroom-näkymä
+ Tärkeitä linkkejä
+ Opiskeluhakemukset
+ Henkilökunnan yhteistiedot
+ Valitse kurssi
+ Tärkeitä linkkejä
+ Opettaja
+ Apuopettaja
+ Resurssisi näkyvät täällä.
+ Resurssien lataus epäonnistui
+ Resurssien päivitys epäonnistuiAvaa lähestyttävämpi vaihtoehtoinen näkymä
+
VastaanottajatAiheValitse kurssi, %s
+
Tämän opiskelijan vastaukset on kätketty, koska tehtävä on nimetön.Opiskelijan huomautusasiakirjat (tukematon)Opiskelijan huomautuksia ei tällä hetkellä tueta matkapuhelimessa.Lähetystyyppiä ei tuetaLähetystä ei voida näyttää, koska opiskelijan huomautuksia ei tällä hetkellä tueta matkapuhelimessa.
+ Olet merkinnyt sen tehdyksi.
diff --git a/libs/pandares/src/main/res/values-fr-rCA/strings.xml b/libs/pandares/src/main/res/values-fr-rCA/strings.xml
index 4bc07de3c7..e8a26f722b 100644
--- a/libs/pandares/src/main/res/values-fr-rCA/strings.xml
+++ b/libs/pandares/src/main/res/values-fr-rCA/strings.xml
@@ -55,10 +55,8 @@
Tentatives utiliséesAucune tentative restanteEnvoyer la tâche de nouveau
-
Lancement de l\'outil externe...Lancer l\'outil externe
-
AperçuLe téléversement d\'un ou de plusieurs fichiers a échoué. Vérifiez votre connexion Internet et réessayez l’envoi.%1$s sur %2$s
@@ -69,19 +67,20 @@
Envoi suppriméManquantNoté
+
Aucun aperçu disponible pour les URL utilisant http://Veuillez saisir une URL valideSaisissez un URL ici pour votre envoiAfficher les options d\'ajout de fichierEnregistrer le son
-
Enregistrer la vidéo
-
Éditeur d’envoi de texte
+
Écrire…Un problème est survenu lors du téléversement de l’envoi. Envoyer de nouveau.
+
EnvoiVersions des envois
@@ -104,6 +103,7 @@
Cette tâche comporte des liens vers un outil externe pour les envois.Ouvrir OutilTexte de dépôt
+
de %s pointsde %s pts
@@ -115,10 +115,12 @@
Bas : %sMoyenne : %sÉlevé : %s
+
Score personnaliséIl n\'y a aucune rubrique pour cette tâche.Afficher description longue
+
Téléversement du fichier %1$d sur %2$dTéléversement du commentaire pour %s
@@ -138,6 +140,7 @@
Fichier multimédiaAudioVidéo
+
Version :
@@ -187,6 +190,8 @@
Dû le %1$s à %2$sampm
+
+
VerrouilléTâches
@@ -205,15 +210,17 @@
Trier les boutons des tâches, trier par typeTâches triées par heureTâches triées par type
+
Points\u0020EnregistrerAnnuler
+
Les réponses ne sont visibles que pour ceux qui ont publié au moins une réponse.
-
Ce fichier est présentement verrouilléÉditeur des réponses de discussion
+
CommenceSe termine
@@ -232,8 +239,6 @@
Score hypothétique%1$s/%2$s (%3$s)%1$s de %2$s points, %3$s
-
-
Boîte de réceptionNon luArchivé
@@ -399,8 +404,8 @@
ÉtudiantsObservateurUn programme n’a pas été ajouté.
-
Une erreur est survenue lors du chargement de vos modules.
+
CanvasChoisir un ou des destinataire(s)Ce message n\'a aucun destinataire actuellement
@@ -426,6 +431,7 @@
Supprimé
+
Chargement du contenu de Canvas…UnknownDevice
@@ -442,9 +448,11 @@
Arrêter d\'agir en tant qu\'utilisateurID utilisateurUne erreur s\'est produite lors de la tentative d’agir en tant qu’utilisateur
+
L\'identifiant ne peut être videAller au questionnaire
+
QuestionnairesDernière publication
@@ -503,8 +511,8 @@
Se rendre aux ModulesMarquer comme terminéÉlément de module introuvable
-
Une erreur est survenue lors du chargement de vos modules.
+
AideQuestion de l\'instructeurLien
@@ -520,11 +528,11 @@
Saisie de texteURL en ligneCet envoi n’accepte qu’un seul téléversement de fichier
-
-
Ajouter des entrées du site WebEnregistrements multimédiasEnvoyer
+
+
Progrès non sauvegardéToute information non enregistrée sera perdue. Désirez-vous continuer?
@@ -539,6 +547,7 @@
SuivantAjouter un commentaire…
+
AccueilNotifications
@@ -568,6 +577,8 @@
Un instantané du site a été capturé lorsque vous l\'avez rendu. Taper et maintenir l\'image ci-dessous pour ouvrir ou télécharger l\'image entière.Cet envoi correspond à une URL vers une page externe. Gardez à l’esprit que cette page peut avoir été modifiée depuis l’envoi d’origine.Un aperçu de l\'url soumise
+
+
%1$s ne sont pas pris en charge.Le lien n\'est pas pris en charge.Ouvrir dans le navigateur
@@ -583,15 +594,19 @@
Faits de panda : RetirerFondateurs
+
CLUFPolitique de confidentialitéConditions d\'utilisationCanvas sur GitHub
+
Questionnaires de la tâcheQuestionnaires d\'entraînementEnquêtes notéesEnquêtes
+
+
À faireNotes
@@ -637,12 +652,12 @@
Paramètres de profilPréférences du compteNIP et empreinte
-
Jumeler avec l’observateurDemandez à votre parent de numériser ce code QR à partir de l\'application Canvas Parent pour le jumeler avec vous. Ce code viendra à échéance dans sept jours, ou après une seule utilisation.Code de jumelage : \u0020Erreur du jumelage de codeImpossible de récupérer un code de jumelage. Cette fonctionnalité n\'est prise en charge que pour les étudiants.
+
Lancer Speedgradertâche à faire tâches à faire liste des tâches à faire
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Curseur de notation
+
Créer un nouvel événementDésactiver les pandas
@@ -801,6 +816,7 @@
Soyez informé lorsqu\'un enregistrement de conférence est prêt.Illimité
+
Question%d questions
@@ -811,16 +827,17 @@
%s point%s points
+
Limite de temps%1$s Nouvelles notificationsmention « J\'aime »Laisser une rétroaction de type « J’aime » à cette entréementions « J\'aime »
-
%s « j’aime »%s « j’aime »
+
RougeRose vifLavande
@@ -879,6 +896,7 @@
Modifier le tableau de bordSélectionnez les cours que vous souhaitez voir sur le tableau de bordModifier votre liste des cours
+
Tableau de bordPrécédentPage %d sur %d
@@ -906,8 +924,8 @@
Tapoter pour afficher l’annonce.AccepterDécliner
-
sur
+
Il n\'y a aucun fichier associé à ce cours.Il n\'y a aucun fichier associé à ce groupe.
@@ -968,6 +986,7 @@
Nous n\'arrivons pas à trouver une application externe pour voit cet outil LTI.Téléversement de commentaire
+
Page de couvertureModifier une pageDescription
@@ -980,10 +999,10 @@
Les conférences ne sont pas encore prises en charge sur mobile.Image de prévisualisation du fichierUne erreur s\'est produite en essayant de charger ce PDF.
-
Désolé! Cette fonctionnalité n\'est pas autorisée en mode Étudiant.Rien à voir iciFonctionnalité non prise en charge
+
Ajouter un étudiantSaisir le code de jumelage de l’étudiant qui vous a été fourni.
@@ -991,6 +1010,7 @@
Le jumelage a échoué. Assurez-vous que votre code de jumelage est adéquat et dans les délais d\'utilisation.TerminéIncomplet
+
Définitions de publicationPublier les notes
@@ -1021,10 +1041,10 @@
Notation après publicationSurclasser la notationNote actuelle
-
S’ouvre dans Canvas ÉtudiantVue étudiant
+
Entête choisi : %sCorps choisi : %s
@@ -1078,7 +1098,6 @@
Ajouter un commentaire vidéoAjouter un commentaire audio
-
Une erreur inattendue s\'est produite lors de l\'enregistrement du son.Une erreur inattendue s\'est produite lors de l\'enregistrement de la vidéo.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s Minute%s Minutes
@@ -1117,6 +1137,7 @@
Détails de la conférenceEnregistrementsConférences en cours
+
Notes du critère %s%s, %splus d\'informations
@@ -1143,6 +1164,7 @@
Tout retirer du tableau de bordAjouter au tableau de bordTout ajouter au tableau de bord
+
CompteClasse titulaire
@@ -1151,28 +1173,50 @@
RessourcesBienvenue, %1$s!Mes Objets
-
Voir les annonces précédentesÉchec lors du chargement de la classe titulaireÉchec lord de l\'actualisation de la classe titulaire
-
-
Rien n’est dû aujourd’hui%1$s dû aujourd’hui%1$s manquantBienvenue!Vos sujets apparaissent ici.Vous n’avez actuellement aucun sujet.
-
-
+ Redémarrage de l\'application requis
+ Sélectionner
+ Choisir la période de notation
+ Période de notation actuelle
+ Échec lors du chargement des notes
+ Échec lors du chargement des notes pour la période de notation
+ Échec lors de l’actualisation des notes
+ Aucune note à afficher
+ Non noté
+ Changer la période de notation
+ Notes non disponibles
+ Classe titulaire
+
+ Vue de la salle d’accueil
+ Liens importants
+ Dossiers de l’étudiant
+ Coordonnées du personnel
+ Choisir un cours
+ Liens importants
+ Enseignant
+ Assistants d’enseignement
+ Vos ressources apparaissent ici.
+ Échec lors du chargement de ressource
+ Échec lors de l’actualisation des ressourcesOuvrir une vue alternative plus accessible
+
DestinatairesObjetSélectionner un cours, %s
+
Les réponses de cet étudiant sont masquées du fait que cette tâche est anonyme.Annotation d’étudiant (non pris en charge)L’annotation d’étudiant n’est actuellement pas prise en charge sur les mobiles.Type d’envoi non pris en chargeL’envoi ne peut pas être affiché, car l’annotation d’étudiant n’est actuellement pas prise en charge sur mobile.
+ Vous l’avez marqué comme terminé.
diff --git a/libs/pandares/src/main/res/values-fr/strings.xml b/libs/pandares/src/main/res/values-fr/strings.xml
index c9926e0756..0f40060f14 100644
--- a/libs/pandares/src/main/res/values-fr/strings.xml
+++ b/libs/pandares/src/main/res/values-fr/strings.xml
@@ -55,10 +55,8 @@
Tentatives utiliséesAucune tentative restanteSoumettre à nouveau le travail
-
Lancement de l\'outil externe...Lancer l\'outil externe
-
AperçuL’envoi d\'un ou plusieurs fichiers a échoué. Vérifiez l’état de votre connexion internet, puis réessayez.%1$s de %2$s
@@ -69,19 +67,20 @@
Soumission suppriméeManquantNoté
+
Aucun aperçu disponible pour les URLs utilisant « http:// »Veuillez saisir une URL valideEntrez une URL ici pour votre soumissionAfficher les options d\'ajout de fichierEnregistrer de l’audio
-
Enregistrer la vidéo
-
Éditeur de soumission de texte
+
Écrire…Un problème est survenu lors de l’envoi de la soumission. Relancez la soumission.
+
SoumissionVersions de soumission
@@ -104,6 +103,7 @@
Cette tâche renvoie à un outil externe pour les envois.Ouvrir outilTexte de la soumission
+
Sur %s pointsSur %s pts
@@ -115,10 +115,12 @@
Faible : %sMoyen : %sÉlevé : %s
+
Score personnaliséIl n’existe aucun barème pour ce travailAfficher la description longue
+
Envoi du fichier %1$d sur %2$dEnvoi du commentaire pour %s
@@ -138,6 +140,7 @@
Fichier multimédiaAudioVidéo
+
Version :
@@ -187,6 +190,8 @@
Dû le %1$s à %2$sampm
+
+
VerrouilléDevoirs
@@ -205,15 +210,17 @@
Bouton de tri des travaux, tri par typeTravaux triés par heureTravaux triés par type
+
Points\u0020EnregistrerAnnuler
+
Les réponses ne sont visibles que pour ceux qui ont publié au moins une réponse.
-
Ce fichier est actuellement verrouilléÉditeur de réponses de discussion
+
DémarrePrend fin
@@ -232,8 +239,6 @@
Score hypothétique%1$s/%2$s (%3$s)%1$s sur %2$s points, %3$s
-
-
Boîte de réceptionNon luArchivé
@@ -399,8 +404,8 @@
ÉlèvesObservateursUn programme n’a pu être chargé.
-
Une erreur est survenue durant le chargement de vos modules.
+
CanvasChoisir un ou des destinataire(s)Ce message n\'a aucun destinataire actuellement
@@ -426,6 +431,7 @@
Supprimé
+
Chargement du contenu Canvas…UnknownDevice
@@ -442,9 +448,11 @@
Cesser d\'agir en tant qu\'utilisateurID utilisateurUne erreur est survenue lors de la tentative d\'agir au nom d’un utilisateur.
+
L\'identifiant ne peut être viergeAller au questionnaire
+
QuestionnairesDernière publication
@@ -503,8 +511,8 @@
Se rendre sur ModulesMarquer comme terminéÉlément de module introuvable
-
Une erreur est survenue durant le chargement de vos modules.
+
AideQuestion de l\'instructeurLien
@@ -520,11 +528,11 @@
Saisie de texteURL en ligneCette soumission n’accepte qu’un seul téléchargement de fichier
-
-
Ajouter une entrée de site webEnregistrements multimédiasSoumettre
+
+
Progrès non sauvegardéLes informations non enregistrées seront perdues. Souhaitez-vous continuer ?
@@ -539,6 +547,7 @@
SuivantAjouter un commentaire…
+
AccueilNotifications
@@ -568,6 +577,8 @@
Un instantané du site a été capturé lorsque vous l\'avez rendu. Taper et maintenir l\'image ci-dessous pour ouvrir ou téléverser l\'image entière.Cet envoi correspond à une URL vers une page externe. Gardez à l’esprit que cette page peut avoir été modifiée depuis l’envoi d’origine.Un aperçu de l\'url soumise
+
+
%1$s n’est pas pris en chargé.Le lien n\'est pas supporté.Ouvrir dans le navigateur
@@ -583,15 +594,19 @@
Le panda a à vous dire : SupprimerFondateurs
+
CGUPolitique de confidentialitéConditions d\'utilisationCanvas sur GitHub
+
Quiz sous forme de devoirQuiz d’entraînementEnquêtes notéesSondages
+
+
À faireNotes
@@ -637,12 +652,12 @@
Paramètres de profilPréférences du comptePIN et empreinte digitale
-
Paire avec l\'observateurFaites scanner ce code QR par vos parents depuis l’application Canvas Parent pour les jumeler à vous. Ce code expirera dans sept jours, ou lorsqu\'il aura été utilisé une fois.Code de jumelage :\u0020Erreur de code de jumelageImpossible de récupérer un code de jumelage. Cette fonctionnalité n’est disponible que pour les élèves.
+
Lancer Speedgradertâche à faire tâches à faire liste des tâches à faire
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Curseur des notes
+
Créer un nouvel événementDésactiver les pandas
@@ -801,6 +816,7 @@
Être notifié lorsqu’un nouvel enregistrement de conférence est prêt.Illimité
+
Question%d question
@@ -811,16 +827,17 @@
%s point%s points
+
Limite de temps%1$s nouvelles notificationsmention "J\'aime"Aimer l’entréementions "J\'aime"
-
%s like%s likes
+
RougeRose vifLavande
@@ -879,6 +896,7 @@
Modifier le tableau de bordSélectionnez les cours que vous souhaitez voir sur le tableau de bordModifier votre liste de cours
+
Tableau de bordPrécédentPage %d sur %d
@@ -906,8 +924,8 @@
Appuyez pour voir l’annonceAccepterRefuser
-
sur
+
Il n\'y a aucun fichier associé à ce cours.Il n\'y a aucun fichier associé à ce groupe.
@@ -968,6 +986,7 @@
Nous n\'arrivons pas à trouver une application externe pour visualiser cet outil LTI.Téléchargement de commentaires
+
Première pageModifier la pageDescription
@@ -980,10 +999,10 @@
Les conférences ne sont pas encore prises en charge sur mobile.Image de prévisualisation de fichierUne erreur est survenue lors du chargement de ce PDF.
-
Désolé ! Cette fonctionnalité n’est pas autorisée en vue « Élève »Rien à voir iciFonction non prise en charge
+
Ajouter un étudiantSaisir le code de jumelage de l’étudiant qui vous a été fourni.
@@ -991,6 +1010,7 @@
Le jumelage a échoué. Assurez-vous que votre code de jumelage est correct et dans les limites de durée d’utilisation prévues.TerminéIncomplet
+
Paramètres de publicationPublier les notes
@@ -1021,10 +1041,10 @@
Note après publicationRemplacement de la noteNote actuelle
-
S’ouvre dans Canvas StudentAffichage élève
+
Tête sélectionnée : %sCorps sélectionné : %s
@@ -1078,7 +1098,6 @@
Ajouter un commentaire vidéoAjouter un commentaire audio
-
Une erreur inattendue s\'est produite lors de l\'enregistrement du son.Une erreur inattendue s\'est produite lors de l\'enregistrement vidéo.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s Minute%s Minutes
@@ -1117,6 +1137,7 @@
Détails de la conférenceEnregistrementsConférence en cours
+
Notes du critère %s%s, %sPlus d\'informations
@@ -1143,6 +1164,7 @@
Supprimer tout du tableau de bordAjouter au tableau de bordAjouter tout au tableau de bord
+
CompteSalle d\'accueil
@@ -1151,28 +1173,50 @@
RessourcesBienvenue, %1$s !Mes sujets
-
Afficher les annonces précédentesImpossible de charger la salle de classeImpossible de rafraîchir la salle de classe
-
-
Rien à rendre aujourd\'hui%1$s à rendre aujourd\'hui%1$s manquantBienvenue !Vos sujets s\'affichent ici.Vous n\'avez actuellement aucun sujet.
-
-
+ Nécessite de redémarrer l’application
+ Sélectionner
+ Sélectionner la période de notation
+ Période de notation actuelle
+ Échec dans le chargement des notes
+ Échec du chargement des notes pour la période de notation
+ Échec de l\'actualisation des notes
+ Aucune note à afficher
+ Non noté
+ Modifier la période de notation
+ Notes indisponibles
+ Salle d\'accueil
+
+ Vue de la classe d’attache
+ Liens importants
+ Applications des élèves
+ Coordonnées du personnel
+ Choisir un cours
+ Liens importants
+ Enseignant
+ Assistant d\'enseignement
+ Vos ressources s\'affichent ici
+ Échec du chargement des ressources
+ Échec de l\'actualisation des ressourcesOuvrir une vue alternative plus accessible
+
DestinatairesSujetSélectionner un cours, %s
+
Les réponses de cet élève sont masquées car ce travail est anonyme.Annotation de l\'élève (non prise en charge)L\'annotation des élèves n\'est actuellement pas prise en charge sur les téléphones mobiles.Type de soumission non pris en chargeLa soumission ne peut pas être affichée, car l\'annotation de l\'élève n\'est actuellement pas prise en charge sur les téléphones mobiles.
+ Vous avez tout indiqué comme terminé.
diff --git a/libs/pandares/src/main/res/values-ht/strings.xml b/libs/pandares/src/main/res/values-ht/strings.xml
index a2cadd52be..f0ba4c395e 100644
--- a/libs/pandares/src/main/res/values-ht/strings.xml
+++ b/libs/pandares/src/main/res/values-ht/strings.xml
@@ -55,10 +55,8 @@
Tantativ ItilizePa Rete TantativResoumèt Sesyon
-
Lansman Zouti EkstènLanse Zouti Eksteryè
-
ApèsiGen yonn oswa plizyè fichye pa rive transfere. Verifye koneksyon entènèt ou a epi eseye re voye yo ankò%1$s de %2$s
@@ -69,19 +67,20 @@
Soumisyon EfaseMankeKlase
+
Okenn apèsi disponib pou URL ki itilize \'http://\'Tanpri antre yon URL ki validAntre yon URL la a pou soumisyon anAfiche Opsyon Ajoute FichyeAnrejistre Son
-
Anrejistre Videyo
-
Editè Soumisyon Tèks
+
Ekri…Gen yon bagay ki pase mal pandan soumisyon ap transfere. Soumèt ankò.
+
SoumisyonVèsyon soumisyon
@@ -104,6 +103,7 @@
Sesyon sa a relye a yon zouti ekstèn pou soumisyon.Ouvri ZoutiTèks soumisyon
+
Sou %s pwenSou %s pts
@@ -115,10 +115,12 @@
Ba: %sMwayen: %sWo: %s
+
Nòt pèsonalizePa gen ribrik pou devwa sa aAfiche tout deskripsyon
+
Transfè fichye %1$d de %2$dTransfè Kòmantè pou %s
@@ -138,6 +140,7 @@
Fichye MiltimedyaOdyoVideyo
+
Vèsyon:
@@ -187,6 +190,8 @@
Delè %1$s a %2$sampm
+
+
BlokeSesyon
@@ -205,15 +210,17 @@
Bouton pou triye devwa yo, triye pa tipDevwa triye pa lèDevwa triye pa tip
+
Pwen\u0020AnrejistreAnile
+
Sèlman moun ki poste yon repons pou pi piti k ap ka wè repons yo.
-
Fichye sa a bloke pou kounye aEditè Repons Diskisyon
+
KòmanseFini
@@ -232,8 +239,6 @@
Kisa-Si Nòt%1$s/%2$s (%3$s)%1$s sou %2$s pwen, %3$s
-
-
Bwat resepsyonPoko liAchive
@@ -399,8 +404,8 @@
ElèvObsèvatèPa gen yon pwogram ki ajoute.
-
Te gen yon erè pandan chajman modil ou yo.
+
CanvasChwazi Destinatè...Mesaj sa a pa gen destinatè.
@@ -426,6 +431,7 @@
Efase
+
Chajman Kontni Canvas…UnknownDevice
@@ -442,9 +448,11 @@
Sispann Pase Pou ItilizatèID ItilizatèTe gen erè pandan w ap eseye pase pou itilizatè
+
ID a pa kapab vidAle nan Quiz
+
QuizDènye piblikasyon
@@ -503,8 +511,8 @@
Ale nan ModilMake tankou li finiEleman Modil Entwouvab
-
Te gen yon erè pandan chajman modil ou yo.
+
ÈdKesyon EnstriktèLyen
@@ -520,11 +528,11 @@
Antre TèksURL an liySoumisyon sa a aksepte transfè yon fichye sèlman
-
-
Ajoute Antre SitAnrejistreman MedyaSoumèt
+
+
Pwogrè non AnrejistreEnfòmasyon ki pa sovgade ap pèdi. Èske w vle kontinye?
@@ -539,6 +547,7 @@
PwochenAjoute yon kòmantè…
+
AkèyNotifikasyon
@@ -568,6 +577,8 @@
Yo te pran yon apèsi sit entènèt la lè w aktive li a. Tape epi kenbe imaj pi ba a pou ouvri oswa telechaje imaj la an antye.Soumisyon sa a se te yon URL a yon paj ekstèn. Sonje ke sa ka rive ke paj sa a chanje aprè soumisyon orijinal la.Yon apèsi url ki te soumèt la
+
+
%1$s pa sipòte.Lyen an pa sipòte.Ouvri nan Navigatè
@@ -583,15 +594,19 @@
Panda Fact: ElimineFondatè
+
EULAPolitik KonfidansyaliteKondisyon ItilizasyonCanvas sou GitHub
+
Sesyon QuizQuiz PratikAnkèt KlaseAnkèt
+
+
Pou FèNòt
@@ -637,12 +652,12 @@
Paramèt PwofiPreferans KontPIN ak Anprent
-
Asosye avèk ObsèvatèFè paran w eskane Kòd QR sa a ak app Canvas Parent lan pou li kapab asosye ak ou. Kòd sa a ap ekspire nan sèt jou oswa aprè yon itilizasyon.Kòd Kouplaj:\u0020Erè Kòd KouplajEnposib pou rekipere kòd kouplaj la. Fonksyon sa a se sèlman pou elèv.
+
Ouvri Speedgraderlis tout sa pou fè
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Ba klasman
+
Kreye Nouvo AktiviteFèmen Panda a
@@ -801,6 +816,7 @@
Resevwa avètisman lè anrejistreman yon konferans pare.Ilimite
+
Kesyon%d kesyon
@@ -811,16 +827,17 @@
%s pwen%s pwen
+
Limit Tan%1$s Nouvo AvètismanjèmLike Antrejèm
-
%s like%s likes
+
WoujWozLavand
@@ -879,6 +896,7 @@
Modifye TabloSeleksyone ki kou ou ta renmen wè nan Tablo aModifye lis kou w la
+
TabloAnvanPaj %d sou %d
@@ -906,8 +924,8 @@
Tape pou afiche anons.AksepteRefize
-
sou
+
Pa gen fichye ki asosye ak kou sa a.Pa gen fichye ki asosye ak gwoup sa a.
@@ -968,6 +986,7 @@
Nou pa an mezi pou nou jwenn yon app ekstèn pou afiche zouti LTI sa a.Kòmantè Soumèt
+
Premye PajModifye PajDeskripsyon
@@ -980,10 +999,10 @@
Konferans yo pa posib sou aparèy mobil.Fichye apèsi imajGen yon erè ki fèt pandan esè chajman PDF sa a.
-
Nou byen regrèt sa! Yo pa otorize fonksyon sa a nan afichaj elèv.Pa Gen Anyen pou Wè La aFonksyon ki pa Valide
+
Ajoute ElèvAntre kòd kouplaj elèv yo ba ou a.
@@ -991,6 +1010,7 @@
Asosyasyon Echwe... asire w ke kòd kouplaj ou a kòrèke li pa depase limit itilizasyon an.FiniEnkonplè
+
Paramèt PiblikasyonPiblikasyon Nòt
@@ -1021,10 +1041,10 @@
Nòt aprè piblikasyonRanplasman nòtNòt aktyèl
-
Ouvri nan Canvas StudentAfichaj Elèv
+
Antèt chwazi: %sKò chwazi: %s
@@ -1078,7 +1098,6 @@
Ajoute yon kòmantè videyoAjoute yon kòmantè odyo
-
Gen yon erè ki fèt sanzatann pandan y ap eseye anrejistre yon odyo.Gen yon erè ki fèt sanzatann pandan y ap eseye anrejistre yon videyo.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s Minit%s Minit
@@ -1117,6 +1137,7 @@
Detay KonferansAnrejistremanKonferans ap fèt
+
Evalyasyon kritè %s%s, %splis enfòmasyon
@@ -1143,6 +1164,7 @@
Elimine tout nan tabloAjoute nan tabloAjoute tout nan tablo
+
KontSal kou
@@ -1151,28 +1173,50 @@
ResousByenvini, %1$s!Sijè Pa m
-
Afiche Ansyen AnonsEchèk chajman Sal akèyEchèk pou rafrechi Sal akèy
-
-
Pa gen Anyen ki gen Delè pou Jodi a%1$s delè jodi a%1$s mankeBienveni!Matyè ou yo afiche la a.Pou kounye a ou pa gen matyè.
-
-
+ App la bezwen redemare
+ Seleksyone
+ Seleksyone Peryòd Klasman
+ Peryòd Klasman Aktyèl
+ Pa reyisi chaje nòt yo
+ Enposib pou chaje nòt yo pou peryòd notasyon an
+ Echèk aktyalizasyon nòt yo
+ Okenn nòt pou afiche
+ Pa Klase
+ Chanje peryòd klasman
+ Nòt pa disponib
+ Sal kou
+
+ Apèsi Sal la
+ Lyen Enpòtan
+ Aplikasyon Elèv
+ Kontakte Anplwaye
+ Chwazi yon Kou
+ Lyen Enpòtan
+ Pwofesè
+ Asistan Pwofesè
+ Resous ou yo ap afiche la a.
+ Enposib pou chaje resous yo
+ Enposib pou aktyalize resous yoOuvri yon lòt afichaj ki pi aksesib
+
DestinatèSijèSeleksyone yon kou, %s
+
Repons elèv sa a kache paske devwa sa a anonim.Nòt Elèv (Pa sipòte)Nòt Elèv yo pa disponib sou mobil.Tip Soumisyon Pa SipòteSoumisyon an pa kapab afiche paske Nòt Elèv yo pa disponib sou mobil.
+ Ou te make li konplè.
diff --git a/libs/pandares/src/main/res/values-is/strings.xml b/libs/pandares/src/main/res/values-is/strings.xml
index 57fc00d033..00a0d241d5 100644
--- a/libs/pandares/src/main/res/values-is/strings.xml
+++ b/libs/pandares/src/main/res/values-is/strings.xml
@@ -75,8 +75,8 @@
Sýna bæta við skrá valkostiTaka upp hljóðTaka upp myndband
-
Ritill fyrir textaskil
+
Skrifa…Eitthvað fór úrskeiðis við upphleðslu skila. Skila aftur.
@@ -100,10 +100,10 @@
Þetta verkefni leyfir ekki netskilÞetta verkefni leyfir ekki netskilEngin netskil
-
Þetta verkefni er með tengil á ytra verkfæri fyrir skil.Opna verkfæriSkilatexti
+
Af %s stigumAf %s st
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s af %2$s stigumFæra inn spáða einkunn
-
Lágt: %sMeðal: %sHátt: %s
+
Sérsniðin einkunnÞað eru engin matsviðmið fyrir þetta verkefni
@@ -199,7 +199,6 @@
Engin verkefni í þessum hópiÞetta verkefni er undanskilið og verður ekki með í heildarútreikningumDæmi
-
Raða eftirTímiTegund
@@ -211,6 +210,7 @@
Hnappur til að raða verkefnum, raða eftir gerðVerkefnum raðað eftir tímaVerkefnum raðað eftir gerð
+
Punktar\u0020Vista
@@ -218,9 +218,9 @@
Svör eru bara sýnileg þeim sem hafa birt minnst eitt svar.
-
Þessi skrá er læst eins og erRitill fyrir umræðusvör
+
ByrjarLýkur
@@ -448,6 +448,7 @@
Hætta að vera notandiAuðkenni notandaVilla kom upp við að reyna að vera notandi
+
Auðkenni getur ekki verið auttFarðu í próf
@@ -604,6 +605,8 @@
ÆfingaprófKannanir með einkunnKannanir
+
+
VerkefniEinkunnir
@@ -649,12 +652,12 @@
UppsetningarstillingarKjörstillingar reikningsPIN-númer og fingrafar
-
Para við áhorfandaLáttu foreldra þína skanna þennan QR-kóða úr Canvas foreldra appinu til að para við þig. Þessi kóði rennur út á sjö dögum, eða eftir eina notkun.Pörunarkóði:\u0020Pörunarkóða villaEkki tókst að endurheimta pörunarkóða. Þessi eiginleiki er bara studdur fyrir nemendur.
+
Opna Speedgraderverkefni verkefnalisti
@@ -725,8 +728,8 @@
SpeedGraderMælir
-
Einkunnasleði
+
Stofna nýjan viðburðSlökkva á pöndunum
@@ -813,6 +816,7 @@
Fá tilkynningu þegar ráðstefnuupptaka er tilbúin.Ótakmarkað
+
Spurning%d spurning
@@ -823,6 +827,7 @@
%s punktur%s punktar
+
Tímamörk%1$s nýjar tilkynningarlíka við
@@ -994,10 +999,10 @@
Ráðstefnur eru ekki enn studdar á farsíma.Forskoðun skráar myndVilla kom upp við að sækja þetta PDF.
-
Því miður! Þessi eiginleiki er ekki leyfður fyrir nemendur.Hér er ekkert að sjáÓstuddur eiginleiki
+
Bæta við nemandaSettu inn pörunarkóða nemanda sem þú fékkst.
@@ -1036,10 +1041,10 @@
Gefðu einkunn eftir birtinguHnekking einkunnarNúverandi einkunn
-
Opnast í Canvas StudentNemandasýn
+
Valinn haus: %sValið meginmál: %s
@@ -1111,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s Mínúta%s mínútur
@@ -1131,6 +1137,7 @@
Upplýsingar um ráðstefnurUpptökurRáðstefna í gangi
+
Viðmiðunarmat %s%s, %sfrekari upplýsingar
@@ -1157,6 +1164,7 @@
Fjarlægja allt af stjórnborðiBæta við stjórnborðBæta öllu við stjórnborð
+
ReikningurNámssalur
@@ -1165,28 +1173,50 @@
HjálparefniVelkomin/n, %1$s!Námsefnið mitt
-
Skoða fyrri tilkynningarMistókst að hlaða inn SkólastofuMistókst að endurglæða Skólastofu
-
-
Ekkert á skilum í dag%1$s á skilum í dag%1$s vantarVelkomin(n)!Efni þitt sést hér.Þú hefur engin efni eins og er.
-
-
+ Krefst endurræsingar á forritinu
+ Velja
+ Velja einkunnatímabil
+ Núverandi einkunnatímabil
+ Ekki tókst að sækja einkunnir
+ Ekki tókst að sækja einkunnir fyrir einkunnatímabil
+ Mistókst að endurglæða einkunnir
+ Engar einkunnir til að sýna
+ Ekki metið
+ Breyta einkunnatímabili
+ Einkunnir ekki í boði
+ Námssalur
+
+ Yfirlit yfir námssal
+ Mikilvægir tenglar
+ Forrit nemenda
+ Samskiptaupplýsingar starfsfólks
+ Velja námskeið
+ Mikilvægir tenglar
+ Kennari
+ Aðstoðarkennari
+ Auðlindir þínar sjást hér.
+ Mistókst að hlaða auðlindum
+ Mistókst að endurglæða auðlindirOpnið annað auðveldara útlit
+
ViðtakendurEfniVeldu námskeið, %s
+
Svör nemanda eru falin því þetta verkefni er nafnlaust.Athugasemd nemanda (Enginn stuðningur)Ekki er stuðningur við athugasemd nemanda á farsímum.Gerð skila ekki studdEkki hægt að birta skil því ekki er stuðningur við athugasemd nemanda á farsímum.
+ Þú hefur merkt þetta sem lokið.
diff --git a/libs/pandares/src/main/res/values-it/strings.xml b/libs/pandares/src/main/res/values-it/strings.xml
index 08ecab918c..b61e257b2c 100644
--- a/libs/pandares/src/main/res/values-it/strings.xml
+++ b/libs/pandares/src/main/res/values-it/strings.xml
@@ -75,8 +75,8 @@
Mostra opzioni Aggiungi fileRegistra audioRegistra video
-
Editor Consegna testo
+
Scrivi…Si è verificato un errore durante il caricamento della consegna. Invia di nuovo.
@@ -100,10 +100,10 @@
Questo compito non consente consegne onlineQuesto compito non consente consegne onlineNessuna consegna online
-
Questo compito include link a uno strumento esterno per le consegne.Apri strumentoTesto invio
+
Su %s puntisu %s pt
@@ -112,10 +112,10 @@
%1$s/%2$s%1$s su %2$s puntiInserisci punteggio What-If
-
Basso: %sMedio: %sAlto: %s
+
Punteggio personalizzatoNon c’è alcuna rubrica per questo compito
@@ -199,7 +199,6 @@
Nessun compito in questo gruppoQuesto compito è giustificato e non verrà considerato nel calcolo totaleES
-
Ordina perOraDigita
@@ -211,6 +210,7 @@
Pulsante Ordina compiti, ordina per tipoCompiti ordinati per tempoCompiti ordinati per tipo
+
Punti\u0020Salva
@@ -219,8 +219,8 @@
Le risposte sono visibili solo a chi ha pubblicato almeno una risposta.Questo file è attualmente bloccato
-
Editor risposta discussione
+
IniziaTermina
@@ -448,6 +448,7 @@
Smetti di agire come utenteID utenteSi è verificato un errore durante il tentativo di agire come utente
+
L’ID non può essere lasciato vuotoVai al quiz
@@ -604,6 +605,8 @@
Quiz di esercitazioneSondaggi con valutazioneSondaggi
+
+
Elenco attivitàVoti
@@ -649,12 +652,12 @@
Impostazioni profiloPreferenze accountPIN e impronta digitale
-
Accoppia con osservatoreIl tuo elemento principale deve eseguire la scansione di questo codice QR dall’app Canvas Parent per accoppiarlo con te. Questo codice scadrà tra sette giorni o dopo un utilizzo.Accoppiamento codice:\u0020Errore codice accoppiamentoImpossibile recuperare un codice accoppiamento. Questa funzione è supportata solo per gli studenti.
+
Apri SpeedGraderelenco attività attività attività attività
@@ -725,8 +728,8 @@
SpeedGraderGauge
-
Cursore valutazione
+
Crea nuovo eventoDisattiva i panda
@@ -813,6 +816,7 @@
Ricevi notifiche quando è pronta una registrazione della conferenza.Senza limiti
+
Domanda%d domanda
@@ -823,6 +827,7 @@
%s punto%s punti
+
Limite di tempo%1$s nuove notifichemi piace
@@ -994,10 +999,10 @@
Le conferenze non sono ancora supportate sul dispositivo mobile.Immagine anteprima fileSi è verificato un errore durante il tentativo di caricare questo PDF.
-
Spiacenti! Questa funzione non è consentita nella visualizzazione studenti.Non c’è niente da vedere quiFunzione non supportata
+
Aggiungi studenteInserisci il codice accoppiamento studente fornito.
@@ -1036,10 +1041,10 @@
Voto dopo pubblicazioneSostituzione votoVoto attuale
-
Si apre in Canvas StudentStudent View
+
Testa scelta: %sCorpo scelto: %s
@@ -1111,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minuto%s minuti
@@ -1131,6 +1137,7 @@
Dettagli conferenzaRegistrazioniConferenza in corso
+
Valutazione basata su criterio %s%s, %smaggiori informazioni
@@ -1157,6 +1164,7 @@
Rimuovi tutto dalla dashboardAggiungi alla dashboardAggiungi tutto alla dashboard
+
AccountHomeroom
@@ -1165,28 +1173,50 @@
RisorseBenvenuto %1$s!Le mie materie
-
Visualizza annunci precedentiImpossibile caricare homeroomImpossibile aggiornare homeroom
-
-
Niente in scadenza oggi%1$s in scadenza oggi%1$s mancanteBenvenuto!Le materie sono visualizzate qui.Attualmente non hai materie.
-
-
+ Richiede il riavvio dell’app
+ Seleziona
+ Seleziona Periodo di valutazione
+ Periodo di valutazione attuale
+ Impossibile caricare voti
+ Impossibile caricare voti per il periodo di valutazione
+ Impossibile aggiornare i voti
+ Nessun voto da visualizzare
+ Non valutato
+ Cambia periodo di valutazione
+ Voti non disponibili
+ Homeroom
+
+ Visualizzazione homeroom
+ Link importanti
+ Applicazioni studente
+ Informazioni di contatto del personale
+ Scegli un corso
+ Link importanti
+ Insegnante
+ Assistente del docente
+ Le tue risorse vengono visualizzate qui
+ Impossibile caricare le risorse
+ Impossibile aggiornare le risorsaApri una vista alternativa più accessibile
+
DestinatariOggettoSeleziona un corso, %s
+
Le risposte di questo studente sono nascoste perché questo compito è anonimo.Annotazione studente (non supportata)L’annotazione studente non è attualmente supportata sui dispositivi mobili.Tipo di consegna non supportatoImpossibile visualizzare consegna perché l’annotazione studente non è attualmente supportata sui dispositivi mobili.
+ È stato contrassegnato come fatto.
diff --git a/libs/pandares/src/main/res/values-ja/strings.xml b/libs/pandares/src/main/res/values-ja/strings.xml
index 83f8442f63..bd1bebd5de 100644
--- a/libs/pandares/src/main/res/values-ja/strings.xml
+++ b/libs/pandares/src/main/res/values-ja/strings.xml
@@ -54,10 +54,8 @@
企図が使用されました残り試行回数なし課題を再提出する
-
外部ツールを起動中外部ツールを起動する
-
プレビュー1つ以上のファイルのアップロードに失敗しました。インターネット接続をチェックしてから、提出を再試行してください。%1$s / %2$s
@@ -68,19 +66,20 @@
提出を削除しました欠如採点済み
+
URL を \'http://\' でプレビューすることはできません有効なURLを入力してください提出するにはこちらに URL を入力してください追加ファイルオプションを表示するオーディオを録音する
-
動画を録画する
-
テキスト提出エディタ
+
… を書く提出物のアップロードで不具合が発生しました。もう一度提出します。
+
提出提出バージョン
@@ -103,6 +102,7 @@
この課題は、提出用の外部ツールにリンクします。オープンツールテキストを提出する
+
/%s 点/%s 点
@@ -114,10 +114,12 @@
低:%s中程度:%s高:%s
+
カスタムスコアこの課題に説明はありません長い説明を表示
+
%1$d / %2$d のアップロード%s へのコメントのアップロード
@@ -137,6 +139,7 @@
メディアファイルオーディオビデオ
+
バージョン:
@@ -186,6 +189,8 @@
%1$s の %2$s が期限ampm
+
+
ロックされています課題
@@ -204,15 +209,17 @@
課題の並べ替えボタン、タイプごとに並べ替え時間ごとに並べ替えされた課題タイプごとに並べ替えされた課題
+
ポイント\u0020保存キャンセル
+
回答は、少なくとも一つの回答を掲載している人にのみ表示されます。
-
このファイルは現在ロックされていますディスカッション返信エディタ
+
開始終了
@@ -231,8 +238,6 @@
仮定のスコア%1$s/%2$s (%3$s)%1$s / %2$s 点、%3$s
-
-
受信トレイ未読アーカイブ済み
@@ -393,8 +398,8 @@
受講生オブザーバーシラバスが追加されていません。
-
モジュールの読込時にエラーが発生しました。
+
Canvas受信者(複数可)を選択このメッセージには現在、受信者はいません。
@@ -420,6 +425,7 @@
削除されました
+
Canvas コンテンツ…を読み込み中UnknownDevice
@@ -436,9 +442,11 @@
ユーザーとして行動することを止めるユーザーIDユーザーとして行動しようとした際にエラーが発生しました
+
IDは空欄にはできませんクイズへ移動
+
クイズ最後のポスト
@@ -497,8 +505,8 @@
モジュールに移動完了にするモジュールアイテムが見つかりません
-
モジュールの読込時にエラーが発生しました。
+
ヘルプ講師の質問リンク
@@ -591,6 +599,8 @@
練習クイズ採点されたアンケートサーベイ
+
+
タスク成績
@@ -636,12 +646,12 @@
プロファイル設定アカウントの設定PIN と指紋
-
オブザーバーとペアにするあなたとペアリングするために、親にCanvas ParentアプリからこのQRコードをスキャンしてもらいます。このコードは7日間後または1回使用した後に有効期限が切れます。ペアリングコード:\u0020ペアリングコードエラーペアリングコードを取得できません。この機能は受講生にのみサポートされています。
+
SpeedGraderを開くするべきこと するべきこと するべきことリスト
@@ -712,8 +722,8 @@
SpeedGraderGauge
-
成績スライダー
+
新しいイベントを作成パンダをオフにする
@@ -800,6 +810,7 @@
会議録画の準備ができたときに通知を受け取ります。無制限
+
質問%dの質問
@@ -808,6 +819,7 @@
%s 点
+
時間制限%1$s 新規通知いいね
@@ -976,10 +988,10 @@
会議はまだモバイルではサポートされていません。ファイルプレビュー画像このPDFを削除しようとしてエラーが発生しました。
-
申し訳ありません!この機能は受講生のビューでは許可されていません。ここには何もありませんサポートされていない機能
+
受講生を追加提供されている受講生ペアリングコードを入力します。
@@ -1016,10 +1028,10 @@
投稿後の成績成績上書き現在の成績
-
Canvas受講生内で開く受講生ビュー
+
選択した頭部:%s選択したボディ:%s
@@ -1091,6 +1103,7 @@
%s.%s %s%s %s, %s
+
%s 分
@@ -1110,6 +1123,7 @@
会議の詳細録画会議進行中
+
基準の評価 %s%s、%s詳細はこちら
@@ -1136,6 +1150,7 @@
ダッシュボードすべてから削除ダッシュボードに追加ダッシュボードにすべてを追加
+
アカウントホームルーム
@@ -1144,28 +1159,50 @@
リソースようこそ、%1$sさん!マイ教科
-
前の通知を表示するホームルーム読み込みに失敗ホームルーム更新に失敗
-
-
今日が期限のものなし%1$s 今日が期限%1$s 欠如どういたしまして!あなたの科目はここに表示されます。現在、科目はありません。
-
-
+ アプリの再起動が必要
+ 選択
+ 採点期間を選択
+ 現在の採点期間
+ 成績読み込みに失敗
+ 採点期間中に成績をロードできませんでした
+ 成績更新に失敗
+ 表示する成績なし
+ 未採点
+ 採点期間を変更
+ 利用可能な成績なし
+ ホームルーム
+
+ ホームルームビュー
+ 重要なリンク
+ 生徒アプリケーション
+ スタッフの連絡先情報
+ コースを選択する
+ 重要なリンク
+ 講師
+ 講師の助手
+ あなたリソースはここに表示されます。
+ リソース読み込みに失敗
+ リソース更新に失敗よりアクセスしやすい代替ビューを開く
+
受信者件名コースを選択する、%s
+
この課題は匿名であるため、この学生の応答は表示されません。学生の注釈(サポートされていません)現在、学生の注釈はモバイルではサポートされていません。サポートされていない提出タイプ現在、学生の注釈はモバイルでサポートされていないため、提出物を表示できません。
+ 「終了」としてマークしました。
diff --git a/libs/pandares/src/main/res/values-mi/strings.xml b/libs/pandares/src/main/res/values-mi/strings.xml
index 7ad657b5c5..3a4412e013 100644
--- a/libs/pandares/src/main/res/values-mi/strings.xml
+++ b/libs/pandares/src/main/res/values-mi/strings.xml
@@ -1176,24 +1176,47 @@
Titiro ngā Pānuitanga o muriI rahua te uta i te KaingarūmaI rahua ki te whakahou te Kaingaruma
-
Kaore e Tika ana mō tēnei rā%1$s e tika ana mo tēnei rā%1$s e ngaro anaNau Mai!Ka kitea ō marau i konei.Kaore koe e whiwhi marau i tēnei wā.
-
-
+ E hiahia taupanga timata
+ Tīpakohia
+ Tīpakohia te Kōeke Wā
+ Hanga Kōeke Wā
+ I rāhua te uta i ngā kōeke
+ I rāhua te uta i ngā kōeke mo te wā o te kōeke
+ I rāhua te whakahou i ngā kōeke
+ Kaore he kōeke hei whakaatu
+ Kāore i kōekehia
+ Huri ana i ngā wā kōeke
+ Kāore ngā kōeke i te wātea
+ Kainga rūma
+
+ Kainga rūma Tiro
+ Ngā Hono Mea Nui
+ Ākonga Tono
+ Kaimahi Whakapā Pūrongo
+ Kōwhiri he Akoranga
+ Ngā Hono Mea Nui
+ Kaiako
+ Kaiako Kaiāwhina
+ Ka kitea ō rauemi i konei.
+ I rahua te uta i ngā rauemi
+ I rāhua te whakahou i ngā rauemiHuaki he wāhanga whakauru ngāwari mo tētahi tiro atuNgā KaiwhiwhiKaupapaTīpako he akoranga, %s
+
Kei te hunahia ngā whakautu a tēnei ākonga no te mea he ingoamuna tēnei whakataunga.Ākonga Tuhipoka (Kaore e tautokotia)Ākonga Tuhipoka kaore i te tautokotia i tēnei wā i runga i te waea haerere.Kaore i Tautokotia tēnei Momo TapaetangaKaore e taea te whakaatu atu i te tāpaetanga no te mea Ākonga Tuhipoka kaore i te tautokotia i tēnei wā i runga i te waea haerere.
+ Kua tohua e koe kua oti.
diff --git a/libs/pandares/src/main/res/values-nb/strings.xml b/libs/pandares/src/main/res/values-nb/strings.xml
index 70215f1cd2..8a4a5831a1 100644
--- a/libs/pandares/src/main/res/values-nb/strings.xml
+++ b/libs/pandares/src/main/res/values-nb/strings.xml
@@ -55,10 +55,8 @@
Forsøk bruktIngen forsøk igjenSend oppgaven inn på nytt
-
Starter eksternt verktøy…Åpne eksternt verktøy
-
ForhåndsvisningEn eller flere filer kunne ikke lastes opp. Sjekk internettforbindelsen din og prøv å lever på nytt.%1$s av %2$s
@@ -69,19 +67,20 @@
Innlevering slettetManglerKaraktersatt
+
Det er ingen forhåndsvisnings-URL ved bruk av \'http://\'Skriv inn gyldig URLSkriv inn en URL her for innleveringen dinVis Legg til fil alternativerLydopptak
-
Ta opp video
-
Redigeringsprogram for tekstinnlevering
+
Skriv…Noe gikk galt ved opplasting av innlevering. Lever på nytt.
+
InnleveringInnlevering versjoner
@@ -104,6 +103,7 @@
Denne oppgaven er lenket til et eksternt verktøy for innleveringer.Åpne verktøyInnleveringstekst
+
Av %s poengAv %s poeng
@@ -115,10 +115,12 @@
Lav: %sGjennomsnitt: %sHøy: %s
+
Egendefinert resultatDet er ingen vurderingsveiledning for denne oppgavenVis lang beskrivelse
+
Laster opp fil %1$d av %2$dLast opp kommentar for %s
@@ -138,6 +140,7 @@
MediefilLydVideo
+
Versjon:
@@ -187,6 +190,8 @@
Frist %1$s klokken %2$sampm
+
+
LåstOppgaver
@@ -205,15 +210,17 @@
Sorter oppgaver-knapp, sorter etter typeOppgaver sortert etter tidOppgaver sortert etter type
+
Poeng\u0020LagreAvbryt
+
Svarene er kun synlige for dem som har postet minst ett svar.
-
Denne filen er for tiden låstRedigeringsprogram for diskusjonssvar
+
StarterSlutter
@@ -232,8 +239,6 @@
Hva-om poengsum%1$s/%2$s (%3$s)%1$s av %2$s poeng, %3$s
-
-
InnboksUlestArkivert
@@ -399,8 +404,8 @@
StudenterObservatørerEt sammendrag har ikke blitt lagt til.
-
Det var et avvik ved lasting av modulene dine.
+
CanvasVelg mottaker(e)Denne meldingen har foreløpig ingen mottakere.
@@ -426,6 +431,7 @@
Slettet
+
Laster Canvas-innhold…UnknownDevice
@@ -442,9 +448,11 @@
Slutt å opptre som brukerBruker-IDDet oppsto en feil da du prøvde å opptre som bruker
+
ID kan ikke være tomGå til quiz
+
TesterSiste innlegg
@@ -503,8 +511,8 @@
Gå til modulerMerk ferdigModulen ble ikke funnet
-
Det var et avvik ved lasting av modulene dine.
+
HjelpInstruktør-spørsmålLenke
@@ -520,11 +528,11 @@
Innlegging av tekstURL på nettInnleveringen tillater kun én filopplasting
-
-
Legg inn nettside-innleggMedieinnspillingerSend inn
+
+
Ulagret fremdriftAll ulagret informasjon vil bli tapt. Ønsker du å fortsette?
@@ -539,6 +547,7 @@
NesteLegg til en kommentar…
+
HjemVarslinger
@@ -568,6 +577,8 @@
Det ble tatt et øyeblikksbilde av nettstedet når du leverte det inn. Tapp og hold på bilde nedenfor for å åpne eller laste ned hele bildet.Denne innleveringen var en URL til en ekstern side. Husk at denne siden kan ha endret seg siden innleveringen opprinnelig skjedde.Forhåndsvisning av den vedlagte url
+
+
%1$s støttes ikke.Lenken støttes ikke.Åpne i nettleser
@@ -583,15 +594,19 @@
Panda-fakta: FjernGrunnleggere
+
EULAPersonvernreglerBruksvilkårCanvas på GitHub
+
OppgavetesterØvelsestesterKaraktergivende spørreundersøkelserSpørreundersøkelser
+
+
Å gjøreKarakterer
@@ -637,12 +652,12 @@
ProfilinnstillingerKontoinnstillingerPIN og fingeravtrykk
-
Par med observatørFå en forelder til å skanne denne QR-koden fra Canvas foreldreapplikasjonen for å pare med deg. Denne koden vil utløpe om sju dager, eller etter en gangs bruk.Paringskode:\u0020Feil med paringskodeKunne ikke hente en paringskode. Denne funksjonen støttes kun for studenter.
+
Åpne Speedgradertodo to do todos todo list
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Karakterglidebryter
+
Opprett ny hendelseSkru av pandaene
@@ -801,6 +816,7 @@
Bli varslet når et konferanseopptak er klart.Ubegrenset
+
Spørsmål%d spørsmål
@@ -811,16 +827,17 @@
%s poeng%s poeng
+
Tidsbegrensning%1$s Nye varslerlikerAngi likerklikkliker
-
%s liker%s likerklikk
+
RødVarmrosaLavendel
@@ -879,6 +896,7 @@
Rediger dashbordVelg hvilke emner du vil se på dashbordetRediger emnelisten
+
InstrumentbordForrigeSide %d av %d
@@ -906,8 +924,8 @@
Trykk for å vise kunngjøringGodtaAvslå
-
ut av
+
Det finnes ingen filer assosiert med dette kurset.Det finnes ingen filer assosiert med denne gruppa.
@@ -968,6 +986,7 @@
Vi finner ikke et eksternt program for å vise dette LTI-verktøyet.Last opp kommentar
+
ForsideEndre sideBeskrivelse
@@ -980,10 +999,10 @@
Konferanser støttes ikke på mobil.Forhåndsvisning av filenEn feil oppsto under innlastingen av denne PDF-en.
-
Beklager! Denne funksjonen er ikke tillatt i studentvisning.Ingenting å se herIkke støttet funksjon
+
Legg til studentOppgi studentparingskoden som ble gitt til deg.
@@ -991,6 +1010,7 @@
Paring mislyktes. Pass på at paringskoden er korrekt og innenfor tidsbegrensningen for bruk.GodkjentIkke godkjent
+
PubliseringsinnstillingerPublisert karakterer
@@ -1021,10 +1041,10 @@
Karakter etter publiseringOverskriv karakterGjeldende karakter
-
Åpnes i Canvas StudentStudentvisning
+
Valgt hode: %sValgt kropp: %s
@@ -1078,7 +1098,6 @@
Legg til en videokommentarLegg til en lydkommentar
-
En uventet feil oppsto ved lydopptak.En uventet feil oppsto ved videoopptak.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minutt%s minutter
@@ -1117,6 +1137,7 @@
Konferanse informasjonOpptakKonferanse pågår
+
Kriterievurdering %s%s, %smer informasjon
@@ -1143,6 +1164,7 @@
Fjern alle fra dashbordLegg til dashbordLegg til alle til dashbord
+
KontoHjem
@@ -1151,28 +1173,50 @@
RessurserVelkommen, %1$s!Mine emner
-
Vis tidligere kunngjøringerKunne ikke laste inn HomeroomKunne ikke oppdatere Homeroom
-
-
Ingenting forfaller i dag%1$s forfaller i dag%1$s manglerVelkommen!Emnene dine vises her.Du har for øyeblikket ingen emner.
-
-
+ Krever omstart av appen
+ Velg
+ Velg vurderingsperiode
+ Gjeldende vurderingsperiode
+ Kunne ikke laste karakterer
+ Kunne ikke laste inn karakterer for vurderingsperioden
+ Kunne ikke oppdatere karakterer
+ Ingen karakterer å vise
+ Ikke karaktersatt
+ Endre vurderingsperiode
+ Karakterer er ikke tilgjengelig
+ Hjem
+
+ Vis hjemmerom
+ Viktige lenker
+ Studentsøknader
+ Kontaktinformasjon personale
+ Velg et emne
+ Viktige lenker
+ Lærer
+ Lærerassistent
+ Ressusrene dine vises her.
+ Kunne ikke laste opp ressurser
+ Kunne ikke oppdatere ressurserÅpne en mer tilgjengelig alternativ visning
+
MottakereEmnetittelVelg et emne, %s
+
Denne studentens svar er skjult fordi denne oppgaven er anonym.Studentmerknad (ikke støttet)Studentmerknader støttes for øyeblikket ikke på mobil.Ustøttet innleveringstypeInnlevering kan ikke vises fordi Studentmerknader for øyeblikket ikke støttes på mobil.
+ Du har merket det som fullført.
diff --git a/libs/pandares/src/main/res/values-nl/strings.xml b/libs/pandares/src/main/res/values-nl/strings.xml
index 2a4f2e6ab5..f666c72ca7 100644
--- a/libs/pandares/src/main/res/values-nl/strings.xml
+++ b/libs/pandares/src/main/res/values-nl/strings.xml
@@ -55,10 +55,8 @@
Gebruikte pogingenGeen resterende pogingenOpdracht opnieuw inleveren
-
Externe tool starten...Extern hulpmiddel lanceren
-
VoorbeeldEr kunnen een of meer bestanden niet worden geüpload. Controleer je internetverbinding en probeer opnieuw in te leveren.%1$s van %2$s
@@ -69,19 +67,20 @@
Inlevering verwijderdOntbrekendBeoordeeld
+
Geen voorbeeld beschikbaar voor URL\'s die \'http://\' gebruikenVoer een geldige URL inVoer hier een URL in voor uw inzendingOpties weergeven voor bestand toevoegenAudio opnemen
-
Video opnemen
-
Editor voor tekstinlevering
+
Schrijven…Er is iets mis gegaan bij het uploaden van de opdracht. Opnieuw inleveren.
+
InzendingInzendingsversies
@@ -104,6 +103,7 @@
Deze opdracht heeft een link naar een externe tool voor inleveringen.Tool openenInleveringstekst
+
Zonder %s puntenZonder %s punten
@@ -115,10 +115,12 @@
Laag: %sGemiddelde: %sHoog: %s
+
Aangepaste scoreEr is geen rubriek voor deze opdrachtUitgebreide beschrijving bekijken
+
Bestand %1$d van %2$d wordt geüploadOpmerking uploaden voor %s
@@ -138,6 +140,7 @@
MediabestandAudioVideo
+
Versie:
@@ -187,6 +190,8 @@
In te leveren op %1$s om %2$ss ochtendss middags
+
+
VergrendeldOpdrachten
@@ -205,15 +210,17 @@
Knop voor opdrachten sorteren, sorteren op typeOpdrachten gesorteerd op tijdOpdrachten gesorteerd op type
+
Punten\u0020OpslaanAnnuleren
+
Antwoorden zijn alleen zichtbaar voor degenen die minimaal één antwoord hebben geplaatst.
-
Dit bestand is momenteel vergrendeldEditor voor discussieantwoorden
+
Begint opEindigt
@@ -232,8 +239,6 @@
What-If score%1$s/%2$s (%3$s)%1$s van de %2$s punten, %3$s
-
-
InboxOngelezenGearchiveerd
@@ -399,8 +404,8 @@
CursistenWaarnemersEr is geen syllabus toegevoegd.
-
Er is een fout opgetreden bij het laden van uw modules.
+
CanvasOntvanger(s) kiezenDit bericht heeft momenteel geen ontvangers.
@@ -426,6 +431,7 @@
Verwijderd
+
Canvas Content aan het uploaden…UnknownDevice
@@ -442,9 +448,11 @@
Stoppen met optreden als gebruikerGebruikers-ID:Er heeft zich een fout voorgedaan bij optreden als gebruiker
+
Het ID mag niet leeg zijnGa naar toets
+
ToetsenLaatste bericht
@@ -503,8 +511,8 @@
Ga naar modulenGereed markerenModule-item niet gevonden
-
Er is een fout opgetreden bij het laden van uw modules.
+
HelpInstructeurvraagLink
@@ -520,11 +528,11 @@
TekstinvoerOnline URLJe kunt niet meer dan één bestand uploaden
-
-
Ingang voor website toevoegenMedia opnamesInleveren
+
+
Onbeveiligde voortgangNiet-opgeslagen informatie gaat verloren. Wil je doorgaan?
@@ -539,6 +547,7 @@
VolgendeEen opmerking toevoegen…
+
StartpaginaMeldingen
@@ -568,6 +577,8 @@
Er is een snapshot van de website gemaakt toen je het inleverde. Tik en houd het onderstaande beeld vast om het volledige beeld te openen of downloaden.Deze inzending was een URL naar een externe pagina. Houd in gedachten dat deze pagina gewijzigd kan zijn na de oorspronkelijke inzending.Een voorbeeld van de ingediende url
+
+
%1$s worden niet ondersteund.De link wordt niet ondersteund.Openen in browser
@@ -583,15 +594,19 @@
Panda feit: VerwijderenStichters
+
EULAPrivacybeleidGebruiksvoorwaardenCanvas op GitHub
+
Toetsen met opdrachtenPracticumtoetsenBeoordeelde onderzoekenEnquêtes
+
+
To-do lijstCijfers
@@ -637,12 +652,12 @@
ProfielinstellingenAccountvoorkeurenPIN en vingerafdruk
-
Koppelen met waarnemerLaat je ouder deze QR-code scannen van de Canvas Parent-app om met jou te koppelen. Deze code vervalt over zeven dagen of na één keer gebruiken.Koppelingscode:\u0020KoppelingscodefoutKan koppelingscode niet ophalen. Deze functie wordt alleen ondersteund voor cursisten.
+
Speedgrader openento do to do to do to do lijst
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Cijferslider
+
Een nieuwe gebeurtenis makenPanda\'s uitzetten
@@ -801,6 +816,7 @@
Ontvang een melding wanneer er een opname van een vergadering klaar is.Onbeperkt
+
Vraag%d vraag
@@ -811,16 +827,17 @@
%s punt%s punten
+
Tijdslimiet%1$s Nieuwe meldingenlikeInvoeren van likeslikes
-
%s like%s likes
+
RoodFelrozeLavendel
@@ -879,6 +896,7 @@
Dashboard bewerkenSelecteer de cursussen die je in het Dashboard wilt weergevenBewerk je lijst met cursussen
+
DashboardVorigePagina %d van %d
@@ -906,8 +924,8 @@
Tik om aankondiging weer te gevenAccepterenWeigeren
-
van
+
Er zijn geen bestanden gekoppeld aan deze cursus.Er zijn geen bestanden gekoppeld aan deze groep.
@@ -968,6 +986,7 @@
We kunnen geen externe app vinden om deze LTI-tool weer te geven.Opmerking uploaden
+
VoorpaginaPagina bewerkenBeschrijving
@@ -980,10 +999,10 @@
Vergaderingen worden nog niet ondersteund op mobiele apparatenó.Voorbeeldweergave bestandEr is een fout opgetreden bij het laden van deze PDF.
-
Sorry! Deze functie is niet toegestaan in de cursistweergave.Niets te zien hierNiet ondersteunde functie
+
Cursist toevoegenVoer de koppelingscode cursist in die je gekregen hebt.
@@ -991,6 +1010,7 @@
Koppelen mislukt. Zorg ervoor dat je koppelingscode juist is en dat deze binnen de ingestelde tijdslimiet valt.VoltooidIncompleet
+
Instellingen voor postenCijfers posten
@@ -1021,10 +1041,10 @@
Beoordelen na postenCijferoverschrijvingHuidig cijfer
-
Opent in Canvas-cursistWeergave voor cursisten
+
Gekozen kop: %sGekozen hoofdtekst: %s
@@ -1078,7 +1098,6 @@
Videocommentaar toevoegenAudiocommentaar toevoegen
-
Er heeft zich een onverwachte fout voorgedaan bij het opnemen van audio.Er heeft zich een onverwachte fout voorgedaan bij het opnemen van video.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minuut%s minuten
@@ -1117,6 +1137,7 @@
VergaderingdetailsOpnamenVergadering is bezig
+
Criteriabeoordeling %s%s, %smeer informatie
@@ -1143,6 +1164,7 @@
Alles verwijderen uit DashboardToevoegen aan DashboardAlles toevoegen aan Dashboard
+
AccountKlas
@@ -1151,28 +1173,50 @@
HulpbronnenWelkom, %1$s!Mijn onderwerpen
-
Vorige aankondigingen bekijkenLaden van klas misluktVerversen van klas mislukt
-
-
Vandaag niets in te leveren%1$s vandaag in te leveren%1$s ontbreektWelkom!Je vakken worden hier getoond.Je hebt momenteel geen vakken.
-
-
+ Vereist herstarten van app
+ Selecteren
+ Beoordelingsperiode selecteren
+ Huidige beoordelingsperiode
+ Laden van cijfers mislukt
+ Kan cijfers voor beoordelingsperiode niet laden
+ Verversen van cijfers mislukt
+ Geen cijfers om weer te geven
+ Niet beoordeeld
+ Beoordelingsperiode wijzigen
+ Cijfers niet beschikbaar
+ Klas
+
+ Huisklasweergave
+ Belangrijke links
+ Cursistapplicaties
+ Contactgegevens van medewerkers
+ Een cursus kiezen
+ Belangrijke links
+ Docent
+ Onderwijsassistent
+ Je bronnen worden hier getoond.
+ Laden van bronnen mislukt
+ Verversen van bronnen misluktOpen een meer toegankelijke alternatieve weergave
+
GeadresseerdenVakSelecteer een cursus, %s
+
De antwoorden van deze cursist zijn verborgen omdat deze opdracht anoniem is.Cursistaantekening (niet ondersteund)Cursistaantekening wordt momenteel niet ondersteund op mobiel apparaat.Niet-ondersteund inleveringstypeInlevering kan niet worden weergegeven omdat cursistaantekening momenteel niet wordt ondersteund op mobiel apparaat.
+ Je hebt het als klaar gemarkeerd.
diff --git a/libs/pandares/src/main/res/values-pl/strings.xml b/libs/pandares/src/main/res/values-pl/strings.xml
index cb5857dd20..a0874855a2 100644
--- a/libs/pandares/src/main/res/values-pl/strings.xml
+++ b/libs/pandares/src/main/res/values-pl/strings.xml
@@ -77,8 +77,8 @@
Pokaż opcje dodawania plikówNagraj dźwiękNagraj dźwięk
-
Edytor zgłoszeń tekstowych
+
Napisz…Wystąpił problem z przesyłaniem. Prześlij ponownie.
@@ -102,10 +102,10 @@
To zadanie nie umożliwia przesyłania onlineTo zadanie nie umożliwia przesyłania onlineBrak przesyłek online
-
To zadanie odnosi się do zewnętrznego narzędzia do przesyłania.Otwórz narzędzieTekst przesyłki
+
Z %s pktZ %s pkt
@@ -114,10 +114,10 @@
%1$s/%2$s%1$s z %2$s pktWprowadź wynik dla a-co-jeśli
-
Niski: %sŚredni: %sWysoki: %s
+
Niestandardowy wynik punktowyNie ma rubryki dla tego zadania
@@ -201,7 +201,6 @@
Brak zadań w danej grupieTo zadanie zostało usprawiedliwione i nie zostanie ujęte w ostatecznym rozliczeniuEX
-
Sortuj wgCzasTyp
@@ -213,6 +212,7 @@
Przycisk sortowania zadań, sortuj wg typuZadania posortowane wg czasuZadania posortowane wg typu
+
Punkty\u0020Zapisz
@@ -220,9 +220,9 @@
Odpowiedzi są widoczne tylko dla tych osób, które opublikowały co najmniej jedną odpowiedź.
-
Aktualnie plik jest zablokowanyEdytor odpowiedzi w dyskusji
+
Rozpoczyna sięKończy się
@@ -460,6 +460,7 @@
Przestań działać jako użytkownikID użytkownikaWystąpił błąd podczas działania jako użytkownik
+
Pole identyfikatora nie może być pustePrzejdź do testu
@@ -616,6 +617,8 @@
Testy próbneOcenione ankietyAnkiety
+
+
Lista zadańOceny
@@ -661,12 +664,12 @@
Ustawienia profiluPreferencje kontaKod PIN i linie papilarne
-
Paruj z obserwującymPoproś rodzica o zeskanowanie tego kodu QR w aplikacji Canvas Parent w celu sparowania. Ten kod wygaśnie za siedem dni lub po jednym jego wykorzystaniu.Kod parowania:\u0020Błąd kodu parowaniaNie udało się pobrać kodu parowania. Funkcji tej mogą używać tylko uczestnicy.
+
Otwórz Speedgraderdo zrobienia do zrobienia lista do zrobienia
@@ -737,8 +740,8 @@
SpeedGraderGauge
-
Suwak stopni
+
Utwórz nowe wydarzenieWyłącz pandy
@@ -825,6 +828,7 @@
Powiadamiaj mnie o gotowości do nagrywania konferencji.Nieograniczony
+
Pytanie%d pytanie
@@ -839,6 +843,7 @@
%s punkty%s punkty
+
Limit czasuNowe powiadomienia: %1$slubię to
@@ -1016,10 +1021,10 @@
Konferencje nie mają jeszcze wsparcia dla urządzeń przenośnych.Obraz podglądu plikuPodczas wczytywania tego pliku PDF wystąpił błąd.
-
Przepraszamy! Funkcja ta nie jest dozwolona w widoku uczestnika.Nic tu nie maNieobsługiwana funkcja
+
Dodaj uczniaWpisz otrzymany kod parowania uczestnika.
@@ -1062,10 +1067,10 @@
Ocena po opublikowaniuNadpisująca ocenaBieżąca ocena
-
Otwiera się w Canvas StudentWidok uczestnika
+
Wybrana głowa: %sWybrany tułów: %s
@@ -1137,6 +1142,7 @@
%s.%s %s%s %s, %s
+
%s min%s min
@@ -1159,6 +1165,7 @@
Szczegóły konferencjiNagrańKonferencja w toku
+
Punktacja dla danego kryterium %s%s, %swięcej informacji
@@ -1185,6 +1192,7 @@
Usuń wszystko z panelu nawigacyjnegoDodaj do panelu nawigacyjnegoDodaj wszystko do panelu nawigacyjnego
+
KontoPokój główny
@@ -1193,28 +1201,50 @@
ZasobyWitamy, %1$s!Moje tematy
-
Wyświetl poprzednie ogłoszeniaNie udało się załadować pokoju głównegoNie udało się odświeżyć pokoju głównego
-
-
Brak elementów z dzisiejszym terminem%1$s z terminem dzisiajBrak %1$sWitamy!Twoje tematy pojawią się tutaj.Nie masz obecnie żadnych tematów.
-
-
+ Wymaga ponownego uruchomienia aplikacji
+ Wybierz
+ Wybierz okres oceniania
+ Bieżący okres oceniania
+ Nie udało się załadować ocen
+ Nie udało się wczytać ocen dla okresu oceniania
+ Nie udało się odświeżyć ocen
+ Brak ocen do wyświetlenia
+ Nie oceniono
+ Zmień okres oceniania
+ Brak dostępnych ocen
+ Pokój główny
+
+ Widok pokoju głównego
+ Ważne łącza
+ Aplikacje uczestników
+ Dane kontaktowe pracowników
+ Wybierz kurs
+ Ważne łącza
+ Nauczyciel
+ Asystent instruktora
+ Twoje zasoby pojawią się tutaj.
+ Nie udało się załadować zasobów
+ Nie udało się odświeżyć zasobówOtwórz alternatywny widok z ułatwieniami dostępu
+
OdbiorcyTematWybierz kurs, %s
+
Odpowiedzi tego uczestnika są ukryte, ponieważ to zadanie jest anonimowe.Adnotacja uczestnika (nieobsługiwana)Adnotacja uczestnika nie jest obecnie obsługiwana na urządzeniach przenośnych.Nieobsługiwany typ zgłoszeniaPrzesyłki nie można wyświetlić, ponieważ Adnotacja uczestnika nie jest obecnie obsługiwana na urządzeniach przenośnych.
+ Oznaczono je jako gotowe.
diff --git a/libs/pandares/src/main/res/values-pt-rBR/strings.xml b/libs/pandares/src/main/res/values-pt-rBR/strings.xml
index 602a525053..36a8ab08f0 100644
--- a/libs/pandares/src/main/res/values-pt-rBR/strings.xml
+++ b/libs/pandares/src/main/res/values-pt-rBR/strings.xml
@@ -75,8 +75,8 @@
Exibir Adicionar opções de arquivoGravar ÁudioGravar Vídeo
-
Editor de envio de texto
+
Gravar…Algo deu errado com o carregamento do envio. Envie novamente.
@@ -100,10 +100,10 @@
Esta tarefa não permite envios on-lineEsta tarefa não permite envios on-lineNenhum envio on-line
-
Esta tarefa é vinculada a uma ferramenta interna para envios.Abrir ferramentaTexto de submissão
+
De %s pontosDe %s pts
@@ -111,11 +111,11 @@
Nota final: %s%1$s/%2$s%1$s de %2$s pontos
-
Inserir pontuação E-SeBaixa: %sMédia: %sAlta: %s
+
Pontuação personalizadaNão há nenhuma rubrica para essa tarefa
@@ -199,7 +199,6 @@
Sem tarefas neste grupoEsta tarefa está dispensada e não fará parte do cálculo totalEX
-
Ordenar porTempoTipo
@@ -211,6 +210,7 @@
Botão Classificar tarefas, classificar por tipoTarefas classificadas por tempoTarefas classificadas por tipo
+
Pontos\u0020Salvar
@@ -218,9 +218,9 @@
As respostas são visíveis apenas àqueles que postaram pelo menos uma resposta.
-
Este arquivo está travado no momentoEditor de resposta da discussão
+
ComeçaTermina
@@ -448,6 +448,7 @@
Parar de agir como usuárioID de UsuárioOcorreu um erro ao tentar agir como usuário
+
O ID não pode ser brancoIr para teste
@@ -598,11 +599,14 @@
Política de privacidadeTermos de UsoCanvas no GitHub
+
Testes da tarefaTestes de práticaPesquisas avaliadasPesquisas
+
+
Lista de TarefasNotas
@@ -648,12 +652,12 @@
Configurações do perfilPreferências da contaPIN e Impressão digital
-
Emparelhar com observadorFaça o seu pai escanear este código QR a partir do app Canvas Parent para emparelhar com você. Este código expirará em sete dias ou após um uso.Código de emparelhamento:\u0020Erro do código de emparelhamentoIncapaz de recuperar um código de emparelhamento. Este recurso é suportado apenas para alunos.
+
Abrir Speedgraderafazer a fazer afazeres lista de afazeres
@@ -724,8 +728,8 @@
SpeedGraderGauge
-
Controle deslizante de nota
+
Criar novo eventoDesligar os pandas
@@ -812,6 +816,7 @@
Ser notificado quando uma gravação de conferência estiver pronta.Sem limite
+
Pergunta%d pergunta
@@ -822,16 +827,17 @@
%s ponto%s pontos
+
Limite de tempo%1$s Novas notificaçõescurtirEntrada de curtidascurte
-
%s curtida%s curtidas
+
VermelhoRosa quenteLavanda
@@ -993,10 +999,10 @@
Conferências ainda não são suportadas em dispositivos móveis.Imagem de pré-visualização do arquivoOcorreu um erro ao tentar carregar este PDF.
-
Desculpe-nos! Este recurso não é permitido na visualização do aluno.Nada para ver aquiRecurso não suportado
+
Adicionar EstudanteInsira o código de emparelhamento do aluno fornecido.
@@ -1035,10 +1041,10 @@
Nota após postarSubstituição da notaNota atual
-
Abrir no Canvas StudentVisualização do aluno
+
Cabeça escolhida: %sCorpo escolhido: %s
@@ -1131,6 +1137,7 @@
Detalhes da conferênciaGravaçõesConferência em andamento
+
Avaliação de critério %s%s, %smais informações
@@ -1157,6 +1164,7 @@
Remover todos do painelAdicionar ao painelAdicionar todos ao painel
+
ContaSala de aula
@@ -1165,28 +1173,50 @@
RecursosBem-vindo, %1$s!Minhas disciplinas
-
Exibir anúncios anterioresFalha em carregar o HomeroomFalha em atualizar o Homeroom
-
-
Nada com prazo vencendo hoje%1$s vence hoje%1$s ausenteBem-vindo(a)!Suas disciplinas aparecem aqui.Você atualmente não tem disciplinas.
-
-
+ Precisa reiniciar o aplicativo
+ Selecionar
+ Selecionar período de avaliação
+ Período de pontuação atual
+ Falha em carregar notas
+ Falha ao carregar notas para o período de avaliação
+ Falha ao atualizar notas
+ Sem notas para mostrar
+ Sem nota
+ Alterar período de classificação
+ Notas não disponíveis
+ Sala de aula
+
+ Visualização da sala de aula
+ Links importantes
+ Inscrições de alunos
+ Informações de contato da equipe
+ Escolher um curso
+ Links importantes
+ Professor
+ Professor assistente
+ Seus recursos aparecem aqui.
+ Falha ao carregar recursos
+ Falha ao atualizar recursosAbra uma exibição alternativa mais acessível
+
RecipientesAssuntoSelecionar um curso, %s
+
As respostas deste aluno estão ocultas porque esta tarefa é anônima.Anotação do aluno (não compatível)Atualmente, a anotação do aluno não é compatível com dispositivos móveis.Tipo de envio não suportadoO envio não pode ser exibido, porque a Anotação do Aluno não é compatível com dispositivos móveis.
+ Você marcou como concluído.
diff --git a/libs/pandares/src/main/res/values-pt-rPT/strings.xml b/libs/pandares/src/main/res/values-pt-rPT/strings.xml
index 12666bd656..4374fab19e 100644
--- a/libs/pandares/src/main/res/values-pt-rPT/strings.xml
+++ b/libs/pandares/src/main/res/values-pt-rPT/strings.xml
@@ -55,10 +55,8 @@
Tentativas utilizadasNão há mais tentativasVoltar a submeter tarefa
-
Lançar Ferramenta Externa...Lançar Ferramenta Externa
-
VisualizaçãoUm ou mais arquivos não foram carregados. Verifique sua conexão com a Internet e tente enviar novamente.%1$s de %2$s
@@ -69,19 +67,20 @@
Submissão eliminadaEm faltaClassificado
+
Nenhuma visualização disponível para URLs usando \'http://\'Por favor, insira um URL válidoInsira um URL aqui para o seu envioMostrar opções de adicionar ficheiroGravar Áudio
-
Gravar Vídeo
-
Editor de Submissão de Texto
+
Escrever…Algo deu errado no carregamento do envio. Envie novamente.
+
SubmissãoVersões de envio
@@ -104,6 +103,7 @@
Esta atribuição liga a uma ferramenta externa para submissões.Abrir ferramentaTexto de submissão
+
Fora de %s pontosFora de %s pts
@@ -115,10 +115,12 @@
Baixa: %sMédia: %sAlta: %s
+
Personalizar pontuaçãoNão existe rubrica para esta atribuiçãoVer descrição longa
+
A carregar ficheiro %1$d de %2$dA carregar comentário para %s
@@ -138,6 +140,7 @@
Ficheiro de mídiaÁudioVídeo
+
Versão:
@@ -187,6 +190,8 @@
Termina %1$s a %2$sampm
+
+
BloqueadoTarefas
@@ -205,15 +210,17 @@
Classificar botão de tarefas, classificar por tipoTarefas classificadas por tempoTarefas classificadas por tipo
+
Pontos\u0020GuardarCancelar
+
As respostas apenas estão visíveis para aqueles que publicaram pelo menos uma resposta.
-
Este ficheiro está bloqueado de momentoEditor da Resposta de Discussão
+
IniciaTermina
@@ -232,8 +239,6 @@
Pontuação Potencial%1$s/%2$s (%3$s)%1$s de %2$s pontos, %3$s
-
-
Caixa de entradaNão lidaArquivada
@@ -399,8 +404,8 @@
AlunosObservadoresUm plano de estudos não foi adicionado.
-
Houve um erro ao carregar seus módulos.
+
CanvasEscolher Destinatário(s)Esta mensagem atualmente não tem destinatários.
@@ -426,6 +431,7 @@
Eliminado
+
A carregar o conteúdo da tela…UnknownDevice
@@ -442,9 +448,11 @@
Parar de agir como utilizadorID do utilizadorHouve um erro ao tentar agir como utilizador
+
A ID não pode estar vaziaIr Para Questionário
+
TestesÚltima Publicação
@@ -503,8 +511,8 @@
Ir Para MódulosMarcar terminadoItem do Módulo não encontrado
-
Houve um erro ao carregar seus módulos.
+
AjudaQuestões do FormadorLigação
@@ -520,11 +528,11 @@
Entrada de textoURL On-lineEste envio apenas aceita o carregamento de um ficheiro
-
-
Adicionar entrada de siteGravações de multimédiaSubmeter
+
+
Progresso não guardadoAs informações não guardadas serão perdidas. Deseja continuar?
@@ -539,6 +547,7 @@
PróximoAdicionar um comentário…
+
InícioNotificações
@@ -568,6 +577,8 @@
Foi tirada uma imagem deste site quando fez a entrega. Insira e mantenha a imagem abaixo para abrir ou descarregar a imagem completa.Este envio era um URL de uma página externa. Tenha em mente que esta página pode ter sido alterada desde o envio original.Uma previsualização do url entregue
+
+
%1$s não são suportados.A ligação não é suportada.Abrir no Navegador
@@ -583,15 +594,19 @@
Fat. Panda: EliminarFundadores
+
EULAPolítica de PrivacidadeTermos de usoCanvas no GitHub
+
Testes da tarefaTestes práticosInquéritos ClassificadosInquéritos
+
+
A FazerClassificações
@@ -637,12 +652,12 @@
Configurações de perfilPreferências da contaPIN e impressão digital
-
Par com o observadorPeça ao seu pai para digitalizar este código QR da aplicação Canvas Parent para parelhar com você. Este código expirará em sete dias, ou após uma utilização.Código de pareamento:\u0020Erro de Código de pareamentoIncapaz de recuperar um código de pareamento. Esta funcionalidade só é suportada por estudantes.
+
Abrir Speedgraderafazer a fazer afazer lista a fazer
@@ -713,8 +728,8 @@
SpeedGraderGauge
-
Seletor de classificação
+
Criar Novo EventoDesligar os pandas
@@ -801,6 +816,7 @@
Receba notificações quando uma gravação de conferência estiver pronta.Ilimitado
+
Pergunta%d pergunta
@@ -811,16 +827,17 @@
%s ponto%s pontos
+
Limite de tempo%1$s Novas NotificaçõesgostarEntrada gostosgostos
-
%s gosto%s gostos
+
EncarnadoRosaLavanda
@@ -879,6 +896,7 @@
Editar painelSelecionar quais disciplinas você gostaria de ver no PainelEditar sua lista de disciplinas
+
Painel de controloAnteriorPágina %d de %d
@@ -906,8 +924,8 @@
Toque para ver o anúncio.AceitarRecusar
-
de
+
Não há ficheiros associados a esta disciplina.Não há ficheiros associados a este grupo.
@@ -968,6 +986,7 @@
Não conseguimos encontrar uma aplicação externa para visualizar essa ferramenta LTI.Carregamento de comentários
+
Primeira páginaEditar PáginaDescrição
@@ -980,10 +999,10 @@
Conferências ainda não são suportadas no telemóvel.Imagem de pré-visualização de ficheiroOcorreu um erro ao tentar carregar este PDF.
-
Lamento imenso! Esta funcionalidade não é permitida na visão dos alunos.Nada Para Ver AquiCaracterística não suportada
+
Adicionar alunoInsira o código de pareamento de alunos fornecido a você.
@@ -991,6 +1010,7 @@
O emparelhamento falhou. Certifique-se de que seu código de pareamento esteja correto e dentro do limite de tempo de uso.CompletoIncompleto
+
Publicar configuraçõesPublicar Classificações
@@ -1021,10 +1041,10 @@
Classificação após publicaçãoSubstituir classificação Classificação Atual
-
Abrir no Aluno CanvasVista de aluno
+
Cabeça escolhida: %sCorpo escolhido: %s
@@ -1078,7 +1098,6 @@
Adicionar comentário de vídeoAdicionar comentário de vídeo
-
Ocorreu um erro inesperado ao tentar gravar áudio.Ocorreu um erro inesperado ao tentar gravar vídeo.
@@ -1097,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minuto%s minutos
@@ -1117,6 +1137,7 @@
Detalhes da ConferênciaGravaçõesConferência em curso
+
Critério de classificação %s%s, %smais informações
@@ -1143,6 +1164,7 @@
Retirar tudo do painelAdicionar ao painelAdicionar tudo ao painel
+
ContaCasa de família
@@ -1151,28 +1173,50 @@
RecursosBem-vindo, %1$s!Minhas Disciplinas
-
Ver Anúncios AnterioresFalha no carregamento da sala de estarFalha ao atualizar a sala de estar
-
-
Nada Devido Hoje%1$s devido hoje%1$s em faltaBem-vindo!Os seus assuntos aparecem aqui.Atualmente, não tem assuntos.
-
-
+ Requer reinicialização da aplicação
+ Selecionar
+ Selecionar Período de classificação
+ Período de classificação atual
+ Falha ao carregar classificações
+ Falha no carregamento das classificações para o período de classificação
+ Falha em atualizar classificações
+ Sem classificações a exibir
+ Sem classificação
+ Período de classificação das alterações
+ Classificações não disponíveis
+ Casa de família
+
+ Exibição da sala de aula
+ Ligações importantes
+ Aplicações para alunos
+ Informações de contato do pessoal
+ Escolha uma Disciplina
+ Ligações importantes
+ Professor
+ Assistente de ensino
+ Os seus recursos aparecem aqui.
+ Falha no carregamento de recursos
+ Falha na actualização de recursosAbrir uma vista alternativa mais acessível
+
DestinatáriosAssuntoSeleccione uma disciplina, %s
+
As respostas deste aluno são ocultas porque esta tarefa é anónima.Anotação do aluno (Sem suporte)A anotação do aluno não é actualmente suportada em telemóvel.Tipo de apresentação não suportadaA submissão não pode ser exibida, porque a Anotação do aluno não é atualmente suportada em telemóvel.
+ Marcou-o como feito.
diff --git a/libs/pandares/src/main/res/values-ru/strings.xml b/libs/pandares/src/main/res/values-ru/strings.xml
index 98e53c022c..c41f6aa27a 100644
--- a/libs/pandares/src/main/res/values-ru/strings.xml
+++ b/libs/pandares/src/main/res/values-ru/strings.xml
@@ -77,8 +77,8 @@
Показать параметры добавления файлаЗаписать аудиоЗаписать видео
-
Редактор текстовой отправки
+
Запись…Произошла ошибка при загрузке предоставляемых данных. Отправьте еще раз.
@@ -102,10 +102,10 @@
Это задание не допускает отправку данных онлайнЭто задание не допускает отправку данных онлайнОтправка данных онлайн невозможна
-
Это задание содержит ссылку на внешний инструмент для отправки.Открыть инструментТекст для отправки
+
Из %s балловИз %s баллов
@@ -114,10 +114,10 @@
%1$s/%2$s%1$s из %2$s балловВвести оценку «что, если»
-
Низкий: %sСредний: %sВысокий: %s
+
Персонализированная оценкаНет раздела для этого задания
@@ -201,7 +201,6 @@
Нет Заданий в этой группеДанное задание не обязательно и не будет учитываться в общем подсчетеEX
-
Сортировать поВремяУкажите
@@ -213,6 +212,7 @@
Кнопка «Сортировать задания», сортировка по типуЗадания, отсортированные по времениНазначения, отсортированные по типу
+
Баллы\u0020Сохранить
@@ -220,9 +220,9 @@
Ответы видны только тем, кто опубликовал не менее одного ответа.
-
Файл в настоящее время заблокированРедактор ответов в обсуждении
+
НачинаетсяЗаканчивается
@@ -460,6 +460,7 @@
Перестать действовать как пользовательИдентификатор пользователяПроизошла ошибка при попытке действовать как пользователь
+
ID не может быть пустымПерейти к тестовому заданию
@@ -616,6 +617,8 @@
Практические тестыОцениваемые опросыОпросы
+
+
ЗадачиОценки
@@ -661,12 +664,12 @@
Настройки профиляПараметры учетной записиPIN-код и отпечаток пальца
-
Сопряжение с наблюдателемОтсканировал ли ваш родитель этот QR-код из приложения Canvas Parent для сопряжения с вами. Срок действия этого кода истекает через семь дней или после однократного использования.Код сопряжения:\u0020Ошибка кода сопряженияНевозможно получить код сопряжения. Эта функция поддерживается только для студентов.
+
Открыть Speedgraderдля выполнения выполнить список заданий список задач
@@ -737,8 +740,8 @@
SpeedGraderGauge
-
Ползунок оценивания
+
Создать новое событиеВыключить панд
@@ -825,6 +828,7 @@
Получайте уведомления о готовности к записи конференции.Неограниченно
+
ВопросВопрос %d
@@ -839,18 +843,19 @@
%s баллов%s баллов
+
Ограничение по времениНовые уведомления %1$sнравятсяДобавление лайканравится
-
%s лайк%s лайка(-ов)%s лайка(-ов)%s лайка(-ов)
+
КрасныйЯрко-розовыйЛавандовый
@@ -1016,10 +1021,10 @@
Конференции еще не поддерживаются на мобильных устройствах.Изображение для предварительного просмотраОшибка при попытке скачать этот PDF.
-
Очень жаль! Эта функция недоступна в представлении для студентов.Здесь ничего нет для просмотраНеподдерживаемая функция
+
Добавить студентаВведите полученный код привязки студента.
@@ -1062,10 +1067,10 @@
Оценка после публикацииИгнорировать оценкуТекущая оценка
-
Открыть в Canvas StudentПросмотр студентом
+
Выбранная голова: %sВыбранное тело: %s
@@ -1137,6 +1142,7 @@
%s.%s %s%s %s, %s
+
%s минута%s минут
@@ -1159,6 +1165,7 @@
Данные веб-конференцииЗаписиИдет конференция
+
Оценка критерия %s%s, %sдополнительная информация
@@ -1185,6 +1192,7 @@
Удалить все информационной панелиДобавить на информационную панельДобавить все на информационную панель
+
Учетная запись«Домашняя комната»
@@ -1193,28 +1201,50 @@
РесурсыДобро пожаловать, %1$s!Мои темы
-
Просмотреть предыдущие объявленияНе удалось загрузить «Домашнюю комнату»Не удалось обновить «Домашнюю комнату»
-
-
Сегодня ничего сдавать не нужноСрок сдачи %1$s — сегодня%1$s отсутствуетДобро пожаловать!Ваши темы будут отображаться здесь.В настоящее время у вас нет тем.
-
-
+ Требуется перезапуск приложения
+ Выбрать
+ Выбрать период оценивания
+ Текущий период оценки
+ Не удалось загрузить оценки
+ Не удалось загрузить оценки для академического периода
+ Не удалось обновить оценки
+ Нет оценок для отображения
+ Оценка не выставлена
+ Изменить академический период
+ Оценки недоступны
+ «Домашняя комната»
+
+ Представление домашней комнаты
+ Важные ссылки
+ Заявки учащихся
+ Контактная информация персонала
+ Выбрать курс
+ Важные ссылки
+ Преподаватель
+ Ассистент преподавателя
+ Ваши ресурсы будут отображаться здесь.
+ Не удалось загрузить ресурсы
+ Не удалось обновить ресурсыОткройте более доступный альтернативный вид
+
ПолучателиТемаВыбрать курс, %s
+
Ответы студентов скрыты, поскольку это задание анонимно.Аннотация учащегося (не поддерживается)В настоящее время функция Аннотация учащегося не поддерживается на мобильных устройствах.Неподдерживаемый тип отправкиНевозможно отобразить отправку, так как Аннотация учащегося в настоящее время не поддерживается на мобильном устройстве.
+ Вы отметили это как выполненное.
diff --git a/libs/pandares/src/main/res/values-sl/strings.xml b/libs/pandares/src/main/res/values-sl/strings.xml
index f928968b51..b809060cb3 100644
--- a/libs/pandares/src/main/res/values-sl/strings.xml
+++ b/libs/pandares/src/main/res/values-sl/strings.xml
@@ -15,7 +15,6 @@
~
-->
-
Ni najdenih datotek.Poišči datotekeVnesite iskalno besedo s tremi ali več znaki.
@@ -33,6 +32,7 @@
Izbrano: %s
+
Podrobnosti o nalogiVrste oddaj
@@ -75,8 +75,8 @@
Prikaži možnosti za Dodaj datotekoPosnemi zvokPosnemi videoposnetek
-
Urejevalnik oddaje besedila
+
Zapis…Prišlo je do težav pri nalaganju oddaje. Ponovno pošlji.
@@ -1046,6 +1046,7 @@
+
Izbrana glava: %sIzbrano telo: %sIzbrane noge: %s
@@ -1164,6 +1165,7 @@
Odstrani vse z nadzorne ploščeDodaj na nadzorno ploščoDodaj vse na nadzorno ploščo
+
RačunMatični
@@ -1172,28 +1174,50 @@
ViriDobrodošli, %1$s!Moji kratki opisi predmetov
-
Prikaži predhodna obvestilaNalaganje matičnega razreda ni uspeloOsveževanje matičnega razreda ni uspelo
-
-
Danes nič ne poteče%1$s poteče danes%1$s manjkaDobrodošli.Vaši predmeti se prikažejo tukaj.Trenutno nimate premetov.
-
+ Zahteva ponovni zagon aplikacije
+ Izberi
+ Izberi ocenjevalno obdobje
+ Trenutno ocenjevalno obdobje
+ Nalaganje ocen ni uspelo.
+ Ocen za obdobje ocenjevanja ni bilo mogoče naložiti
+ Osveževanje ocen ni uspelo
+ Ni ocen za prikaz
+ Ni ocenjeno
+ Sprememba ocenjevalnega obdobja
+ Ocene niso na voljo
+ Matični razred
+ Pogled matičnega razreda
+ Pomembne povezave
+ Vloge študentov
+ Kontaktni podatki osebja
+ Izberite predmet
+ Pomembne povezave
+ Izvajalec
+ Demonstrator
+ Tukaj se prikažejo vaši viri.
+ Nalaganje virov ni uspelo
+ Osveževanje virov ni uspeloOdpri dostopnejši alternativni pogled
+
PrejemnikiZadevaIzberite predmet, %s
+
Odgovori tega študenta so skriti, ker je ta naloga anonimna.Opomba študenta (ni podprto)Opomba študenta v mobilnem prikazu trenutno ni podprta.Nepodprta vrsta oddajeOddaje ni mogoče prikazati, ker funkcija Opomba študenta v mobilnem prikazu trenutno ni podprta.
+ To ste označili kot končano.
diff --git a/libs/pandares/src/main/res/values-sv/strings.xml b/libs/pandares/src/main/res/values-sv/strings.xml
index b106d33c22..5d11231ec2 100644
--- a/libs/pandares/src/main/res/values-sv/strings.xml
+++ b/libs/pandares/src/main/res/values-sv/strings.xml
@@ -58,7 +58,6 @@
Startar externt verktyg…Starta externt verktygFörhandsgranska
-
En eller fler filer laddades inte upp. Kontrollera din internetanslutning och försök lämna in igen.%1$s av %2$sInlämningen slutfördes!
@@ -68,20 +67,20 @@
Inlämningen borttagenSaknasHar bedömts
+
Ingen förhandsgranskning är tillgänglig för URL:er som använder \'http://\'Ange en giltig URLAnge en URL här för din inlämningVisa alternativ för Lägg till filSpela in ljud
-
Spela in video
-
Textinlämningsredigerare
+
Skriv…
-
Något gick fel vid uppladdning av inlämningen. Lämna in igen.
+
InskickningInskickningsversioner
@@ -98,25 +97,25 @@
Inlämning inte tillåtenFörhandsgranskning av angiven URLWebbplatsens URL
-
Den här uppgiften tillåter inte onlineinlämningarDen här uppgiften tillåter inte onlineinlämningarInga onlineinlämningarDen här uppgiften länkar till ett externt verktyg för inlämningar.Öppna verktygInlämningstext
+
Av %s poängAv %s poängUrsäktadSlutbedömning: %s
-
%1$s/%2$s%1$s av %2$s poängAnge Vad om-resultatLåg: %sMedel: %sHög: %s
+
Anpassat resultatDet finns ingen matris för den här uppgiften
@@ -128,7 +127,7 @@
Laddar upp mediefilUppladdningen av kommentaren misslyckades för %sBifoga filer till dina kommentarer genom att välja ett alternativ nedan
- Har du frågor om din uppgift?\nSkicka ett meddelande till din instruktör.
+ Har du frågor om din uppgift?\nSkicka ett meddelande till din lärare.Detta meddelande kunde inte skickas. Tryck för att försöka igen.MedieuppladdningMediauppladdning – ljud
@@ -141,6 +140,7 @@
MediafilLjudVideo
+
Version:
@@ -210,15 +210,17 @@
Sortera uppgiftsknappar, sortera efter typUppgifter sorterade efter tidUppgifter sorterade efter typ
+
Poäng\u0020SparaAvbryt
+
Svaren är endast synliga för de som har publicerat minst ett svar.
-
Den här filen är för närvarande låstDiskussionssvarsredigerare
+
BörjarSlutar
@@ -337,7 +339,7 @@
ÖppnaÖppna med en alternativ appÖppnar fil…
- Ladda ned
+ Ladda nerMeddelandebilagorBilagaBilageikon
@@ -353,7 +355,7 @@
Välj en kurs eller gruppInga meddelandenTa bort bilaga
- Ladda ned bilaga
+ Ladda ner bilagaMeddelandealternativVidarebefordraSvara alla
@@ -429,6 +431,7 @@
Borttagen
+
Läser in Canvas-innehåll…UnknownDevice
@@ -445,9 +448,11 @@
Sluta uppträda som användareAnvändar-IDEtt fel inträffade vid försök att uppträda som användaren.
+
ID kan inte lämnas tomtGå till quiz
+
QuizSenaste inlägg
@@ -525,9 +530,9 @@
Denna inskickning accepterar endast en filuppladdningLägg till webbplatsinläggMediainspelningar
+ Skicka
- SkickaOsparat arbeteOsparad information kommer att förloras. Vill du fortsätta?
@@ -542,6 +547,7 @@
NästaLägg till en kommentar…
+
StartsidaAviseringar
@@ -571,6 +577,8 @@
En ögonblicksbild av webbplatsen togs när du lämnade in den. Tryck och håll på bilden nedan för att öppna eller ladda ner hela bilden.Den här inskickningen var en URL till en extern sida. Kom ihåg att den här sidan kan ha ändrats sedan inlämningen ursprungligen gjordes.Förhandsvisning av den skickade URL:en
+
+
%1$s stöds inte.Länken stöds inte.Öppna i webbläsare
@@ -586,15 +594,19 @@
Panda-fakta: Ta bortGrundare
+
EULAIntegritetspolicyAnvändningsvillkorCanvas på GitHub
+
UppgiftsquizÖvningsquizBedömda enkäterEnkäter
+
+
Att göraOmdömen
@@ -640,12 +652,12 @@
ProfilinställningarKontoinställningarPIN och fingeravtryck
-
Koppla med observatörBe din förälder scanna QR-koden från Canvas Parent-appen för att parkopplas med dig. Den här koden upphör att gälla om sju dagar, eller efter en användning.Parkopplingskod:\u0020ParkopplingskodfelDet gick inte att hämta en parkopplingskod. Den här funktionen stöds endast för studenter.
+
Öppna Speedgraderatt göra att göra lista
@@ -716,8 +728,8 @@
SpeedGraderMätare
-
Omdömesreglage
+
Skapa ny händelseStäng av pandorna
@@ -735,7 +747,7 @@
KursaktiviteterDiskussioner
- Konversationer
+ InkorgenSchemaläggningGrupperLarm
@@ -757,7 +769,7 @@
DiskussionsinläggLägg till i konversation
- Konversationsmeddelande
+ Meddelande i InkorgenKonversationer som skapats av digStudentens mötesregistreringar
@@ -786,24 +798,25 @@
Få aviseringar när du skapar ett anslag, och när någon svarar på ditt anslag.Få aviseringar när en uppgift/inlämning har bedömts/ändrats, och när en omdömesvikt ändrats.Få aviseringar om inbjudningar till webbkonferenser, grupper, samarbeten, kamratresponsuppgifter och påminnelser.
- Endast lärare och admin. Få aviseringar när en uppgift lämnas in för första gången eller lämnas in på nytt.
- Endast lärare och admin. Få aviseringar när en sen uppgift skickas in.
+ Endast lärare och administratörer. Få aviseringar när en uppgift lämnas in för första gången eller lämnas in på nytt.
+ Endast lärare och administratörer. Få aviseringar när en sen uppgift skickas in.Få aviseringar när en kommentar görs om din inlämning.Få aviseringar när det finns ett nytt diskussionsämne i din kurs.Få aviseringar när det finns ett nytt inlägg i en diskussion som du prenumererar på.Få aviseringar när du läggs till i en konversation.Få aviseringar när du har ett nytt meddelande i din inkorg.Få aviseringar när du skapar en ny konversation.
- Endast lärare och admin. Få aviseringar när det finns en mötesregistrering.
+ Endast lärare och administratörer. Få aviseringar när det finns en mötesregistrering.Få aviseringar när det finns en ny registrering på din kalender.Få aviseringar vid avbeställning av tid.Få aviseringar när en möteslucka blir tillgänglig.Få aviseringar om nya och uppdaterade kalenderobjekt.Endast admin, väntande registrering aktiverad. Få avisering när en gruppregistrering accepteras eller nekas.
- Endast lärare och admin. Få aviseringar om kursregistreringar, genererade rapporter, exporterat innehåll, migrationsrapporter, nya kontoanvändare, och nya studentgrupper.
+ Endast lärare och administratörer. Få aviseringar om kursregistreringar, genererade rapporter, exporterat innehåll, migrationsrapporter, nya kontoanvändare, och nya studentgrupper.Få aviseringar när en konferensinspelning är klar.Obegränsade
+
Fråga%d fråga
@@ -814,16 +827,17 @@
%s poäng%s poäng
+
Tidsgräns%1$s Nya aviseringargilla-markeringGilla postgilla-markeringar
-
%s gilla-markering%s gilla-markeringar
+
RödKlarrosaLavendel
@@ -972,6 +986,7 @@
Vi kunde inte hitta någon extern app för visning av det här LTI-verktyget.Ladda upp kommentar
+
FörstasidanRedigera sidanBeskrivning
@@ -984,21 +999,21 @@
Konferenser stöds ännu inte på den här mobilen.Förhandsvisningsbild för filEtt fel uppstod vid inläsning av den här PDF:en.
-
Tyvärr! Den här funktionen tillåts inte i studentvyn.Inget att se härFunktionen saknar stöd
+
Lägg till studentAnge kopplingskoden du fått för studenter.Kopplingskod ...Det gick inte att koppla. Se till att din kopplingskod är korrekt och att dess tidsgräns inte har överskridits.Färdig
- ofullständig
+ Inte färdigPubliceringsinställningar
- Offentliggör omdömen
+ Publicera omdömenDölj omdömen%d omdöme publicerat för närvarande
@@ -1020,7 +1035,7 @@
Alla omdömen har publicerats.Publicerade omdömenDolda omdömen
- Det gick inte att offentliggör omdömen
+ Det gick inte att publicera omdömenDet gick inte att dölja omdömenBedöm före publiceringBedöm efter publicering
@@ -1029,6 +1044,7 @@
Öppnas i Canvas StudentStudentens vy
+
Vald rubrik: %sVald brödtext: %s
@@ -1082,7 +1098,6 @@
Lägg till videokommentarLägg till ljudkommentar
-
Ett oväntat fel inträffade under ljudinspelningen.Ett oväntat fel inträffade under videoinspelningen.
@@ -1101,6 +1116,7 @@
%s.%s %s%s %s, %s
+
%s minut%s minuter
@@ -1121,6 +1137,7 @@
KonferensinformationInspelningarKonferens pågår...
+
Kriteriebedömning %s%s, %smer information
@@ -1147,6 +1164,7 @@
Ta bort alla från översiktenLägg till i översiktenLägga till alla i översikten
+
KontoFjärrundervisningsrum
@@ -1155,28 +1173,50 @@
ResurserVälkommen %1$s!Mina ämnen
-
Visa tidigare meddelandenDet gick inte att läsa in fjärrundervisningsrummet.Det gick inte att uppdatera fjärrundervisningsrummet.
-
-
Inget måste lämnas in i dag%1$s måste lämnas in i dag%1$s saknasVälkommen!Dina ämnen visas här.Du har inga ämnen för närvarande.
-
-
+ Kräver omstart av appen
+ Välj
+ Välj omdömesperiod
+ Aktuell omdömesperiod
+ Det gick inte att läsa in omdömen
+ Det gick inte att läsa in omdömen för omdömesperioden
+ Det gick inte att uppdatera omdömen
+ Det finns inga omdömen att visa
+ Ej bedömd
+ Ändra omdömesperiod
+ Omdömen är inte tillgängliga
+ Fjärrundervisningsrum
+
+ Vy för fjärrundervisningsrum
+ Viktiga länkar
+ Studentansökningar
+ Kontaktuppgifter för personal
+ Välj en kurs
+ Viktiga länkar
+ Lärare
+ Lärarassistent
+ Dina resurser visas här.
+ Det gick inte att läsa in resurser
+ Det gick inte att uppdatera resurserÖppna en mer tillgänglig alternativ vy
+
MottagareÄmneVälj en kurs, %s
+
Den här studentens svar är dolda eftersom uppgiften är anonym.Studentnotering (stöds inte)Studentnotering stöds för närvarande inte på mobila enheter.Inlämningstyp som inte stödsInlämningen kan inte visas eftersom studentnotering inte stöds på mobila enheter.
+ Du har markerat den som färdig.
diff --git a/libs/pandares/src/main/res/values-th/strings.xml b/libs/pandares/src/main/res/values-th/strings.xml
index 0076f54a19..0a2d079022 100644
--- a/libs/pandares/src/main/res/values-th/strings.xml
+++ b/libs/pandares/src/main/res/values-th/strings.xml
@@ -1182,7 +1182,30 @@
ยินดีต้อนรับ!วิชาของคุณจะปรากฏขึ้นที่นี่ปัจจุบันคุณไม่มีวิชาใด ๆ
-
+ ต้องรีสตาร์ทแอพ
+ เลือก
+ เลือกระยะเวลาการให้เกรด
+ ระยะเวลาการให้เกรดในปัจจุบัน
+ ไม่สามารถโหลดเกรดได้
+ ไม่สามารถโหลดเกรดสำหรับระยะเวลาให้เกรด
+ ไม่สามารถรีเฟรชเกรดได้
+ ไม่มีเกรดที่จะแสดง
+ ไม่ได้ลงเกรด
+ เปลี่ยนแปลงระยะเวลาการให้เกรด
+ ไม่มีเกรด
+ โฮมรูม
+
+ มุมมองโฮมรูม
+ ลิงค์ที่สำคัญ
+ แอพพลิเคชั่นของผู้เรียน
+ ข้อมูลติดต่อบุคลากร
+ เลือกบทเรียน
+ ลิงค์ที่สำคัญ
+ ผู้สอน
+ ผู้ช่วยสอน
+ ทรัพยากรของคุณจะปรากฏขึ้นที่นี่
+ ไม่สามารถโหลดทรัพยากรได้
+ ไม่สามารถรีเฟรชทรัพยากรได้เปิดมุมมองเผื่อเลือกที่สามารถเข้าถึงได้มากกว่าผู้รับ
@@ -1195,4 +1218,5 @@
หมายเหตุกำกับของผู้เรียนปัจจุบันไม่รองรับสำหรับอุปกรณ์พกพาประเภทการจัดส่งที่ไม่รองรับไม่สามารถแสดงผลงานจัดส่งได้เนื่องจากหมายเหตุกำกับของผู้เรียนปัจจุบันไม่รองรับกับอุปกรณ์พกพา
+ คุณกำกับว่าเสร็จสิ้นแล้ว
diff --git a/libs/pandares/src/main/res/values-zh-rHK/strings.xml b/libs/pandares/src/main/res/values-zh-rHK/strings.xml
index b1b313ee1a..2300ba721c 100644
--- a/libs/pandares/src/main/res/values-zh-rHK/strings.xml
+++ b/libs/pandares/src/main/res/values-zh-rHK/strings.xml
@@ -54,10 +54,8 @@
已使用的遞交嘗試次數不可再次嘗試重新提交作業
-
啟動外部工具中…啟動外部工具
-
預覽上傳一個或多個檔案失敗。檢查您的網際網路連線並重試提交。%1$s / %2$s
@@ -68,19 +66,20 @@
已刪除提交項目缺少已評分
+
沒有使用 \'http://\' 的 URL 的可用預覽請輸入有效的 URL請在此輸入您的提交項目的 URL顯示添加檔案選項錄製音訊
-
錄製視訊
-
文字提交項目編輯器
+
寫入…上傳提交項目時出現問題。重新提交。
+
提交項目提交項目版本
@@ -103,6 +102,7 @@
此作業已連結至外部工具以供提交。開啟工具提交項目文字
+
滿分為 %s 分滿分為 %s 分
@@ -114,10 +114,12 @@
低:%s平均:%s高:%s
+
自訂分數此作業沒有評分標準檢視完整說明
+
正在上傳 %1$d / %2$d 檔案正在上傳 %s 的評論
@@ -137,6 +139,7 @@
媒體檔案音訊視訊
+
版本:
@@ -186,6 +189,8 @@
截止於 %1$s 的 %2$s上午下午
+
+
已鎖定作業列表
@@ -204,15 +209,17 @@
排列作業列表按鈕,依類型排序依時間排序的作業列表依類型排序的作業列表
+
分數\u0020儲存取消
+
只有曾發送一個或以上回覆的才能查閱回覆。
-
此檔案目前已鎖定討論區回覆編輯器
+
開始結束
@@ -231,8 +238,6 @@
可能得分%1$s/%2$s (%3$s)得分為 %1$s,滿分為 %2$s,%3$s
-
-
收件匣未讀已存檔
@@ -393,8 +398,8 @@
學生觀察者尚未添加課程大綱。
-
載入單元時發生錯誤。
+
Canvas選擇收件人本訊息目前無收件人。
@@ -420,6 +425,7 @@
已刪除
+
載入 Canvas 內容…UnknownDevice
@@ -436,9 +442,11 @@
停止作為使用者使用者 ID嘗試作為使用者時出現錯誤
+
ID 不可為空白前往測驗
+
測驗最後發布
@@ -497,8 +505,8 @@
前往單元標記為已完成無法找到單元項目
-
載入單元時發生錯誤。
+
支援導師提問連結
@@ -514,11 +522,11 @@
文字條目線上 URL此提交項目只接受上載一個檔案
-
-
添加網站條目媒體錄製提交
+
+
進度未儲存未儲存的資料將丟失。您確定要繼續嗎?
@@ -533,6 +541,7 @@
下一個添加評論…
+
首頁通知
@@ -562,6 +571,8 @@
您提交時的網站截圖已儲存。點選並抓取下方圖片,或下載完整圖片。此提交項目是連結至外部頁面的 URL。請謹記,自從最初提交之後,此頁面可能已變更。預覽已提交的 url
+
+
不支援 %1$s。不支援連結。在瀏覽器中打開
@@ -577,15 +588,19 @@
關於熊貓的事實: 移除創始人
+
EULA隱私政策使用條款GitHub 上的 Canvas
+
作業測驗練習測驗已評分的調查調查
+
+
待辦事項成績
@@ -631,12 +646,12 @@
個人檔案設定帳戶偏好PIN 和指紋
-
與觀察者配對讓您的父母在 Canvas Parent 應用程式掃描此二維碼與您配對。此代碼將在七天後過期,或使用一次後過期。配對代碼:\u0020配對代碼錯誤無法取得配對代碼。此功能僅支援學生。
+
打開 Speedgrader待辦事項清單
@@ -707,8 +722,8 @@
SpeedGraderGauge
-
評分滑條
+
創建新活動關閉熊貓頭像
@@ -795,6 +810,7 @@
在會議錄製準備就緒時收到通知。無限制
+
問題%d 個問題
@@ -803,15 +819,16 @@
%s 分
+
時間限制%1$s 個新通知贊「讚好」條目贊
-
%s 讚好
+
紅色亮粉色薰衣草色
@@ -870,6 +887,7 @@
編輯控制面板選擇要在控制面板上查看哪些課程。編輯您的課程清單
+
控制面板上一個第 %d 頁,共 %d 頁
@@ -897,8 +915,8 @@
點選檢視通告接受拒絕
-
滿分為
+
沒有與此課程關聯的檔案。沒有與此群組關聯的檔案。
@@ -957,6 +975,7 @@
我們未能找到外部應用程式以檢視此 LTI 工具。上載評論
+
標題頁編輯內容頁說明
@@ -969,10 +988,10 @@
移動設備尚未支援會議。檔案預覽圖像嘗試載入此 PDF 時發生錯誤。
-
很抱歉!此功能無法於學生視圖中使用。這裡沒有內容供瀏覽不支援的功能
+
添加學生輸入已提供予您的學生配對代碼。
@@ -980,6 +999,7 @@
配對失敗。請確保您的配對代碼正確,並在使用限期內。完成未完成
+
公佈設定公佈成績
@@ -1008,10 +1028,10 @@
公佈後評分評分重寫目前評分
-
在 Canvas Student 中開啟學生視圖
+
選擇的頁首:%s選擇的正文:%s
@@ -1065,7 +1085,6 @@
添加視訊評論添加音訊評論
-
在嘗試錄製音訊時發生意外錯誤。在嘗試錄製視訊時發生意外錯誤。
@@ -1084,6 +1103,7 @@
%s。%s %s%s %s, %s
+
%s 分鐘
@@ -1103,6 +1123,7 @@
會議詳情記錄會議進行中
+
標準評級%s%s,%s更多資訊
@@ -1129,6 +1150,7 @@
全部從控制面板移除添加到控制面板中全部添加到控制面板中
+
帳戶主教室
@@ -1137,28 +1159,50 @@
資源歡迎,%1$s!我的主題
-
檢視之前的通告未能載入主教室未能重新整理主教室
-
-
今天沒有任何內容截止%1$s 今天截止%1$s 缺失歡迎!您的主題在此顯示。您目前沒有任何主題。
-
-
+ 需要重新啟動應用程式
+ 選擇
+ 選擇評分期
+ 目前評分期
+ 無法載入成績
+ 無法載入評分期的成績
+ 無法重新整理成績
+ 沒有可顯示的成績
+ 未評分
+ 變更評分期
+ 不提供成績
+ 主教室
+
+ 主教室檢視
+ 重要連結
+ 學生應用程式
+ 員工聯絡資料
+ 選擇一個課程
+ 重要連結
+ 教師
+ 助教
+ 您的資源在此顯示。
+ 無法載入資源
+ 無法重新整理資源開啟更容易存取的替代視圖
+
收件人主題選擇課程,%s
+
這名學生的回覆已隱藏,因為此作業為匿名狀態。學生註釋(不支援)流動電話目前不支援學生註釋。不支援提交項目類型提交項目無法顯示,因為流動電話目前不支援學生註釋。
+ 您已將其標示為完成。
diff --git a/libs/pandares/src/main/res/values-zh/strings.xml b/libs/pandares/src/main/res/values-zh/strings.xml
index f03b94c3d1..07c0fdf329 100644
--- a/libs/pandares/src/main/res/values-zh/strings.xml
+++ b/libs/pandares/src/main/res/values-zh/strings.xml
@@ -54,10 +54,8 @@
已尝试的次数尝试次数已用完重新提交作业
-
正在启动外部工具…启动外部工具
-
预览一份或多份文件上传失败。请检查您的网络连接,并重试提交。%1$s/%2$s
@@ -68,19 +66,20 @@
提交项已删除缺失已评分
+
使用“http://”的 URL 无可用预览请输入有效的 URL请在此处为您的提交项输入 URL显示添加文件选项录制音频
-
录制视频
-
文本提交项编辑器
+
写入…上传提交项时出错。请重新提交。
+
提交提交版本
@@ -103,6 +102,7 @@
此作业关联用于提交的外部工具。打开工具提交项文本
+
满分 %s满分 %s
@@ -114,10 +114,12 @@
低:%s中等:%s高:%s
+
自定义评分此作业没有评估表格查看长说明
+
正在上传%2$d的文件%1$d正在上传关于%s的评论
@@ -137,6 +139,7 @@
媒体文件音频视频
+
版本:
@@ -186,6 +189,8 @@
截止于 %1$s,%2$s上午下午
+
+
已锁定作业
@@ -204,15 +209,17 @@
作业排序按钮,按类型排序作业按时间排序作业按类型排序
+
得分\u0020保存取消
+
回复只对至少发布了一条回复的人可见。
-
此文件目前被锁定讨论回复编辑器
+
开始结束
@@ -231,8 +238,6 @@
假设的分数%1$s/%2$s (%3$s)得分 %1$s,总分 %2$s,%3$s
-
-
收件箱未读已归档
@@ -393,8 +398,8 @@
学生观察员未添加教学大纲。
-
加载单元时出错。
+
Canvas选择收件人(s)这个消息目前还没有收件人。
@@ -420,6 +425,7 @@
已删除
+
正在加载Canvas内容…UnknownDevice
@@ -436,9 +442,11 @@
停止以用户的身份执行操作用户ID尝试以用户的身份执行操作时出现错误
+
该ID不能为空白转到测验
+
测验上一次发布
@@ -497,8 +505,8 @@
转到单元标记为完成找不到单元项目
-
加载单元时出错。
+
帮助讲师的问题链接
@@ -514,11 +522,11 @@
文本输入在线 URL此提交项只接受上传一份文件
-
-
添加网站条目媒体录音提交
+
+
未保存的进度未保存的信息将会丢失。是否要继续?
@@ -533,6 +541,7 @@
下一步添加评论…
+
首页通知
@@ -562,6 +571,8 @@
当您打开该网站时,它就会拍摄快照。点击并按住下面的图片,打开或下载完整的图像。此提交是指向外部页面的一个 URL。请记住,此页面自最初进行提交后可能已更改。提交URL的预览
+
+
%1$s不受支持。链接不受支持。在浏览器里打开
@@ -577,15 +588,19 @@
熊猫资料: 移除创始人
+
最终用户许可协议隐私政策使用条款GitHub上的Canvas
+
作业测验练习测验评分调查调查
+
+
待办事项评分
@@ -631,12 +646,12 @@
个人资料设置帐户首选项PIN和指纹
-
与旁听者配对您的父母是否已从 Canvas Parent 应用程序扫描此二维码与您配对?该代码将在七天后或使用一次后失效。配对码:\u0020配对码错误无法获取配对码。该功能仅支持学生使用。
+
打开 Speedgrader待办事项 待办事项的待办列表
@@ -707,8 +722,8 @@
SpeedGraderGauge
-
评分滑块
+
创建新事件关闭熊猫
@@ -795,6 +810,7 @@
当会议录制准备就绪时收到通知。无限制
+
问题%d 个问题
@@ -803,15 +819,16 @@
%s 分
+
时间限制%1$s 条新通知喜欢点赞喜欢
-
%s 赞
+
红色亮粉色薰衣草
@@ -870,6 +887,7 @@
编辑控制面板选择您想在控制面板上查看哪些课程编辑您的课程列表
+
控制面板上一个第 %d 页,共 %d 页
@@ -897,8 +915,8 @@
单击以查看公告接受拒绝
-
共
+
没有与此课程关联的文件。没有与此小组关联的文件。
@@ -957,6 +975,7 @@
我们找不到查看此 LTI 工具的外部应用。评论上传
+
首页编辑页面说明
@@ -969,10 +988,10 @@
会议目前不支持移动设备。文件预览图像尝试加载此PDF时出错。
-
抱歉!该功能不允许在学生视图中使用。此处看不到任何内容不受支持的功能
+
添加学生输入提供给您的学生配对码。
@@ -980,6 +999,7 @@
配对失败。确保配对码正确,且未超过使用时间限制。完成未完成
+
发布设置发布评分
@@ -1008,10 +1028,10 @@
发布后的评分覆盖评分当前评分
-
在 Canvas 学生版中打开学生视图
+
已选头部:%s已选身体:%s
@@ -1065,7 +1085,6 @@
添加视频评论添加音频评论
-
尝试录制音频时发生意外错误。尝试录制视频时发生意外错误。
@@ -1084,6 +1103,7 @@
%s。%s %s%s %s,%s
+
%s 分钟
@@ -1103,6 +1123,7 @@
会议详情录制文件会议进行中
+
标准评分 %s%s,%s更多信息
@@ -1129,6 +1150,7 @@
全部从控制面板删除添加到控制面板全部添加到控制面板
+
帐户指导教室
@@ -1137,28 +1159,50 @@
资源欢迎您,%1$s!我的主题
-
查看以前的公告无法加载 Homeroom无法刷新 Homeroom
-
-
没有今天到期的项目%1$s个今天到期%1$s个缺失欢迎!您的科目显示在这里。您目前没有科目。
-
-
+ 要求重启 app
+ 选择
+ 选择评分周期
+ 当前评分周期
+ 加载评分失败
+ 无法加载评分周期的评分
+ 无法刷新评分
+ 没有要显示的评分
+ 未评分
+ 更改评分周期
+ 评分不可用
+ 指导教室
+
+ 教室视图
+ 重要链接
+ 学生申请
+ 员工联系信息
+ 选择课程
+ 重要链接
+ 教师
+ 助教
+ 您的资源显示在此处。
+ 无法加载资源
+ 无法刷新资源打开一个更加方便访问的备选视图
+
收件人主题选择课程 %s
+
该学生的回答已隐藏,因为该作业已匿名。学生注释(不支持)目前在移动设备上不支持学生注释。不受支持的提交项类型无法显示提交项,因为目前在移动设备上不支持学生注释。
+ 您已将其标记为“完成”。
diff --git a/libs/pandares/src/main/res/values/colors.xml b/libs/pandares/src/main/res/values/colors.xml
index edde1d655e..8a746ed3fd 100644
--- a/libs/pandares/src/main/res/values/colors.xml
+++ b/libs/pandares/src/main/res/values/colors.xml
@@ -255,6 +255,6 @@
#008EE2#2D3B45
- #C6C6C8
+ #C6C6C8
diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml
index f40b5b67eb..e18df11006 100644
--- a/libs/pandares/src/main/res/values/strings.xml
+++ b/libs/pandares/src/main/res/values/strings.xml
@@ -1183,8 +1183,29 @@
Welcome!Your subjects show up here.You currently have no subjects.
- Elementary ViewRequires app restart
+ Select
+ Select Grading Period
+ Current Grading Period
+ Failed to load grades
+ Failed to load grades for grading period
+ Failed to refresh grades
+ No grades to display
+ Not Graded
+ Change grading period
+ Grades not available
+ Homeroom
+ Homeroom View
+ Important Links
+ Student Applications
+ Staff Contact Info
+ Choose a Course
+ Important Links
+ Teacher
+ Teaching Assistant
+ Your resources show up here.
+ Failed to load resources
+ Failed to refresh resourcesOpen a more accessible alternative view
@@ -1198,4 +1219,9 @@
Student Annotation is currently not supported on mobile.Unsupported Submission TypeSubmission cannot be displayed, because Student Annotation is currently not supported on mobile.
+ You\'ve marked it as done.
+ Cannot change the due date when due in a closed grading period.
+ Jump to Today
+ %1$s, %2$s
+ send message
diff --git a/libs/pandautils/build.gradle b/libs/pandautils/build.gradle
index 3b92bd3eac..bca1702607 100644
--- a/libs/pandautils/build.gradle
+++ b/libs/pandautils/build.gradle
@@ -163,4 +163,6 @@ dependencies {
implementation Libs.VIEW_MODE_SAVED_STATE
implementation Libs.FRAGMENT_KTX
kapt Libs.LIFECYCLE_COMPILER
+
+ implementation 'com.google.android.flexbox:flexbox:3.0.0'
}
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderInterface.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderInterface.kt
new file mode 100644
index 0000000000..c3d3a9e1c6
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderInterface.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.pandautils.adapters.itemdecorations
+
+import androidx.databinding.ViewDataBinding
+import androidx.recyclerview.widget.RecyclerView
+
+interface StickyHeaderInterface {
+
+ fun getHeaderPositionForItem(itemPosition: Int): Int
+
+ fun getHeaderBinding(headerPosition: Int, parent: RecyclerView, hasChildInContact: Boolean): ViewDataBinding
+
+ fun isHeader(itemPosition: Int): Boolean
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderItemDecoration.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderItemDecoration.kt
new file mode 100644
index 0000000000..33a436811d
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/adapters/itemdecorations/StickyHeaderItemDecoration.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.pandautils.adapters.itemdecorations
+
+import android.graphics.Canvas
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+
+class StickyHeaderItemDecoration(private val stickyHeaderAdapter: StickyHeaderInterface) :
+ RecyclerView.ItemDecoration() {
+
+ private var stickyHeaderHeight = 0
+
+ private var xPos = 0
+
+ private var contactPointPair: Pair? = null
+ private var childInContact: View? = null
+
+ override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+ super.onDrawOver(c, parent, state)
+ val topChild = parent.getChildAt(0) ?: return
+ val topChildPosition = parent.getChildAdapterPosition(topChild)
+ if (topChildPosition == RecyclerView.NO_POSITION) {
+ return
+ }
+
+ val headerPos: Int = stickyHeaderAdapter.getHeaderPositionForItem(topChildPosition)
+ if (contactPointPair != null && contactPointPair?.first == headerPos) {
+ childInContact = getChildInContact(parent, contactPointPair!!.second, contactPointPair!!.first)
+ }
+
+ val currentHeader =
+ if (childInContact != null && !stickyHeaderAdapter.isHeader(parent.getChildAdapterPosition(childInContact!!))) {
+ getHeaderViewForItem(headerPos, parent, true)
+ } else {
+ getHeaderViewForItem(headerPos, parent, false)
+ }
+ fixLayoutSize(parent, currentHeader)
+
+ val contactPoint = currentHeader.bottom
+ contactPointPair = Pair(headerPos, contactPoint)
+ if (childInContact != null && stickyHeaderAdapter.isHeader(parent.getChildAdapterPosition(childInContact!!))) {
+ moveHeader(c, currentHeader, childInContact!!)
+ return
+ }
+ drawHeader(c, currentHeader)
+ }
+
+ private fun getHeaderViewForItem(headerPosition: Int, parent: RecyclerView, hasChildInContact: Boolean = false): View {
+ val binding = stickyHeaderAdapter.getHeaderBinding(headerPosition, parent, hasChildInContact)
+ binding.executePendingBindings()
+ return binding.root
+ }
+
+ private fun drawHeader(canvas: Canvas, header: View) {
+ canvas.save()
+ canvas.translate(xPos.toFloat(), 0f)
+ header.draw(canvas)
+ canvas.restore()
+ }
+
+ private fun moveHeader(canvas: Canvas, currentHeader: View, nextHeader: View) {
+ canvas.save()
+ canvas.translate(xPos.toFloat(), (nextHeader.top - currentHeader.height).toFloat())
+ currentHeader.draw(canvas)
+ canvas.restore()
+ }
+
+ private fun getChildInContact(parent: RecyclerView, contactPoint: Int, currentHeaderPos: Int): View? {
+ var childInContact: View? = null
+ for (i in 0 until parent.childCount) {
+ var heightTolerance = 0
+ val child = parent.getChildAt(i)
+
+ if (currentHeaderPos != i) {
+ val childAdapterPosition = parent.getChildAdapterPosition(child)
+ val isChildHeader: Boolean = stickyHeaderAdapter.isHeader(childAdapterPosition)
+ if (isChildHeader) {
+ heightTolerance = stickyHeaderHeight - child.height
+ }
+ }
+
+ val childBottomPosition: Int = if (child.top > 0) {
+ child.bottom + heightTolerance
+ } else {
+ child.bottom
+ }
+ if (childBottomPosition > contactPoint) {
+ if (child.top <= contactPoint) {
+ childInContact = child
+ break
+ }
+ }
+ }
+ return childInContact
+ }
+
+ private fun fixLayoutSize(parent: ViewGroup, view: View) {
+
+ val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
+ val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
+
+ val childWidthSpec =
+ ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingLeft + parent.paddingRight, view.layoutParams.width)
+ val childHeightSpec = ViewGroup.getChildMeasureSpec(
+ heightSpec,
+ parent.paddingTop,
+ view.layoutParams.height
+ )
+ xPos = parent.paddingLeft
+ view.measure(childWidthSpec, childHeightSpec)
+ view.layout(0, 0, view.measuredWidth, view.measuredHeight.also { stickyHeaderHeight = it })
+ }
+
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindableRecyclerViewAdapter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindableRecyclerViewAdapter.kt
index ee5dc43f75..68156ea7ad 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindableRecyclerViewAdapter.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindableRecyclerViewAdapter.kt
@@ -19,19 +19,32 @@ package com.instructure.pandautils.binding
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
+import androidx.databinding.Observable
import androidx.databinding.ViewDataBinding
-import androidx.databinding.library.baseAdapters.BR
import androidx.recyclerview.widget.RecyclerView
+import com.instructure.pandautils.BR
import com.instructure.pandautils.mvvm.ItemViewModel
-class BindableRecyclerViewAdapter : RecyclerView.Adapter() {
+open class BindableRecyclerViewAdapter : RecyclerView.Adapter() {
- var itemViewModels: List = emptyList()
+ var itemViewModels: MutableList = mutableListOf()
private val viewTypeLayoutMap: MutableMap = mutableMapOf()
+ private val groupObserver = object : Observable.OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
+ if (sender is GroupItemViewModel && propertyId == BR.collapsed) {
+ toggleGroup(sender)
+ }
+ }
+
+ }
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindableViewHolder {
- val binding: ViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), viewTypeLayoutMap[viewType]
- ?: 0, parent, false)
+ val binding: ViewDataBinding = DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context), viewTypeLayoutMap[viewType]
+ ?: 0, parent, false
+ )
+
return BindableViewHolder(binding)
}
@@ -50,10 +63,36 @@ class BindableRecyclerViewAdapter : RecyclerView.Adapter() {
}
fun updateItems(items: List?) {
- itemViewModels = items ?: emptyList()
+ itemViewModels = items.orEmpty().toMutableList()
+ val groups = itemViewModels.filterIsInstance()
+ setupGroups(groups)
notifyDataSetChanged()
}
+ private fun setupGroups(groups: List) {
+ groups.forEach {
+ it.removeOnPropertyChangedCallback(groupObserver)
+ it.addOnPropertyChangedCallback(groupObserver)
+ if (!it.collapsed) {
+ itemViewModels.addAll(itemViewModels.indexOf(it) + 1, it.items)
+ notifyItemRangeInserted(itemViewModels.indexOf(it) + 1, it.items.size)
+ setupGroups(it.items.filterIsInstance())
+ }
+ }
+ }
+
+ private fun toggleGroup(group: GroupItemViewModel) {
+ val position = itemViewModels.indexOf(group)
+ if (group.collapsed) {
+ itemViewModels.removeAll(group.items)
+ notifyItemRangeRemoved(position + 1, group.items.size)
+ } else {
+ itemViewModels.addAll(position + 1, group.items)
+ setupGroups(group.items.filterIsInstance())
+ notifyItemRangeInserted(position + 1, group.items.size)
+ }
+ }
+
}
class BindableViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindingAdapters.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindingAdapters.kt
index 6e9e2eb270..baff1b7b23 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindingAdapters.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindingAdapters.kt
@@ -16,22 +16,26 @@
*/
package com.instructure.pandautils.binding
-import android.util.Log
+import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
import android.webkit.JavascriptInterface
import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
import androidx.databinding.BindingAdapter
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import com.bumptech.glide.Glide
import com.instructure.pandautils.BR
import com.instructure.pandautils.mvvm.ItemViewModel
import com.instructure.pandautils.mvvm.ViewState
-import com.instructure.pandautils.utils.setCourseImage
-import com.instructure.pandautils.utils.setGone
-import com.instructure.pandautils.utils.setVisible
+import com.instructure.pandautils.utils.*
import com.instructure.pandautils.views.CanvasWebView
import com.instructure.pandautils.views.EmptyView
import java.net.URLDecoder
@@ -75,9 +79,10 @@ private fun handleErrorState(emptyView: EmptyView, error: ViewState.Error) {
}
}
-@BindingAdapter("recyclerViewItemViewModels")
-fun bindItemViewModels(recyclerView: RecyclerView, itemViewModels: List?) {
- val adapter = getOrCreateAdapter(recyclerView)
+@BindingAdapter("recyclerViewItemViewModels", "adapter", requireAll = false)
+fun bindItemViewModels(recyclerView: RecyclerView, itemViewModels: List?, bindableAdapter: BindableRecyclerViewAdapter?) {
+ val adapter = bindableAdapter ?: getOrCreateAdapter(recyclerView)
+ recyclerView.adapter = adapter
adapter.updateItems(itemViewModels)
}
@@ -91,7 +96,6 @@ private fun getOrCreateAdapter(recyclerView: RecyclerView): BindableRecyclerView
recyclerView.adapter as BindableRecyclerViewAdapter
} else {
val bindableRecyclerAdapter = BindableRecyclerViewAdapter()
- recyclerView.adapter = bindableRecyclerAdapter
bindableRecyclerAdapter
}
}
@@ -117,10 +121,69 @@ private class JSInterface(private val onLtiButtonPressed: OnLtiButtonPressed) {
}
}
-@BindingAdapter(value = ["imageUrl", "overlayColor"], requireAll = true)
+@BindingAdapter(value = ["imageUrl", "overlayColor"], requireAll = false)
fun bindImageWithOverlay(imageView: ImageView, imageUrl: String?, overlayColor: Int?) {
if (overlayColor != null) {
- imageView.setCourseImage(imageUrl, overlayColor, true)
+ imageView.post {
+ imageView.setCourseImage(imageUrl, overlayColor, true)
+ }
+ } else {
+ Glide.with(imageView)
+ .load(imageUrl)
+ .into(imageView)
+ }
+}
+
+@BindingAdapter(value = ["borderColor", "borderWidth", "backgroundColor", "borderCornerRadius"], requireAll = false)
+fun addBorderToContainer(view: View, borderColor: Int?, borderWidth: Int?, backgroundColor: Int?, borderCornerRadius: Int?) {
+ val border = GradientDrawable()
+ val background = backgroundColor ?: 0xffffff
+ val strokeColor = borderColor
+ ?: 0x000000
+ border.setColor(background)
+ border.setStroke(borderWidth?.toPx ?: 2.toPx, strokeColor)
+ border.cornerRadius = borderCornerRadius?.toPx?.toFloat() ?: 4.toPx.toFloat()
+ view.background = border
+}
+@BindingAdapter("layout_constraintWidth_percent")
+fun bindConstraintWidthPercentage(view: View, percentage: Float) {
+ val params = view.layoutParams as ConstraintLayout.LayoutParams
+ params.matchConstraintPercentWidth = percentage
+ view.layoutParams = params
+}
+
+@BindingAdapter("imageRes")
+fun bindImageResource(imageView: ImageView, @DrawableRes imageRes: Int) {
+ imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, imageRes))
+}
+
+
+@BindingAdapter("accessibilityClickDescription")
+fun bindAccesibilityDelegate(view: View, clickDescription: String) {
+ view.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info?.addAction(AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, clickDescription))
+ }
}
}
+@BindingAdapter("android:layout_marginBottom")
+fun setBottomMargin(view: View, bottomMargin: Int) {
+ val layoutParams: ViewGroup.MarginLayoutParams = view.layoutParams as ViewGroup.MarginLayoutParams
+ layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin,
+ layoutParams.rightMargin, bottomMargin)
+ view.layoutParams = layoutParams
+}
+
+@BindingAdapter(value = ["userAvatar", "userName"], requireAll = true)
+fun bindUserAvatar(imageView: ImageView, userAvatarUrl: String?, userName: String?) {
+ ProfileUtils.loadAvatarForUser(imageView, userName, userAvatarUrl)
+}
+
+@BindingAdapter("accessibleTouchTarget")
+fun bindAccessibleTouchTarget(view: View, accessibleTouchTarget: Boolean?) {
+ if (accessibleTouchTarget == true) {
+ view.accessibleTouchTarget()
+ }
+}
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/binding/GroupItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/GroupItemViewModel.kt
new file mode 100644
index 0000000000..4aea5325ab
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/binding/GroupItemViewModel.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.pandautils.binding
+
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.instructure.pandautils.BR
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+abstract class GroupItemViewModel(
+ val collapsable: Boolean,
+ @get:Bindable var collapsed: Boolean = collapsable,
+ val items: List
+) : ItemViewModel, BaseObservable() {
+
+ open fun toggleItems() {
+ collapsed = !collapsed
+ notifyPropertyChanged(BR.collapsed)
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/di/ApplicationModule.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/di/ApplicationModule.kt
index 340fb0f8d0..693a6693e2 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/di/ApplicationModule.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/di/ApplicationModule.kt
@@ -19,15 +19,9 @@ package com.instructure.pandautils.di
import android.app.NotificationManager
import android.content.Context
import android.content.res.Resources
-import com.google.android.play.core.appupdate.AppUpdateManager
-import com.google.android.play.core.appupdate.AppUpdateManagerFactory
-import com.google.android.play.core.appupdate.testing.FakeAppUpdateManager
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.instructure.canvasapi2.managers.OAuthManager
import com.instructure.pandautils.typeface.TypefaceBehavior
-import com.instructure.pandautils.update.UpdateManager
-import com.instructure.pandautils.update.UpdatePrefs
-import com.instructure.pandautils.utils.ColorApiHelper
import com.instructure.pandautils.utils.ColorKeeper
import com.instructure.pandautils.utils.HtmlContentFormatter
import dagger.Module
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/di/DateTimeModule.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/di/DateTimeModule.kt
new file mode 100644
index 0000000000..4fc2583c80
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/di/DateTimeModule.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.pandautils.di
+
+import com.instructure.pandautils.utils.date.DateTimeProvider
+import com.instructure.pandautils.utils.date.RealDateTimeProvider
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class DateTimeModule {
+
+ @Provides
+ @Singleton
+ fun provideDateTimeProvider(): DateTimeProvider {
+ return RealDateTimeProvider()
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/di/elementary/ScheduleViewModelModule.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/di/elementary/ScheduleViewModelModule.kt
new file mode 100644
index 0000000000..371895ad22
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/di/elementary/ScheduleViewModelModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.pandautils.di.elementary
+
+import com.instructure.pandautils.utils.MissingItemsPrefs
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+
+@Module
+@InstallIn(ViewModelComponent::class)
+class ScheduleViewModelModule {
+
+ @Provides
+ fun provideMissingItemsPrefs(): MissingItemsPrefs {
+ return MissingItemsPrefs
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/ElementaryDashboardPagerAdapter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/ElementaryDashboardPagerAdapter.kt
index bd2582df62..17a811b23d 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/ElementaryDashboardPagerAdapter.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/ElementaryDashboardPagerAdapter.kt
@@ -19,24 +19,13 @@ package com.instructure.pandautils.features.elementary
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
-import com.instructure.canvasapi2.models.CanvasContext
-import com.instructure.pandautils.features.elementary.grades.GradesFragment
import com.instructure.pandautils.features.elementary.homeroom.HomeroomFragment
-import com.instructure.pandautils.features.elementary.resources.ResourcesFragment
-import com.instructure.pandautils.features.elementary.schedule.ScheduleFragment
class ElementaryDashboardPagerAdapter(
- private val canvasContext: CanvasContext,
+ private val fragments: List,
fragmentManager: FragmentManager
) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
- private val fragments = listOf(
- HomeroomFragment.newInstance(),
- ScheduleFragment.newInstance(),
- GradesFragment.newInstance(),
- ResourcesFragment.newInstance()
- )
-
override fun getItem(position: Int): Fragment {
return fragments[position]
}
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesFragment.kt
index 7dca6cd3bb..1d94566197 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesFragment.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesFragment.kt
@@ -16,17 +16,67 @@
*/
package com.instructure.pandautils.features.elementary.grades
+import android.app.AlertDialog
+import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import com.instructure.pandautils.R
+import com.instructure.pandautils.databinding.FragmentGradesBinding
+import com.instructure.pandautils.utils.toast
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+@AndroidEntryPoint
class GradesFragment : Fragment() {
+ @Inject
+ lateinit var gradesRouter: GradesRouter
+
+ private val viewModel: GradesViewModel by viewModels()
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return layoutInflater.inflate(R.layout.fragment_grades, container, false)
+ val binding = FragmentGradesBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+
+ viewModel.events.observe(viewLifecycleOwner, { event ->
+ event.getContentIfNotHandled()?.let {
+ handleAction(it)
+ }
+ })
+
+ return binding.root
+ }
+
+ private fun handleAction(action: GradesAction) {
+ when (action) {
+ is GradesAction.OpenCourseGrades -> gradesRouter.openCourseGrades(action.course)
+ is GradesAction.OpenGradingPeriodsDialog -> showGradingPeriodsDialog(action)
+ GradesAction.ShowGradingPeriodError -> toast(R.string.failedToLoadGradesForGradingPeriod)
+ GradesAction.ShowRefreshError -> toast(R.string.failedToRefreshGrades)
+ }
+ }
+
+ private fun showGradingPeriodsDialog(action: GradesAction.OpenGradingPeriodsDialog) {
+ val gradingPeriodNames = action.gradingPeriods
+ .map { it.name }
+ .toTypedArray()
+
+ AlertDialog.Builder(context, R.style.AccentDialogTheme)
+ .setTitle(R.string.selectGradingPeriod)
+ .setSingleChoiceItems(gradingPeriodNames, action.selectedGradingPeriodIndex) { dialog, which -> sortOrderSelected(dialog, which, action.gradingPeriods) }
+ .setNegativeButton(R.string.sortByDialogCancel) { dialog, _ -> dialog.dismiss() }
+ .show()
+ }
+
+ private fun sortOrderSelected(dialog: DialogInterface?, index: Int, gradingPeriods: List) {
+ dialog?.dismiss()
+ val selectedGradingPeriod = gradingPeriods[index]
+ viewModel.gradingPeriodSelected(selectedGradingPeriod)
}
companion object {
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesRouter.kt
new file mode 100644
index 0000000000..64947f23a6
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesRouter.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.pandautils.features.elementary.grades
+
+import com.instructure.canvasapi2.models.Course
+
+interface GradesRouter {
+
+ fun openCourseGrades(course: Course)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewData.kt
new file mode 100644
index 0000000000..f52acbedff
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewData.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.pandautils.features.elementary.grades
+
+import com.instructure.canvasapi2.models.Course
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+data class GradesViewData(val items: List)
+
+data class GradingPeriod(val id: Long, val name: String)
+
+data class GradeRowViewData(
+ val courseId: Long,
+ val courseName: String,
+ val courseColor: String,
+ val courseImageUrl: String,
+ val score: Double?,
+ val gradeText: String)
+
+sealed class GradesAction {
+ data class OpenCourseGrades(val course: Course) : GradesAction()
+ data class OpenGradingPeriodsDialog(val gradingPeriods: List, val selectedGradingPeriodIndex: Int) : GradesAction()
+ object ShowGradingPeriodError : GradesAction()
+ object ShowRefreshError : GradesAction()
+}
+
+enum class GradesItemViewType(val viewType: Int) {
+ GRADING_PERIOD_SELECTOR(0),
+ GRADE_ROW(1)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewModel.kt
new file mode 100644
index 0000000000..afb6cd97b9
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/GradesViewModel.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.pandautils.features.elementary.grades
+
+import android.content.res.Resources
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.instructure.canvasapi2.managers.CourseManager
+import com.instructure.canvasapi2.managers.EnrollmentManager
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.Enrollment
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.grades.itemviewmodels.GradeRowItemViewModel
+import com.instructure.pandautils.features.elementary.grades.itemviewmodels.GradingPeriodSelectorItemViewModel
+import com.instructure.pandautils.mvvm.Event
+import com.instructure.pandautils.mvvm.ItemViewModel
+import com.instructure.pandautils.mvvm.ViewState
+import com.instructure.pandautils.utils.ColorApiHelper
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+private const val CURRENT_GRADING_PERIOD_ID = -1L
+
+@HiltViewModel
+class GradesViewModel @Inject constructor(
+ private val courseManager: CourseManager,
+ private val resources: Resources,
+ private val enrollmentManager: EnrollmentManager
+) : ViewModel() {
+
+
+ val state: LiveData
+ get() = _state
+ private val _state = MutableLiveData()
+
+ val data: LiveData
+ get() = _data
+ private val _data = MutableLiveData(GradesViewData(emptyList()))
+
+ val events: LiveData>
+ get() = _events
+ private val _events = MutableLiveData>()
+
+ private var gradingPeriodsViewModel: GradingPeriodSelectorItemViewModel? = null
+ private var courses = emptyList()
+
+ init {
+ loadInitialData()
+ }
+
+ private fun loadInitialData() {
+ _state.postValue(ViewState.Loading)
+ loadData(false)
+ }
+
+ private fun loadData(forceNetwork: Boolean, previouslySelectedGradingPeriod: GradingPeriod? = null) {
+ viewModelScope.launch {
+ try {
+ val coursesWithGrades = courseManager.getCoursesWithGradesAsync(forceNetwork).await().dataOrThrow
+ courses = coursesWithGrades
+ .filter { !it.homeroomCourse }
+
+ gradingPeriodsViewModel = createGradingPeriodsViewModel(courses)
+ val gradeRowViewModels = createGradeRowViewModels(courses)
+ val viewData = createViewData(gradeRowViewModels)
+
+ _data.postValue(viewData)
+ if (viewData.items.isEmpty()) {
+ _state.postValue(ViewState.Empty(emptyTitle = R.string.noGradesToDisplay))
+ } else {
+ _state.postValue(ViewState.Success)
+ }
+ } catch (e: Exception) {
+ if (_data.value == null || _data.value?.items?.isNullOrEmpty() == true) {
+ _state.postValue(ViewState.Error(resources.getString(R.string.failedToLoadGrades)))
+ } else {
+ _state.postValue(ViewState.Error())
+ _events.postValue(Event(GradesAction.ShowRefreshError))
+ }
+
+ if (previouslySelectedGradingPeriod != null) {
+ gradingPeriodsViewModel?.selectedGradingPeriod = previouslySelectedGradingPeriod
+ gradingPeriodsViewModel?.notifyChange()
+ }
+ }
+ }
+ }
+
+ private fun createGradingPeriodsViewModel(courses: List): GradingPeriodSelectorItemViewModel? {
+ val gradingPeriods = courses
+ .flatMap { it.gradingPeriods ?: emptyList() }
+ .distinctBy { gradingPeriod -> gradingPeriod.id }
+ .map { gradingPeriod -> GradingPeriod(gradingPeriod.id, gradingPeriod.title ?: "") }
+
+ return if (gradingPeriods.isNotEmpty()) {
+ val currentGradingPeriod = GradingPeriod(CURRENT_GRADING_PERIOD_ID, resources.getString(R.string.currentGradingPeriod))
+ val allGradingPeriods = listOf(currentGradingPeriod).plus(gradingPeriods)
+ GradingPeriodSelectorItemViewModel(allGradingPeriods, currentGradingPeriod, resources)
+ { index -> _events.postValue(Event(GradesAction.OpenGradingPeriodsDialog(allGradingPeriods, index))) }
+ } else {
+ null
+ }
+ }
+
+ private fun createGradeRowViewModels(courses: List): List {
+ return courses
+ .map {
+ val enrollment = it.enrollments?.first()
+ GradeRowItemViewModel(resources,
+ GradeRowViewData(
+ it.id,
+ it.name,
+ getCourseColor(it),
+ it.imageUrl ?: "",
+ if (it.hideFinalGrades) 0.0 else enrollment?.computedCurrentScore,
+ createGradeText(enrollment?.computedCurrentScore, enrollment?.computedCurrentGrade, it.hideFinalGrades, enrollment?.currentGradingPeriodId ?: 0L != 0L))
+ ) { gradeRowClicked(it) }
+ }
+ }
+
+ private fun createViewData(gradeRowItems: List): GradesViewData {
+ val items = mutableListOf()
+
+ if (gradingPeriodsViewModel != null && gradingPeriodsViewModel!!.isNotEmpty() && gradeRowItems.isNotEmpty()) {
+ items.add(gradingPeriodsViewModel!!)
+ }
+ items.addAll(gradeRowItems)
+
+ return GradesViewData(items)
+ }
+
+ private fun createGradeText(score: Double?, grade: String?, hideFinalGrades: Boolean, notGraded: Boolean = true): String {
+ return when {
+ hideFinalGrades -> "--"
+ !grade.isNullOrEmpty() -> grade
+ else -> {
+ val currentScoreRounded = score?.roundToInt()
+ when {
+ currentScoreRounded != null -> "$currentScoreRounded%"
+ notGraded -> resources.getString(R.string.notGraded)
+ else -> "--"
+ }
+ }
+ }
+ }
+
+ private fun getCourseColor(course: Course): String {
+ return if (course.courseColor.isNullOrEmpty()) {
+ ColorApiHelper.K5_DEFAULT_COLOR
+ } else {
+ course.courseColor!!
+ }
+ }
+
+ private fun gradeRowClicked(course: Course) {
+ _events.postValue(Event(GradesAction.OpenCourseGrades(course)))
+ }
+
+ fun refresh() {
+ _state.postValue(ViewState.Refresh)
+ val gradingPeriodId = gradingPeriodsViewModel?.selectedGradingPeriod?.id ?: CURRENT_GRADING_PERIOD_ID
+ if (gradingPeriodId == CURRENT_GRADING_PERIOD_ID) {
+ loadData(true)
+ } else {
+ loadDataForGradingPeriod(gradingPeriodId, null, true)
+ }
+ }
+
+ fun gradingPeriodSelected(gradingPeriod: GradingPeriod) {
+ if (gradingPeriodsViewModel?.selectedGradingPeriod != gradingPeriod) {
+ val previouslySelectedGradingPeriod = gradingPeriodsViewModel?.selectedGradingPeriod
+ gradingPeriodsViewModel?.selectedGradingPeriod = gradingPeriod
+ gradingPeriodsViewModel?.notifyChange()
+
+ _state.postValue(ViewState.Refresh)
+ if (gradingPeriod.id == CURRENT_GRADING_PERIOD_ID) {
+ loadData(false, previouslySelectedGradingPeriod)
+ } else {
+ loadDataForGradingPeriod(gradingPeriod.id, previouslySelectedGradingPeriod, false)
+ }
+ }
+ }
+
+ private fun loadDataForGradingPeriod(id: Long, previouslySelectedGradingPeriod: GradingPeriod?, forceNetwork: Boolean) {
+ viewModelScope.launch {
+ try {
+ val enrollments = enrollmentManager.getEnrollmentsForGradingPeriodAsync(id, forceNetwork).await().dataOrThrow
+
+ val gradeRowItems = createGradeRowsForGradingPeriod(enrollments)
+ val viewData = createViewData(gradeRowItems)
+
+ _state.postValue(ViewState.Success)
+ _data.postValue(viewData)
+ } catch (e: Exception) {
+ _state.postValue(ViewState.Error())
+ _events.postValue(Event(GradesAction.ShowGradingPeriodError))
+
+ if (previouslySelectedGradingPeriod != null) {
+ gradingPeriodsViewModel?.selectedGradingPeriod = previouslySelectedGradingPeriod
+ gradingPeriodsViewModel?.notifyChange()
+ }
+ }
+ }
+ }
+
+ private fun createGradeRowsForGradingPeriod(enrollments: List): List {
+ val enrollmentsMap = enrollments.associateBy { it.courseId }
+ return courses
+ .map { createGradeRowFromEnrollment(it, enrollmentsMap[it.id]) }
+ }
+
+ private fun createGradeRowFromEnrollment(course: Course, enrollment: Enrollment?): GradeRowItemViewModel {
+ val gradeRowViewData = GradeRowViewData(
+ course.id,
+ course.name,
+ getCourseColor(course),
+ course.imageUrl ?: "",
+ enrollment?.grades?.currentScore,
+ createGradeText(enrollment?.grades?.currentScore, enrollment?.grades?.currentGrade, course.hideFinalGrades))
+
+ return GradeRowItemViewModel(resources, gradeRowViewData) { gradeRowClicked(course) }
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradeRowItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradeRowItemViewModel.kt
new file mode 100644
index 0000000000..017029cffd
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradeRowItemViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.pandautils.features.elementary.grades.itemviewmodels
+
+import android.content.res.Resources
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.grades.GradeRowViewData
+import com.instructure.pandautils.features.elementary.grades.GradesItemViewType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class GradeRowItemViewModel(
+ private val resources: Resources,
+ val data: GradeRowViewData,
+ val onRowClicked: () -> Unit
+) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_grade_row
+
+ override val viewType: Int = GradesItemViewType.GRADE_ROW.viewType
+
+ val gradeContentDescription: String
+ get() {
+ return if (data.gradeText == "--") {
+ resources.getString(R.string.a11y_gradesNotAvailableContentDescription)
+ } else {
+ data.gradeText
+ }
+ }
+
+ val percentage: Float
+ get() {
+ if (data.score == null) return 0.0f
+ return data.score.toFloat() / 100
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradingPeriodSelectorItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradingPeriodSelectorItemViewModel.kt
new file mode 100644
index 0000000000..05174e3fcf
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/grades/itemviewmodels/GradingPeriodSelectorItemViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.pandautils.features.elementary.grades.itemviewmodels
+
+import android.content.res.Resources
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.grades.GradesItemViewType
+import com.instructure.pandautils.features.elementary.grades.GradingPeriod
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class GradingPeriodSelectorItemViewModel(
+ private val gradingPeriods: List,
+ @get:Bindable var selectedGradingPeriod: GradingPeriod,
+ resources: Resources,
+ val onItemClick: (Int) -> Unit
+) : BaseObservable(), ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_grading_period_selector
+
+ override val viewType: Int = GradesItemViewType.GRADING_PERIOD_SELECTOR.viewType
+
+ val accessibilityContentDescription: String = resources.getString(R.string.a11y_gradingPeriodSelectorClickDescription)
+
+ fun onClick() {
+ val index = gradingPeriods.indexOfFirst { it.id == selectedGradingPeriod.id }
+ onItemClick(index)
+ }
+
+ fun isNotEmpty() = gradingPeriods.isNotEmpty()
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreator.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreator.kt
index d719f6c5ff..c10ba88059 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreator.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreator.kt
@@ -21,10 +21,7 @@ import androidx.lifecycle.MutableLiveData
import com.instructure.canvasapi2.managers.AnnouncementManager
import com.instructure.canvasapi2.managers.PlannerManager
import com.instructure.canvasapi2.managers.UserManager
-import com.instructure.canvasapi2.models.Assignment
-import com.instructure.canvasapi2.models.Course
-import com.instructure.canvasapi2.models.DiscussionTopicHeader
-import com.instructure.canvasapi2.models.PlannerItem
+import com.instructure.canvasapi2.models.*
import com.instructure.canvasapi2.utils.toApiString
import com.instructure.pandautils.R
import com.instructure.pandautils.features.elementary.homeroom.itemviewmodels.CourseCardItemViewModel
@@ -33,8 +30,6 @@ import com.instructure.pandautils.utils.ColorApiHelper
import kotlinx.coroutines.awaitAll
import org.threeten.bp.LocalDate
-private const val PLANNABLE_TYPE_ASSIGNMENT = "assignment"
-
class CourseCardCreator(
private val plannerManager: PlannerManager,
private val userManager: UserManager,
@@ -101,7 +96,7 @@ class CourseCardCreator(
}
private fun isNotSubmittedAssignment(it: PlannerItem) =
- it.courseId != null && it.submissionState?.submitted == false && it.plannableType == PLANNABLE_TYPE_ASSIGNMENT && it.submissionState?.missing == false
+ it.courseId != null && it.submissionState?.submitted == false && it.plannableType == PlannableType.ASSIGNMENT && it.submissionState?.missing == false
private fun createDueTextForCourse(dueCount: Int): String {
return if (dueCount == 0) {
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomFragment.kt
index 7558241985..e043bc4220 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomFragment.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomFragment.kt
@@ -33,6 +33,7 @@ import com.instructure.pandautils.BuildConfig
import com.instructure.pandautils.R
import com.instructure.pandautils.databinding.FragmentHomeroomBinding
import com.instructure.pandautils.discussions.DiscussionUtils
+import com.instructure.pandautils.navigation.WebViewRouter
import com.instructure.pandautils.utils.children
import com.instructure.pandautils.utils.toast
import com.instructure.pandautils.views.CanvasWebView
@@ -48,6 +49,9 @@ class HomeroomFragment : Fragment() {
@Inject
lateinit var homeroomRouter: HomeroomRouter
+ @Inject
+ lateinit var webViewRouter: WebViewRouter
+
private val viewModel: HomeroomViewModel by viewModels()
private var updateAssignments = false
@@ -129,13 +133,13 @@ class HomeroomFragment : Fragment() {
announcementWebView.settings.loadWithOverviewMode = true
announcementWebView.canvasWebViewClientCallback = object : CanvasWebView.CanvasWebViewClientCallback {
override fun routeInternallyCallback(url: String) {
- homeroomRouter.routeInternally(url)
+ webViewRouter.routeInternally(url)
}
- override fun canRouteInternallyDelegate(url: String): Boolean = homeroomRouter.canRouteInternally(url)
+ override fun canRouteInternallyDelegate(url: String): Boolean = webViewRouter.canRouteInternally(url)
override fun openMediaFromWebView(mime: String, url: String, filename: String) {
- homeroomRouter.openMedia(url)
+ webViewRouter.openMedia(url)
}
override fun onPageStartedCallback(webView: WebView, url: String) = Unit
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomRouter.kt
index b4b1b83bdb..c9de6fdae6 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomRouter.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/homeroom/HomeroomRouter.kt
@@ -22,12 +22,6 @@ import com.instructure.canvasapi2.models.DiscussionTopicHeader
interface HomeroomRouter {
- fun canRouteInternally(url: String): Boolean
-
- fun routeInternally(url: String)
-
- fun openMedia(url: String)
-
fun openAnnouncements(canvasContext: CanvasContext)
fun openCourse(course: Course)
@@ -37,4 +31,5 @@ interface HomeroomRouter {
fun openAnnouncementDetails(course: Course, announcement: DiscussionTopicHeader)
fun updateColors()
+
}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesFragment.kt
index 0be189512b..fe74a91295 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesFragment.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesFragment.kt
@@ -16,17 +16,114 @@
*/
package com.instructure.pandautils.features.elementary.resources
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.webkit.WebView
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.instructure.canvasapi2.models.LTITool
+import com.instructure.pandautils.BuildConfig
import com.instructure.pandautils.R
+import com.instructure.pandautils.databinding.FragmentResourcesBinding
+import com.instructure.pandautils.discussions.DiscussionUtils
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesRouter
+import com.instructure.pandautils.navigation.WebViewRouter
+import com.instructure.pandautils.utils.children
+import com.instructure.pandautils.utils.toast
+import com.instructure.pandautils.views.CanvasWebView
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.android.synthetic.main.fragment_resources.*
+import kotlinx.android.synthetic.main.item_important_links.view.*
+import javax.inject.Inject
+@AndroidEntryPoint
class ResourcesFragment : Fragment() {
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return layoutInflater.inflate(R.layout.fragment_resources, container, false)
+ @Inject
+ lateinit var resourcesRouter: ResourcesRouter
+
+ @Inject
+ lateinit var webViewRouter: WebViewRouter
+
+ private val viewModel: ResourcesViewModel by viewModels()
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ val binding = FragmentResourcesBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+
+ viewModel.events.observe(viewLifecycleOwner, { event ->
+ event.getContentIfNotHandled()?.let {
+ handleAction(it)
+ }
+ })
+
+ return binding.root
+ }
+
+ private fun handleAction(action: ResourcesAction) {
+ when (action) {
+ is ResourcesAction.OpenLtiApp -> showCourseSelectorDialog(action.ltiTools)
+ is ResourcesAction.OpenComposeMessage -> resourcesRouter.openComposeMessage(action.recipient)
+ ResourcesAction.ImportantLinksViewsReady -> setupWebViews()
+ ResourcesAction.ShowRefreshError -> toast(R.string.failedToRefreshResources)
+ is ResourcesAction.WebLtiButtonPressed -> DiscussionUtils.launchIntent(requireContext(), action.url)
+ }
+ }
+
+ private fun showCourseSelectorDialog(ltiTools: List) {
+ val dialogEntries = ltiTools
+ .map { it.contextName }
+ .toTypedArray()
+
+ AlertDialog.Builder(context, R.style.AccentDialogTheme)
+ .setTitle(R.string.chooseACourse)
+ .setItems(dialogEntries) { dialog, which -> openSelectedLti(dialog, which, ltiTools) }
+ .setNegativeButton(R.string.sortByDialogCancel) { dialog, _ -> dialog.dismiss() }
+ .show()
+ }
+
+ private fun openSelectedLti(dialog: DialogInterface?, index: Int, ltiTools: List) {
+ dialog?.dismiss()
+ val ltiTool = ltiTools[index]
+ resourcesRouter.openLti(ltiTool)
+ }
+
+ private fun setupWebViews() {
+ importantLinksContainer.children.forEach {
+ val webView = it.importantLinksWebView
+ if (webView != null) {
+ setupWebView(webView)
+ }
+ }
+ }
+
+ private fun setupWebView(webView: CanvasWebView) {
+ WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
+ webView.setBackgroundColor(Color.WHITE)
+ webView.settings.allowFileAccess = true
+ webView.settings.loadWithOverviewMode = true
+ webView.canvasWebViewClientCallback = object : CanvasWebView.CanvasWebViewClientCallback {
+ override fun routeInternallyCallback(url: String) {
+ webViewRouter.routeInternally(url)
+ }
+
+ override fun canRouteInternallyDelegate(url: String): Boolean = webViewRouter.canRouteInternally(url)
+
+ override fun openMediaFromWebView(mime: String, url: String, filename: String) {
+ webViewRouter.openMedia(url)
+ }
+
+ override fun onPageStartedCallback(webView: WebView, url: String) = Unit
+ override fun onPageFinishedCallback(webView: WebView, url: String) = Unit
+ }
+
+ webView.addVideoClient(requireActivity())
}
companion object {
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewData.kt
new file mode 100644
index 0000000000..71663ec7e9
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewData.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.pandautils.features.elementary.resources
+
+import com.instructure.canvasapi2.models.LTITool
+import com.instructure.canvasapi2.models.User
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+data class ResourcesViewData(val importantLinksItems: List, val actionItems: List) {
+ fun isEmpty() = importantLinksItems.isEmpty() && actionItems.isEmpty()
+}
+
+data class ResourcesHeaderViewData(val title: String, val hasDivider: Boolean = false)
+
+data class LtiApplicationViewData(val title: String, val imageUrl: String, val ltiUrl: String)
+
+data class ContactInfoViewData(val name: String, val description: String, val imageUrl: String)
+
+data class ImportantLinksViewData(val courseName: String, val htmlContent: String, val hasDivider: Boolean = false)
+
+sealed class ResourcesAction {
+ data class OpenLtiApp(val ltiTools: List) : ResourcesAction()
+ data class OpenComposeMessage(val recipient: User) : ResourcesAction()
+ object ImportantLinksViewsReady : ResourcesAction()
+ data class WebLtiButtonPressed(val url: String) : ResourcesAction()
+ object ShowRefreshError : ResourcesAction()
+}
+
+enum class ResourcesItemViewType(val viewType: Int) {
+ RESOURCES_HEADER(0),
+ LTI_APPLICATION(1),
+ CONTACT_INFO(2)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModel.kt
new file mode 100644
index 0000000000..0d5c381dc2
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModel.kt
@@ -0,0 +1,228 @@
+/*
+ * 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.pandautils.features.elementary.resources
+
+import android.content.res.Resources
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.instructure.canvasapi2.managers.CourseManager
+import com.instructure.canvasapi2.managers.ExternalToolManager
+import com.instructure.canvasapi2.managers.OAuthManager
+import com.instructure.canvasapi2.managers.UserManager
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.Enrollment
+import com.instructure.canvasapi2.models.LTITool
+import com.instructure.canvasapi2.models.User
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ContactInfoItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ImportantLinksItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.LtiApplicationItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesHeaderViewModel
+import com.instructure.pandautils.mvvm.Event
+import com.instructure.pandautils.mvvm.ItemViewModel
+import com.instructure.pandautils.mvvm.ViewState
+import com.instructure.pandautils.utils.HtmlContentFormatter
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import java.util.regex.Pattern
+import javax.inject.Inject
+
+@HiltViewModel
+class ResourcesViewModel @Inject constructor(
+ private val resources: Resources,
+ private val courseManager: CourseManager,
+ private val userManager: UserManager,
+ private val externalToolManager: ExternalToolManager,
+ private val oAuthManager: OAuthManager,
+ private val htmlContentFormatter: HtmlContentFormatter
+) : ViewModel() {
+
+ val state: LiveData
+ get() = _state
+ private val _state = MutableLiveData()
+
+ val data: LiveData
+ get() = _data
+ private val _data = MutableLiveData(ResourcesViewData(emptyList(), emptyList()))
+
+ val events: LiveData>
+ get() = _events
+ private val _events = MutableLiveData>()
+
+ init {
+ _state.postValue(ViewState.Loading)
+ loadData(false)
+ }
+
+ private fun loadData(forceNetwork: Boolean) {
+ viewModelScope.launch {
+ try {
+ val coursesResult = courseManager.getCoursesWithSyllabusAsyncWithActiveEnrollmentAsync(forceNetwork).await()
+
+ val courses = coursesResult.dataOrThrow
+ .filter { !it.homeroomCourse }
+
+ val homeroomCourses = coursesResult.dataOrThrow.filter { it.homeroomCourse }
+ val importantLinks = createImportantLinks(homeroomCourses)
+
+ val actionItems = createActionItems(courses, homeroomCourses, forceNetwork)
+
+ val viewData = ResourcesViewData(importantLinks, actionItems)
+ _data.postValue(viewData)
+ if (viewData.isEmpty()) {
+ _state.postValue(ViewState.Empty(emptyTitle = R.string.resourcesEmptyMessage))
+ } else {
+ _state.postValue(ViewState.Success)
+ }
+ } catch (e: Exception) {
+ if (_data.value == null || _data.value?.isEmpty() == true) {
+ _state.postValue(ViewState.Error(resources.getString(R.string.failedToLoadResources)))
+ } else {
+ _state.postValue(ViewState.Error())
+ _events.postValue(Event(ResourcesAction.ShowRefreshError))
+ }
+ }
+ }
+ }
+
+ private fun createImportantLinks(homeroomCourses: List): List {
+ val homeroomCoursesWithSyllabus = homeroomCourses.filter { !it.syllabusBody.isNullOrEmpty() }
+ return if (homeroomCoursesWithSyllabus.size > 1) {
+ homeroomCoursesWithSyllabus
+ .mapIndexed { index, course -> ImportantLinksItemViewModel(
+ ImportantLinksViewData(course.name, course.syllabusBody ?: "", index < homeroomCoursesWithSyllabus.size - 1),
+ ::ltiButtonPressed) }
+ } else {
+ homeroomCoursesWithSyllabus
+ .map { ImportantLinksItemViewModel(ImportantLinksViewData("", it.syllabusBody ?: ""), ::ltiButtonPressed) }
+ }
+ }
+
+ private fun ltiButtonPressed(html: String, htmlContent: String) {
+ viewModelScope.launch {
+ try {
+ val matcher = Pattern.compile("src=\"([^\"]+)\"").matcher(htmlContent)
+ matcher.find()
+ val url = matcher.group(1)
+
+ if (url == null) {
+ _events.postValue(Event(ResourcesAction.WebLtiButtonPressed(html)))
+ return@launch
+ }
+
+ // Get an authenticated session so the user doesn't have to log in
+ val authenticatedSessionURL = oAuthManager.getAuthenticatedSessionAsync(url).await().dataOrThrow.sessionUrl
+ val newUrl = htmlContentFormatter.createAuthenticatedLtiUrl(html, authenticatedSessionURL)
+
+ _events.postValue(Event(ResourcesAction.WebLtiButtonPressed(newUrl)))
+ } catch (e: Exception) {
+ // Couldn't get the authenticated session, try to load it without it
+ _events.postValue(Event(ResourcesAction.WebLtiButtonPressed(html)))
+ }
+ }
+ }
+
+ private suspend fun createActionItems(courses: List, homeroomCourses: List, forceNetwork: Boolean): List {
+ val actionItems = mutableListOf()
+
+ val ltiApps = if (courses.isNotEmpty()) {
+ createLtiApps(courses, forceNetwork)
+ } else {
+ emptyList()
+ }
+ if (ltiApps.isNotEmpty()) {
+ actionItems.add(ResourcesHeaderViewModel(ResourcesHeaderViewData(resources.getString(R.string.studentApplications))))
+ actionItems.addAll(ltiApps)
+ }
+
+ val staffInfo = createStaffInfo(homeroomCourses, forceNetwork)
+ if (staffInfo.isNotEmpty()) {
+ actionItems.add(ResourcesHeaderViewModel(ResourcesHeaderViewData(resources.getString(R.string.staffContactInfo), true)))
+ actionItems.addAll(staffInfo)
+ }
+
+ return actionItems
+ }
+
+ private suspend fun createLtiApps(courses: List, forceNetwork: Boolean): List {
+ val contextIds = courses
+ .map { it.contextId }
+
+ val ltiTools = externalToolManager.getExternalToolsForCoursesAsync(contextIds, forceNetwork).await().dataOrNull
+
+ val ltiToolsMapById = mutableMapOf>()
+ ltiTools?.forEach {
+ if (!ltiToolsMapById.contains(it.id)) {
+ ltiToolsMapById[it.id] = mutableListOf()
+ }
+ ltiToolsMapById[it.id]?.add(it)
+ }
+
+ val displayedLtiTools = ltiTools?.distinctBy { it.id }
+
+ return displayedLtiTools
+ ?.mapIndexed { i: Int, ltiTool: LTITool ->
+ createLtiApplicationItem(ltiTool, i == displayedLtiTools.size - 1, ltiToolsMapById[ltiTool.id] ?: emptyList()) }
+ ?: emptyList()
+ }
+
+ private fun createLtiApplicationItem(ltiTool: LTITool, isLast: Boolean, courseLtiTools: List): LtiApplicationItemViewModel {
+ val margin = if (isLast) resources.getDimension(R.dimen.ltiAppsBottomMargin).toInt() else 0
+ return LtiApplicationItemViewModel(
+ LtiApplicationViewData(
+ ltiTool.courseNavigation?.text ?: ltiTool.name ?: "",
+ ltiTool.iconUrl ?: "",
+ ltiTool.url ?: ""),
+ margin
+ ) { _events.postValue(Event(ResourcesAction.OpenLtiApp(courseLtiTools))) }
+ }
+
+ private suspend fun createStaffInfo(homeroomCourses: List, forceNetwork: Boolean): List {
+ val teachers = homeroomCourses
+ .map { userManager.getTeacherListForCourseAsync(it.id, forceNetwork) }
+ .awaitAll()
+ .map { result -> result.dataOrNull ?: emptyList() }
+ .flatten()
+ .distinctBy { user: User -> user.id }
+
+ return teachers.map {
+ ContactInfoItemViewModel(ContactInfoViewData(it.shortName ?: "", getRoleString(it.enrollments[0].role), it.avatarUrl ?: "")) {
+ _events.postValue(Event(ResourcesAction.OpenComposeMessage(it)))
+ }
+ }
+ }
+
+ private fun getRoleString(role: Enrollment.EnrollmentType?): String {
+ return when (role) {
+ Enrollment.EnrollmentType.Teacher -> resources.getString(R.string.staffRoleTeacher)
+ Enrollment.EnrollmentType.Ta -> resources.getString(R.string.staffRoleTeacherAssistant)
+ else -> ""
+ }
+ }
+
+ fun refresh() {
+ _state.postValue(ViewState.Refresh)
+ loadData(true)
+ }
+
+ fun onImportantLinksViewsReady() {
+ _events.postValue(Event(ResourcesAction.ImportantLinksViewsReady))
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ContactInfoItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ContactInfoItemViewModel.kt
new file mode 100644
index 0000000000..137a3f2ff3
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ContactInfoItemViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.pandautils.features.elementary.resources.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.ContactInfoViewData
+import com.instructure.pandautils.features.elementary.resources.ResourcesItemViewType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ContactInfoItemViewModel(
+ val data: ContactInfoViewData,
+ val onClick: () -> Unit
+) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_contact_info
+
+ override val viewType: Int = ResourcesItemViewType.CONTACT_INFO.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ImportantLinksItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ImportantLinksItemViewModel.kt
new file mode 100644
index 0000000000..841a6e3779
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ImportantLinksItemViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.pandautils.features.elementary.resources.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.ImportantLinksViewData
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ImportantLinksItemViewModel(
+ val data: ImportantLinksViewData,
+ private val onLtiButtonPressed: (url: String, content: String) -> Unit
+) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_important_links
+
+ fun onLtiButtonPressed(url: String) {
+ onLtiButtonPressed(url, data.htmlContent)
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/LtiApplicationItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/LtiApplicationItemViewModel.kt
new file mode 100644
index 0000000000..17c0912e83
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/LtiApplicationItemViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.pandautils.features.elementary.resources.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.LtiApplicationViewData
+import com.instructure.pandautils.features.elementary.resources.ResourcesItemViewType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class LtiApplicationItemViewModel(
+ val data: LtiApplicationViewData,
+ val marginBottom: Int,
+ val onClick: () -> Unit) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_lti_application
+
+ override val viewType: Int = ResourcesItemViewType.LTI_APPLICATION.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesHeaderViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesHeaderViewModel.kt
new file mode 100644
index 0000000000..efeb42a983
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesHeaderViewModel.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.pandautils.features.elementary.resources.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.ResourcesHeaderViewData
+import com.instructure.pandautils.features.elementary.resources.ResourcesItemViewType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ResourcesHeaderViewModel(val data: ResourcesHeaderViewData) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_resources_header
+
+ override val viewType: Int = ResourcesItemViewType.RESOURCES_HEADER.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesRouter.kt
new file mode 100644
index 0000000000..678934297f
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/resources/itemviewmodels/ResourcesRouter.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.pandautils.features.elementary.resources.itemviewmodels
+
+import com.instructure.canvasapi2.models.LTITool
+import com.instructure.canvasapi2.models.User
+
+interface ResourcesRouter {
+ fun openLti(ltiTool: LTITool)
+ fun openComposeMessage(user: User)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleFragment.kt
index 486ffa1d17..f536821b61 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleFragment.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleFragment.kt
@@ -21,17 +21,116 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
-import com.instructure.pandautils.R
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.instructure.pandautils.databinding.FragmentScheduleBinding
+import com.instructure.pandautils.features.elementary.schedule.pager.SchedulePagerFragment
+import com.instructure.pandautils.utils.StringArg
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.android.synthetic.main.fragment_schedule.*
+import javax.inject.Inject
+@AndroidEntryPoint
class ScheduleFragment : Fragment() {
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return layoutInflater.inflate(R.layout.fragment_schedule, container, false)
+ @Inject
+ lateinit var scheduleRouter: ScheduleRouter
+
+ private val viewModel: ScheduleViewModel by viewModels()
+
+ private val adapter = ScheduleRecyclerViewAdapter()
+
+ private var startDateString by StringArg()
+
+ private var recyclerView: RecyclerView? = null
+
+ private val onScrollListener = object : RecyclerView.OnScrollListener() {
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ super.onScrolled(recyclerView, dx, dy)
+ checkFirstPosition()
+ }
}
- companion object {
- fun newInstance(): ScheduleFragment {
- return ScheduleFragment()
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ val binding = FragmentScheduleBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ binding.adapter = adapter
+ recyclerView = binding.scheduleRecyclerView
+
+ viewModel.getDataForDate(startDateString)
+
+ viewModel.events.observe(viewLifecycleOwner, { event ->
+ event.getContentIfNotHandled()?.let {
+ handleAction(it)
+ }
+ })
+
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ recyclerView?.addOnScrollListener(onScrollListener)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ recyclerView?.removeOnScrollListener(onScrollListener)
+ }
+
+ private fun handleAction(action: ScheduleAction) {
+ when (action) {
+ is ScheduleAction.OpenCourse -> scheduleRouter.openCourse(action.course)
+ is ScheduleAction.OpenAssignment -> scheduleRouter.openAssignment(action.canvasContext, action.assignmentId)
+ is ScheduleAction.OpenCalendarEvent -> scheduleRouter.openCalendarEvent(
+ action.canvasContext,
+ action.scheduleItemId
+ )
+ is ScheduleAction.OpenQuiz -> {
+ scheduleRouter.openQuiz(action.canvasContext, action.htmlUrl)
+ }
+ is ScheduleAction.OpenDiscussion -> {
+ scheduleRouter.openDiscussion(action.canvasContext, action.id, action.title)
+ }
+ is ScheduleAction.JumpToToday -> {
+ jumpToToday()
+ }
}
}
+
+ fun jumpToToday() {
+ if (recyclerView?.layoutManager is LinearLayoutManager) {
+ (recyclerView?.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
+ viewModel.todayPosition,
+ 0
+ )
+ }
+ }
+
+ override fun setMenuVisibility(menuVisible: Boolean) {
+ if (menuVisible) {
+ checkFirstPosition()
+ }
+ super.setMenuVisibility(menuVisible)
+ }
+
+ fun checkFirstPosition() {
+ val firstItemPosition =
+ (scheduleRecyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
+ val todayRange = viewModel.getTodayRange()
+ if (todayRange != null) {
+ toggleJumpToTodayButton(firstItemPosition !in todayRange)
+ }
+ }
+
+ private fun toggleJumpToTodayButton(visible: Boolean) {
+ (requireParentFragment() as SchedulePagerFragment).setTodayButtonVisibility(visible)
+ }
+
+ companion object {
+
+ fun newInstance(startDate: String) = ScheduleFragment().apply { startDateString = startDate }
+ }
}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRecyclerViewAdapter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRecyclerViewAdapter.kt
new file mode 100644
index 0000000000..2f02fca717
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRecyclerViewAdapter.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.pandautils.features.elementary.schedule
+
+import android.view.LayoutInflater
+import androidx.databinding.ViewDataBinding
+import androidx.databinding.library.baseAdapters.BR
+import androidx.recyclerview.widget.RecyclerView
+import com.instructure.pandautils.adapters.itemdecorations.StickyHeaderInterface
+import com.instructure.pandautils.adapters.itemdecorations.StickyHeaderItemDecoration
+import com.instructure.pandautils.binding.BindableRecyclerViewAdapter
+import com.instructure.pandautils.databinding.ItemScheduleDayHeaderBinding
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.ScheduleDayGroupItemViewModel
+
+class ScheduleRecyclerViewAdapter : BindableRecyclerViewAdapter(), StickyHeaderInterface {
+
+ override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+ super.onAttachedToRecyclerView(recyclerView)
+ recyclerView.addItemDecoration(StickyHeaderItemDecoration(this))
+ }
+
+ override fun getHeaderPositionForItem(itemPosition: Int): Int {
+ var startPosition = itemPosition
+ var headerPosition = 0
+ do {
+ if (isHeader(startPosition)) {
+ headerPosition = startPosition
+ break
+ }
+ startPosition -= 1
+ } while (startPosition >= 0)
+ return headerPosition
+ }
+
+ override fun getHeaderBinding(
+ headerPosition: Int,
+ parent: RecyclerView,
+ hasChildInContact: Boolean
+ ): ViewDataBinding {
+ val binding = ItemScheduleDayHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ binding.setVariable(BR.itemViewModel, itemViewModels[headerPosition])
+ binding.hasDivider = hasChildInContact
+ binding.invalidateAll()
+ return binding
+ }
+
+ override fun isHeader(itemPosition: Int): Boolean {
+ return if (itemPosition == RecyclerView.NO_POSITION) {
+ false
+ } else {
+ itemViewModels[itemPosition] is ScheduleDayGroupItemViewModel
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRouter.kt
new file mode 100644
index 0000000000..52324f9330
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleRouter.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.pandautils.features.elementary.schedule
+
+import com.instructure.canvasapi2.models.CanvasContext
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.DiscussionTopicHeader
+
+interface ScheduleRouter {
+
+ fun openAssignment(canvasContext: CanvasContext, assignmentId: Long)
+
+ fun openCalendarEvent(canvasContext: CanvasContext, scheduleItemId: Long)
+
+ fun openAnnouncementDetails(course: Course, announcement: DiscussionTopicHeader)
+
+ fun openQuiz(canvasContext: CanvasContext, htmlUrl: String)
+
+ fun openDiscussion(canvasContext: CanvasContext, discussionId: Long, discussionTitle: String)
+
+ fun openCourse(course: Course)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewData.kt
new file mode 100644
index 0000000000..49ea8ca8d8
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewData.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.pandautils.features.elementary.schedule
+
+import androidx.annotation.*
+import com.instructure.canvasapi2.models.CanvasContext
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.DiscussionTopicHeader
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.ScheduleDayGroupItemViewModel
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.SchedulePlannerItemTagItemViewModel
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.SchedulePlannerItemViewModel
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+data class ScheduleViewData(val itemViewModels: List)
+
+data class ScheduleCourseViewData(
+ val courseName: String,
+ val openable: Boolean,
+ val courseColor: String,
+ val imageUrl: String,
+ val plannerItems: List
+)
+
+data class SchedulePlannerItemData(
+ val title: String,
+ val type: PlannerItemType,
+ val points: String?,
+ val dueDate: String?,
+ val openable: Boolean,
+ val contentDescription: String,
+ val chips: List
+)
+
+data class ScheduleEmptyViewData(
+ val title: String
+)
+
+data class SchedulePlannerItemTag(
+ val text: String,
+ @ColorInt val color: Int
+)
+
+data class ScheduleMissingItemData(
+ val title: String?,
+ val dueString: String?,
+ val points: String?,
+ val type: PlannerItemType,
+ val courseName: String?,
+ val courseColor: String,
+ val contentDescription: String
+)
+
+enum class PlannerItemType(@DrawableRes val iconRes: Int) {
+ ANNOUNCEMENT(R.drawable.ic_announcement),
+ ASSIGNMENT(R.drawable.ic_assignment),
+ QUIZ(R.drawable.ic_quiz),
+ DISCUSSION(R.drawable.ic_discussion),
+ PEER_REVIEW(R.drawable.ic_peer_review),
+ CALENDAR_EVENT(R.drawable.ic_calendar),
+ PAGE(R.drawable.ic_pages),
+ TO_DO(R.drawable.ic_calendar)
+}
+
+enum class ScheduleItemViewModelType(val viewType: Int) {
+ COURSE(1),
+ DAY_HEADER(2),
+ PLANNER_ITEM(3),
+ EMPTY(4),
+ MISSING_HEADER(5),
+ MISSING_ITEM(6)
+}
+
+sealed class PlannerItemTag(val text: Int, @ColorRes val color: Int) {
+ object Excused: PlannerItemTag(R.string.schedule_tag_excused, R.color.textLightGray)
+ object Graded : PlannerItemTag(R.string.schedule_tag_graded, R.color.textLightGray)
+ data class Replies(val replyCount: Int) : PlannerItemTag(R.plurals.schedule_tag_replies, R.color.textLightGray)
+ object Feedback : PlannerItemTag(R.string.schedule_tag_feedback, R.color.textLightGray)
+ object Late : PlannerItemTag(R.string.schedule_tag_late, R.color.canvasRed)
+ object Redo : PlannerItemTag(R.string.schedule_tag_redo, R.color.canvasRed)
+ object Missing : PlannerItemTag(R.string.schedule_tag_missing, R.color.canvasRed)
+}
+
+sealed class ScheduleAction {
+ data class OpenCourse(val course: Course) : ScheduleAction()
+ data class OpenAssignment(val canvasContext: CanvasContext, val assignmentId: Long) : ScheduleAction()
+ data class OpenCalendarEvent(val canvasContext: CanvasContext, val scheduleItemId: Long) : ScheduleAction()
+ data class OpenQuiz(val canvasContext: CanvasContext, val htmlUrl: String) : ScheduleAction()
+ data class OpenDiscussion(val canvasContext: CanvasContext, val id: Long, val title: String) : ScheduleAction()
+ object JumpToToday : ScheduleAction()
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModel.kt
new file mode 100644
index 0000000000..66002aa8b5
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModel.kt
@@ -0,0 +1,572 @@
+/*
+ * 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.pandautils.features.elementary.schedule
+
+import android.content.res.Resources
+import android.util.Range
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.instructure.canvasapi2.managers.*
+import com.instructure.canvasapi2.models.*
+import com.instructure.canvasapi2.utils.ApiPrefs
+import com.instructure.canvasapi2.utils.toApiString
+import com.instructure.canvasapi2.utils.toDate
+import com.instructure.pandautils.R
+import com.instructure.pandautils.binding.GroupItemViewModel
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.*
+import com.instructure.pandautils.mvvm.Event
+import com.instructure.pandautils.mvvm.ItemViewModel
+import com.instructure.pandautils.mvvm.ViewState
+import com.instructure.pandautils.utils.*
+import com.instructure.pandautils.utils.date.DateTimeProvider
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import java.text.DecimalFormat
+import java.text.SimpleDateFormat
+import java.util.*
+import javax.inject.Inject
+
+const val TODO_COLOR = "#0081BD"
+
+@HiltViewModel
+class ScheduleViewModel @Inject constructor(
+ private val apiPrefs: ApiPrefs,
+ private val resources: Resources,
+ private val plannerManager: PlannerManager,
+ private val courseManager: CourseManager,
+ private val userManager: UserManager,
+ private val calendarEventManager: CalendarEventManager,
+ private val assignmentManager: AssignmentManager,
+ private val missingItemsPrefs: MissingItemsPrefs,
+ private val dateTimeProvider: DateTimeProvider
+) : ViewModel() {
+
+ private lateinit var startDate: Date
+
+ private lateinit var missingSubmissions: List
+ private lateinit var calendarEvents: Map
+ private lateinit var discussions: Map
+ private lateinit var plannerItems: List
+ private lateinit var coursesMap: Map
+
+ private var todayHeader: ScheduleDayGroupItemViewModel? = null
+ private val simpleDateFormat = SimpleDateFormat("hh:mm aa", Locale.getDefault())
+
+ var todayPosition: Int = -1
+
+ val state: LiveData
+ get() = _state
+ private val _state = MutableLiveData()
+
+ val data: LiveData
+ get() = _data
+ private val _data = MutableLiveData()
+
+ val events: LiveData>
+ get() = _events
+ private val _events = MutableLiveData>()
+
+ fun getDataForDate(dateString: String) {
+ _state.postValue(ViewState.Loading)
+ startDate = dateString.toDate() ?: dateTimeProvider.getCalendar().time
+ getData(false)
+ }
+
+ fun refresh() {
+ _state.postValue(ViewState.Refresh)
+ getData(true)
+ }
+
+ private fun jumpToToday() {
+ if (todayPosition != -1) {
+ _events.postValue(Event(ScheduleAction.JumpToToday))
+ }
+ }
+
+ private fun getData(forceNetwork: Boolean) {
+ viewModelScope.launch {
+ try {
+ val weekStart = startDate.getLastSunday()
+
+ val courses = courseManager.getCoursesAsync(forceNetwork).await()
+ coursesMap = courses.dataOrThrow
+ .associateBy { it.id }
+
+ plannerItems = plannerManager.getPlannerItemsAsync(
+ forceNetwork,
+ weekStart.toApiString(),
+ startDate.getNextSaturday().toApiString()
+ )
+ .await()
+ .dataOrNull
+ .orEmpty()
+
+ missingSubmissions =
+ userManager.getAllMissingSubmissionsAsync(forceNetwork).await().dataOrNull.orEmpty()
+
+ calendarEvents = plannerItems.filter { it.plannableType == PlannableType.CALENDAR_EVENT }
+ .map { calendarEventManager.getCalendarEventAsync(it.plannable.id, forceNetwork) }
+ .awaitAll()
+ .map { it.dataOrNull }
+ .associateBy { it?.id }
+
+ discussions =
+ plannerItems.filter { (it.plannableType == PlannableType.ANNOUNCEMENT || it.plannableType == PlannableType.DISCUSSION_TOPIC) && it.courseId != null }
+ .map { assignmentManager.getAssignmentAsync(it.plannable.id, it.courseId!!, forceNetwork) }
+ .awaitAll()
+ .map { it.dataOrNull }
+ .associateBy { it?.id }
+
+ val itemViewModels = mutableListOf()
+ for (i in 0..6) {
+ val calendar = dateTimeProvider.getCalendar()
+ calendar.time = weekStart
+ calendar.add(Calendar.DATE, i)
+ val date = calendar.time
+
+ itemViewModels.add(createItemsForDate(date))
+ }
+ _data.postValue(ScheduleViewData(itemViewModels))
+ _state.postValue(ViewState.Success)
+ todayPosition = calculateTodayPosition(itemViewModels)
+ jumpToToday()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ _state.postValue(ViewState.Error(resources.getString(R.string.schedule_error_message)))
+ }
+ }
+ }
+
+ private fun createItemsForDate(date: Date): ScheduleDayGroupItemViewModel {
+ val items = mutableListOf()
+
+ items.addAll(createCourseItems(date))
+
+ val today = dateTimeProvider.getCalendar().time
+ if (date.isSameDay(today) && missingSubmissions.isNotEmpty()) {
+ items.add(createMissingItems())
+ }
+
+ val dayHeader = createDayHeader(date, items)
+
+ if (date.isSameDay(today)) {
+ todayHeader = dayHeader
+ }
+
+ return dayHeader
+ }
+
+ private fun createMissingItems(): ScheduleMissingItemsGroupItemViewModel {
+ val missingItems = missingSubmissions.map { assignment ->
+ ScheduleMissingItemViewModel(
+ data = ScheduleMissingItemData(
+ title = assignment.name,
+ dueString = assignment.dueDate?.let {
+ resources.getString(
+ R.string.schedule_due_text,
+ simpleDateFormat.format(it)
+ )
+ },
+ points = getPointsText(assignment.pointsPossible),
+ type = if (assignment.discussionTopicHeader != null) PlannerItemType.DISCUSSION else PlannerItemType.ASSIGNMENT,
+ courseName = coursesMap[assignment.courseId]?.name,
+ courseColor = getCourseColor(coursesMap[assignment.courseId]),
+ contentDescription = createMissingItemContentDescription(assignment)
+ ),
+ open = {
+ val course = coursesMap[assignment.courseId]
+ if (course != null) {
+ if (assignment.discussionTopicHeader != null) {
+ _events.postValue(
+ Event(
+ ScheduleAction.OpenDiscussion(
+ course,
+ assignment.discussionTopicHeader!!.id,
+ assignment.discussionTopicHeader!!.title
+ ?: ""
+ )
+ )
+ )
+ } else {
+ _events.postValue(Event(ScheduleAction.OpenAssignment(course, assignment.id)))
+ }
+ }
+ }
+ )
+ }
+ return ScheduleMissingItemsGroupItemViewModel(missingItemsPrefs = missingItemsPrefs, items = missingItems)
+ }
+
+ private fun createMissingItemContentDescription(assignment: Assignment): String {
+ val typeContentDescription = if (assignment.discussionTopicHeader != null) resources.getString(R.string.a11y_discussion_topic) else resources.getString(R.string.a11y_assignment)
+ val pointsContentDescription = resources.getQuantityString(R.plurals.a11y_schedule_points, assignment.pointsPossible.toInt(), assignment.pointsPossible)
+ val dueContentDescription = assignment.dueDate?.let {
+ resources.getString(
+ R.string.schedule_due_text,
+ simpleDateFormat.format(it)
+ )
+ }
+ val courseContentDescription = resources.getString(R.string.a11y_schedule_course_header_content_description, coursesMap[assignment.courseId]?.name)
+
+ return "$typeContentDescription ${assignment.name} $courseContentDescription $pointsContentDescription $dueContentDescription"
+ }
+
+ private fun createDayHeader(date: Date, items: List): ScheduleDayGroupItemViewModel {
+ return ScheduleDayGroupItemViewModel(
+ getTitleForDate(date),
+ SimpleDateFormat("MMMM dd", Locale.getDefault()).format(date),
+ !dateTimeProvider.getCalendar().time.isSameDay(date),
+ items
+ )
+ }
+
+ private fun getTitleForDate(date: Date): String {
+ val today = dateTimeProvider.getCalendar().time
+ if (date.isSameDay(today)) return resources.getString(R.string.today)
+ if (date.isNextDay(today)) return resources.getString(R.string.tomorrow)
+ if (date.isPreviousDay(today)) return resources.getString(R.string.yesterday)
+ return SimpleDateFormat("EEEE", Locale.getDefault()).format(date)
+ }
+
+ private fun createCourseItems(date: Date): List {
+ val coursePlannerMap = plannerItems
+ .filter {
+ date.isSameDay(it.plannableDate)
+ }
+ .groupBy { coursesMap[it.courseId ?: it.plannable.courseId] }
+
+
+ val courseViewModels = coursePlannerMap.entries
+ .sortedBy { it.key?.name }
+ .map { entry ->
+ val scheduleViewData = ScheduleCourseViewData(
+ entry.key?.name ?: resources.getString(R.string.schedule_todo_title),
+ entry.key != null,
+ getCourseColor(entry.key),
+ entry.key?.imageUrl ?: "",
+ entry.value.map {
+ createPlannerItemViewModel(it)
+ }
+ )
+ ScheduleCourseItemViewModel(
+ scheduleViewData
+ ) {
+ entry.key?.let { course ->
+ _events.postValue(Event(ScheduleAction.OpenCourse(course)))
+ }
+ }
+ }
+
+ return if (courseViewModels.isEmpty()) {
+ listOf(
+ ScheduleEmptyItemViewModel(
+ ScheduleEmptyViewData(resources.getString(R.string.nothing_planned_yet))
+ )
+ )
+ } else {
+ courseViewModels
+ }
+ }
+
+ private fun createChips(plannerItem: PlannerItem): List {
+ val chips = mutableListOf()
+
+ if (plannerItem.submissionState?.graded == true && plannerItem.submissionState?.excused == false) {
+ chips.add(PlannerItemTag.Graded)
+ }
+
+ if (plannerItem.submissionState?.excused == true) {
+ chips.add(PlannerItemTag.Excused)
+ }
+
+ if (plannerItem.submissionState?.withFeedback == true) {
+ chips.add(PlannerItemTag.Feedback)
+ }
+
+ if (plannerItem.submissionState?.missing == true) {
+ chips.add(PlannerItemTag.Missing)
+ }
+
+ if (plannerItem.submissionState?.late == true) {
+ chips.add(PlannerItemTag.Late)
+ }
+
+ if (plannerItem.submissionState?.redoRequest == true) {
+ chips.add(PlannerItemTag.Redo)
+ }
+
+ if (plannerItem.plannableType == PlannableType.DISCUSSION_TOPIC || plannerItem.plannableType == PlannableType.ANNOUNCEMENT) {
+ val discussion = discussions[plannerItem.plannable.id]
+ discussion?.discussionTopicHeader?.unreadCount?.let { unreadCount ->
+ if (unreadCount > 0) {
+ chips.add(PlannerItemTag.Replies(unreadCount))
+ }
+ }
+ }
+
+ return chips.map {
+ SchedulePlannerItemTagItemViewModel(
+ SchedulePlannerItemTag(
+ if (it is PlannerItemTag.Replies) resources.getQuantityString(it.text, it.replyCount, it.replyCount)
+ else resources.getString(it.text),
+ resources.getColor(it.color)
+ )
+ )
+ }
+ }
+
+ private fun createPlannerItemViewModel(plannerItem: PlannerItem): SchedulePlannerItemViewModel {
+ return SchedulePlannerItemViewModel(
+ SchedulePlannerItemData(
+ plannerItem.plannable.title,
+ getTypeForPlannerItem(plannerItem),
+ getPointsText(plannerItem.plannable.pointsPossible),
+ getDueText(plannerItem),
+ isPlannableOpenable(plannerItem),
+ createContentDescription(plannerItem),
+ createChips(plannerItem)
+ ),
+ plannerItem.plannerOverride?.markedComplete ?: false || plannerItem.submissionState?.submitted ?: false,
+ { scheduleItemViewModel, markedAsDone ->
+ updatePlannerOverride(
+ plannerItem,
+ scheduleItemViewModel,
+ markedAsDone
+ )
+ },
+ { openPlannable(plannerItem) }
+ )
+ }
+
+ private fun createContentDescription(plannerItem: PlannerItem): String {
+ val typeContentDescription = createTypeContentDescription(plannerItem.plannableType)
+ val dateContentDescription = getDueText(plannerItem)
+ val markedAsDoneContentDescription =
+ if (plannerItem.plannerOverride?.markedComplete == true || plannerItem.submissionState?.submitted == true) resources.getString(R.string.a11y_marked_as_done) else resources.getString(
+ R.string.a11y_not_marked_as_done
+ )
+
+ return "$typeContentDescription ${plannerItem.plannable.title} $dateContentDescription $markedAsDoneContentDescription"
+ }
+
+ private fun createTypeContentDescription(plannableType: PlannableType): String {
+ return when (plannableType) {
+ PlannableType.ANNOUNCEMENT -> resources.getString(R.string.a11y_announcement)
+ PlannableType.DISCUSSION_TOPIC -> resources.getString(R.string.a11y_discussion_topic)
+ PlannableType.CALENDAR_EVENT -> resources.getString(R.string.a11y_calendar_event)
+ PlannableType.ASSIGNMENT -> resources.getString(R.string.a11y_assignment)
+ PlannableType.PLANNER_NOTE -> resources.getString(R.string.a11y_planner_note)
+ PlannableType.QUIZ -> resources.getString(R.string.a11y_quiz)
+ PlannableType.TODO -> resources.getString(R.string.a11y_todo)
+ PlannableType.WIKI_PAGE -> resources.getString(R.string.a11y_page)
+ }
+ }
+
+ private fun updatePlannerOverride(
+ plannerItem: PlannerItem,
+ itemViewModel: SchedulePlannerItemViewModel,
+ markedAsDone: Boolean
+ ) {
+ if (itemViewModel.completed == markedAsDone) return
+
+ viewModelScope.launch {
+ itemViewModel.apply {
+ completed = markedAsDone
+ notifyChange()
+ }
+ try {
+ if (plannerItem.plannerOverride == null) {
+ val plannerOverride = PlannerOverride(
+ plannableType = plannerItem.plannableType,
+ plannableId = plannerItem.plannable.id,
+ markedComplete = markedAsDone
+ )
+ val createdOverride =
+ plannerManager.createPlannerOverrideAsync(true, plannerOverride).await().dataOrThrow
+ plannerItem.plannerOverride = createdOverride
+ } else {
+ val updatedOverride =
+ plannerManager.updatePlannerOverrideAsync(true, markedAsDone, plannerItem.plannerOverride?.id!!)
+ .await().dataOrThrow
+ plannerItem.plannerOverride = updatedOverride
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ itemViewModel.apply {
+ completed = !markedAsDone
+ notifyChange()
+ }
+ }
+ }
+
+ }
+
+ private fun openPlannable(plannerItem: PlannerItem) {
+ when (plannerItem.plannableType) {
+ PlannableType.ASSIGNMENT -> _events.postValue(
+ Event(
+ ScheduleAction.OpenAssignment(
+ plannerItem.canvasContext,
+ plannerItem.plannable.id
+ )
+ )
+ )
+ PlannableType.CALENDAR_EVENT -> _events.postValue(
+ Event(
+ ScheduleAction.OpenCalendarEvent(
+ plannerItem.canvasContext,
+ plannerItem.plannable.id
+ )
+ )
+ )
+ PlannableType.DISCUSSION_TOPIC -> _events.postValue(
+ Event(
+ ScheduleAction.OpenDiscussion(
+ plannerItem.canvasContext,
+ plannerItem.plannable.id,
+ plannerItem.plannable.title
+ )
+ )
+ )
+ PlannableType.QUIZ -> {
+ if (plannerItem.plannable.assignmentId != null) {
+ // This is a quiz assignment, go to the assignment page
+ _events.postValue(
+ Event(
+ ScheduleAction.OpenAssignment(
+ plannerItem.canvasContext,
+ plannerItem.plannable.id
+ )
+ )
+ )
+ } else {
+ var htmlUrl = plannerItem.htmlUrl.orEmpty()
+ if (htmlUrl.startsWith('/')) htmlUrl = apiPrefs.fullDomain + htmlUrl
+ _events.postValue(Event(ScheduleAction.OpenQuiz(plannerItem.canvasContext, htmlUrl)))
+ }
+ }
+ PlannableType.ANNOUNCEMENT -> _events.postValue(
+ Event(
+ ScheduleAction.OpenDiscussion(
+ plannerItem.canvasContext,
+ plannerItem.plannable.id,
+ plannerItem.plannable.title
+ )
+ )
+ )
+ else -> Unit
+ }
+ }
+
+ private fun isPlannableOpenable(plannerItem: PlannerItem): Boolean {
+ return when (plannerItem.plannableType) {
+ PlannableType.PLANNER_NOTE -> false
+ PlannableType.CALENDAR_EVENT -> true
+ else -> plannerItem.courseId != null
+ }
+ }
+
+ private fun getTypeForPlannerItem(plannerItem: PlannerItem): PlannerItemType {
+ return when (plannerItem.plannableType) {
+ PlannableType.ASSIGNMENT -> PlannerItemType.ASSIGNMENT
+ PlannableType.ANNOUNCEMENT -> PlannerItemType.ANNOUNCEMENT
+ PlannableType.QUIZ -> PlannerItemType.QUIZ
+ PlannableType.WIKI_PAGE -> PlannerItemType.PAGE
+ PlannableType.CALENDAR_EVENT -> PlannerItemType.CALENDAR_EVENT
+ PlannableType.DISCUSSION_TOPIC -> PlannerItemType.DISCUSSION
+ PlannableType.PLANNER_NOTE -> PlannerItemType.TO_DO
+ PlannableType.TODO -> PlannerItemType.TO_DO
+ }
+ }
+
+ private fun getPointsText(points: Double?): String? {
+ if (points == null) return null
+ val numberFormatter = DecimalFormat("##.##")
+ return resources.getQuantityString(R.plurals.schedule_points, points.toInt(), numberFormatter.format(points))
+ }
+
+ private fun getDueText(plannerItem: PlannerItem): String {
+ return when (plannerItem.plannableType) {
+ PlannableType.ANNOUNCEMENT -> simpleDateFormat.format(plannerItem.plannableDate)
+ PlannableType.CALENDAR_EVENT -> getCalendarEventDueText(plannerItem)
+ PlannableType.PLANNER_NOTE -> resources.getString(
+ R.string.schedule_todo_due_text,
+ simpleDateFormat.format(plannerItem.plannable.todoDate.toDate() ?: plannerItem.plannableDate)
+ )
+ else -> resources.getString(R.string.schedule_due_text, simpleDateFormat.format(plannerItem.plannableDate))
+ }
+ }
+
+ private fun getCalendarEventDueText(plannerItem: PlannerItem): String {
+ val calendarEvent = calendarEvents[plannerItem.plannable.id]
+ if (calendarEvent?.isAllDay == true) {
+ return resources.getString(R.string.schedule_all_day_event_text)
+ } else {
+ val startText = calendarEvent?.startDate?.let { simpleDateFormat.format(it) }
+ val endText = calendarEvent?.endDate?.let { simpleDateFormat.format(it) }
+ if (startText != null && endText != null) {
+ return resources.getString(R.string.schedule_calendar_event_interval_text, startText, endText)
+ }
+ }
+
+ return resources.getString(
+ R.string.schedule_calendar_event_due_text,
+ simpleDateFormat.format(plannerItem.plannableDate)
+ )
+ }
+
+ private fun calculateTodayPosition(items: List): Int {
+ var position = -1
+ if (todayHeader != null) {
+ items.forEach {
+ position++
+ if (it == todayHeader) return position
+ position += getGroupOpenChildCount(it)
+ }
+ }
+ return position
+ }
+
+ private fun getGroupOpenChildCount(group: GroupItemViewModel): Int {
+ var childCount = 0
+ if (!group.collapsed) {
+ childCount += group.items.size
+ group.items.filterIsInstance().forEach {
+ childCount += getGroupOpenChildCount(it)
+ }
+ }
+ return childCount
+ }
+
+ private fun getCourseColor(course: Course?): String {
+ return when {
+ !course?.courseColor.isNullOrEmpty() -> course?.courseColor!!
+ !course?.name.isNullOrEmpty() -> ColorApiHelper.K5_DEFAULT_COLOR
+ else -> TODO_COLOR
+ }
+ }
+
+ fun getTodayRange(): Range? {
+ todayHeader?.let {
+ return Range(todayPosition, todayPosition + getGroupOpenChildCount(it))
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleCourseItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleCourseItemViewModel.kt
new file mode 100644
index 0000000000..cd890b669a
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleCourseItemViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.ScheduleCourseViewData
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.features.elementary.schedule.ScheduleViewData
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ScheduleCourseItemViewModel(
+ val data: ScheduleCourseViewData,
+ val onHeaderClick: () -> Unit
+) : ItemViewModel {
+ override val layoutId: Int = R.layout.item_schedule_course
+
+ override val viewType: Int = ScheduleItemViewModelType.COURSE.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleDayGroupItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleDayGroupItemViewModel.kt
new file mode 100644
index 0000000000..27d1094b1d
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleDayGroupItemViewModel.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.binding.GroupItemViewModel
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ScheduleDayGroupItemViewModel(
+ val dayText: String,
+ val dateText: String,
+ val todayVisible: Boolean,
+ items: List
+) : GroupItemViewModel(collapsable = false, items = items) {
+
+ override val layoutId: Int = R.layout.item_schedule_day_header
+
+ override val viewType: Int = ScheduleItemViewModelType.DAY_HEADER.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleEmptyItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleEmptyItemViewModel.kt
new file mode 100644
index 0000000000..46db2611d3
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleEmptyItemViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.ScheduleEmptyViewData
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ScheduleEmptyItemViewModel(
+ val data: ScheduleEmptyViewData
+) : ItemViewModel {
+
+ override val layoutId: Int = R.layout.item_schedule_empty
+
+ override val viewType: Int = ScheduleItemViewModelType.EMPTY.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemViewModel.kt
new file mode 100644
index 0000000000..425d1d461c
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemViewModel.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.features.elementary.schedule.ScheduleMissingItemData
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class ScheduleMissingItemViewModel(
+ val data: ScheduleMissingItemData,
+ val open: () -> Unit
+) : ItemViewModel {
+ override val layoutId: Int = R.layout.item_schedule_missing_item
+ override val viewType: Int = ScheduleItemViewModelType.MISSING_ITEM.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemsGroupItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemsGroupItemViewModel.kt
new file mode 100644
index 0000000000..71b2b2600b
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/ScheduleMissingItemsGroupItemViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.binding.GroupItemViewModel
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.utils.MissingItemsPrefs
+
+class ScheduleMissingItemsGroupItemViewModel(
+ private val missingItemsPrefs: MissingItemsPrefs,
+ items: List
+) : GroupItemViewModel(collapsable = true, collapsed = missingItemsPrefs.itemsCollapsed, items = items) {
+ override val layoutId: Int = R.layout.item_schedule_missing_header
+
+ override val viewType: Int = ScheduleItemViewModelType.MISSING_HEADER.viewType
+
+ override fun toggleItems() {
+ super.toggleItems()
+ missingItemsPrefs.itemsCollapsed = collapsed
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemTagItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemTagItemViewModel.kt
new file mode 100644
index 0000000000..3a7407938d
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemTagItemViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.pandautils.features.elementary.schedule.itemviewmodels
+
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.PlannerItemTag
+import com.instructure.pandautils.features.elementary.schedule.SchedulePlannerItemTag
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class SchedulePlannerItemTagItemViewModel(
+ val data: SchedulePlannerItemTag
+) : ItemViewModel {
+ override val layoutId: Int = R.layout.item_schedule_planner_item_tag
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemViewModel.kt
new file mode 100644
index 0000000000..338f41f73d
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/itemviewmodels/SchedulePlannerItemViewModel.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.pandautils.features.elementary.schedule.itemviewmodels
+
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.ScheduleItemViewModelType
+import com.instructure.pandautils.features.elementary.schedule.SchedulePlannerItemData
+import com.instructure.pandautils.mvvm.ItemViewModel
+
+class SchedulePlannerItemViewModel(
+ val data: SchedulePlannerItemData,
+ @get:Bindable var completed: Boolean,
+ val markAsDone: (itemViewModel: SchedulePlannerItemViewModel, done: Boolean) -> Unit,
+ val open: () -> Unit
+) : ItemViewModel, BaseObservable() {
+ override val layoutId: Int = R.layout.item_schedule_planner_item
+
+ override val viewType: Int = ScheduleItemViewModelType.PLANNER_ITEM.viewType
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerFragment.kt
new file mode 100644
index 0000000000..5a1a5a9442
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerFragment.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.pandautils.features.elementary.schedule.pager
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import androidx.viewpager2.widget.ViewPager2
+import com.instructure.pandautils.databinding.FragmentSchedulePagerBinding
+import com.instructure.pandautils.features.elementary.schedule.ScheduleFragment
+import com.instructure.pandautils.utils.isAccessibilityEnabled
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.android.synthetic.main.fragment_schedule_pager.*
+
+@AndroidEntryPoint
+class SchedulePagerFragment : Fragment() {
+
+ private val viewModel: SchedulePagerViewModel by viewModels()
+
+ private val todayButtonLiveData = MutableLiveData()
+
+ private var todayFragment: ScheduleFragment? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val binding = FragmentSchedulePagerBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ viewModel.data.observe(viewLifecycleOwner, { schedulePagerViewData ->
+ schedulePagerViewData?.let {
+ schedulePager.adapter = object : FragmentStateAdapter(this) {
+ override fun getItemCount(): Int = it.pageStartDates.size
+
+ override fun createFragment(position: Int): Fragment {
+ val fragment = ScheduleFragment.newInstance(it.pageStartDates[position])
+ if (position == THIS_WEEKS_POSITION) {
+ todayFragment = fragment
+ }
+ return fragment
+ }
+
+ }
+ }
+ })
+
+ viewModel.events.observe(viewLifecycleOwner, { event ->
+ event.getContentIfNotHandled()?.let {
+ handleAction(it)
+ }
+ })
+
+ schedulePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ if (position != THIS_WEEKS_POSITION) {
+ setTodayButtonVisibility(true)
+ }
+
+ if (position == 0) {
+ previousWeekButton.visibility = View.GONE
+ } else if (position == schedulePager.childCount - 1) {
+ nextWeekButton.visibility = View.GONE
+ } else {
+ previousWeekButton.visibility = View.VISIBLE
+ nextWeekButton.visibility = View.VISIBLE
+ }
+ }
+ })
+
+ if (isAccessibilityEnabled(requireContext())) {
+ movePagerControlToTop()
+ }
+ }
+
+ private fun movePagerControlToTop() {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(schedulePage)
+ constraintSet.clear(controls.id, ConstraintSet.BOTTOM)
+ constraintSet.clear(schedulePager.id, ConstraintSet.BOTTOM)
+ constraintSet.clear(schedulePager.id, ConstraintSet.TOP)
+ constraintSet.connect(controls.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
+ constraintSet.connect(schedulePager.id, ConstraintSet.TOP, controls.id, ConstraintSet.BOTTOM)
+ constraintSet.connect(schedulePager.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
+ constraintSet.applyTo(schedulePage)
+ }
+
+ private fun handleAction(action: SchedulePagerAction) {
+ when (action) {
+ is SchedulePagerAction.SelectPage -> schedulePager.setCurrentItem(action.position, false)
+ is SchedulePagerAction.MoveToNext -> schedulePager.setCurrentItem(schedulePager.currentItem + 1, true)
+ is SchedulePagerAction.MoveToPrevious -> schedulePager.setCurrentItem(schedulePager.currentItem - 1, true)
+ }
+ }
+
+ fun getTodayButtonVisibility(): LiveData {
+ return todayButtonLiveData
+ }
+
+ fun setTodayButtonVisibility(visible: Boolean) {
+ todayButtonLiveData.postValue(visible)
+ }
+
+ fun jumpToToday() {
+ schedulePager.setCurrentItem(THIS_WEEKS_POSITION, true)
+ todayFragment?.jumpToToday()
+ }
+
+ companion object {
+ fun newInstance() = SchedulePagerFragment()
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewData.kt
new file mode 100644
index 0000000000..a021a6197e
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewData.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.pandautils.features.elementary.schedule.pager
+
+data class SchedulePagerViewData(val pageStartDates: List)
+
+sealed class SchedulePagerAction {
+ data class SelectPage(val position: Int) : SchedulePagerAction()
+ object MoveToNext : SchedulePagerAction()
+ object MoveToPrevious: SchedulePagerAction()
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewModel.kt
new file mode 100644
index 0000000000..17c6c34b30
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/elementary/schedule/pager/SchedulePagerViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.pandautils.features.elementary.schedule.pager
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.instructure.canvasapi2.utils.toApiString
+import com.instructure.pandautils.mvvm.Event
+import com.instructure.pandautils.utils.date.DateTimeProvider
+import dagger.hilt.android.lifecycle.HiltViewModel
+import java.util.*
+import javax.inject.Inject
+
+
+const val SCHEDULE_PAGE_COUNT = 53
+const val THIS_WEEKS_POSITION = 27
+
+@HiltViewModel
+class SchedulePagerViewModel @Inject constructor(
+ dateTimeProvider: DateTimeProvider
+) : ViewModel() {
+
+ val data: LiveData
+ get() = _data
+ private val _data = MutableLiveData()
+
+ val events: LiveData>
+ get() = _events
+ private val _events = MutableLiveData>()
+
+ init {
+ val calendar = dateTimeProvider.getCalendar()
+ calendar.roll(Calendar.WEEK_OF_YEAR, -28)
+ val startDates = (0..SCHEDULE_PAGE_COUNT).map {
+ calendar.roll(Calendar.WEEK_OF_YEAR, true)
+ calendar.time.toApiString()
+ }
+
+ _data.postValue(SchedulePagerViewData(startDates))
+ _events.postValue(Event(SchedulePagerAction.SelectPage(THIS_WEEKS_POSITION)))
+ }
+
+ fun onPreviousWeekClick() {
+ _events.postValue(Event(SchedulePagerAction.MoveToPrevious))
+ }
+
+ fun onNextWeekClick() {
+ _events.postValue(Event(SchedulePagerAction.MoveToNext))
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/navigation/WebViewRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/navigation/WebViewRouter.kt
new file mode 100644
index 0000000000..b0061f4058
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/navigation/WebViewRouter.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.pandautils.navigation
+
+interface WebViewRouter {
+
+ fun canRouteInternally(url: String): Boolean
+
+ fun routeInternally(url: String)
+
+ fun openMedia(url: String)
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt
index 5d9195085b..b2a9c6f426 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt
@@ -43,4 +43,19 @@ object ColorUtils {
canvas.drawBitmap(mutableBitmap, 0f, 0f, paint)
return mutableBitmap
}
+
+ @JvmStatic
+ @JvmOverloads
+ fun parseColor(colorCode: String, defaultColorCode: String = ColorApiHelper.K5_DEFAULT_COLOR): Int {
+ return try {
+ val fullColorCode = if (colorCode.length == 4 && colorCode[0].toString() == "#") {
+ "#${colorCode[1]}${colorCode[1]}${colorCode[2]}${colorCode[2]}${colorCode[3]}${colorCode[3]}"
+ } else {
+ colorCode
+ }
+ Color.parseColor(fullColorCode)
+ } catch (e: IllegalArgumentException) {
+ Color.parseColor(defaultColorCode)
+ }
+ }
}
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/DateExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/DateExtensions.kt
index 7ea3698f20..62c6de2f20 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/DateExtensions.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/DateExtensions.kt
@@ -20,6 +20,7 @@ import org.threeten.bp.LocalDate
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.DateTimeFormatterBuilder
+import java.util.*
fun OffsetDateTime.getShortMonthAndDay(): String {
// Get year if the year of the due date isn't the current year
@@ -30,4 +31,43 @@ fun OffsetDateTime.getShortMonthAndDay(): String {
fun OffsetDateTime.getTime(): String {
val pattern = DateTimeFormatterBuilder().appendPattern("h:mm a").toFormatter()
return format(pattern).toLowerCase()
-}
\ No newline at end of file
+}
+
+fun Date.isSameDay(date: Date?): Boolean {
+ if (date == null) return false
+ val calendar1: Calendar = Calendar.getInstance()
+ calendar1.time = this
+ val calendar2: Calendar = Calendar.getInstance()
+ calendar2.time = date
+ return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR) && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH) && calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH)
+}
+
+fun Date.isNextDay(date: Date?): Boolean {
+ if (date == null) return false
+ val calendar = Calendar.getInstance()
+ calendar.time = date
+ calendar.roll(Calendar.DAY_OF_YEAR, true)
+ return calendar.time.isSameDay(this)
+}
+
+fun Date.isPreviousDay(date: Date?): Boolean {
+ if (date == null) return false
+ val calendar = Calendar.getInstance()
+ calendar.time = date
+ calendar.roll(Calendar.DAY_OF_YEAR, false)
+ return calendar.time.isSameDay(this)
+}
+
+fun Date.getLastSunday(): Date {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ calendar.add(Calendar.DAY_OF_WEEK, -(calendar.get(Calendar.DAY_OF_WEEK) - 1))
+ return calendar.time
+}
+
+fun Date.getNextSaturday(): Date {
+ val calendar = Calendar.getInstance()
+ calendar.time = this
+ calendar.add(Calendar.DAY_OF_WEEK, Calendar.SATURDAY - calendar.get(Calendar.DAY_OF_WEEK))
+ return calendar.time
+}
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/HtmlContentFormatter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/HtmlContentFormatter.kt
index e16b6bb17e..87924ce9a9 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/HtmlContentFormatter.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/HtmlContentFormatter.kt
@@ -58,15 +58,6 @@ class HtmlContentFormatter(
// Handle the LTI case
val newIframe = externalToolIframe(srcUrl, iframe, context)
newHTML = newHTML.replace(iframe, newIframe)
- } else if (srcUrl.contains("media_objects_iframe")) {
- // Handle the new RCE iframe case
- val dataMediaIdMatcher = Pattern.compile("data-media-id=\"([^\"]+)\"").matcher(iframe)
- if (dataMediaIdMatcher.find()) {
- val dataMediaId = dataMediaIdMatcher.group(1)
-
- val newIframe = newRceVideoElement(dataMediaId);
- newHTML = newHTML.replace(iframe, newIframe)
- }
} else if (iframe.contains("id=\"cnvs_content\"")) {
// Handle the cnvs_content special case for some schools
val authenticatedUrl = authenticateLTIUrl(srcUrl)
@@ -115,18 +106,6 @@ class HtmlContentFormatter(
return awaitApi { oAuthManager.getAuthenticatedSession(ltiUrl, it) }.sessionUrl
}
- private fun newRceVideoElement(dataMediaId: String): String {
- // We need to make a new src url with the dataMediaId
- val newSrcUrl = "/users/self/media_download?entryId=$dataMediaId&media_type=video&redirect=1"
-
- // We can't just update the src url in the iframe, as iframes always auto load/play the src
- return """
-
- """.trimIndent()
- }
-
fun createAuthenticatedLtiUrl(html: String, authenticatedSessionUrl: String?): String {
if (authenticatedSessionUrl == null) return html
// Now we need to swap out part of the old url for this new authenticated url
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MissingItemsPrefs.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MissingItemsPrefs.kt
new file mode 100644
index 0000000000..5ee5cdaf78
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MissingItemsPrefs.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.pandautils.utils
+
+import com.instructure.canvasapi2.utils.BooleanPref
+import com.instructure.canvasapi2.utils.PrefManager
+
+object MissingItemsPrefs : PrefManager("missing-items-prefs") {
+
+ var itemsCollapsed by BooleanPref()
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt
index a9f1a10b8c..25aabfa90d 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ProfileUtils.kt
@@ -25,7 +25,13 @@ import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.view.View
+import android.widget.ImageView
+import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
import com.instructure.canvasapi2.models.Author
import com.instructure.canvasapi2.models.BasicUser
import com.instructure.canvasapi2.models.Conversation
@@ -92,6 +98,48 @@ object ProfileUtils {
}
}
+ fun loadAvatarForUser(imageView: ImageView, name: String?, url: String?) {
+ val context = imageView.context
+ if (shouldLoadAltAvatarImage(url)) {
+ val avatarDrawable = createAvatarDrawable(context, name ?: "")
+ imageView.setImageDrawable(avatarDrawable)
+ } else {
+ Glide.with(imageView)
+ .load(url)
+ .placeholder(R.drawable.recipient_avatar_placeholder)
+ .circleCrop()
+ .addListener(object : RequestListener {
+ override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean {
+ val avatarDrawable = createAvatarDrawable(context, name ?: "")
+ imageView.setImageDrawable(avatarDrawable)
+ return false
+ }
+
+ override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
+ return false
+ }
+
+ })
+ .into(imageView)
+ }
+ }
+
+ private fun createAvatarDrawable(context: Context, userName: String): Drawable {
+ val initials = getUserInitials(userName)
+ val color = ContextCompat.getColor(context, R.color.gray)
+ return TextDrawable.builder()
+ .beginConfig()
+ .height(context.resources.getDimensionPixelSize(R.dimen.avatar_size))
+ .width(context.resources.getDimensionPixelSize(R.dimen.avatar_size))
+ .toUpperCase()
+ .useFont(Typeface.DEFAULT_BOLD)
+ .textColor(color)
+ .withBorder(context.DP(0.5f).toInt())
+ .withBorderColor(color)
+ .endConfig()
+ .buildRound(initials, Color.WHITE)
+ }
+
/**
* Sets up the provided [avatar] for the given [conversation].
*
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt
index cb0342c4cc..61e06d1eb8 100644
--- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt
@@ -38,7 +38,6 @@ import java.util.regex.Pattern
* This currently handles three iframe cases:
* -cnvs_content src authentication
* -lti iframe src auth and launch button
- * -new rce videos in iframes
*
* We should now be able to call this function, preceded by a simple check for iframes, for all html webview content
*/
@@ -65,15 +64,6 @@ fun WebView.loadHtmlWithIframes(context: Context, isTablet: Boolean, html: Strin
hasLtiTool = true
val newIframe = inBackground { externalToolIframe(srcUrl, iframe, context); }
newHTML = newHTML.replace(iframe, newIframe)
- } else if(srcUrl.contains("media_objects_iframe")) {
- // Handle the new RCE iframe case
- val dataMediaIdMatcher = Pattern.compile("data-media-id=\"([^\"]+)\"").matcher(iframe)
- if (dataMediaIdMatcher.find()) {
- val dataMediaId = dataMediaIdMatcher.group(1)
-
- val newIframe = newRceVideoElement(dataMediaId);
- newHTML = newHTML.replace(iframe, newIframe)
- }
} else if(iframe.contains("id=\"cnvs_content\"")) {
// Handle the cnvs_content special case for some schools
val authenticatedUrl = inBackground { authenticateLTIUrl(srcUrl) }
@@ -123,18 +113,6 @@ suspend fun externalToolIframe(srcUrl: String, iframe: String, context: Context)
return newIframe + htmlButton
}
-fun newRceVideoElement(dataMediaId: String): String {
- // We need to make a new src url with the dataMediaId
- val newSrcUrl = "/users/self/media_download?entryId=$dataMediaId&media_type=video&redirect=1"
-
- // We can't just update the src url in the iframe, as iframes always auto load/play the src
- return """
-
- """.trimIndent()
-}
-
fun handleLTIPlaceHolders(placeHolderList: ArrayList, html: String): String {
var newHtml = html
for (holder in placeHolderList) {
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/DateTimeProvider.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/DateTimeProvider.kt
new file mode 100644
index 0000000000..d9f1bd57e0
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/DateTimeProvider.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.pandautils.utils.date
+
+import java.util.*
+
+interface DateTimeProvider {
+
+ fun getCalendar(): Calendar
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/RealDateTimeProvider.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/RealDateTimeProvider.kt
new file mode 100644
index 0000000000..c07b123eed
--- /dev/null
+++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/date/RealDateTimeProvider.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.pandautils.utils.date
+
+import java.util.*
+
+class RealDateTimeProvider : DateTimeProvider {
+
+ override fun getCalendar(): Calendar = Calendar.getInstance()
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/color/schedule_checkbox_color_selector.xml b/libs/pandautils/src/main/res/color/schedule_checkbox_color_selector.xml
new file mode 100644
index 0000000000..78cc3db00f
--- /dev/null
+++ b/libs/pandautils/src/main/res/color/schedule_checkbox_color_selector.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle.xml b/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle.xml
new file mode 100644
index 0000000000..d6ee277edd
--- /dev/null
+++ b/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle_filled.xml b/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle_filled.xml
new file mode 100644
index 0000000000..1ca36c1cfe
--- /dev/null
+++ b/libs/pandautils/src/main/res/drawable/bg_rounded_rectangle_filled.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/drawable/ic_chevron_filled_left.xml b/libs/pandautils/src/main/res/drawable/ic_chevron_filled_left.xml
new file mode 100644
index 0000000000..5064ef4adf
--- /dev/null
+++ b/libs/pandautils/src/main/res/drawable/ic_chevron_filled_left.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandautils/src/main/res/drawable/ic_chevron_filled_right.xml b/libs/pandautils/src/main/res/drawable/ic_chevron_filled_right.xml
new file mode 100644
index 0000000000..010a8501ea
--- /dev/null
+++ b/libs/pandautils/src/main/res/drawable/ic_chevron_filled_right.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml b/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml
new file mode 100644
index 0000000000..14ab7a1e97
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout-sw720dp/item_schedule_course.xml b/libs/pandautils/src/main/res/layout-sw720dp/item_schedule_course.xml
new file mode 100644
index 0000000000..da60b5f0a7
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout-sw720dp/item_schedule_course.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout-w720dp/item_schedule_planner_item.xml b/libs/pandautils/src/main/res/layout-w720dp/item_schedule_planner_item.xml
new file mode 100644
index 0000000000..ad1427acad
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout-w720dp/item_schedule_planner_item.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/fragment_grades.xml b/libs/pandautils/src/main/res/layout/fragment_grades.xml
index 5be2e3e00a..9d7ff690e7 100644
--- a/libs/pandautils/src/main/res/layout/fragment_grades.xml
+++ b/libs/pandautils/src/main/res/layout/fragment_grades.xml
@@ -14,10 +14,46 @@
~ along with this program. If not, see .
~
-->
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/fragment_resources.xml b/libs/pandautils/src/main/res/layout/fragment_resources.xml
index 47f31fd21e..135417c22a 100644
--- a/libs/pandautils/src/main/res/layout/fragment_resources.xml
+++ b/libs/pandautils/src/main/res/layout/fragment_resources.xml
@@ -14,10 +14,87 @@
~ along with this program. If not, see .
~
-->
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/fragment_schedule.xml b/libs/pandautils/src/main/res/layout/fragment_schedule.xml
index 13567a7a79..ac3f88752c 100644
--- a/libs/pandautils/src/main/res/layout/fragment_schedule.xml
+++ b/libs/pandautils/src/main/res/layout/fragment_schedule.xml
@@ -14,10 +14,48 @@
~ along with this program. If not, see .
~
-->
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/fragment_schedule_pager.xml b/libs/pandautils/src/main/res/layout/fragment_schedule_pager.xml
new file mode 100644
index 0000000000..c3e132e68e
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/fragment_schedule_pager.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_announcement.xml b/libs/pandautils/src/main/res/layout/item_announcement.xml
index 5f61013170..3a462cc35d 100644
--- a/libs/pandautils/src/main/res/layout/item_announcement.xml
+++ b/libs/pandautils/src/main/res/layout/item_announcement.xml
@@ -84,6 +84,6 @@
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginBottom="24dp"
- android:background="@color/k5AnnouncementSeparatorColor" />
+ android:background="@color/k5SeparatorColor" />
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_contact_info.xml b/libs/pandautils/src/main/res/layout/item_contact_info.xml
new file mode 100644
index 0000000000..87a15f0fac
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_contact_info.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_course_card.xml b/libs/pandautils/src/main/res/layout/item_course_card.xml
index f14b2a689e..7bd1ca5f07 100644
--- a/libs/pandautils/src/main/res/layout/item_course_card.xml
+++ b/libs/pandautils/src/main/res/layout/item_course_card.xml
@@ -25,7 +25,7 @@
-
+
@@ -144,7 +144,7 @@
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:src="@drawable/ic_homeroom_announcement"
- android:tint="@{Color.parseColor(itemViewModel.data.courseColor)}"
+ android:tint="@{ColorUtils.parseColor(itemViewModel.data.courseColor)}"
android:importantForAccessibility="no"
tools:tint="@color/canvasDefaultAccent" />
diff --git a/libs/pandautils/src/main/res/layout/item_grade_row.xml b/libs/pandautils/src/main/res/layout/item_grade_row.xml
new file mode 100644
index 0000000000..3de15707ba
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_grade_row.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_grading_period_selector.xml b/libs/pandautils/src/main/res/layout/item_grading_period_selector.xml
new file mode 100644
index 0000000000..974f84f331
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_grading_period_selector.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_important_links.xml b/libs/pandautils/src/main/res/layout/item_important_links.xml
new file mode 100644
index 0000000000..f9b3d58253
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_important_links.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_lti_application.xml b/libs/pandautils/src/main/res/layout/item_lti_application.xml
new file mode 100644
index 0000000000..a987568313
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_lti_application.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_resources_header.xml b/libs/pandautils/src/main/res/layout/item_resources_header.xml
new file mode 100644
index 0000000000..163926e0e3
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_resources_header.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_course.xml b/libs/pandautils/src/main/res/layout/item_schedule_course.xml
new file mode 100644
index 0000000000..7f168e699b
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_course.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_day_header.xml b/libs/pandautils/src/main/res/layout/item_schedule_day_header.xml
new file mode 100644
index 0000000000..d31897a28d
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_day_header.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_empty.xml b/libs/pandautils/src/main/res/layout/item_schedule_empty.xml
new file mode 100644
index 0000000000..28479da423
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_empty.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_missing_header.xml b/libs/pandautils/src/main/res/layout/item_schedule_missing_header.xml
new file mode 100644
index 0000000000..7e1f44de3b
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_missing_header.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_missing_item.xml b/libs/pandautils/src/main/res/layout/item_schedule_missing_item.xml
new file mode 100644
index 0000000000..c1cacda5d8
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_missing_item.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_planner_item.xml b/libs/pandautils/src/main/res/layout/item_schedule_planner_item.xml
new file mode 100644
index 0000000000..cb5e00b919
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_planner_item.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/layout/item_schedule_planner_item_tag.xml b/libs/pandautils/src/main/res/layout/item_schedule_planner_item_tag.xml
new file mode 100644
index 0000000000..253bb518cb
--- /dev/null
+++ b/libs/pandautils/src/main/res/layout/item_schedule_planner_item_tag.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/pandautils/src/main/res/values-ar/strings.xml b/libs/pandautils/src/main/res/values-ar/strings.xml
index 5f50f157a6..d3ce69e2e1 100644
--- a/libs/pandautils/src/main/res/values-ar/strings.xml
+++ b/libs/pandautils/src/main/res/values-ar/strings.xml
@@ -17,7 +17,6 @@
-->
-
اختيار الوسائطالتقاط فيديولا توجد كاميرا
@@ -42,8 +41,11 @@
إخطارات التحميلإخطارات Canvas لعمليات التحميل الجارية.
+
+
إضافة ملف
+
الكاميراالمعرضالجهاز
@@ -53,7 +55,6 @@
الملفالملف %sالمجلد %s
-
إضافة عنصرالمهمةالملفات المرفقة
@@ -97,6 +98,7 @@
جاري تحميل الإرسال لـ %sالإرسال فشل لـ %sتم إرسال %s بنجاح
+
حدث خطأ غير متوقع.حدث خطأ بالخادم.
@@ -183,9 +185,9 @@
يبدو أنه لم يتم إنشاء وحدات نمطية حتى الآن.لا توجد صفحاتيبدو أنه لم يتم إنشاء أي صفحات حتى الآن.
-
تم تعطيل وحدات نمطية لهذا المساق.حدث خطأ ما
+
نعملا
@@ -337,6 +339,7 @@
+
لا يمكن أن يكون تاريخ إلغاء التأمين بعد تاريخ الاستحقاقلا يمكن أن يكون تاريخ التأمين قبل تاريخ الاستحقاقلا يمكن أن يكون تاريخ التأمين قبل تاريخ إلغاء التأمين
@@ -390,16 +393,17 @@
التحميل إلى تعليق الإرسالإرفاق ملفمع
+
إخطار Canvasإخطارات Canvas عامةيمكن إجراء تكوين إضافي للإخطارات داخل قسم تفضيلات إخطار Canvas.
-
%d من الإعلامات الجديدة%d من الإعلامات الجديدة%d من الإعلامات الجديدة%d من الإعلامات الجديدة
+
نسخ عنوان الارتباطنسخ الرابطتم نسخ الارتباط
@@ -429,7 +433,6 @@
جارٍ تحميل الصورة...خطأ في تحميل الصورةإعادة المحاولة
-
بحثالبحث عن المهامالبحث عن الإعلانات
@@ -437,11 +440,36 @@
البحث عن الصفحاتالبحث عن الاختبارات الموجزةلا توجد عناصر مطابقة لـ \"%s\"
+
تم التحديدإضافة إعلامات التحديثإعلامات Canvas للتحديثات داخل التطبيق.التطبيق جاهز للتحديثإعادة تشغيل التطبيق لتثبيت الإصدار الجديد
+ لم يتم تخطيط أي شيء بعد
+ تم تقييم الدرجة
+ ردود
+ الملاحظات
+ متأخر
+ إعادة
+ معفى
+ غدًا
+ أمس
+ في %s
+ قائمة المهام: %s
+ تاريخ الاستحقاق: %s
+ إظهار %d من العناصر المفقودة
+ إخفاء %d من العناصر المفقودة
+ لا يمكن إحضار جدولك
+ قائمة المهام
+
+ %s نقاط
+ %s نقطة
+ %s نقاط
+ %s نقاط
+ %s نقاط
+ %s نقاط
+
diff --git a/libs/pandautils/src/main/res/values-b+da+instk12/strings.xml b/libs/pandautils/src/main/res/values-b+da+instk12/strings.xml
index 64b8fb359a..bf9e7b4132 100644
--- a/libs/pandautils/src/main/res/values-b+da+instk12/strings.xml
+++ b/libs/pandautils/src/main/res/values-b+da+instk12/strings.xml
@@ -17,7 +17,6 @@
-->
-
Vælg medieTag videoIntet kamera
@@ -42,8 +41,11 @@
Upload meddelelserCanvas-meddelelser til løbende uploads.
+
+
Tilføj fil
+
KameraGalleriEnhed
@@ -96,6 +98,7 @@
Uploader aflevering til %sAflevering mislykkedes for %s%s blev indsendt
+
En uventet fejl opstod.En serverfejl opstod.
@@ -182,9 +185,9 @@
Det ser ud til, at der ikke er oprettet nogen forløb endnu.Ingen sierDet ser ud til, at der ikke er oprettet nogen sider endnu.
-
Forløb er blevet deaktiveret for dette fagNoget gik galt
+
JaNej
@@ -325,6 +328,7 @@
+
Oplåsningsdatoen kan ikke ligge efter afleveringsdatoLåsningsdato kan ikke ligge før afleveringsdatoerLåsningsdato kan ikke ligge før oplåsningsdato
@@ -378,13 +382,14 @@
Upload til afleveringskommentarVedhæft en filmed
+
Canvas-meddelelseAlmindelige Canvas-meddelelserYderligere konfiguration af meddelelser kan ske i sektionen Canvas-meddelelsesindstillinger.
-
%d nye meddelelser
+
Kopier linkets adresseKopier linkLink kopieret
@@ -428,5 +433,26 @@
Canvas-meddelelser til opdateringer i appen.App klar til opdateringGenstart appen for at installere den nye version
+ Intet planlagt endnu
+ Bedømt
+ Svar
+ Feedback
+ Sen
+ Annuller fortryd
+ Undskyldt
+ I morgen
+ I går
+ Kl. %s
+ Opgaveliste: %s
+ Forfalder: %s
+ Vis %d manglende elementer
+ Skjul %d manglende elementer
+ Din tidsplan kunne ikke hentes
+ Opgaveliste
+
+ %s point
+ %s point
+ %s point
+
diff --git a/libs/pandautils/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/pandautils/src/main/res/values-b+en+AU+unimelb/strings.xml
index 8b5e6564aa..236edd72af 100644
--- a/libs/pandautils/src/main/res/values-b+en+AU+unimelb/strings.xml
+++ b/libs/pandautils/src/main/res/values-b+en+AU+unimelb/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choose MediaTake VideoNo Camera
@@ -42,8 +41,11 @@
Upload NotificationsCanvas notifications for ongoing uploads.
+
+
Add File
+
CameraGalleryDevice
@@ -53,7 +55,6 @@
FileFile %sFolder %s
-
Add ItemAssignmentAttached Files
@@ -97,6 +98,7 @@
Uploading submission for %sSubmission failed for %sSuccessfully submitted %s
+
An unexpected error occurred.A server error has occurred.
@@ -183,9 +185,9 @@
It looks like there aren\'t any modules created yet.No PagesIt looks like no pages have been created yet.
-
Modules have been disabled for this subject.Something Went Wrong
+
YesNo
@@ -326,6 +328,7 @@
+
Unlock date cannot be after due dateLock date cannot be before due dateLock date cannot be before unlock date
@@ -379,13 +382,14 @@
Upload to Submission CommentAttach a filewith
+
Canvas NotificationGeneral Canvas NotificationsFurther configuration of notifications can be done within the Canvas Notification Preferences section.
-
%d new notifications
+
Copy link addressCopy LinkLink copied
@@ -415,7 +419,6 @@
Image Uploading...Image Upload ErrorRetry
-
SearchSearch AssignmentsSearch Announcements
@@ -423,11 +426,33 @@
Search PagesSearch QuizzesNo items match \"%s\"
+
SelectedApp update notificationsCanvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Graded
+ Replies
+ Feedback
+ Late
+ Redo
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ To Do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To Do
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-b+nb+instk12/strings.xml b/libs/pandautils/src/main/res/values-b+nb+instk12/strings.xml
index 37207f734f..22060f8032 100644
--- a/libs/pandautils/src/main/res/values-b+nb+instk12/strings.xml
+++ b/libs/pandautils/src/main/res/values-b+nb+instk12/strings.xml
@@ -17,7 +17,6 @@
-->
-
Velg mediaTa videoIngen kamera
@@ -42,8 +41,11 @@
Varslinger for opplastingerCanvas-varslinger for pågående opplastinger.
+
+
Legg til fil
+
KameraGalleriEnhet
@@ -96,6 +98,7 @@
Last opp innlevering for %sKunne ikke innlevere for %sInnlevering fullført %s
+
Det oppsto en uventet feil.En serverfeil har oppstått.
@@ -182,9 +185,9 @@
Det ser ut som det ikke er laget noen moduler ennå.Ingen siderDet ser ut som det ikke er laget noen sider ennå.
-
Moduler er deaktivert for dette faget.Noe gikk galt
+
JaNei
@@ -325,6 +328,7 @@
+
Opplåsingsdatoen kan ikke være etter forfallsdatoLåsedato kan ikke være før forfallsdatoLåsedato kan ikke være før opplåsingsdato
@@ -378,13 +382,14 @@
Last opp til sendt inn kommentarLegg ved en filmed
+
Canvas NotificationGenerelle Canvas NotificationsYtterligere konfigurering av varsler kan gjøres inne i seksjonen for Canvas Notification Preferences.
-
%d nye varslinger
+
Kopier lenke-adresseKopier lenkeLenke kopiert
@@ -428,5 +433,26 @@
Canvas-varslinger for oppdateringer i appenAppen er klar til å oppdateresStart appen på nytt for å installere den nye versjonen
+ Ingenting planlagt enda
+ Vurdert
+ Svar
+ Tilbakemelding
+ Sent
+ Gjør om
+ Fritatt
+ I morgen
+ I går
+ Den %s
+ Å gjøre: %s
+ Frist: %s
+ Vis %d manglende elementer
+ Skjul %d manglende elementer
+ Kunne ikke hente timeplanen din
+ Å gjøre
+
+ %s poeng
+ %s poeng
+ %s poeng
+
diff --git a/libs/pandautils/src/main/res/values-b+sv+instk12/strings.xml b/libs/pandautils/src/main/res/values-b+sv+instk12/strings.xml
index 6d547b084e..fc8e53ad4d 100644
--- a/libs/pandautils/src/main/res/values-b+sv+instk12/strings.xml
+++ b/libs/pandautils/src/main/res/values-b+sv+instk12/strings.xml
@@ -17,7 +17,6 @@
-->
-
Välj mediaSpela in videoIngen kamera
@@ -46,6 +45,7 @@
Lägg till fil
+
KameraGalleriEnhet
@@ -91,7 +91,7 @@
FilikonLaddar upp %d av %d%s har låsts.
- Filerna är nu redo att ladda ned. Försök igen.
+ Filen är nu klar att laddas ner. Försök igen.Filen kan inte laddas ned utan att ge behörighet.Behörighet beviljades. Försök igen..Välj en fil
@@ -121,6 +121,7 @@
En eller flera filer har ett filtillägg som inte är tillåtetDen valda filtypen är inte tillåten.Tillåtna tillägg:
+
Kontrollera din dataanslutning och försök igen. Din enhet har inte några program installerade som kan hantera den här filen.
@@ -184,9 +185,9 @@
Det verkar inte som om några moduler har skapats än.Inga sidorDet verkar inte som om några sidor har skapats än.
-
Moduler har inaktiverats för den här kursen.Något gick fel
+
JaNej
@@ -327,6 +328,7 @@
+
Upplåsningsdatumet kan inte vara efter sista inlämningsdatumetLåsdatumet kan inte vara före inlämningsdatumetLåsdatumet kan inte vara före upplåsningsdatumet
@@ -343,7 +345,7 @@
Lägg till ny
- Ladda ned bilaga
+ Ladda ner bilagaTa bort bilaga
@@ -384,10 +386,10 @@
Canvas-aviseringAllmänna Canvas-aviseringarYtterligare konfiguration av aviseringar kan göras i sektionen för inställningar för Canvas-aviseringar.
-
%d nya aviseringar
+
Kopiera länkadressKopiera länkLänken har kopierats
@@ -431,5 +433,26 @@
Canvas-aviseringar för uppdateringar i appen.Appen är klar för uppdateringStarta om appen för att installera den nya versionen
+ Inget planerat ännu
+ Har bedömts
+ Svar
+ Återkoppling
+ Sen
+ Gör om
+ Ursäktad
+ I morgon
+ I går
+ Kl. %s
+ Att göra: %s
+ Inlämningsdatum: %s
+ Visa %d saknade objekt
+ Dölj %d saknade objekt
+ Det gick inte att hämta ditt schema
+ Att göra
+
+ %s poäng
+ %s poäng
+ %s poäng
+
diff --git a/libs/pandautils/src/main/res/values-ca/strings.xml b/libs/pandautils/src/main/res/values-ca/strings.xml
index 72f1f7914c..9aa9ab9768 100644
--- a/libs/pandautils/src/main/res/values-ca/strings.xml
+++ b/libs/pandautils/src/main/res/values-ca/strings.xml
@@ -17,7 +17,6 @@
-->
-
Tria un element multimèdiaEnregistra vídeoSense càmera
@@ -46,6 +45,7 @@
Afegeix un fitxer
+
CàmeraGaleriaDispositiu
@@ -121,6 +121,7 @@
Un o més fitxers tenen una extensió de fitxer que no està permesaEl tipus de fitxer seleccionat no està permès.Extensions permeses:
+
Reviseu la connexió de dades i torneu a provar-ho. El dispositiu no té cap aplicació instal·lada per obrir aquest fitxer.
@@ -184,9 +185,9 @@
Sembla que encara no s\'ha creat cap mòdul.No hi ha cap pàginaSembla que encara no s\'ha creat cap pàgina.
-
S’han inhabilitat mòduls per a aquest curs.Alguna cosa no ha anat bé
+
SíNo
@@ -327,6 +328,7 @@
+
La data de desbloqueig no pot ser posterior a la data de vencimentLa data de bloqueig no pot ser anterior a la data de vencimentLa data de bloqueig no pot ser anterior a la data de desbloqueig
@@ -384,10 +386,11 @@
Notificació de CanvasNotificacions de Canvas generalsA la secció de Preferències de notificacions de Canvas es pot ampliar la configuració de les notificacions.
-
+
%d notificacions noves
+
Copia l\'adreça de l\'enllaçCopia l\'enllaçEnllaç copiat
@@ -431,5 +434,26 @@
Notificació de Canvas d’actualitzacions de l’aplicació.Tot és a punt per fer l’actualització de l’aplicacióReinicieu l’aplicació per instal·lar la versió nova
+ Encara no hi ha res planificat
+ Qualificat
+ Respostes
+ Suggeriments
+ Tardà
+ Refés
+ Excusat
+ Demà
+ Ahir
+ A les %s
+ Tasques pendents: %s
+ Venciment: %s
+ Mostra %d elements que falten
+ Amaga %d elements que falten
+ No es pot obtenir la vostra planificació
+ Tasques pendents
+
+ %s punt
+ %s punt
+ %s punts
+
diff --git a/libs/pandautils/src/main/res/values-cy/strings.xml b/libs/pandautils/src/main/res/values-cy/strings.xml
index 62d6b46145..3e0c8bcb70 100644
--- a/libs/pandautils/src/main/res/values-cy/strings.xml
+++ b/libs/pandautils/src/main/res/values-cy/strings.xml
@@ -17,7 +17,6 @@
-->
-
Dewiswch GyfryngauGwnewch FideoDim Camera
@@ -42,8 +41,11 @@
Llwytho Hysbysiadau i fynyHysbysiadau Canvas ar gyfer ffeiliau sy’n cael eu llwytho i fyny ar hyn o bryd.
+
+
Ychwanegu Ffeil
+
CameraOrielDyfais
@@ -53,7 +55,6 @@
FfeilFfeil %sFfolder %s
-
Ychwanegu EitemAseiniadFfeiliau wedi’u hatodi
@@ -97,6 +98,7 @@
Wrthi\'n llwytho cyflwyniad i fyny ar gyfer %sCyflwyniad wedi methu ar gyfer %sWedi llwyddo i gyflwyno %s
+
Gwall annisgwyl.Gwall ar y gweinydd.
@@ -183,9 +185,9 @@
Mae’n edrych yn debyg nad oes unrhyw fodiwlau wedi’u creu eto.Dim TudalennauMae’n edrych yn debyg nad os tudalennau wedi’u creu eto.
-
Mae modiwlau wedi’u hanalluogi ar gyfer y cwrs hwn.Aeth rhywbeth o’i le
+
IawnNa
@@ -326,6 +328,7 @@
+
Does dim modd i’r dyddiad datgloi fod ar ôl y dyddiad erbynDoes dim modd i’r dyddiad cloi fod cyn y dyddiad erbynDoes dim modd i’r dyddiad cloi fod cyn y dyddiad datgloi
@@ -379,13 +382,14 @@
Llwytho i fyny i Sylw ar y cyflwyniadAtodi ffeilgyda
+
Hysbysiad CanvasHysbysiadau Cyffredinol CanvasGellir ffurfweddu hysbysiadau ymhellach o fewn yr adran Dewisiadau Hysbysiadau Canvas.
-
%d hysbysiad newydd
+
Copïo cyfeiriad dolenCopïo DolenWedi copïo dolen
@@ -415,7 +419,6 @@
Wrthi’n Llwytho’r Ddelwedd i Fyny...Gwall wrth Lwytho’r Ddelwedd i FynyRhoi cynnig arall arni
-
ChwilioChwilio AseiniadauChwilio Cyhoeddiadau
@@ -423,11 +426,33 @@
Chwilio TudalennauChwilio CwisiauDim eitemau’n cyfateb \"%s\"
+
Wedi dewisHysbysiadau diweddaru apHysbysiadau Canvas ar gyfer diweddariadau mewn apAp yn barod i’w ddiweddaruAil-gychwynnwch yr ap i osod y fersiwn newydd
+ Does dim byd wedi’i drefnu hyd yma
+ Wedi graddio
+ Ateb
+ Adborth
+ Yn Hwyr
+ Ail-wneud
+ Wedi esgusodi
+ Yfory
+ Ddoe
+ Am %s
+ Tasgau i’w Gwneud: %s
+ Erbyn: %s
+ Dangos %d eitem coll
+ Cuddio %d eitem coll
+ Doedd dim modd nôl eich amserlen
+ Tasgau i’w Gwneud
+
+ %s pwynt
+ %s pwynt
+ %s pwynt
+
diff --git a/libs/pandautils/src/main/res/values-da/strings.xml b/libs/pandautils/src/main/res/values-da/strings.xml
index 4207293ce1..49d12ccdd8 100644
--- a/libs/pandautils/src/main/res/values-da/strings.xml
+++ b/libs/pandautils/src/main/res/values-da/strings.xml
@@ -17,7 +17,6 @@
-->
-
Vælg medieTag videoIntet kamera
@@ -42,8 +41,11 @@
Upload meddelelserCanvas-meddelelser til løbende uploads.
+
+
Tilføj fil
+
KameraGalleriEnhed
@@ -53,7 +55,6 @@
FilFil %sMappe %s
-
Tilføj elementOpgaveVedhæftede filer
@@ -97,6 +98,7 @@
Uploader aflevering til %sAflevering mislykkedes for %s%s blev indsendt
+
En uventet fejl opstod.En serverfejl opstod.
@@ -183,9 +185,9 @@
Det ser ud til, at der ikke er oprettet nogen moduler endnu.Ingen sierDet ser ud til, at der ikke er oprettet nogen sider endnu.
-
Moduler er blevet deaktiveret for dette fag.Noget gik galt
+
JaNej
@@ -326,6 +328,7 @@
+
Oplåsningsdatoen kan ikke ligge efter afleveringsdatoLåsningsdato kan ikke ligge før afleveringsdatoerLåsningsdato kan ikke ligge før oplåsningsdato
@@ -379,13 +382,14 @@
Upload til afleveringskommentarVedhæft en filmed
+
Canvas-meddelelseAlmindelige Canvas-meddelelserYderligere konfiguration af meddelelser kan ske i sektionen Canvas-meddelelsesindstillinger.
-
%d nye meddelelser
+
Kopier linkadresseKopier linkLink kopieret
@@ -415,7 +419,6 @@
Uploader foto ...Fejl ved upload af fotoPrøv igen
-
SøgSøg opgaverSøg beskeder
@@ -423,11 +426,33 @@
Søg siderSøg testIngen elementer matcher \"%s\"
+
ValgtOpdateringsmeddelelser for appenCanvas-meddelelser til opdateringer i appen.App klar til opdateringGenstart appen for at installere den nye version
+ Intet planlagt endnu
+ Bedømt
+ Svar
+ Feedback
+ Sen
+ Annuller fortryd
+ Undskyldt
+ I morgen
+ I går
+ Kl. %s
+ Opgaveliste: %s
+ Forfalder: %s
+ Vis %d manglende elementer
+ Skjul %d manglende elementer
+ Din tidsplan kunne ikke hentes
+ Opgaveliste
+
+ %s point
+ %s point
+ %s point
+
diff --git a/libs/pandautils/src/main/res/values-de/strings.xml b/libs/pandautils/src/main/res/values-de/strings.xml
index 863933ae8a..8cbd2474b7 100644
--- a/libs/pandautils/src/main/res/values-de/strings.xml
+++ b/libs/pandautils/src/main/res/values-de/strings.xml
@@ -17,7 +17,6 @@
-->
-
Media auswählenVideo aufnehmenKeine Kamera
@@ -42,8 +41,11 @@
Benachrichtigungen hochladenCanvas-Benachrichtigungen für laufende Uploads
+
+
Datei hinzufügen
+
KameraGalerieGerät
@@ -53,7 +55,6 @@
DateiDatei %sOrdner %s
-
Objekt hinzufügenAufgabeAngehängte Dateien
@@ -97,6 +98,7 @@
Abgabe für %s hochladenAbgabe für %s fehlgeschlagen%s erfolgreich abgegeben
+
Es ist ein unerwarteter Fehler aufgetreten.Ein Server-Fehler ist aufgetreten.
@@ -183,9 +185,9 @@
Es wurden scheinbar noch keine Module erstellt.Keine SeitenEs wurden scheinbar noch keine Seiten erstellt.
-
Module wurden für diesen Kurs deaktiviert.Etwas ging schief
+
JaKein
@@ -326,6 +328,7 @@
+
Das Freigabedatum kann nicht nach dem Abgabetermin liegen.Das Sperrdatum darf nicht vor dem Abgabetermin liegen.Das Sperrdatum darf nicht nach dem Freigabedatum liegen.
@@ -379,13 +382,14 @@
Hochladen zum SendekommentarEine Datei anhängenmit
+
Canvas-BenachrichtigungAllgemeine Canvas-BenachrichtigungenDie weitere Konfiguration von Benachrichtigungen kann im Abschnitt „Canvas-Benachrichtigungseinstellungen“ vorgenommen werden.
-
%d neue Benachrichtigungen
+
Link-Adresse kopierenLink kopierenLink kopiert
@@ -415,7 +419,6 @@
Bild wird hochgeladen ...Fehler beim BildhochladenErneut versuchen
-
SuchenAufgaben suchenAnkündigungen suchen
@@ -423,11 +426,33 @@
Seiten suchenQuizze suchenKeine übereinstimmenden Objekte mit \"%s\"
+
AusgewähltApp-AktualisierungsbenachrichtigungenCanvas-Benachrichtigungen für App-interne Updates.App zum Update bereitStarten Sie die App erneut, um die neue Version zu installieren.
+ Noch nichts geplant
+ Benotet
+ Antworten
+ Feedback
+ Verspätet
+ Wiederholen
+ Entschuldigt
+ Morgen
+ Gestern
+ Um %s
+ Zu erledigen: %s
+ Fällig: %s
+ %d fehlende Elemente anzeigen
+ %d fehlende Elemente ausblenden
+ Ihr Zeitplan konnte nicht abgerufen werden
+ Zu erledigen
+
+ %s Pkt.
+ %s Pkt.
+ %s Pkte.
+
diff --git a/libs/pandautils/src/main/res/values-en-rAU/strings.xml b/libs/pandautils/src/main/res/values-en-rAU/strings.xml
index dc3b40e4cf..7182d2e6b1 100644
--- a/libs/pandautils/src/main/res/values-en-rAU/strings.xml
+++ b/libs/pandautils/src/main/res/values-en-rAU/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choose MediaTake VideoNo Camera
@@ -42,8 +41,11 @@
Upload NotificationsCanvas notifications for ongoing uploads.
+
+
Add File
+
CameraGalleryDevice
@@ -53,7 +55,6 @@
FileFile %sFolder %s
-
Add ItemAssignmentAttached Files
@@ -97,6 +98,7 @@
Uploading submission for %sSubmission failed for %sSuccessfully submitted %s
+
An unexpected error occurred.A server error has occurred.
@@ -183,9 +185,9 @@
It looks like there aren\'t any modules created yet.No PagesIt looks like no pages have been created yet.
-
Modules have been disabled for this course.Something Went Wrong
+
YesNo
@@ -326,6 +328,7 @@
+
Unlock date cannot be after due dateLock date cannot be before due dateLock date cannot be before unlock date
@@ -379,13 +382,14 @@
Upload to Submission CommentAttach a filewith
+
Canvas NotificationGeneral Canvas NotificationsFurther configuration of notifications can be done within the Canvas Notification Preferences section.
-
%d new notifications
+
Copy link addressCopy LinkLink copied
@@ -415,7 +419,6 @@
Image Uploading...Image Upload ErrorRetry
-
SearchSearch AssignmentsSearch Announcements
@@ -423,11 +426,33 @@
Search PagesSearch QuizzesNo items match \"%s\"
+
SelectedApp update notificationsCanvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Marked
+ Replies
+ Feedback
+ Late
+ Redo
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ To Do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To Do
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-en-rCA/strings.xml b/libs/pandautils/src/main/res/values-en-rCA/strings.xml
index bf2c43510f..cd00f23d46 100644
--- a/libs/pandautils/src/main/res/values-en-rCA/strings.xml
+++ b/libs/pandautils/src/main/res/values-en-rCA/strings.xml
@@ -434,5 +434,26 @@
Canvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Graded
+ Replies
+ Feedback
+ Late
+ Redo
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ To Do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To Do
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-en-rCY/strings.xml b/libs/pandautils/src/main/res/values-en-rCY/strings.xml
index 8edca27c2d..13c6106e5b 100644
--- a/libs/pandautils/src/main/res/values-en-rCY/strings.xml
+++ b/libs/pandautils/src/main/res/values-en-rCY/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choose mediaTake videoNo camera
@@ -46,6 +45,7 @@
Add file
+
CameraGalleryDevice
@@ -121,6 +121,7 @@
One or more files has a file extension that isn\'t allowedThe selected file type is not allowed.Allowed extensions:
+
Please check your data connection and try again. Your device doesn\'t have any applications installed that can handle this file.
@@ -184,9 +185,9 @@
It looks like there aren\'t any units created yet.No pagesIt looks like no pages have been created yet.
-
Units have been disabled for this module.Something Went Wrong
+
YesNo
@@ -327,6 +328,7 @@
+
Unlock date cannot be after due dateLock date cannot be before due dateLock date cannot be before the unlock date
@@ -384,10 +386,10 @@
Canvas notificationGeneral Canvas notificationsFurther configuration of notifications can be done within the Canvas notification preferences section.
-
%d new notifications
+
Copy link addressCopy linkLink copied
@@ -431,5 +433,26 @@
Canvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Graded
+ Replies
+ Feedback
+ Late
+ Re-do
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ To-do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To-do
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-en-rGB/strings.xml b/libs/pandautils/src/main/res/values-en-rGB/strings.xml
index f726ad5ecd..2609140e33 100644
--- a/libs/pandautils/src/main/res/values-en-rGB/strings.xml
+++ b/libs/pandautils/src/main/res/values-en-rGB/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choose mediaTake videoNo camera
@@ -42,8 +41,11 @@
Upload NotificationsCanvas notifications for ongoing uploads.
+
+
Add file
+
CameraGalleryDevice
@@ -53,7 +55,6 @@
FileFile %sFolder %s
-
Add itemAssignmentAttached files
@@ -97,6 +98,7 @@
Uploading submission for %sSubmission failed for %sSuccessfully submitted %s
+
An unexpected error occurred.A server error has occurred.
@@ -183,9 +185,9 @@
It looks like there aren\'t any modules created yet.No pagesIt looks like no pages have been created yet.
-
Modules have been disabled for this course.Something Went Wrong
+
YesNo
@@ -326,6 +328,7 @@
+
Unlock date cannot be after due dateLock date cannot be before due dateLock date cannot be before the unlock date
@@ -379,13 +382,14 @@
Upload to submission commentAttach a filewith
+
Canvas notificationGeneral Canvas notificationsFurther configuration of notifications can be done within the Canvas notification preferences section.
-
%d new notifications
+
Copy link addressCopy linkLink copied
@@ -415,7 +419,6 @@
Image uploading...Image upload errorRetry
-
SearchSearch assignmentsSearch announcements
@@ -423,11 +426,33 @@
Search pagesSearch quizzesNo items match \"%s\"
+
SelectedApp update notificationsCanvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Graded
+ Replies
+ Feedback
+ Late
+ Re-do
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ To-do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To-do
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-es/strings.xml b/libs/pandautils/src/main/res/values-es/strings.xml
index 0ec035d01b..c422fbdbd5 100644
--- a/libs/pandautils/src/main/res/values-es/strings.xml
+++ b/libs/pandautils/src/main/res/values-es/strings.xml
@@ -17,7 +17,6 @@
-->
-
Escoger multimediaTomar videoSin cámara
@@ -46,6 +45,7 @@
Agregar archivo
+
CámaraGaleríaDispositivo
@@ -121,6 +121,7 @@
Uno o más archivos tienen una extensión de archivo que no se permiteNo se permite el tipo de archivo seleccionado.Extensiones permitidas:
+
Compruebe su conexión de datos y vuelva a intentarlo. Su dispositivo no tiene aplicaciones instaladas que puedan manejar este archivo.
@@ -184,9 +185,9 @@
Al parecer no se han creado módulos todavía.No hay páginasAl parecer no se han creado páginas todavía.
-
Se deshabilitaron los módulos para este curso.Algo salió mal
+
SíNo
@@ -327,6 +328,7 @@
+
La fecha de desbloqueo no puede ser posterior a la fecha de entregaLa fecha de bloqueo no puede ser anterior a la fecha de entregaLa fecha de bloqueo no puede ser anterior a la fecha de desbloqueo
@@ -384,10 +386,10 @@
Notificación de CanvasNotificaciones generales de CanvasSe puede realizar una posterior configuración de las notificaciones dentro de la sección de Preferencias de notificaciones de Canvas.
-
%d nuevas notificaciones
+
Copiar dirección del enlaceCopiar enlaceEnlace copiado
@@ -431,5 +433,26 @@
Notificaciones de Canvas para actualizaciones en la aplicación.Aplicación lista para ser actualizadaReinicie la aplicación para instalar la nueva versión
+ Todavía no hay nada planificado
+ Calificado
+ Respuestas
+ Comentarios
+ Atrasado
+ Rehacer
+ Justificado
+ Mañana
+ Ayer
+ A las %s
+ Por hacer: %s
+ Fecha límite: %s
+ Mostrar %d ítems faltantes
+ Ocultar %d ítems faltantes
+ No se pudo recuperar su cronograma
+ Por hacer
+
+ %s pto.
+ %s pto.
+ %s ptos.
+
diff --git a/libs/pandautils/src/main/res/values-fi/strings.xml b/libs/pandautils/src/main/res/values-fi/strings.xml
index 9c79b852d8..31f789379b 100644
--- a/libs/pandautils/src/main/res/values-fi/strings.xml
+++ b/libs/pandautils/src/main/res/values-fi/strings.xml
@@ -17,7 +17,6 @@
-->
-
Valitse mediaOta videoEi kameraa
@@ -42,8 +41,11 @@
LatausilmoituksetCanvas-ilmoitukset meneillään olevien latausten tilasta.
+
+
Tiedoston lisäysikkuna
+
KameraGalleriaLaite
@@ -96,6 +98,7 @@
Ladataan lähetystä kohteelle %sLähetys epäonnistui kohteelle %sLähetetty onnistuneesti %s
+
Ilmeni odottamaton virhe.Palvelinvirhe ilmeni.
@@ -118,6 +121,7 @@
Yhden tai useamman tiedoston tiedostotunniste ei ole sallittua muotoaValittu tiedoston tyyppi ei kelpaa.Sallitut tiedostotunnisteet:
+
Tarkasta datayhteytesi ja yritä uudelleen. Laitteessasi ei ole asennettuna sovelluksia, jotka voisivat käsitellä tämän tiedoston.
@@ -181,9 +185,9 @@
Näyttäisi siltä, että yhtään moduulia ei ole vielä luotu.Ei sivujaNäyttäisi siltä, että sivuja ei ole vielä julkaistu.
-
Tälle kurssille moduulit on poistettu käytöstä.Jotakin meni pieleen
+
KylläEi
@@ -324,6 +328,7 @@
+
Lukituspäivä ei voi olla määräpäivän jälkeen.Lukituspäivä ei voi olla ennen määräpäivää.Lukituspäivä ei voi olla ennen lukituksen poistopäivää.
@@ -381,10 +386,10 @@
Canvas-ilmoitusYleiset Canvas-ilmoituksetIlmoitusten lisäkonfigurointi voidaan tehdä Canvas-ilmoitusasetusten osassa.
-
%d uudet ilmoitukset
+
Kopioi linkin osoiteKopioi linkkiLinkki kopioitu
@@ -428,5 +433,26 @@
Canvas-ilmoitukset sovelluksen sisäisille päivityksille.Sovellus valmis päivitettäväksiKäynnistä sovellus uudelleen asentaaksesi uusi versio
+ Ei mitään vielä suunniteltuna
+ Arvioitu
+ Vastausta
+ Palaute
+ Myöhään
+ Suorita uudelleen
+ Vapautettu
+ Huomenna
+ Eilen
+ Kohteessa %s
+ Tehtävälista: %s
+ Määräpäivä: %s
+ Näytä %d puuttuvaa kohdetta
+ Piilota %d puuttuvaa kohdetta
+ Aikataulun nouto ei onnistu
+ Tehtävälista
+
+ %s piste
+ %s piste
+ %s pistettä
+
diff --git a/libs/pandautils/src/main/res/values-fr-rCA/strings.xml b/libs/pandautils/src/main/res/values-fr-rCA/strings.xml
index e1d7385c62..eff4283b54 100644
--- a/libs/pandautils/src/main/res/values-fr-rCA/strings.xml
+++ b/libs/pandautils/src/main/res/values-fr-rCA/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choisir un médiaEnregistrer une vidéoAucune caméra
@@ -42,8 +41,11 @@
Notifications de téléversementNotifications de Canvas pour les téléversements en cours.
+
+
Ajouter un fichier
+
CaméraGalerieDispositif
@@ -53,7 +55,6 @@
FichierFichier %sDossier %s
-
Ajouter un élémentTâcheFichiers joints
@@ -97,6 +98,7 @@
Téléversement de l’envoi pour %sÉchec de l’envoi pour %s%s envoyé avec succès!
+
Une erreur inattendue s’est produite.Une erreur de serveur s\'est produite.
@@ -183,9 +185,9 @@
Il semblerait qu\'il n\'y ait aucun module créé pour le moment.Aucune pageIl semblerait qu\'aucune page n\'ait encore été créée.
-
Les modules ont été désactivés pour ce cours.Une erreur est survenue
+
OuiNon
@@ -326,6 +328,7 @@
+
La date de déverrouillage ne peut pas être postérieure à la date d’échéance.La date de verrouillage ne peut pas être antérieure à la date d’échéance.La date de verrouillage ne peut pas être antérieure à la date de déverrouillage.
@@ -379,13 +382,14 @@
Téléverser au commentaire de soumissionJoindre un fichieravec
+
Notification de CanvasNotifications générales de CanvasD\'autres configurations de notifications peuvent être effectuées dans la section Préférences de notification de Canvas.
-
%d nouvelles notifications
+
Copier l’adresse du lienCopier le lienLien copié
@@ -415,7 +419,6 @@
Téléversement d’image(s) en cours...Erreur de téléversement d’imageRéessayer
-
RechercherRechercher des tâchesRecherche des annonces
@@ -423,11 +426,33 @@
Rechercher des pagesRechercher des questionnairesAucun élément ne correspond à \"%s\"
+
SélectionnéNotification de mise à jour de l’applicationNotifications Canvas pour les mises à jour intégrées à l\'application.Application prête à être mise à jourRedémarrez l\'application pour installer la nouvelle version
+ Rien de planifié en ce moment
+ Noté
+ Réponses
+ Rétroaction
+ En retard
+ Rétablir
+ Exempté
+ Demain
+ Hier
+ À %s
+ À faire : %s
+ Échéance : %s
+ Afficher les %d éléments manquants
+ Masquer les %d éléments manquants
+ Impossible d\'obtenir votre horaire
+ À faire
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-fr/strings.xml b/libs/pandautils/src/main/res/values-fr/strings.xml
index ffdc303732..ed6297426d 100644
--- a/libs/pandautils/src/main/res/values-fr/strings.xml
+++ b/libs/pandautils/src/main/res/values-fr/strings.xml
@@ -17,7 +17,6 @@
-->
-
Choisir un médiaFaire une vidéoAucune caméra
@@ -42,8 +41,11 @@
Notifications de téléchargementNotifications Canvas pour les téléchargements en cours.
+
+
Ajouter un fichier
+
CaméraGallerieAppareil
@@ -53,7 +55,6 @@
FichierFichier %sDossier %s
-
Ajouter un élémentTravauxFichiers joints
@@ -97,6 +98,7 @@
Téléchargement de la soumission pour %sLa soumission a échoué pour %s%s soumis avec succès
+
Une erreur inattendue est survenue.Une erreur de serveur est survenue.
@@ -183,9 +185,9 @@
Il semblerait qu\'aucun module n\'ait encore été créé.Aucune pageIl semblerait qu\'aucune page n\'ait encore été créée.
-
Les modules ont été désactivés pour ce cours.Un problème est survenu
+
OuiNon
@@ -326,6 +328,7 @@
+
La date de déverrouillage ne peut pas être postérieure à la date d’échéance.La date de verrouillage ne peut pas être antérieure à la date d’échéance.La date de verrouillage ne peut pas être antérieure à la date de déverrouillage.
@@ -379,13 +382,14 @@
Télécharger vers le commentaire de soumissionJoindre un fichieravec
+
Notification CanvasNotifications Canvas généralesUne configuration plus complète des notifications est disponible dans la section Préférences des notifications Canvas.
-
%d nouvelles notifications
+
Copier l\'adresse du lienCopier le lienLien copié
@@ -415,7 +419,6 @@
Téléchargement d\'images...Erreur de téléchargement d\'imageRéessayer
-
RechercherChercher des travaux assignésRechercher des annonces
@@ -423,11 +426,33 @@
Rechercher des pagesRechercher des questionnairesAucun élément ne correspond \"%s\"
+
SélectionnéNotifications de mise à jour de l’applicationNotifications Canvas de mises à jour in-app.Application prête à la mise à jourRedémarrez l\'application pour installer la nouvelle version.
+ Aucune planification pour le moment.
+ Noté
+ Réponses
+ Commentaire
+ En retard
+ Refaire
+ Excusé
+ Demain
+ Hier
+ À %s
+ À faire : %s
+ À rendre le : %s
+ Afficher %d éléments manquants
+ Masquer %d éléments manquants
+ Impossible de récupérer votre emploi du temps
+ À faire
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-ht/strings.xml b/libs/pandautils/src/main/res/values-ht/strings.xml
index 2bc2c585fc..b492fef01e 100644
--- a/libs/pandautils/src/main/res/values-ht/strings.xml
+++ b/libs/pandautils/src/main/res/values-ht/strings.xml
@@ -17,7 +17,6 @@
-->
-
Chwazi MedyaAnrejistre VideyoPa gen Kamera
@@ -42,8 +41,11 @@
Notifikasyon TransfèNotifikasyon Canvas pou transfè k ap fèt.
+
+
Ajoute Fichye
+
KameraGaleriAparèy
@@ -53,7 +55,6 @@
FichyeFichye %sDosye %s
-
Ajoute ElemanSesyonFichye Ajoute
@@ -97,6 +98,7 @@
Transfè soumisyon pou %sSoumisyon echwe pou %sSoumisyon reyisi %s
+
Yon erè fèt sanzatann.Gen yon erè sèvè ki fèt.
@@ -183,9 +185,9 @@
Ta sanble poko gen modil ki kreye.Okenn PajTa sanble poko gen paj ki kreye.
-
Yo dezaktive modil yo pou kou sa a.Gen yon Bagay ki Pase Mal
+
WiNon
@@ -326,6 +328,7 @@
+
Dat deblokaj la pa kapab aprè delè aDat blokaj la pa dwe anvan delè aDat blokaj la pa dwe anvan dat deblokaj la
@@ -379,13 +382,14 @@
Soumèt nan Kòmantè SoumisyonAjoute yon fichyeavèk
+
Avètisman CanvasAvètisman Jeneral CanvasPlis konfigirasyon avètisman posib nan seksyon Preferans Avètisman Canvas la.
-
%d Nouvo notifikasyon
+
Kopye lyenKopye LyenLyen kopye
@@ -415,7 +419,6 @@
Transfè Imaj...Erè Transfè ImajRe eseye
-
ChècheChèche SesyonChèche Anons
@@ -423,11 +426,33 @@
Chèche PajChèche QuizPa gen eleman ki koresponn \"%s\"
+
ChwaziNotifikasyon aktyalizasyon appNotifikasyon Canvas pou achte nan app la.App pare pou aktyalizeRelanse app la pou ka enstale nouvo vèsyon an
+ Anyen poko planifye
+ Klase
+ Repons
+ Kòmantè
+ An reta
+ Refè
+ Egzante
+ Demen
+ Yè
+ Nan %s
+ Tach: %s
+ Delè: %s
+ Afiche %d eleman ki manke
+ Kache %d eleman ki manke
+ Enposib pou rekipere agenda w la
+ Pou Fè
+
+ %s pwen
+ %s pwen
+ %s pwen
+
diff --git a/libs/pandautils/src/main/res/values-is/strings.xml b/libs/pandautils/src/main/res/values-is/strings.xml
index cd5fb2dc0e..d17f9b8b03 100644
--- a/libs/pandautils/src/main/res/values-is/strings.xml
+++ b/libs/pandautils/src/main/res/values-is/strings.xml
@@ -17,7 +17,6 @@
-->
-
Velja miðilTaka myndbandEngin myndavél
@@ -46,6 +45,7 @@
Bæta við skrá
+
MyndavélGalleríTæki
@@ -121,6 +121,7 @@
Ein eða fleiri af skránum er með nafnauka skrár sem er ekki leyfðValin skráargerð er ekki leyfileg.Leyfðir nafnaukar:
+
Athugaðu gagnatengingu þína og reyndu aftur. Tækið er ekki með uppsett app sem getur opnað þessa skrá.
@@ -184,9 +185,9 @@
Það lítur ekki út fyrir að einingar hafi verið búnar til.Engar síðurEngin síður virðast hafa verið stofnaðar enn.
-
Slökkt hefur verið á námsefni fyrir þetta námskeið.Eitthvað fór úrskeiðis
+
JáNei
@@ -327,6 +328,7 @@
+
Aflæsingardagur getur ekki verið eftir skiladagLæsingardagur getur ekki verið fyrir skiladagLæsingardagur getur ekki verið fyrir opnunardag
@@ -384,10 +386,10 @@
Canvas-tilkynningarAlmennar Canvas-tilkynningarFrekari fínstillingar á tilkynningum má gera innan kjörstillingahluta Canvas-tilkynninga.
-
%d nýjar tilkynningar
+
Afrita slóð tengilsAfrita tengilTengill afritaður
@@ -431,5 +433,26 @@
Canvas tilkynningar vegna uppfærslu í smáforriti.Smáforrit tilbúið til uppfærsluEndurræstu smáforritið til að setja upp nýja útgáfu
+ Ekkert skipulagt ennþá
+ Metið
+ Svör
+ Endurgjöf
+ Seint
+ Endurtaka
+ Undanþegið
+ Á morgun
+ Í gær
+ Á %s
+ Verkefni: %s
+ Skil: %s
+ Sýna %d hluti sem vantar
+ Fela %d hluti sem vantar
+ Get ekki náð í stundaskrá þína
+ Verkefnalisti
+
+ %s punktur
+ %s punktur
+ %s punktar
+
diff --git a/libs/pandautils/src/main/res/values-it/strings.xml b/libs/pandautils/src/main/res/values-it/strings.xml
index 719f0321c9..76a63af84c 100644
--- a/libs/pandautils/src/main/res/values-it/strings.xml
+++ b/libs/pandautils/src/main/res/values-it/strings.xml
@@ -17,7 +17,6 @@
-->
-
Scegli supporto multimedialeRealizza videoNessuna videocamera
@@ -46,6 +45,7 @@
Aggiungi file
+
VideocameraGalleriaDispositivo
@@ -121,6 +121,7 @@
Uno o più file hanno un’estensione file non consentitaIl tipo di file selezionato non è consentito.Estensioni consentite:
+
Verificare la connessione dati e riprovare. Il tuo dispositivo non ha alcuna applicazione installata in grado di gestire questo file.
@@ -184,9 +185,9 @@
Sembra che non sia stato ancora creato alcun modulo.Nessuna paginaSembra che non siano ancora state create delle pagine.
-
I moduli sono stati disabilitati per questo corso.Si è verificato un problema
+
SìNo
@@ -327,6 +328,7 @@
+
La data di sblocco non può essere successiva alla data di scadenzaLa data di blocco non può essere precedente alla data di scadenzaLa data di blocco non può essere precedente alla data di sblocco
@@ -384,10 +386,10 @@
Notifica CanvasNotifiche Canvas generaliUn\'ulteriore configurazione delle notifiche può eseguita all\'interno della sezione Preferenze notifiche Canvas.
-
%d nuove notifiche
+
Copia indirizzo linkCopia linkLink copiato
@@ -431,5 +433,26 @@
Notifiche Canvas per aggiornamenti in-app.App pronta per l’aggiornamentoRiavvia l’app per installare la nuova versione
+ Ancora nulla programmato
+ Valutato
+ Risposte
+ Feedback
+ In ritardo
+ Ripeti
+ Giustificato
+ Domani
+ Ieri
+ Alle %s
+ Elenco attività: %s
+ Scadenza: %s
+ Mostra %d elementi mancanti
+ Nascondi %d elementi mancanti
+ Impossibile recuperare programma
+ Elenco attività
+
+ %s pt
+ %s pt
+ %s pt.
+
diff --git a/libs/pandautils/src/main/res/values-ja/strings.xml b/libs/pandautils/src/main/res/values-ja/strings.xml
index 6b1f954ada..80e3fd74e8 100644
--- a/libs/pandautils/src/main/res/values-ja/strings.xml
+++ b/libs/pandautils/src/main/res/values-ja/strings.xml
@@ -17,7 +17,6 @@
-->
-
メディアを選択動画を撮影するカメラが使用出来ません
@@ -42,8 +41,11 @@
アップロード通知アップロード中の Canvas 通知。
+
+
ファイルを追加
+
カメラギャラリーデバイス
@@ -53,7 +55,6 @@
ファイルファイル%sフォルダ%s
-
アイテム課題添付されたファイル
@@ -97,6 +98,7 @@
%s の提出をアップロードしています%s の提出に失敗しました%s の提出に成功しました
+
予期しないエラーが発生しました。サーバーエラーが発生しました。
@@ -183,9 +185,9 @@
まだ作成されているモジュールはないようです。ページなしまだページが作成されていないようです。
-
このコースではモジュールが無効になっています。なにかが失敗しました
+
はいいいえ
@@ -323,6 +325,7 @@
+
ロック解除日を締切日より後にすることはできませんロック日を締切日より前にすることはできませんロック日をロック解除日より前にすることはできません
@@ -376,13 +379,14 @@
コメント送信をアップロードファイルを添付すると
+
Canvas 通知一般的な Canvas 通知通知の詳細設定は、Canvas キャンバス通知設定のセクションで行うことができます。
-
%dの新規通知
+
リンクアドレスをコピーリンクをコピーリンクがコピーされました
@@ -412,7 +416,6 @@
画像をアップロード中...画像アップロードエラー再試行
-
検索課題の検索お知らせ検索
@@ -420,11 +423,32 @@
ページ検索クイズ検索\"%s\"にマッチするアイテムなし
+
選択されましたアプリの更新通知アプリ内更新の Canvas 通知。更新の準備ができているアプリアプリを再開して、新しいバージョンをインストールする
+ 計画はまだありません
+ 採点済み
+ 返信
+ フィードバック
+ 提出遅れ
+ やり直し
+ 免除
+ 明日
+ 昨日
+ %sで
+ タスク:%s
+ 期限:%s
+ %d欠如アイテムを表示する
+ %d欠如アイテムを非表示にする
+ スケジュールを取得できません
+ タスク
+
+ %s 点
+ %s 点
+
diff --git a/libs/pandautils/src/main/res/values-mi/strings.xml b/libs/pandautils/src/main/res/values-mi/strings.xml
index 2d54c72a96..bbe2383935 100644
--- a/libs/pandautils/src/main/res/values-mi/strings.xml
+++ b/libs/pandautils/src/main/res/values-mi/strings.xml
@@ -17,7 +17,6 @@
-->
-
Kōwhiri pāpāhoTangohia ataataKaore he Kāmera
@@ -42,8 +41,11 @@
Kaweake ngā whakamōhiotangaCanvas whakamōhitanga mo ngā kaweake e haere tonu ana
+
+
Tāpiri Kōnae
+
kāmeraTaiwhangapūrere
@@ -96,6 +98,7 @@
Kaweake ana nghā whakamōhitanga mo %sI hapa te tapaetanga mo %sI pai te tuku %s
+
He hapa ohorere i puta.He hapa tūmau i puta
@@ -182,9 +185,9 @@
Ko te āhua nei kaore anō ngā kōwae i hangaia.Kaore ngā WhārangiKo te āhua nei kaore anō ngā whārangi i hangaia.
-
Kaore ngā kōwae i whakahohetia mō tēnei akoranga.I raruraru tētahi mea
+
Aekahore
@@ -325,6 +328,7 @@
+
E kore e taea e iriti te rā i muri i te rā tikaE kore e taea e Maukati te rā kia mua i te rā e tika anaE kore e taea e Maukati te rā i mua i te rā e wetekina ana
@@ -378,13 +382,14 @@
Tukuake kī Tapaetanga KōreroTāpiri he kōnaeki
+
Canvas WhakamohiotangaNgā Canvas Whānui WhakamōhiotangaKa taea te whakahohe i ētahi atu whakamōhiotanga i roto i te wāhanga Canvas Whakamōhio Hiahiatanga.
-
%d whakamōhiotanga whakahou
+
Tārua wāhitau honoTārua honongaKua tāruatia te hono
@@ -428,5 +433,26 @@
Canvas – ngā whakamōhiotanga mō roto taupānga whakahoutangaTaupānga kua reri mo te whakahouTīmata anō te taupānga ki te whakauru te wāhanga hou
+ Heoi kaore he mahere
+ Kōekehia
+ Ngā whakautu
+ Urupare
+ Tūreiti
+ Mahi anō
+ Whakawātea
+ Apopo
+ Inanahi
+ Ī %s
+ Hei Mahi: %s
+ E tika ana: %s
+ Whakāturia %d ngā tūemi e ngaro ana
+ Huna %d ngā tuemi e ngaro ana
+ Kaore e taea te tiki i tō pūwhakarite
+ Hei mahi
+
+ %s koinga
+ %s koinga
+ %s ngā koinga
+
diff --git a/libs/pandautils/src/main/res/values-nb/strings.xml b/libs/pandautils/src/main/res/values-nb/strings.xml
index 678e2e4085..231ea8ec70 100644
--- a/libs/pandautils/src/main/res/values-nb/strings.xml
+++ b/libs/pandautils/src/main/res/values-nb/strings.xml
@@ -17,7 +17,6 @@
-->
-
Velg mediaTa videoIngen kamera
@@ -42,8 +41,11 @@
Varslinger for opplastingerCanvas-varslinger for pågående opplastinger.
+
+
Legg til fil
+
KameraGalleriEnhet
@@ -53,7 +55,6 @@
FilFil %sMapper %s
-
Legg til punktOppgaveVedlagte filer
@@ -97,6 +98,7 @@
Last opp innlevering for %sKunne ikke innlevere for %sInnlevering fullført %s
+
Det oppsto en uventet feil.En serverfeil har oppstått.
@@ -183,9 +185,9 @@
Det ser ut som det ikke er laget noen emner ennå.Ingen siderDet ser ut som det ikke er laget noen sider ennå.
-
Moduler er deaktivert for dette emnet.Noe gikk galt
+
JaNei
@@ -326,6 +328,7 @@
+
Opplåsingsdatoen kan ikke være etter forfallsdatoLåsedato kan ikke være før forfallsdatoLåsedato kan ikke være før opplåsingsdato
@@ -379,13 +382,14 @@
Last opp til sendt inn kommentarLegg ved en filmed
+
Canvas NotificationGenerelle Canvas NotificationsYtterligere konfigurering av varsler kan gjøres inne i seksjonen for Canvas Notification Preferences.
-
%d nye varslinger
+
Kopier lenkeadresseKopiert lenkeLenke kopiert
@@ -415,7 +419,6 @@
Bilde lastes opp...Avvik i opplasting av bildeForsøk igjen
-
SøkOppgavesøkSøk kunngjøringer
@@ -423,11 +426,33 @@
Søk siderSøk testerIngen matchende punkter \"%s\"
+
ValgtVarslinger om app-oppdateringCanvas-varslinger for oppdateringer i appenAppen er klar til å oppdateresStart appen på nytt for å installere den nye versjonen
+ Ingenting planlagt enda
+ Vurdert
+ Svar
+ Tilbakemelding
+ Sent
+ Gjør om
+ Fritatt
+ I morgen
+ I går
+ Den %s
+ Å gjøre: %s
+ Frist: %s
+ Vis %d manglende elementer
+ Skjul %d manglende elementer
+ Kunne ikke hente timeplanen din
+ Å gjøre
+
+ %s poeng
+ %s poeng
+ %s poeng
+
diff --git a/libs/pandautils/src/main/res/values-nl/strings.xml b/libs/pandautils/src/main/res/values-nl/strings.xml
index e319a29504..00555462eb 100644
--- a/libs/pandautils/src/main/res/values-nl/strings.xml
+++ b/libs/pandautils/src/main/res/values-nl/strings.xml
@@ -17,7 +17,6 @@
-->
-
Media kiezenVideo makenGeen camera
@@ -42,8 +41,11 @@
Meldingen voor uploadenCanvas-meldingen voor actieve uploads.
+
+
Een bestand toevoegen
+
CameraGalerijApparaat
@@ -53,7 +55,6 @@
BestandBestand %sMap %s
-
Item toevoegenOpdrachtBijgevoegde bestanden
@@ -97,6 +98,7 @@
Bezig met uploaden van inlevering voor %sInlevering mislukt voor %sInlevering van %s is gelukt
+
Er is een onverwachte fout opgetreden.Er is een serverfout opgetreden.
@@ -183,9 +185,9 @@
Het lijkt erop dat er nog geen modules zijn gemaakt.Geen pagina\'sHet lijkt erop dat er nog geen pagina\'s zijn gemaakt.
-
Er zijn voor deze cursus modules uitgeschakeld.Er is iets misgegaan
+
JaNee
@@ -326,6 +328,7 @@
+
Vergrendeldatum kan niet na inleverdatum vallenVergrendeldatum kan niet voor vervaldatum vallenVergrendeldatum kan niet voor ontgrendeldatum vallen
@@ -379,13 +382,14 @@
Uploaden naar Opmerking bij inleveringBestand bijvoegenmet
+
Canvas-meldingAlgemene Canvas-meldingenVerdere configuratie van meldingen kan worden uitgevoerd in de sectie Voorkeuren Canvas-meldingen.
-
%d nieuwe meldingen
+
Linkadres kopiërenLink kopierenLink gekopieerd
@@ -415,7 +419,6 @@
Afbeelding wordt geüpload...Fout tijdens afbeelding uploadenOpnieuw proberen
-
ZoekenZoek opdrachtenAankondigingen zoeken
@@ -423,11 +426,33 @@
Pagina\'s zoekenToetsen zoekenEr komen geen items overeen met \"%s\"
+
GeselecteerdMeldingen voor app-updateCanvas-meldingen voor in-app updates.App klaar voor updateStart de app opnieuw om de nieuwe versie te installeren
+ Nog niets gepland
+ Beoordeeld
+ Antwoorden
+ Feedback
+ Te laat
+ Opnieuw
+ Vrijgesteld
+ Morgen
+ Gisteren
+ Om %s
+ Takenlijst: %s
+ Inleverdatum: %s
+ %d ontbrekende items tonen
+ %d ontbrekende items verbergen
+ Kan je planning niet ophalen
+ Takenlijst
+
+ %s pt
+ %s pt
+ %s punten
+
diff --git a/libs/pandautils/src/main/res/values-pl/strings.xml b/libs/pandautils/src/main/res/values-pl/strings.xml
index 09e06ff76f..9ee54bb84b 100644
--- a/libs/pandautils/src/main/res/values-pl/strings.xml
+++ b/libs/pandautils/src/main/res/values-pl/strings.xml
@@ -17,7 +17,6 @@
-->
-
Wybierz mediaNagraj wideoBrak kamery
@@ -42,8 +41,11 @@
Powiadomienia dotyczące przesyłekPowiadomienia Canvas dotyczące przesyłanych obecnie plików.
+
+
Dodaj plik
+
KameraGaleriaUrządzenie
@@ -53,7 +55,6 @@
PlikPlik %sFolder %s
-
Dodaj elementZadanieZałączone pliki
@@ -97,6 +98,7 @@
Przesyłanie plików dla %sNie udało się przesłać plików dla %sPrzesłano pomyślnie %s
+
Wystąpił nieoczekiwany błąd.Wystąpił błąd serwera.
@@ -183,9 +185,9 @@
Wygląda na to, że nie utworzono jeszcze modułów.Brak stronWygląda na to, że nie utworzono jeszcze stron.
-
Wyłączono moduły dla tego kursu.Coś poszło nie tak
+
TakNie
@@ -332,6 +334,7 @@
+
Data odblokowania nie może przypadać po terminie dostarczeniaData zablokowania nie może przypadać przed terminem dostarczeniaData zablokowania nie może przypadać przed datą odblokowania
@@ -385,15 +388,16 @@
Prześlij do komentarzyDołącz plikz
+
Powiadomienie CanvasOgólne powiadomienia CanvasDalszą konfigurację powiadomień można wykonać w sekcji Preferencje powiadomień Canvas.
-
Nowe powiadomienia: %dNowe powiadomienia: %dNowe powiadomienia: %d
+
Kopiuj adres łączaKopiuj łączeSkopiowano łącze
@@ -423,7 +427,6 @@
Przesyłanie obrazu...Błąd przesyłania obrazuPonów próbę
-
WyszukajWyszukaj zadaniaWyszukaj ogłoszenia
@@ -431,11 +434,35 @@
Wyszukaj stronyWyszukaj testyNie znaleziono pasujących do \"%s\"
+
WybranoPowiadomienia o aktualizacji w aplikacjiPowiadomienia Canvas dotyczące aktualizacji dostępnych w aplikacji.Aplikacja gotowa do zaktualizowaniaUruchom ponownie aplikację, aby zainstalować nową wersję.
+ Nic jeszcze nie zaplanowano
+ Oceniono
+ Odpowiedzi
+ Informacje zwrotne
+ Późno
+ Cofnij
+ Usprawiedliwiony
+ Jutro
+ Wczoraj
+ O %s
+ Lista zadań: %s
+ Termin: %s
+ Pokaż brakujące elementy: %d
+ Ukryj brakujące elementy: %d
+ Nie można pobrać harmonogramu
+ Lista zadań
+
+ %s pkt
+ %s pkt
+ %s pkt
+ %s pkt
+ %s pkt
+
diff --git a/libs/pandautils/src/main/res/values-pt-rBR/strings.xml b/libs/pandautils/src/main/res/values-pt-rBR/strings.xml
index 64b5cced4d..529621e626 100644
--- a/libs/pandautils/src/main/res/values-pt-rBR/strings.xml
+++ b/libs/pandautils/src/main/res/values-pt-rBR/strings.xml
@@ -17,7 +17,6 @@
-->
-
Escolher MídiaFazer VídeoNenhuma Câmera
@@ -46,6 +45,7 @@
Adicionar arquivo
+
CâmeraGaleriaDispositivo
@@ -121,6 +121,7 @@
Um ou mais arquivos tem uma extensão de arquivo que não é permitidaO tipo de arquivo selecionado não é permitido.Extensões permitidas:
+
Por favor, verifique sua conexão de dados e tente novamente. O seu dispositivo não tem nenhum aplicativo instalado que pode lidar com esse arquivo.
@@ -184,9 +185,9 @@
Parece que ainda não há quaisquer módulos criados.Sem páginasParece que nenhuma página foi criada ainda.
-
Os módulos foram desativados para este curso.Algo deu errado
+
SimNão
@@ -327,6 +328,7 @@
+
A data de desbloqueio não pode ser posterior ao prazo de entregaA data de bloqueio não pode ser anterior ao prazo de entregaA data de bloqueio não pode ser anterior à data de desbloqueio
@@ -384,10 +386,10 @@
Notificação do CanvasNotificações gerais do CanvasUma maior configuração de notificações pode ser feita dentro da turma de Preferências de Notificação do Canvas.
-
%d novas notificações
+
Copiar endereço do linkCopiar LinkLink copiado
@@ -431,5 +433,26 @@
Notificações do Canvas para atualizações no aplicativo.Aplicativo pronto para atualizarReinicie o aplicativo para instalar a nova versão
+ Nada planejado ainda
+ Avaliado
+ Respostas
+ Feedback
+ Atrasado
+ Refazer
+ Dispensado
+ Amanhã
+ Ontem
+ Em %s
+ Para fazer: %s
+ Vencimento: %s
+ Mostrar %d itens ausentes
+ Ocultar %d itens ausentes
+ Incapaz de buscar sua programação
+ Lista de Tarefas
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-pt-rPT/strings.xml b/libs/pandautils/src/main/res/values-pt-rPT/strings.xml
index d89139f1b9..678a96c1fb 100644
--- a/libs/pandautils/src/main/res/values-pt-rPT/strings.xml
+++ b/libs/pandautils/src/main/res/values-pt-rPT/strings.xml
@@ -17,7 +17,6 @@
-->
-
Escolher MultimédiaFazer VídeoSem Câmara
@@ -42,8 +41,11 @@
Notificações de transferênciaNotificações de exrã para transferências em andamento.
+
+
Adicionar ficheiro
+
CâmaraGaleriaDispositivo
@@ -53,7 +55,6 @@
FicheiroFicheiro %sPasta %s
-
Adicionar ItemTarefaFicheiros Anexados
@@ -97,6 +98,7 @@
Enviando envio para %sA submissão falhou para %sSubmetido com sucesso %s
+
Ocorreu um erro inesperado.Ocorreu um erro no servidor.
@@ -183,9 +185,9 @@
Parece que não há nenhum módulo criado ainda.Sem páginasParece que nenhuma página foi criada ainda.
-
Módulos foram definidos para esta disciplina.Algo deu errado
+
SimNão
@@ -326,6 +328,7 @@
+
A data de desbloqueio não pode ser posterior à data de entregaA data de bloqueio não pode ser anterior à data de entregaA data de bloqueio não pode ser anterior à data de desbloqueio
@@ -379,13 +382,14 @@
Carregar para comentário de envioAnexar um ficheirocom
+
Notificação CanvasNotificações gerais do CanvasA configuração adicional das notificações pode ser feita dentro da seção de Preferências de Notificação de Canvas.
-
%d novas notificações
+
Copiar endereço da ligaçãoCopiar ligaçãoLigação copiada
@@ -415,7 +419,6 @@
Carregamento de imagem...Erro ao carregar imagemTentar novamente
-
PesquisarPesquisar TarefasPesquisar Anúncios
@@ -423,11 +426,33 @@
Pesquisar Páginas Pesquisar testesNenhum item corresponde \"%s\"
+
SelecionadoNotificações de atualização de aplicaçõesNotificações de lona para atualizações no Canvas.Aplicação pronta a actualizarReiniciar a aplicação para instalar a nova versão
+ Nada planeado ainda
+ Classificado
+ Respostas
+ Resposta
+ Atrasado
+ Refazer
+ Desculpado
+ Amanhã
+ Ontem
+ Em %s
+ Tarefa: %s
+ Vencimento: %s
+ Mostrar %d itens em falta
+ Ocultar %d itens em falta
+ Incapaz de ir buscar a sua agenda
+ A Fazer
+
+ %s pt
+ %s pt
+ %s pts
+
diff --git a/libs/pandautils/src/main/res/values-ru/strings.xml b/libs/pandautils/src/main/res/values-ru/strings.xml
index d5ffeb0156..fd6c0f5a45 100644
--- a/libs/pandautils/src/main/res/values-ru/strings.xml
+++ b/libs/pandautils/src/main/res/values-ru/strings.xml
@@ -17,7 +17,6 @@
-->
-
Выбрать медиаСнять видеоНет камеры
@@ -42,8 +41,11 @@
Загрузить уведомленияУведомления Canvas для текущих загрузок.
+
+
Добавить файл
+
КамераГалереяУстройство
@@ -53,7 +55,6 @@
ФайлФайл %sПапка %s
-
Добавить элементЗаданиеПрикрепленные файлы
@@ -97,6 +98,7 @@
Загрузка отправки для %sСбой отправки для %sУспешно отправлено %s
+
Произошла непредвиденная ошибка.Произошла ошибка сервера.
@@ -183,9 +185,9 @@
Похоже, что модули еще не создавались.Страницы отсутствуютПохоже, что страницы еще не создавались.
-
Модули были отключены для этого курса.Что-то пошло не так
+
ДаНет
@@ -332,6 +334,7 @@
+
Дата разблокирования не может быть позже даты выполненияДата блокировки не может быть раньше даты выполненияДата блокирования не может быть раньше даты разблокирования
@@ -385,15 +388,16 @@
Загрузить в комментарий к отправкеПрикрепить файлс
+
Уведомление CanvasОбщие уведомления CanvasДополнительная настройка уведомлений осуществляется в разделе настроек уведомлений Canvas.
-
%d новых уведомлений%d новых уведомлений%d новых уведомлений
+
Копировать адрес ссылкиКопировать ссылкуСсылка скопирована
@@ -423,7 +427,6 @@
Выполняется загрузка изображения...Ошибка при загрузке изображенияПовторить
-
ПоискПоиск заданийПоиск объявлений
@@ -431,11 +434,35 @@
Поиск страницПоиск тестовНет совпадений по элементам \"%s\"
+
ВыбраноУведомления об обновлениях приложенияУведомления Canvas для обновлений в приложении.Приложение готово к обновлениюПерезапустите приложение, чтобы установить новую версию
+ Пока ничего не запланировано
+ С оценкой
+ Ответы
+ Оценка
+ Поздно
+ Повторить
+ По уважительной причине
+ Завтра
+ Вчера
+ В %s
+ Задачи: %s
+ Срок: %s
+ Показать %d отсутствующие элементы
+ Скрыть %d отсутствующие элементы
+ Не удалось получить ваше расписание
+ Задачи
+
+ %s баллов
+ %s баллов
+ %s баллов
+ %s баллов
+ %s баллов
+
diff --git a/libs/pandautils/src/main/res/values-sl/strings.xml b/libs/pandautils/src/main/res/values-sl/strings.xml
index 2d1647268d..9143a2b154 100644
--- a/libs/pandautils/src/main/res/values-sl/strings.xml
+++ b/libs/pandautils/src/main/res/values-sl/strings.xml
@@ -17,7 +17,6 @@
-->
-
Izberi medijPosnemi videoposnetekNi fotoaparata.
@@ -42,8 +41,11 @@
Obvestila o nalaganjuObvestila sistema Canvas za nalaganja v teku.
+
+
Dodaj datoteko
+
FotoaparatGalerijaNaprava
@@ -96,6 +98,7 @@
Nalagam oddaje za %sOddaja za %s ni uspela%s uspešno poslana
+
Prišlo je do nepričakovane napake.Prišlo je do napake strežnika.
@@ -182,9 +185,9 @@
Zdi se, da ni ustvarjen še noben modul.Ni strani.Zdi se, da ni ustvarjene še nobene strani.
-
Za ta predmet so bili nekateri moduli onemogočeni.Prišlo je do težav
+
DaNe
@@ -325,6 +328,7 @@
+
Datum sprostitve zapore ne more biti po roku.Datum zapore objave ne more biti pred rokom.Datum zapore objave ne more biti pred datumom sprostitve zapore.
@@ -378,13 +382,14 @@
Naloži v komentar oddajeDodaj datotekos/z
+
Sporočila sistema CanvasSplošna sporočila sistema CanvasDodatno konfiguriranje sporočil je mogoče v sekciji sistema Canvas »Prednostne nastavitve«.
-
%d novih obvestil
+
Kopiraj naslov povezaveKopiraj povezavoPovezava je kopirana.
@@ -428,5 +433,26 @@
Obvestila Canvas za posodobitve v aplikaciji.Aplikacija pripravljena za posodobitevAplikacijo zaženite znova, da namestite novo različico
+ Nič še ni načrtovano
+ Ocenjeno
+ Odgovori
+ Povratne informacije
+ Zamuda
+ Uveljavi
+ Opravičeno
+ Jutri
+ Včeraj
+ Ob %s
+ Čakajoča opravila: %s
+ Rok: %s
+ Prikaži %d manjkajočih elementov
+ Skrij %d manjkajočih elementov
+ Vašega urnika ni mogoče pridobiti
+ Čakajoča opravila
+
+ %s točka
+ %s točka
+ %s točk
+
diff --git a/libs/pandautils/src/main/res/values-sv/strings.xml b/libs/pandautils/src/main/res/values-sv/strings.xml
index 1b5599bac9..1372433e3e 100644
--- a/libs/pandautils/src/main/res/values-sv/strings.xml
+++ b/libs/pandautils/src/main/res/values-sv/strings.xml
@@ -17,7 +17,6 @@
-->
-
Välj mediaSpela in videoIngen kamera
@@ -46,6 +45,7 @@
Lägg till fil
+
KameraGalleriEnhet
@@ -91,7 +91,7 @@
FilikonLaddar upp %d av %d%s har låsts.
- Filerna är nu redo att ladda ned. Försök igen.
+ Filen är nu klar att laddas ner. Försök igen.Filen kan inte laddas ned utan att ge behörighet.Behörighet beviljades. Försök igen..Välj en fil
@@ -121,6 +121,7 @@
En eller flera filer har ett filtillägg som inte är tillåtetDen valda filtypen är inte tillåten.Tillåtna tillägg:
+
Kontrollera din dataanslutning och försök igen. Din enhet har inte några program installerade som kan hantera den här filen.
@@ -184,9 +185,9 @@
Det verkar inte som om några moduler har skapats än.Inga sidorDet verkar inte som om några sidor har skapats än.
-
Moduler har inaktiverats för den här kursen.Något gick fel
+
JaNej
@@ -327,6 +328,7 @@
+
Upplåsningsdatumet kan inte vara efter sista inlämningsdatumetLåsdatumet kan inte vara före inlämningsdatumetLåsdatumet kan inte vara före upplåsningsdatumet
@@ -343,7 +345,7 @@
Lägg till ny
- Ladda ned bilaga
+ Ladda ner bilagaTa bort bilaga
@@ -384,10 +386,10 @@
Canvas-aviseringAllmänna Canvas-aviseringarYtterligare konfiguration av aviseringar kan göras i sektionen för inställningar för Canvas-aviseringar.
-
%d nya aviseringar
+
Kopiera länkadressKopiera länkLänken har kopierats
@@ -431,5 +433,26 @@
Canvas-aviseringar för uppdateringar i appen.Appen är klar för uppdateringStarta om appen för att installera den nya versionen
+ Inget planerat ännu
+ Har bedömts
+ Svar
+ Feedback
+ Sen
+ Gör om
+ Ursäktad
+ I morgon
+ I går
+ kl. %s
+ Att göra: %s
+ Förfallodatum: %s
+ Visa %d saknade objekt
+ Dölj %d saknade objekt
+ Det gick inte att hämta ditt schema
+ Att göra
+
+ %s poäng
+ %s poäng
+ %s poäng
+
diff --git a/libs/pandautils/src/main/res/values-th/strings.xml b/libs/pandautils/src/main/res/values-th/strings.xml
index ac57f40d89..a5c77e8fd3 100644
--- a/libs/pandautils/src/main/res/values-th/strings.xml
+++ b/libs/pandautils/src/main/res/values-th/strings.xml
@@ -17,7 +17,6 @@
-->
-
เลือกไฟล์สื่อถ่ายวิดีโอไม่มีกล้อง
@@ -329,6 +328,7 @@
+
วันที่ปลดล็อคจะอยู่หลังจากวันครบกำหนดไม่ได้วันที่ล็อคจะอยู่ก่อนวันครบกำหนดไม่ได้วันที่ล็อคจะอยู่ก่อนวันที่ปลดล็อคไม่ได้
@@ -434,5 +434,26 @@
การแจ้งข้อมูลจาก Canvas สำหรับข้อมูลอัพเดตในแอพแอพพร้อมสำหรับการอัพเดตรีสตาร์ทแอพเพื่อติดตั้งเวอร์ชั่นใหม่
+ ยังไม่มีแผนตอนนี้
+ ให้เกรดแล้ว
+ การตอบกลับ
+ ผลตอบรับ
+ ล่าช้า
+ ทำซ้ำ
+ ได้รับการยกเว้น
+ พรุ่งนี้
+ เมื่อวานนี้
+ เมื่อ %s
+ สิ่งที่ต้องทำ: %s
+ ครบกำหนด: %s
+ แสดง %d รายการที่ขาดหาย
+ ซ่อน %d รายการที่ขาดหาย
+ ไม่สามารถสืบค้นกำหนดเวลาของคุณได้
+ สิ่งที่ต้องทำ
+
+ %s คะแนน
+ %s คะแนน
+ %s คะแนน
+
diff --git a/libs/pandautils/src/main/res/values-zh-rHK/strings.xml b/libs/pandautils/src/main/res/values-zh-rHK/strings.xml
index a195b93cb4..499bc22a3b 100644
--- a/libs/pandautils/src/main/res/values-zh-rHK/strings.xml
+++ b/libs/pandautils/src/main/res/values-zh-rHK/strings.xml
@@ -17,7 +17,6 @@
-->
-
選擇媒體錄製視頻無攝像頭
@@ -42,8 +41,11 @@
上傳通知持續上傳的 Canvas 通知。
+
+
添加檔案
+
攝像頭圖片庫設備
@@ -53,7 +55,6 @@
檔案檔案 %s資料夾 %s
-
添加項目作業附加的檔案
@@ -97,6 +98,7 @@
上傳 %s 的提交項目%s 的提交項目提交失敗成功提交 %s
+
意外錯誤出現。伺服器發生錯誤。
@@ -183,9 +185,9 @@
看來尚未建立任何單元。無頁面看來尚未建立任何頁面。
-
已停用此課程的單元。發生問題
+
是否
@@ -323,6 +325,7 @@
+
解除鎖定日期不能在截止日期之後鎖定日期不能在截止日期之前鎖定日期不能在解鎖日期之前
@@ -376,13 +379,14 @@
上傳到提交項目評論附加檔案與
+
Canvas 通知一般 Canvas 通知可於 Canvas 通知偏好的部分進行更多通知配置。
-
%d 個新通知
+
複製連結地址複製連結已複製連結
@@ -412,7 +416,6 @@
正在上傳圖像...圖像上傳錯誤重試
-
搜尋搜尋作業搜尋通告
@@ -420,11 +423,32 @@
搜尋頁面搜尋測驗沒有吻合的項目 \"%s\"
+
已選擇應用程式更新通知Canvas 的應用程式內更新通知應用程式準備好更新重新啟動應用程式以安裝新版本
+ 尚未計劃
+ 已評分
+ 回覆
+ 反饋
+ 逾期
+ 重做
+ 已免除
+ 明天
+ 昨天
+ 時間 %s
+ 待辦事項:%s
+ 截止日期:%s
+ 顯示 %d 個缺少的項目
+ 隱藏 %d 個缺少的項目
+ 無法取得您的排程
+ 待辦事項
+
+ %s 分
+ %s 分
+
diff --git a/libs/pandautils/src/main/res/values-zh/strings.xml b/libs/pandautils/src/main/res/values-zh/strings.xml
index 77c5a3e885..2205760df9 100644
--- a/libs/pandautils/src/main/res/values-zh/strings.xml
+++ b/libs/pandautils/src/main/res/values-zh/strings.xml
@@ -17,7 +17,6 @@
-->
-
选择媒体录制视频无摄像头
@@ -42,8 +41,11 @@
上传通知Canvas关于正在上传项的通知。
+
+
添加文件
+
摄像机、照相机画廊设备
@@ -53,7 +55,6 @@
文件文件%s文件夹%s
-
添加项目作业附加文件
@@ -97,6 +98,7 @@
正在为%s上传提交项为%s提交失败已成功提交%s
+
发生意外错误。发生服务器错误。
@@ -183,9 +185,9 @@
似乎没有创建任何单元。无页面似乎没有创建任何页面。
-
已对此课程禁用模块。遇到错误
+
是否
@@ -323,6 +325,7 @@
+
解锁时间不能晚于截止日期锁定时间不能早于截止时间锁定时间不能早于解锁时间
@@ -376,13 +379,14 @@
上传到提交项评论附加文件与
+
Canvas 通知常规 Canvas 通知在 Canvas 通知首选项部分可对通知进行进一步配置。
-
%d 条新通知
+
复制链接地址复制链接已复制链接
@@ -412,7 +416,6 @@
正在上传图像...图像上传错误重试
-
搜索搜索作业搜索公告
@@ -420,11 +423,32 @@
搜索页面搜索测验无项目匹配\"%s\"
+
已选择应用程序更新通知Canvas应用内更新通知。可更新的应用程序重启应用程序以安装新版本
+ 暂无任何计划
+ 已评分
+ 回复
+ 反馈
+ 迟交
+ 重新操作
+ 已免除
+ 明天
+ 昨天
+ %s
+ 待办事项:%s
+ 到期:%s
+ 显示 %d 个缺失的项目
+ 隐藏 %d 个缺失的项目
+ 无法获取您的日程计划
+ 待办事项
+
+ %s 分
+ %s 分
+
diff --git a/libs/pandautils/src/main/res/values/dimens.xml b/libs/pandautils/src/main/res/values/dimens.xml
index 89e1826442..89f5c3a986 100644
--- a/libs/pandautils/src/main/res/values/dimens.xml
+++ b/libs/pandautils/src/main/res/values/dimens.xml
@@ -48,5 +48,7 @@
24dp8dp
- 360dp
+ 320dp
+
+ 28dp
diff --git a/libs/pandautils/src/main/res/values/strings.xml b/libs/pandautils/src/main/res/values/strings.xml
index 04a1d6f31c..2abcff0827 100644
--- a/libs/pandautils/src/main/res/values/strings.xml
+++ b/libs/pandautils/src/main/res/values/strings.xml
@@ -434,5 +434,53 @@
Canvas notifications for in-app updates.App ready to updateRestart the app to install the new version
+ Nothing planned yet
+ Graded
+
+ %d Reply
+ %d Replies>
+
+ Feedback
+ Late
+ Redo
+ Excused
+ Tomorrow
+ Yesterday
+ At %s
+ %1$s to %2$s
+ To Do: %s
+ Due: %s
+ Show %d missing items
+ Hide %d missing items
+ Unable to fetch your schedule
+ To Do
+
+ %s pt
+ %s pt
+ %s pts
+
+
+ %.1f points
+ %.1f point
+ %.1f points
+
+ All Day
+ Missing
+ Previous Week
+ Next Week
+ %1$s, %2$s
+ Course %s
+ Announcement
+ Discussion
+ Calendar event
+ Assignment
+ Planner note
+ Quiz
+ To Do
+ Page
+ Marked as done
+ Not marked as done
+ Mark as done
+ mark_as_not_done
diff --git a/libs/pandautils/src/main/res/values/styles.xml b/libs/pandautils/src/main/res/values/styles.xml
index bf3a13abbc..4edd39a8ab 100644
--- a/libs/pandautils/src/main/res/values/styles.xml
+++ b/libs/pandautils/src/main/res/values/styles.xml
@@ -204,4 +204,9 @@
center_vertical
+
+
diff --git a/libs/pandautils/src/main/res/values/themes_canvasthemes.xml b/libs/pandautils/src/main/res/values/themes_canvasthemes.xml
index 2f3f31d860..5f187373e6 100644
--- a/libs/pandautils/src/main/res/values/themes_canvasthemes.xml
+++ b/libs/pandautils/src/main/res/values/themes_canvasthemes.xml
@@ -59,4 +59,19 @@
@android:style/Animation
+
+
+
+
diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/grades/GradesViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/grades/GradesViewModelTest.kt
new file mode 100644
index 0000000000..baebb2a4f3
--- /dev/null
+++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/grades/GradesViewModelTest.kt
@@ -0,0 +1,364 @@
+/*
+ * 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.pandautils.features.elementary.grades
+
+import android.content.res.Resources
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.instructure.canvasapi2.managers.CourseManager
+import com.instructure.canvasapi2.managers.EnrollmentManager
+import com.instructure.canvasapi2.models.Course
+import com.instructure.canvasapi2.models.Enrollment
+import com.instructure.canvasapi2.models.GradingPeriod
+import com.instructure.canvasapi2.utils.DataResult
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.grades.itemviewmodels.GradeRowItemViewModel
+import com.instructure.pandautils.features.elementary.grades.itemviewmodels.GradingPeriodSelectorItemViewModel
+import com.instructure.pandautils.mvvm.ViewState
+import com.instructure.pandautils.utils.ColorApiHelper
+import io.mockk.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import com.instructure.pandautils.features.elementary.grades.GradingPeriod as GradingPeriodView
+
+@ExperimentalCoroutinesApi
+class GradesViewModelTest {
+
+ @get:Rule
+ var instantExecutorRule = InstantTaskExecutorRule()
+
+ private val lifecycleOwner: LifecycleOwner = mockk(relaxed = true)
+ private val lifecycleRegistry = LifecycleRegistry(lifecycleOwner)
+
+ private val testDispatcher = TestCoroutineDispatcher()
+
+ private val courseManager: CourseManager = mockk(relaxed = true)
+ private val resources: Resources = mockk(relaxed = true)
+ private val enrollmentManager: EnrollmentManager = mockk(relaxed = true)
+
+ private lateinit var viewModel: GradesViewModel
+
+ @Before
+ fun setUp() {
+ every { resources.getString(R.string.currentGradingPeriod) } returns "Current"
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ testDispatcher.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun `Show error state if fetching courses fails`() {
+ // Given
+ every { resources.getString(R.string.failedToLoadGrades) } returns "Failed to load grades"
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Fail()
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Error)
+ assertEquals("Failed to load grades", (viewModel.state.value as ViewState.Error).errorMessage)
+ }
+
+ @Test
+ fun `Show empty state if there are no courses`() {
+ // Given
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(emptyList())
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Empty)
+ assertEquals(R.string.noGradesToDisplay, (viewModel.state.value as ViewState.Empty).emptyTitle)
+ }
+
+ @Test
+ fun `Show success state and grades with correct data without grading periods if there are no grading periods`() {
+ // Given
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A")
+ val course2 = createCourseWithGrades(2, "Course with Score", "#123456", "www.1.com", 75.6, "")
+ val course3 = createCourseWithGrades(3, "Course without scores", "#456789", "www.1.com", null, null)
+ val course4 = createCourseWithGrades(4, "Hide Final Grades", "#456789", "www.1.com", 50.0, "C", hideFinalGrades = true)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1, course2, course3, course4))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(4, viewModel.data.value!!.items.size) // We only expect 4 items here, because we don't have the grading period selector
+
+ val gradeRows = viewModel.data.value!!.items.map { it as GradeRowItemViewModel }
+
+ val expectedGradeRow1 = GradeRowViewData(1, "Course with Grade", ColorApiHelper.K5_DEFAULT_COLOR, "www.1.com", 90.0, "A")
+ val expectedGradeRow2 = GradeRowViewData(2, "Course with Score", "#123456", "www.1.com", 75.6, "76%")
+ val expectedGradeRow3 = GradeRowViewData(3, "Course without scores", "#456789", "www.1.com", null, "--")
+ val expectedGradeRow4 = GradeRowViewData(4, "Hide Final Grades", "#456789", "www.1.com", 0.0, "--")
+
+ assertEquals(expectedGradeRow1, gradeRows[0].data)
+ assertEquals(expectedGradeRow2, gradeRows[1].data)
+ assertEquals(expectedGradeRow3, gradeRows[2].data)
+ assertEquals(expectedGradeRow4, gradeRows[3].data)
+
+ assertEquals(0.9f, gradeRows[0].percentage)
+ assertEquals(0.756f, gradeRows[1].percentage)
+ assertEquals(0.0f, gradeRows[2].percentage)
+ assertEquals(0.0f, gradeRows[3].percentage)
+ }
+
+ @Test
+ fun `Show success state and grades with correct data with grading periods`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(2, viewModel.data.value!!.items.size) // We have 1 item for grading period selector and 1 course item
+
+ val gradeRows = viewModel.data.value!!.items
+ assertTrue(gradeRows[0] is GradingPeriodSelectorItemViewModel)
+ assertTrue(gradeRows[1] is GradeRowItemViewModel)
+
+ val gradingPeriodsViewModel = gradeRows[0] as GradingPeriodSelectorItemViewModel
+ assertTrue(gradingPeriodsViewModel.isNotEmpty())
+ assertEquals(GradingPeriodView(-1, "Current"), gradingPeriodsViewModel.selectedGradingPeriod)
+ }
+
+ @Test
+ fun `Refresh error for current grading period sends refresh error event`() {
+ // Given
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A")
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returnsMany listOf(DataResult.Success(listOf(course1)), DataResult.Fail())
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+ viewModel.refresh()
+
+ // Then
+ assertEquals(GradesAction.ShowRefreshError, viewModel.events.value!!.getContentIfNotHandled()!!)
+ assertEquals(ViewState.Error(), viewModel.state.value!!)
+ }
+
+ @Test
+ fun `Do nothing when the same grading period is selected`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ clearMocks(courseManager, enrollmentManager)
+
+ viewModel.gradingPeriodSelected(GradingPeriodView(-1, "Current"))
+
+ // Then
+ verify(exactly = 0) { courseManager.getCoursesWithGradesAsync(any()) }
+ verify(exactly = 0) { enrollmentManager.getEnrollmentsForGradingPeriodAsync(any(), any()) }
+ }
+
+ @Test
+ fun `Load grades for grading period if different grading period is selected`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ clearMocks(courseManager, enrollmentManager)
+
+ viewModel.gradingPeriodSelected(GradingPeriodView(11, "Period 11"))
+
+ // Then
+ verify(exactly = 0) { courseManager.getCoursesWithGradesAsync(any()) }
+ verify(exactly = 1) { enrollmentManager.getEnrollmentsForGradingPeriodAsync(eq(11), any()) }
+ }
+
+ @Test
+ fun `Reselect previous grading period if there is an error loading grades for grading period`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ every { enrollmentManager.getEnrollmentsForGradingPeriodAsync(any(), any()) } returns mockk() {
+ coEvery { await() } returns DataResult.Fail()
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ clearMocks(courseManager, enrollmentManager)
+
+ viewModel.gradingPeriodSelected(GradingPeriodView(11, "Period 11"))
+
+ // Then
+ assertEquals(GradingPeriodView(-1, "Current"), (viewModel.data.value!!.items[0] as GradingPeriodSelectorItemViewModel).selectedGradingPeriod)
+ }
+
+ @Test
+ fun `Load grades for course is current grading period is reselected`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returnsMany listOf(DataResult.Success(listOf(course1)), DataResult.Fail())
+ }
+
+ every { enrollmentManager.getEnrollmentsForGradingPeriodAsync(any(), any()) } returns mockk() {
+ coEvery { await() } returns DataResult.Success(emptyList())
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+ viewModel.gradingPeriodSelected(GradingPeriodView(11, "Period 11"))
+
+ clearMocks(courseManager, enrollmentManager)
+
+ viewModel.gradingPeriodSelected(GradingPeriodView(-1, "Current"))
+
+ // Then
+ verify(exactly = 1) { courseManager.getCoursesWithGradesAsync(any()) }
+ verify(exactly = 0) { enrollmentManager.getEnrollmentsForGradingPeriodAsync(eq(11), any()) }
+ }
+
+ @Test
+ fun `Open grades page whe grades row clicked`() {
+ // Given
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A")
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ val gradeRowViewModel = viewModel.data.value!!.items[0] as GradeRowItemViewModel
+ gradeRowViewModel.onRowClicked()
+
+ // Then
+ assertEquals(GradesAction.OpenCourseGrades(course1), viewModel.events.value!!.getContentIfNotHandled())
+ }
+
+ @Test
+ fun `Show grading period selector dialog if grading period is clicked`() {
+ // Given
+ val gradingPeriods = listOf(GradingPeriod(11, "Period 11"), GradingPeriod(12, "Period 12"))
+ val course1 = createCourseWithGrades(1, "Course with Grade", "", "www.1.com", 90.0, "A", gradingPeriods)
+
+ every { courseManager.getCoursesWithGradesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course1))
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ val gradingPeriodsViewModel = viewModel.data.value!!.items[0] as GradingPeriodSelectorItemViewModel
+ gradingPeriodsViewModel.onClick()
+
+ // Then
+ val expectedGradingPeriods = listOf(
+ GradingPeriodView(-1, "Current"),
+ GradingPeriodView(11, "Period 11"),
+ GradingPeriodView(12, "Period 12")
+ )
+ assertEquals(GradesAction.OpenGradingPeriodsDialog(expectedGradingPeriods, 0), viewModel.events.value!!.getContentIfNotHandled())
+ }
+
+ private fun createViewModel() = GradesViewModel(courseManager, resources, enrollmentManager)
+
+ private fun createCourseWithGrades(
+ id: Long,
+ name: String,
+ color: String,
+ imageUrl: String,
+ score: Double?,
+ grade: String?,
+ gradingPeriods: List? = null,
+ hideFinalGrades: Boolean = false
+ ): Course {
+ val enrollment = Enrollment(id = 123, computedCurrentScore = score, computedCurrentGrade = grade)
+ return Course(
+ id = id,
+ name = name,
+ courseColor = color,
+ imageUrl = imageUrl,
+ enrollments = mutableListOf(enrollment),
+ gradingPeriods = gradingPeriods,
+ hideFinalGrades = hideFinalGrades)
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreatorTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreatorTest.kt
index fa9b1c55d4..3e3c665418 100644
--- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreatorTest.kt
+++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/homeroom/CourseCardCreatorTest.kt
@@ -131,13 +131,13 @@ class CourseCardCreatorTest {
val courses = listOf(Course(id = 1), Course(id = 2))
val plannerItems = listOf(
- createPlannerItem(1, "assignment", false, false),
- createPlannerItem(1, "todo", false, false),
- createPlannerItem(1, "assignment", false, true),
- createPlannerItem(1, "assignment", true, false),
- createPlannerItem(2, "assignment", false, false),
- createPlannerItem(2, "assignment", false, false),
- createPlannerItem(3, "assignment", false, false),
+ createPlannerItem(1, PlannableType.ASSIGNMENT, false, false),
+ createPlannerItem(1, PlannableType.TODO, false, false),
+ createPlannerItem(1, PlannableType.ASSIGNMENT, false, true),
+ createPlannerItem(1, PlannableType.ASSIGNMENT, true, false),
+ createPlannerItem(2, PlannableType.ASSIGNMENT, false, false),
+ createPlannerItem(2, PlannableType.ASSIGNMENT, false, false),
+ createPlannerItem(3, PlannableType.ASSIGNMENT, false, false),
)
val announcementsDeferred: Deferred>> = mockk()
@@ -165,10 +165,10 @@ class CourseCardCreatorTest {
val courses = listOf(Course(id = 1))
val plannerItems = listOf(
- createPlannerItem(1, "todo", false, false),
- createPlannerItem(1, "assignment", false, true),
- createPlannerItem(1, "assignment", true, false),
- createPlannerItem(2, "assignment", false, false),
+ createPlannerItem(1, PlannableType.TODO, false, false),
+ createPlannerItem(1, PlannableType.ASSIGNMENT, false, true),
+ createPlannerItem(1, PlannableType.ASSIGNMENT, true, false),
+ createPlannerItem(2, PlannableType.ASSIGNMENT, false, false),
)
every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
@@ -193,8 +193,8 @@ class CourseCardCreatorTest {
val assignments = listOf(
Assignment(id = 1, courseId = 1),
Assignment(id = 2, courseId = 2),
- Assignment(id = 3, courseId = 1, plannerOverride = PlannerOverride(false)),
- Assignment(id = 4, courseId = 1, plannerOverride = PlannerOverride(true)))
+ Assignment(id = 3, courseId = 1, plannerOverride = PlannerOverride(plannableId = 3, plannableType = PlannableType.ASSIGNMENT, dismissed = false)),
+ Assignment(id = 4, courseId = 1, plannerOverride = PlannerOverride(plannableId = 4, plannableType = PlannableType.ASSIGNMENT, dismissed = true)))
every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
coEvery { await() } returns DataResult.Success(assignments)
}
@@ -216,7 +216,7 @@ class CourseCardCreatorTest {
val assignments = listOf(
Assignment(id = 1, courseId = 2),
- Assignment(id = 4, courseId = 1, plannerOverride = PlannerOverride(true)))
+ Assignment(id = 4, courseId = 1, plannerOverride = PlannerOverride(plannableId = 4, plannableType = PlannableType.ASSIGNMENT, dismissed = true)))
every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
coEvery { await() } returns DataResult.Success(assignments)
}
@@ -242,8 +242,8 @@ class CourseCardCreatorTest {
assertEquals("#394B58", courseCards[0].data.courseColor)
}
- private fun createPlannerItem(courseId: Long, plannableType: String, submitted: Boolean, missing: Boolean): PlannerItem {
+ private fun createPlannerItem(courseId: Long, plannableType: PlannableType, submitted: Boolean, missing: Boolean): PlannerItem {
val plannable = mockk()
- return PlannerItem(courseId, null, null, null, null, plannableType, plannable, Date(), null, SubmissionState(submitted, missing))
+ return PlannerItem(courseId, null, null, null, null, plannableType, plannable, Date(), null, SubmissionState(submitted, missing), false)
}
}
\ No newline at end of file
diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModelTest.kt
new file mode 100644
index 0000000000..971ad12804
--- /dev/null
+++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/resources/ResourcesViewModelTest.kt
@@ -0,0 +1,388 @@
+package com.instructure.pandautils.features.elementary.resources/*
+ * 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 .
+ *
+ */
+
+import android.content.res.Resources
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.instructure.canvasapi2.managers.CourseManager
+import com.instructure.canvasapi2.managers.ExternalToolManager
+import com.instructure.canvasapi2.managers.OAuthManager
+import com.instructure.canvasapi2.managers.UserManager
+import com.instructure.canvasapi2.models.*
+import com.instructure.canvasapi2.utils.DataResult
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ContactInfoItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ImportantLinksItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.LtiApplicationItemViewModel
+import com.instructure.pandautils.features.elementary.resources.itemviewmodels.ResourcesHeaderViewModel
+import com.instructure.pandautils.mvvm.ViewState
+import com.instructure.pandautils.utils.HtmlContentFormatter
+import io.mockk.*
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@ExperimentalCoroutinesApi
+class ResourcesViewModelTest {
+
+ @get:Rule
+ var instantExecutorRule = InstantTaskExecutorRule()
+
+ private val lifecycleOwner: LifecycleOwner = mockk(relaxed = true)
+ private val lifecycleRegistry = LifecycleRegistry(lifecycleOwner)
+
+ private val testDispatcher = TestCoroutineDispatcher()
+
+ private val resources: Resources = mockk(relaxed = true)
+ private val courseManager: CourseManager = mockk(relaxed = true)
+ private val userManager: UserManager = mockk(relaxed = true)
+ private val externalToolManager: ExternalToolManager = mockk(relaxed = true)
+ private val oAuthManager: OAuthManager = mockk(relaxed = true)
+ private val htmlContentFormatter: HtmlContentFormatter = mockk(relaxed = true)
+
+ private lateinit var viewModel: ResourcesViewModel
+
+ @Before
+ fun setUp() {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ Dispatchers.setMain(testDispatcher)
+ coEvery { htmlContentFormatter.formatHtmlWithIframes(any()) } returnsArgument 0
+
+ mockkStatic("kotlinx.coroutines.AwaitKt")
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ testDispatcher.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun `Show error state if fetching courses fails`() {
+ // Given
+ every { resources.getString(R.string.failedToLoadResources) } returns "Error"
+ initMockData(courses = DataResult.Fail())
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Error)
+ assertEquals("Error", (viewModel.state.value as ViewState.Error).errorMessage)
+ }
+
+ @Test
+ fun `Show empty state if there are no courses`() {
+ // Given
+ initMockData()
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Empty)
+ assertEquals(R.string.resourcesEmptyMessage, (viewModel.state.value as ViewState.Empty).emptyTitle)
+ }
+
+ @Test
+ fun `Do not create important links when we have a course but syllabus is empty`() {
+ // Given
+ val course = Course(syllabusBody = "")
+ initMockData(courses = DataResult.Success(listOf(course)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Empty)
+ assertEquals(R.string.resourcesEmptyMessage, (viewModel.state.value as ViewState.Empty).emptyTitle)
+ }
+
+ @Test
+ fun `Create important links from homeroom course syllabus body without course name if only 1 homeroom course is present`() {
+ // Given
+ val course = Course(homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ initMockData(courses = DataResult.Success(listOf(course)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(1, viewModel.data.value!!.importantLinksItems.size)
+
+ val expectedHtmlContent = "This link is really important: www.tamaskozmer.com"
+ assertFalse(viewModel.data.value!!.isEmpty())
+ val viewData = (viewModel.data.value!!.importantLinksItems[0] as ImportantLinksItemViewModel).data
+ assertEquals(ImportantLinksViewData("", expectedHtmlContent), viewData)
+ }
+
+ @Test
+ fun `Create important links from homeroom course syllabus body with course name if there are more than 1 homeroom courses`() {
+ // Given
+ val course = Course(name = "Course 1", homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ val course2 = Course(name = "Course 2", homeroomCourse = true, syllabusBody = "Something really important")
+ initMockData(courses = DataResult.Success(listOf(course, course2)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(2, viewModel.data.value!!.importantLinksItems.size)
+ assertFalse(viewModel.data.value!!.isEmpty())
+
+ val viewData1 = (viewModel.data.value!!.importantLinksItems[0] as ImportantLinksItemViewModel).data
+ val viewData2 = (viewModel.data.value!!.importantLinksItems[1] as ImportantLinksItemViewModel).data
+ assertEquals(ImportantLinksViewData("Course 1", "This link is really important: www.tamaskozmer.com", true), viewData1)
+ assertEquals(ImportantLinksViewData("Course 2", "Something really important"), viewData2)
+ }
+
+ @Test
+ fun `Do not create important links from non-homeroom course syllabus body`() {
+ // Given
+ val course = Course(syllabusBody = "This is a syllabus, not important links")
+ initMockData(courses = DataResult.Success(listOf(course)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Empty)
+ assertEquals(R.string.resourcesEmptyMessage, (viewModel.state.value as ViewState.Empty).emptyTitle)
+ }
+
+ @Test
+ fun `Do not request lti tools if there are no non-homeroom courses`() {
+ // Given
+ val course = Course(homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ initMockData(courses = DataResult.Success(listOf(course)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ verify(exactly = 0) { externalToolManager.getExternalToolsForCoursesAsync(any(), any()) }
+ }
+
+ @Test
+ fun `Do not create action items and headers if no external tools and staff info received`() {
+ // Given
+ val course = Course(id = 1, homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ val course2 = Course(id = 2, homeroomCourse = false)
+ initMockData(courses = DataResult.Success(listOf(course, course2)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.data.value!!.actionItems.isEmpty())
+ }
+
+ @Test
+ fun `Create lti tools items from course lti tools and remove duplicated items`() {
+ // Given
+ val course = Course(id = 1, name = "Course uno")
+ val course2 = Course(id = 2, name = "Course due")
+
+ val ltiTools = listOf(
+ LTITool(id = 1, contextId = course.id, contextName = course.name, courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png"),
+ LTITool(id = 1, contextId = course2.id, contextName = course2.name, courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png"),
+ LTITool(id = 2, name = "New Quizzes", contextId = course.id, contextName = course.name, url = "new.quizzes.com", iconUrl = "newquizzes.png")
+ )
+ initMockData(courses = DataResult.Success(listOf(course, course2)), externalTools = DataResult.Success(ltiTools))
+ every { resources.getString(R.string.studentApplications) } returns "Student Applications"
+ every { resources.getDimension(R.dimen.ltiAppsBottomMargin) } returns 10f
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(3, viewModel.data.value!!.actionItems.size) // We have 3 items, 2 LTI tools with the header
+ assertFalse(viewModel.data.value!!.isEmpty())
+
+ val header = viewModel.data.value!!.actionItems[0] as ResourcesHeaderViewModel
+ assertEquals(ResourcesHeaderViewData("Student Applications"), header.data)
+
+ val ltiTool1 = viewModel.data.value!!.actionItems[1] as LtiApplicationItemViewModel
+ assertEquals(0, ltiTool1.marginBottom)
+ assertEquals(LtiApplicationViewData("Google Drive", "drive.png", "google.com"), ltiTool1.data)
+
+ val ltiTool2 = viewModel.data.value!!.actionItems[2] as LtiApplicationItemViewModel
+ assertEquals(10, ltiTool2.marginBottom)
+ assertEquals(LtiApplicationViewData("New Quizzes", "newquizzes.png", "new.quizzes.com"), ltiTool2.data)
+ }
+
+ @Test
+ fun `Lti app click opens Lti app dialog with the list of courses for that lti app`() {
+ // Given
+ val course = Course(id = 1, name = "Course uno")
+ val course2 = Course(id = 2, name = "Course due")
+
+ val ltiTools = listOf(
+ LTITool(id = 1, contextId = course.id, contextName = course.name, courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png"),
+ LTITool(id = 1, contextId = course2.id, contextName = course2.name, courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png"),
+ LTITool(id = 2, name = "New Quizzes", contextId = course.id, contextName = course.name, url = "new.quizzes.com", iconUrl = "newquizzes.png")
+ )
+ initMockData(courses = DataResult.Success(listOf(course, course2)), externalTools = DataResult.Success(ltiTools))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ val ltiTool = viewModel.data.value!!.actionItems[1] as LtiApplicationItemViewModel
+ ltiTool.onClick()
+
+ // Then
+ val event = viewModel.events.value!!.getContentIfNotHandled()!!
+ assertTrue(event is ResourcesAction.OpenLtiApp)
+
+ val expectedLtiToolList = listOf(
+ LTITool(id = 1, contextId = 1, contextName = "Course uno", courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png"),
+ LTITool(id = 1, contextId = 2, contextName = "Course due", courseNavigation = CourseNavigation("Google Drive"), url = "google.com", iconUrl = "drive.png")
+ )
+ assertEquals(expectedLtiToolList, (event as ResourcesAction.OpenLtiApp).ltiTools)
+ }
+
+ @Test
+ fun `Create staff info views with header and remove duplicates`() {
+ // Given
+ val course = Course(id = 1, homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ val teachers1 = listOf(
+ User(id = 1, shortName = "Tamas Kozmer", avatarUrl = "http://a.b", enrollments = listOf(Enrollment(role = Enrollment.EnrollmentType.Teacher))),
+ User(id = 2, shortName = "Balint Bartok", avatarUrl = "http://b.c", enrollments = listOf(Enrollment(role = Enrollment.EnrollmentType.Ta)))
+ )
+ val teachers2 = listOf(User(id = 1, shortName = "Tamas Kozmer", avatarUrl = "http://a.b", enrollments = listOf(Enrollment(role = Enrollment.EnrollmentType.Teacher))))
+ initMockData(courses = DataResult.Success(listOf(course)), teachers = listOf(DataResult.Success(teachers1), DataResult.Success(teachers2)))
+
+ every { resources.getString(R.string.staffContactInfo) } returns "Staff Info"
+ every { resources.getString(R.string.staffRoleTeacher) } returns "Teacher"
+ every { resources.getString(R.string.staffRoleTeacherAssistant) } returns "Assistant"
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ // Then
+ assertTrue(viewModel.state.value is ViewState.Success)
+ assertEquals(3, viewModel.data.value!!.actionItems.size) // We have 3 items, 2 staff info items with the header
+ assertFalse(viewModel.data.value!!.isEmpty())
+
+ val header = viewModel.data.value!!.actionItems[0] as ResourcesHeaderViewModel
+ assertEquals(ResourcesHeaderViewData("Staff Info", hasDivider = true), header.data)
+
+ val contactInfo1 = viewModel.data.value!!.actionItems[1] as ContactInfoItemViewModel
+ assertEquals(ContactInfoViewData("Tamas Kozmer", "Teacher", "http://a.b"), contactInfo1.data)
+
+ val contactInfo2 = viewModel.data.value!!.actionItems[2] as ContactInfoItemViewModel
+ assertEquals(ContactInfoViewData("Balint Bartok", "Assistant", "http://b.c"), contactInfo2.data)
+ }
+
+ @Test
+ fun `Clicking contact info opens compose message`() {
+ // Given
+ val course = Course(id = 1, homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ val teachers = listOf(User(id = 1, shortName = "Tamas Kozmer", avatarUrl = "http://a.b", enrollments = listOf(Enrollment(role = Enrollment.EnrollmentType.Teacher))))
+ initMockData(courses = DataResult.Success(listOf(course)), teachers = listOf(DataResult.Success(teachers)))
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ val contactInfo = viewModel.data.value!!.actionItems[1] as ContactInfoItemViewModel
+ contactInfo.onClick()
+
+ // Then
+ val event = viewModel.events.value!!.getContentIfNotHandled()!!
+ assertTrue(event is ResourcesAction.OpenComposeMessage)
+ assertEquals(ResourcesAction.OpenComposeMessage(teachers[0]), event)
+ }
+
+ @Test
+ fun `Error after refresh should trigger refresh error event if data is already available`() {
+ // Given
+ val course = Course(id = 1, homeroomCourse = true, syllabusBody = "This link is really important: www.tamaskozmer.com")
+ initMockData(courses = DataResult.Success(listOf(course)))
+ every { courseManager.getCoursesWithSyllabusAsyncWithActiveEnrollmentAsync(any()) } returns mockk {
+ coEvery { await() }.returnsMany(DataResult.Success(listOf(course)), DataResult.Fail())
+ }
+
+ // When
+ viewModel = createViewModel()
+ viewModel.state.observe(lifecycleOwner, {})
+
+ viewModel.refresh()
+
+ // Then
+ assertEquals(ViewState.Error(), viewModel.state.value)
+ assertEquals(ResourcesAction.ShowRefreshError, viewModel.events.value!!.getContentIfNotHandled()!!)
+ }
+
+ @Test
+ fun `OnImportantLinksViewsReady should send event`() {
+ // When
+ viewModel = createViewModel()
+ viewModel.events.observe(lifecycleOwner, {})
+ viewModel.onImportantLinksViewsReady()
+
+ // Then
+ assertEquals(ResourcesAction.ImportantLinksViewsReady, viewModel.events.value!!.getContentIfNotHandled()!!)
+ }
+
+ private fun createViewModel(): ResourcesViewModel {
+ return ResourcesViewModel(resources, courseManager, userManager, externalToolManager, oAuthManager, htmlContentFormatter)
+ }
+
+ private fun initMockData(
+ courses: DataResult> = DataResult.Success(emptyList()),
+ externalTools: DataResult> = DataResult.Success(emptyList()),
+ teachers: List>> = listOf(DataResult.Success(emptyList()))
+ ) {
+ every { courseManager.getCoursesWithSyllabusAsyncWithActiveEnrollmentAsync(any()) } returns mockk {
+ coEvery { await() } returns courses
+ }
+ every { externalToolManager.getExternalToolsForCoursesAsync(any(), any()) } returns mockk() {
+ coEvery { await() } returns externalTools
+ }
+
+ val usersDeferred: Deferred>> = mockk()
+ every { userManager.getTeacherListForCourseAsync(any(), any()) } returns usersDeferred
+ val listOfUsersDeferred = courses.dataOrNull?.map { usersDeferred } ?: emptyList()
+ coEvery { listOfUsersDeferred.awaitAll() } returns teachers
+ }
+}
diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModelTest.kt
new file mode 100644
index 0000000000..98fe312bc4
--- /dev/null
+++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/elementary/schedule/ScheduleViewModelTest.kt
@@ -0,0 +1,929 @@
+/*
+ * 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.pandautils.features.elementary.schedule
+
+import android.content.res.Resources
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.instructure.canvasapi2.managers.*
+import com.instructure.canvasapi2.models.*
+import com.instructure.canvasapi2.utils.ApiPrefs
+import com.instructure.canvasapi2.utils.DataResult
+import com.instructure.canvasapi2.utils.toApiString
+import com.instructure.pandautils.R
+import com.instructure.pandautils.features.elementary.schedule.itemviewmodels.*
+import com.instructure.pandautils.utils.MissingItemsPrefs
+import com.instructure.pandautils.utils.date.RealDateTimeProvider
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.setMain
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.util.*
+
+@ExperimentalCoroutinesApi
+class ScheduleViewModelTest {
+
+ @get:Rule
+ var instantExecutorRule = InstantTaskExecutorRule()
+
+ private val lifecycleOwner: LifecycleOwner = mockk(relaxed = true)
+ private val lifecycleRegistry = LifecycleRegistry(lifecycleOwner)
+
+ private val testDispatcher = TestCoroutineDispatcher()
+
+ private val plannerManager: PlannerManager = mockk(relaxed = true)
+ private val courseManager: CourseManager = mockk(relaxed = true)
+ private val userManager: UserManager = mockk(relaxed = true)
+ private val resources: Resources = mockk(relaxed = true)
+ private val apiPrefs: ApiPrefs = mockk(relaxed = true)
+ private val calendarEventManager: CalendarEventManager = mockk(relaxed = true)
+ private val assignmentManager: AssignmentManager = mockk(relaxed = true)
+ private val missingItemsPrefs: MissingItemsPrefs = mockk(relaxed = true)
+ private val dateTimeProvider = RealDateTimeProvider()
+
+ private lateinit var viewModel: ScheduleViewModel
+
+ @Before
+ fun setUp() {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ Dispatchers.setMain(testDispatcher)
+
+ mockkStatic("kotlinx.coroutines.AwaitKt")
+
+ setupStrings()
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(emptyList())
+ }
+
+ every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(emptyList())
+ }
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(emptyList())
+ }
+
+ every { missingItemsPrefs.itemsCollapsed } returns false
+ }
+
+ @Test
+ fun `Open actions map correctly`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(courseId = course.id, assignmentId = 1, PlannableType.ASSIGNMENT, SubmissionState(submitted = true), Date()),
+ createPlannerItem(courseId = course.id, assignmentId = 2, PlannableType.QUIZ, SubmissionState(submitted = true), Date()),
+ createPlannerItem(courseId = course.id, assignmentId = 3, PlannableType.ANNOUNCEMENT, SubmissionState(submitted = true), Date()),
+ createPlannerItem(courseId = course.id, assignmentId = 4, PlannableType.DISCUSSION_TOPIC, SubmissionState(submitted = true), Date()),
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ val assignments = listOf(
+ createAssignment(3, 1),
+ createAssignment(4, 1)
+ ).map { DataResult.Success(it) }
+
+ mockAssignments(assignments)
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ courseItemViewModel.onHeaderClick.invoke()
+ assertEquals(ScheduleAction.OpenCourse(course), viewModel.events.value?.getContentIfNotHandled())
+
+ val assignment = courseItemViewModel.data.plannerItems.find { it.data.type == PlannerItemType.ASSIGNMENT }
+ assignment?.open?.invoke()
+ assertEquals(
+ ScheduleAction.OpenAssignment(plannerItems[0].canvasContext, plannerItems[0].plannable.id),
+ viewModel.events.value?.getContentIfNotHandled()
+ )
+
+ val quiz = courseItemViewModel.data.plannerItems.find { it.data.type == PlannerItemType.QUIZ }
+ quiz?.open?.invoke()
+ assertEquals(
+ ScheduleAction.OpenAssignment(plannerItems[1].canvasContext, plannerItems[1].plannable.id),
+ viewModel.events.value?.getContentIfNotHandled()
+ )
+
+ val announcement = courseItemViewModel.data.plannerItems.find { it.data.type == PlannerItemType.ANNOUNCEMENT }
+ announcement?.open?.invoke()
+ assertEquals(
+ ScheduleAction.OpenDiscussion(
+ plannerItems[2].canvasContext,
+ plannerItems[2].plannable.id,
+ plannerItems[2].plannable.title
+ ), viewModel.events.value?.getContentIfNotHandled()
+ )
+
+ val discussion = courseItemViewModel.data.plannerItems.find { it.data.type == PlannerItemType.DISCUSSION }
+ discussion?.open?.invoke()
+ assertEquals(
+ ScheduleAction.OpenDiscussion(
+ plannerItems[3].canvasContext,
+ plannerItems[3].plannable.id,
+ plannerItems[3].plannable.title
+ ), viewModel.events.value?.getContentIfNotHandled()
+ )
+ }
+
+
+ @Test
+ fun `Missing items set up correctly`() {
+ val courses = listOf(
+ Course(id = 1, name = "Course 1"),
+ Course(id = 2, name = "Course 2")
+ )
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(courses)
+ }
+
+ val missingItems = listOf(
+ createAssignment(
+ 1,
+ courseId = 1,
+ createSubmission(id = 1, grade = null, late = false, excused = false),
+ name = "Assignment 1"
+ ),
+ createAssignment(
+ 2,
+ courseId = 2,
+ createSubmission(id = 2, grade = null, late = false, excused = false),
+ name = "Assignment 2"
+ )
+ )
+
+ every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(missingItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val missingItemHeader =
+ todayHeader?.items?.find { it is ScheduleMissingItemsGroupItemViewModel } as ScheduleMissingItemsGroupItemViewModel
+ assertEquals(2, missingItemHeader.items.size)
+
+ val firstMissingItem = missingItemHeader.items[0] as ScheduleMissingItemViewModel
+ assertEquals("Assignment 1", firstMissingItem.data.title)
+ assertEquals("Course 1", firstMissingItem.data.courseName)
+
+ val secondMissingItem = missingItemHeader.items[1] as ScheduleMissingItemViewModel
+ assertEquals("Assignment 2", secondMissingItem.data.title)
+ assertEquals("Course 2", secondMissingItem.data.courseName)
+ }
+
+ @Test
+ fun `Missing items are open by default`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val missingItems = listOf(
+ createAssignment(1, courseId = 1, createSubmission(id = 1, grade = null, late = false, excused = false)),
+ createAssignment(2, courseId = 1, createSubmission(id = 2, grade = null, late = false, excused = false))
+ )
+
+ every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(missingItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val missingItemHeader =
+ todayHeader?.items?.find { it is ScheduleMissingItemsGroupItemViewModel } as ScheduleMissingItemsGroupItemViewModel
+ assertEquals(false, missingItemHeader.collapsed)
+ }
+
+ @Test
+ fun `Missing item header changes state correctly`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val missingItems = listOf(
+ createAssignment(1, courseId = 1, createSubmission(id = 1, grade = null, late = false, excused = false)),
+ createAssignment(2, courseId = 1, createSubmission(id = 2, grade = null, late = false, excused = false))
+ )
+
+ every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(missingItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val missingItemHeader =
+ todayHeader?.items?.find { it is ScheduleMissingItemsGroupItemViewModel } as ScheduleMissingItemsGroupItemViewModel
+ assertEquals(false, missingItemHeader.collapsed)
+ missingItemHeader.toggleItems()
+ assertEquals(true, missingItemHeader.collapsed)
+ }
+
+ @Test
+ fun `Only one missing item header is found`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val missingItems = listOf(
+ createAssignment(1, courseId = 1, createSubmission(id = 1, grade = null, late = false, excused = false)),
+ createAssignment(2, courseId = 1, createSubmission(id = 2, grade = null, late = false, excused = false))
+ )
+
+ every { userManager.getAllMissingSubmissionsAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(missingItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ items?.forEach { dayGroup ->
+ if (dayGroup != todayHeader) {
+ assertEquals(0, dayGroup.items.count { it is ScheduleMissingItemsGroupItemViewModel })
+ } else {
+ assertEquals(1, dayGroup.items.count { it is ScheduleMissingItemsGroupItemViewModel })
+ }
+ }
+ }
+
+ @Test
+ fun `Missing items are not visible if there are none`() {
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ assertEquals(null, todayHeader?.items?.find { it is ScheduleMissingItemsGroupItemViewModel })
+ }
+
+ @Test
+ fun `Courses map correctly`() {
+ val courses = listOf(
+ Course(id = 1, name = "Course 1"),
+ Course(id = 2, name = "Course 2")
+ )
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(courses)
+ }
+
+ val plannerItems = listOf(
+ createToDoItem(1, "To Do item"),
+ createPlannerItem(1, 1, PlannableType.ASSIGNMENT, SubmissionState(submitted = true), Date()),
+ createPlannerItem(2, 2, PlannableType.ASSIGNMENT, SubmissionState(submitted = true), Date())
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ assertEquals(3, todayHeader?.items?.count { it is ScheduleCourseItemViewModel })
+
+ val courseItemViewModels = todayHeader?.items?.filterIsInstance()
+ val firstCourseItemViewModel = courseItemViewModels?.find { it.data.courseName == "Course 1" }
+ assertEquals(true, firstCourseItemViewModel?.data?.openable)
+ assertEquals(1, firstCourseItemViewModel?.data?.plannerItems?.size)
+ assertEquals("Plannable 1", firstCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.title)
+ assertEquals(true, firstCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.openable)
+
+ val secondCourseItemViewModel = courseItemViewModels?.find { it.data.courseName == "Course 2" }
+ assertEquals(true, secondCourseItemViewModel?.data?.openable)
+ assertEquals(1, secondCourseItemViewModel?.data?.plannerItems?.size)
+ assertEquals("Plannable 2", secondCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.title)
+ assertEquals(true, secondCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.openable)
+
+ val todoCourseItemViewModel = courseItemViewModels?.find { it.data.courseName == "To Do" }
+ assertEquals(false, todoCourseItemViewModel?.data?.openable)
+ assertEquals(1, todoCourseItemViewModel?.data?.plannerItems?.size)
+ assertEquals("To Do item", todoCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.title)
+ assertEquals(false, todoCourseItemViewModel?.data?.plannerItems?.get(0)?.data?.openable)
+ }
+
+
+ @Test
+ fun `Submitted items are marked as done`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(
+ courseId = course.id, assignmentId = 1, PlannableType.ASSIGNMENT, SubmissionState(submitted = true),
+ Date()
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ every { plannerManager.createPlannerOverrideAsync(any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Fail()
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals(true, courseItemViewModel.data.openable)
+
+ assertEquals(1, courseItemViewModel.data.plannerItems.size)
+ val plannerItemViewModel = courseItemViewModel.data.plannerItems[0]
+
+ assertEquals(true, plannerItemViewModel.completed)
+ }
+
+ @Test
+ fun `Mark item as done error`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(
+ courseId = course.id, assignmentId = 1, PlannableType.ASSIGNMENT, SubmissionState(submitted = false),
+ Date()
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ every { plannerManager.createPlannerOverrideAsync(any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Fail()
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals(true, courseItemViewModel.data.openable)
+
+ assertEquals(1, courseItemViewModel.data.plannerItems.size)
+ val plannerItemViewModel = courseItemViewModel.data.plannerItems[0]
+
+ assertEquals(false, plannerItemViewModel.completed)
+ plannerItemViewModel.markAsDone.invoke(plannerItemViewModel, true)
+ assertEquals(false, plannerItemViewModel.completed)
+ }
+
+ @Test
+ fun `Update item as not done`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(
+ courseId = course.id,
+ assignmentId = 1,
+ PlannableType.ASSIGNMENT,
+ SubmissionState(submitted = true),
+ Date(),
+ createPlannerOverride(1, PlannableType.ASSIGNMENT, 1, true)
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ every { plannerManager.updatePlannerOverrideAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(createPlannerOverride(1, PlannableType.ASSIGNMENT, 1, false))
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals(true, courseItemViewModel.data.openable)
+
+ assertEquals(1, courseItemViewModel.data.plannerItems.size)
+ val plannerItemViewModel = courseItemViewModel.data.plannerItems[0]
+
+ assertEquals(true, plannerItemViewModel.completed)
+ plannerItemViewModel.markAsDone.invoke(plannerItemViewModel, false)
+ assertEquals(false, plannerItemViewModel.completed)
+ }
+
+ @Test
+ fun `Mark item as done`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(
+ courseId = course.id,
+ assignmentId = 1,
+ PlannableType.ASSIGNMENT,
+ SubmissionState(),
+ Date()
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ every { plannerManager.createPlannerOverrideAsync(any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(createPlannerOverride(1, PlannableType.ASSIGNMENT, 1, true))
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals(true, courseItemViewModel.data.openable)
+
+ assertEquals(1, courseItemViewModel.data.plannerItems.size)
+ val plannerItemViewModel = courseItemViewModel.data.plannerItems[0]
+
+ assertEquals(false, plannerItemViewModel.completed)
+ plannerItemViewModel.markAsDone.invoke(plannerItemViewModel, true)
+ assertEquals(true, plannerItemViewModel.completed)
+ }
+
+ @Test
+ fun `ToDo items map correctly`() {
+ val plannerItems = listOf(
+ createToDoItem(1, "To Do item")
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItem = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals("To Do", courseItem.data.courseName)
+ assertEquals(1, courseItem.data.plannerItems.size)
+
+ val plannerItemViewModel = courseItem.data.plannerItems[0]
+
+ assertEquals("To Do item", plannerItemViewModel.data.title)
+ assertEquals(false, plannerItemViewModel.data.openable)
+ assertEquals(PlannerItemType.TO_DO, plannerItemViewModel.data.type)
+ }
+
+ @Test
+ fun `Assignment maps correctly`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(
+ courseId = course.id,
+ assignmentId = 1,
+ PlannableType.ASSIGNMENT,
+ SubmissionState(),
+ Date()
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+
+ val courseItemViewModel = todayHeader?.items?.get(0) as ScheduleCourseItemViewModel
+
+ assertEquals(true, courseItemViewModel.data.openable)
+
+ assertEquals(1, courseItemViewModel.data.plannerItems.size)
+ val plannerItemViewModel = courseItemViewModel.data.plannerItems[0]
+
+ assertEquals("Plannable 1", plannerItemViewModel.data.title)
+ assertEquals(true, plannerItemViewModel.data.openable)
+ assertEquals(PlannerItemType.ASSIGNMENT, plannerItemViewModel.data.type)
+ assertEquals(null, plannerItemViewModel.data.points)
+ }
+
+ @Test
+ fun `Day titles set up correctly`() {
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ assertEquals(7, items?.size)
+
+ val todayHeader = items?.find { it.dayText == "Today" }
+ assert(todayHeader is ScheduleDayGroupItemViewModel)
+ assertEquals("Today", todayHeader?.dayText)
+ val todayIndex = items!!.indexOf(todayHeader)
+
+ if (todayIndex != 0) {
+ val yesterdayHeader = viewModel.data.value?.itemViewModels?.get(todayIndex - 1)
+ val yesterdayHeaderItemViewModel = yesterdayHeader as ScheduleDayGroupItemViewModel
+ assertEquals("Yesterday", yesterdayHeaderItemViewModel.dayText)
+ }
+
+ if (todayIndex != 6) {
+ val tomorrowHeader = viewModel.data.value?.itemViewModels?.get(todayIndex + 1)
+ val tomorrowHeaderItemViewModel = tomorrowHeader as ScheduleDayGroupItemViewModel
+ assertEquals("Tomorrow", tomorrowHeaderItemViewModel.dayText)
+ }
+ }
+
+ @Test
+ fun `Empty view`() {
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+
+ assertEquals(7, items?.size)
+
+ items?.forEach {
+ assertEquals(1, it.items.size)
+ assert(it.items[0] is ScheduleEmptyItemViewModel)
+ }
+
+ }
+
+ @Test
+ fun `Chips are set correctly`() {
+ val course = Course(id = 1)
+
+ every { courseManager.getCoursesAsync(any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(listOf(course))
+ }
+
+ val plannerItems = listOf(
+ createPlannerItem(course.id, 1, PlannableType.ASSIGNMENT, SubmissionState(late = true), Date()),
+ createPlannerItem(course.id, 2, PlannableType.ASSIGNMENT, SubmissionState(graded = true), Date()),
+ createPlannerItem(
+ course.id,
+ 3,
+ PlannableType.ASSIGNMENT,
+ SubmissionState(excused = true, graded = true),
+ Date()
+ ),
+ createPlannerItem(
+ course.id,
+ 4,
+ PlannableType.ASSIGNMENT,
+ SubmissionState(graded = true, late = true),
+ Date()
+ ),
+ createPlannerItem(course.id, 5, PlannableType.ANNOUNCEMENT, SubmissionState(), Date(), newActivity = true),
+ createPlannerItem(
+ course.id,
+ 6,
+ PlannableType.DISCUSSION_TOPIC,
+ SubmissionState(late = true, excused = true, withFeedback = true),
+ Date(),
+ newActivity = true
+ )
+ )
+
+ every { plannerManager.getPlannerItemsAsync(any(), any(), any()) } returns mockk {
+ coEvery { await() } returns DataResult.Success(plannerItems)
+ }
+
+ val assignments = listOf(
+ createAssignment(5, 1, discussionTopicHeader = DiscussionTopicHeader(4, unreadCount = 2)),
+ createAssignment(6, 1, discussionTopicHeader = DiscussionTopicHeader(5, unreadCount = 1))
+ ).map { DataResult.Success(it) }
+
+ mockAssignments(assignments)
+
+ viewModel = createViewModel()
+ viewModel.getDataForDate(Date().toApiString())
+ viewModel.data.observe(lifecycleOwner, {})
+
+ val items = viewModel.data.value?.itemViewModels
+ val dayGroup = items?.find { it.dayText == "Today" }
+ assertEquals("Today", dayGroup?.dayText)
+
+ val courseItem = dayGroup?.items?.get(0)
+ assert(courseItem is ScheduleCourseItemViewModel)
+ val courseItemViewModel = courseItem as ScheduleCourseItemViewModel
+ assertEquals(6, courseItemViewModel.data.plannerItems.size)
+
+ val latePlannerItem = courseItem.data.plannerItems[0]
+ assertEquals(1, latePlannerItem.data.chips.size)
+ assert(latePlannerItem.data.chips.any { it.data.text == "Late" })
+
+ val gradedPlannerItem = courseItem.data.plannerItems[1]
+ assertEquals(1, gradedPlannerItem.data.chips.size)
+ assert(gradedPlannerItem.data.chips.any { it.data.text == "Graded" })
+
+ val excusedPlannerItem = courseItem.data.plannerItems[2]
+ assertEquals(1, excusedPlannerItem.data.chips.size)
+ assert(excusedPlannerItem.data.chips.any { it.data.text == "Excused" })
+
+ val gradedLatePlannerItem = courseItem.data.plannerItems[3]
+ assertEquals(2, gradedLatePlannerItem.data.chips.size)
+ assert(
+ gradedLatePlannerItem.data.chips.any { it.data.text == "Graded" }
+ && gradedLatePlannerItem.data.chips.any { it.data.text == "Late" }
+ )
+
+ val unreadPlannerItem = courseItem.data.plannerItems[4]
+ assertEquals(1, unreadPlannerItem.data.chips.size)
+ assert(unreadPlannerItem.data.chips.any { it.data.text == "2 Replies" })
+
+ val unreadGradedLateExcusedPlannerItem = courseItem.data.plannerItems[5]
+ assertEquals(4, unreadGradedLateExcusedPlannerItem.data.chips.size)
+ assert(
+ unreadGradedLateExcusedPlannerItem.data.chips.any { it.data.text == "1 Reply" }
+ && unreadGradedLateExcusedPlannerItem.data.chips.any { it.data.text == "Late" }
+ && unreadGradedLateExcusedPlannerItem.data.chips.any { it.data.text == "Excused" }
+ && unreadGradedLateExcusedPlannerItem.data.chips.any { it.data.text == "Feedback" }
+ )
+
+ }
+
+ private fun createPlannerItem(
+ courseId: Long,
+ assignmentId: Long,
+ plannableType: PlannableType,
+ submissionState: SubmissionState,
+ date: Date,
+ plannerOverride: PlannerOverride? = null,
+ newActivity: Boolean = false,
+ todoDate: String? = null
+ ): PlannerItem {
+ val plannable = Plannable(
+ id = assignmentId,
+ title = "Plannable $assignmentId",
+ courseId,
+ null,
+ null,
+ null,
+ date,
+ assignmentId,
+ todoDate
+ )
+ return PlannerItem(
+ courseId,
+ null,
+ null,
+ null,
+ null,
+ plannableType,
+ plannable,
+ date,
+ null,
+ submissionState,
+ plannerOverride = plannerOverride,
+ newActivity = newActivity
+ )
+ }
+
+ private fun createPlannerOverride(
+ id: Long,
+ plannableType: PlannableType,
+ plannableId: Long,
+ markedAsComplete: Boolean
+ ): PlannerOverride {
+ return PlannerOverride(
+ id = id,
+ plannableType = plannableType,
+ plannableId = plannableId,
+ markedComplete = markedAsComplete
+ )
+ }
+
+ private fun createAssignment(
+ id: Long,
+ courseId: Long,
+ submission: Submission? = null,
+ discussionTopicHeader: DiscussionTopicHeader? = null,
+ name: String? = null
+ ): Assignment {
+ return Assignment(
+ id = id,
+ submission = submission,
+ discussionTopicHeader = discussionTopicHeader,
+ courseId = courseId,
+ name = name
+ )
+ }
+
+ private fun createSubmission(id: Long, grade: String?, late: Boolean, excused: Boolean): Submission {
+ return Submission(id = id, grade = grade, late = late, excused = excused)
+ }
+
+ private fun createToDoItem(id: Long, title: String): PlannerItem {
+ val plannable = Plannable(id = id, title = title, null, null, null, null, Date(), null, null)
+ return PlannerItem(
+ plannable = plannable,
+ plannableType = PlannableType.PLANNER_NOTE,
+ plannableDate = Date(),
+ courseId = null,
+ contextName = null,
+ contextType = null,
+ groupId = null,
+ htmlUrl = null,
+ plannerOverride = null,
+ submissionState = null,
+ userId = null,
+ newActivity = false
+ )
+ }
+
+ private fun setupStrings() {
+ every { resources.getString(R.string.schedule_tag_graded) } returns "Graded"
+ every { resources.getString(R.plurals.schedule_tag_replies) } returns "Replies"
+ every { resources.getString(R.string.schedule_tag_feedback) } returns "Feedback"
+ every { resources.getString(R.string.schedule_tag_late) } returns "Late"
+ every { resources.getString(R.string.schedule_tag_redo) } returns "Redo"
+ every { resources.getString(R.string.schedule_tag_excused) } returns "Excused"
+ every { resources.getString(R.string.tomorrow) } returns "Tomorrow"
+ every { resources.getString(R.string.yesterday) } returns "Yesterday"
+ every { resources.getString(R.string.today) } returns "Today"
+ every { resources.getString(R.string.schedule_todo_title) } returns "To Do"
+ every { resources.getQuantityString(R.plurals.schedule_tag_replies, 2, 2) } returns "2 Replies"
+ every { resources.getQuantityString(R.plurals.schedule_tag_replies, 1, 1) } returns "1 Reply"
+ }
+
+ private fun createViewModel(): ScheduleViewModel {
+ return ScheduleViewModel(
+ apiPrefs,
+ resources,
+ plannerManager,
+ courseManager,
+ userManager,
+ calendarEventManager,
+ assignmentManager,
+ missingItemsPrefs,
+ dateTimeProvider
+ )
+ }
+
+ private fun mockAssignments(assignments: List> = emptyList()) {
+ val assignmentDeferred: Deferred> = mockk()
+ every { assignmentManager.getAssignmentAsync(any(), any(), any()) } returns assignmentDeferred
+ val listOfAssignmentDeferred = assignments.map { assignmentDeferred }
+ coEvery { listOfAssignmentDeferred.awaitAll() } returns assignments
+ }
+
+ private fun mockCalendarEvents(calendarEvents: List> = emptyList()) {
+ val calendarEventDeferred: Deferred> = mockk()
+ every { calendarEventManager.getCalendarEventAsync(any(), any()) } returns calendarEventDeferred
+ val listOfCalendarEventDeferred = calendarEvents.map { calendarEventDeferred }
+ coEvery { listOfCalendarEventDeferred.awaitAll() } returns calendarEvents
+ }
+}
\ No newline at end of file
diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/utils/DateExtensionsTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/utils/DateExtensionsTest.kt
new file mode 100644
index 0000000000..b7ef26144b
--- /dev/null
+++ b/libs/pandautils/src/test/java/com/instructure/pandautils/utils/DateExtensionsTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.pandautils.utils
+
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import java.util.*
+
+class DateExtensionsTest {
+
+ @Test
+ fun `Get last Sunday`() {
+ val calendar = Calendar.getInstance()
+ calendar.set(2021, 6, 1)
+ val currentDate = calendar.time
+ val lastSunday = currentDate.getLastSunday()
+
+ calendar.set(2021, 5, 27)
+ val expectedDate = calendar.time
+
+ assertEquals(expectedDate, lastSunday)
+ }
+
+ @Test
+ fun `Get next Saturday`() {
+ val calendar = Calendar.getInstance()
+ calendar.set(2021, 5, 29)
+ val currentDate = calendar.time
+ val nextSaturday = currentDate.getNextSaturday()
+
+ calendar.set(2021, 6, 3)
+ val expectedDate = calendar.time
+
+ assertEquals(expectedDate, nextSaturday)
+ }
+
+ @Test
+ fun `Is Yesterday`() {
+ val calendar = Calendar.getInstance()
+ calendar.set(2021, 6, 1)
+ val currentDate = calendar.time
+ calendar.set(2021, 5, 30)
+ val yesterday = calendar.time
+ assert(yesterday.isPreviousDay(currentDate))
+ }
+
+ @Test
+ fun `Is Tomorrow`() {
+ val calendar = Calendar.getInstance()
+ calendar.set(2021, 6, 31)
+ val currentDate = calendar.time
+ calendar.set(2021, 7, 1)
+ val tomorrow = calendar.time
+ assert(tomorrow.isNextDay(currentDate))
+ }
+}
\ No newline at end of file