diff --git a/apps/flutter_parent/lib/l10n/res/intl_ja.arb b/apps/flutter_parent/lib/l10n/res/intl_ja.arb index 4a6e294d49..cf1ca754a3 100644 --- a/apps/flutter_parent/lib/l10n/res/intl_ja.arb +++ b/apps/flutter_parent/lib/l10n/res/intl_ja.arb @@ -171,7 +171,7 @@ "placeholders_order": [], "placeholders": {} }, - "pointsPossible": "{points}の可能なポイント", + "pointsPossible": "配点 {points}", "@pointsPossible": { "description": "Screen reader label used for the points possible for an assignment, quiz, etc.", "type": "text", diff --git a/apps/flutter_parent/lib/models/assignment.dart b/apps/flutter_parent/lib/models/assignment.dart index 6175b081eb..3947713bdf 100644 --- a/apps/flutter_parent/lib/models/assignment.dart +++ b/apps/flutter_parent/lib/models/assignment.dart @@ -145,7 +145,7 @@ abstract class Assignment implements Built { SubmissionStatus getStatus({required String? studentId}) { final submission = this.submission(studentId); - if (!isSubmittable()) { + if (!isSubmittable() && submission == null) { return SubmissionStatus.NONE; } else if (submission?.isLate == true) { return SubmissionStatus.LATE; diff --git a/apps/flutter_parent/lib/screens/assignments/assignment_details_screen.dart b/apps/flutter_parent/lib/screens/assignments/assignment_details_screen.dart index 9043221d75..2814bad688 100644 --- a/apps/flutter_parent/lib/screens/assignments/assignment_details_screen.dart +++ b/apps/flutter_parent/lib/screens/assignments/assignment_details_screen.dart @@ -142,7 +142,8 @@ class _AssignmentDetailsScreenState extends State { final assignment = snapshot.data!.assignment!; final submission = assignment.submission(_currentStudent?.id); final fullyLocked = assignment.isFullyLocked; - final showStatus = assignment.isSubmittable() || submission?.isGraded() == true; + final missing = submission?.missing == true; + final showStatus = missing || assignment.isSubmittable() || submission?.isGraded() == true; final submitted = submission?.submittedAt != null; final submittedColor = submitted ? ParentTheme.of(context)?.successColor : textTheme.bodySmall?.color; @@ -172,9 +173,9 @@ class _AssignmentDetailsScreenState extends State { if (showStatus) SizedBox(width: 8), if (showStatus) Text( - !submitted - ? l10n.assignmentNotSubmittedLabel - : submission?.isGraded() == true ? l10n.assignmentGradedLabel : l10n.assignmentSubmittedLabel, + missing ? l10n.assignmentMissingSubmittedLabel : + !submitted ? l10n.assignmentNotSubmittedLabel : + submission?.isGraded() == true ? l10n.assignmentGradedLabel : l10n.assignmentSubmittedLabel, style: textTheme.bodySmall?.copyWith( color: submittedColor, ), diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 815299e864..f20ac3d427 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -50,8 +50,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 260 - versionName = '7.2.0' + versionCode = 261 + versionName = '7.3.0' vectorDrawables.useSupportLibrary = true multiDexEnabled = true diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt index aa5efe83d0..cd1c0ae152 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt @@ -26,7 +26,7 @@ import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData import com.instructure.dataseeding.api.DiscussionTopicsApi import com.instructure.espresso.ViewUtils -import com.instructure.espresso.getCurrentDateInCanvasFormat +import com.instructure.espresso.getDateInCanvasFormat import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin @@ -154,7 +154,7 @@ class DiscussionsE2ETest: StudentTest() { discussionListPage.assertUnreadReplyCount(newTopicName, 0) Log.d(STEP_TAG, "Assert that the due date is the current date (in the expected format).") - val currentDate = getCurrentDateInCanvasFormat() + val currentDate = getDateInCanvasFormat() discussionListPage.assertDueDate(newTopicName, currentDate) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt index e14c4b2683..7ee14b2b5e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt @@ -419,9 +419,10 @@ class InboxE2ETest: StudentTest() { tokenLogin(teacher) dashboardPage.waitForRender() - Log.d(STEP_TAG,"Open Inbox Page. Assert that the asked question is displayed in the teacher's inbox with the proper recipients ($recipientList) and message ($questionText).") + Log.d(STEP_TAG,"Open Inbox Page. Assert that the asked question is displayed in the teacher's inbox with the proper recipients ($recipientList), subject and message ($questionText).") dashboardPage.clickInboxTab() inboxPage.assertConversationWithRecipientsDisplayed(recipientList) + inboxPage.assertConversationSubject("(No Subject)") inboxPage.assertConversationDisplayed(questionText) Log.d(STEP_TAG, "Open the conversation and assert that there is no subject of the conversation and the message body is equal to which the student typed in the 'Ask Your Instructor' dialog: '$questionText'.") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt index cf15715843..ad88ae8ba2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt @@ -89,12 +89,12 @@ class ModulesE2ETest: StudentTest() { Log.d(PREPARATION_TAG,"Associate '${assignment2.name}' assignment with '${module2.name}' module.") ModulesApi.createModuleItem(course.id, teacher.token, module2.id, assignment2.name, ModuleItemTypes.ASSIGNMENT.stringVal, contentId = assignment2.id.toString()) - Log.d(PREPARATION_TAG,"Associate '${page1.title}' page with '${module2.name}' module.") - ModulesApi.createModuleItem(course.id, teacher.token, module2.id, page1.title, ModuleItemTypes.PAGE.stringVal, pageUrl = page1.url) - Log.d(PREPARATION_TAG,"Associate '${discussionTopic1.title}' discussion topic with '${module2.name}' module.") ModulesApi.createModuleItem(course.id, teacher.token, module2.id, discussionTopic1.title, ModuleItemTypes.DISCUSSION.stringVal, contentId = discussionTopic1.id.toString()) + Log.d(PREPARATION_TAG,"Associate '${quiz1.title}' page with '${module2.name}' module.") + ModulesApi.createModuleItem(course.id, teacher.token, module2.id, page1.title, ModuleItemTypes.PAGE.stringVal, pageUrl = page1.url) + Log.d(STEP_TAG, "Login with user: ${teacher.name}, login id: ${teacher.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -149,9 +149,61 @@ class ModulesE2ETest: StudentTest() { modulesPage.clickOnModuleExpandCollapseIcon(module2.name) modulesPage.assertModulesAndItemsCount(7) // 2 modules titles, 2 module items in first module, 3 items in second module - Log.d(STEP_TAG, "Assert that '${assignment1.name}' module item is displayed and open it. Assert that the Assignment Details page is displayed with the corresponding assignment title.") + Log.d(STEP_TAG, "Assert that '${assignment1.name}' module item is displayed and open it. Assert that the Assignment Details page is displayed with the corresponding assignment name: '${assignment1.name}'.") modulesPage.assertAndClickModuleItem(module1.name, assignment1.name, true) assignmentDetailsPage.assertPageObjects() assignmentDetailsPage.assertAssignmentTitle(assignment1.name) + + Log.d(STEP_TAG, "Assert that the module name, '${module1.name}' is displayed at the bottom.") + assignmentDetailsPage.moduleItemInteractions.assertModuleNameDisplayed(module1.name) + + Log.d(STEP_TAG, "Assert that the previous arrow button is not displayed because the user is on the first module item's details page, but the next arrow button is displayed.") + assignmentDetailsPage.moduleItemInteractions.assertPreviousArrowNotDisplayed() + assignmentDetailsPage.moduleItemInteractions.assertNextArrowDisplayed() + + Log.d(STEP_TAG, "Click on the next arrow button and assert that the '${quiz1.title}' quiz module item's 'Go To Quiz' page is displayed.") + assignmentDetailsPage.moduleItemInteractions.clickOnNextArrow() + goToQuizPage.assertQuizTitle(quiz1.title) + + Log.d(STEP_TAG, "Assert that the module name, '${module1.name}' is displayed at the bottom.") + goToQuizPage.moduleItemInteractions.assertModuleNameDisplayed(module1.name) + + Log.d(STEP_TAG, "Assert that both the previous and the next buttons are displayed (since we are not at the first or the last module item details page).") + goToQuizPage.moduleItemInteractions.assertPreviousArrowDisplayed() + goToQuizPage.moduleItemInteractions.assertNextArrowDisplayed() + + Log.d(STEP_TAG, "Click on the next arrow button and assert that the Assignment Details Page is displayed with the corresponding assignment name: '${assignment2.name}'.") + goToQuizPage.moduleItemInteractions.clickOnNextArrow() + assignmentDetailsPage.assertPageObjects() + assignmentDetailsPage.assertAssignmentTitle(assignment2.name) + + Log.d(STEP_TAG, "Assert that the second module name, '${module2.name}' is displayed at the bottom since we can navigate even between modules with these arrows.") + assignmentDetailsPage.moduleItemInteractions.assertModuleNameDisplayed(module2.name) + + Log.d(STEP_TAG, "Assert that both the previous and the next buttons are displayed (since we are not at the first or the last module item details page, even if it's a first item of a module, but of the second module).") + assignmentDetailsPage.moduleItemInteractions.assertPreviousArrowDisplayed() + assignmentDetailsPage.moduleItemInteractions.assertNextArrowDisplayed() + + Log.d(STEP_TAG, "Click on the next arrow button and assert that the '${discussionTopic1.title}' discussion topic module item's details page is displayed.") + assignmentDetailsPage.moduleItemInteractions.clickOnNextArrow() + discussionDetailsPage.assertTitleText(discussionTopic1.title) + + Log.d(STEP_TAG, "Assert that the second module name, '${module2.name}' is displayed at the bottom.") + discussionDetailsPage.moduleItemInteractions.assertModuleNameDisplayed(module2.name) + + Log.d(STEP_TAG, "Assert that both the previous and the next buttons are displayed.") + discussionDetailsPage.moduleItemInteractions.assertPreviousArrowDisplayed() + discussionDetailsPage.moduleItemInteractions.assertNextArrowDisplayed() + + Log.d(STEP_TAG, "Click on the next arrow button and assert that the '${page1.url}' page module item's details page is displayed.") + discussionDetailsPage.moduleItemInteractions.clickOnNextArrow() + pageDetailsPage.webAssertPageUrl(page1.url) + + Log.d(STEP_TAG, "Assert that the second module name, '${module2.name}' is displayed at the bottom.") + pageDetailsPage.moduleItemInteractions.assertModuleNameDisplayed(module2.name) + + Log.d(STEP_TAG, "Assert that the previous arrow button is displayed but the next arrow button is not displayed because the user is on the last module item's details page.") + pageDetailsPage.moduleItemInteractions.assertPreviousArrowDisplayed() + pageDetailsPage.moduleItemInteractions.assertNextArrowNotDisplayed() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt index 243bcc03c1..d2dd8fb9c2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt @@ -41,6 +41,7 @@ import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Rule import org.junit.Test import org.junit.rules.Timeout +import java.lang.Thread.sleep import java.util.* @HiltAndroidTest @@ -52,7 +53,7 @@ class ScheduleE2ETest : StudentTest() { @Rule @JvmField - var globalTimeout: Timeout = Timeout.millis(600000) // //TODO: workaround for that sometimes this test is running infinite time because of scrollToElement does not find an element. + var globalTimeout: Timeout = Timeout.millis(1200000) // //TODO: workaround for that sometimes this test is running infinite time because of scrollToElement does not find an element. @Stub @E2E @@ -91,7 +92,6 @@ class ScheduleE2ETest : StudentTest() { Log.d(STEP_TAG, "Navigate to K5 Schedule Page and assert it is loaded.") elementaryDashboardPage.selectTab(ElementaryDashboardPage.ElementaryTabType.SCHEDULE) - schedulePage.assertPageObjects() //Depends on how we handle Sunday, need to clarify with calendar team if(currentDateCalendar.get(Calendar.DAY_OF_WEEK) != 1) { schedulePage.assertIfCourseHeaderAndScheduleItemDisplayed(homeroomCourse.name, homeroomAnnouncement.title) } @@ -110,13 +110,13 @@ class ScheduleE2ETest : StudentTest() { schedulePage.assertIfCourseHeaderAndScheduleItemDisplayed(nonHomeroomCourses[2].name, testMissingAssignment.name) Log.d(STEP_TAG, "Scroll to 'Missing Items' section and verify that a missing assignment (${testMissingAssignment.name}) is displayed there with 100 points.") - schedulePage.scrollToItem(R.id.missingItemLayout, testMissingAssignment.name) - schedulePage.assertMissingItemDisplayed(testMissingAssignment.name, nonHomeroomCourses[2].name, "100 pts") + schedulePage.scrollToItem(R.id.metaLayout, testMissingAssignment.name) + schedulePage.assertMissingItemDisplayedOnPlannerItem(testMissingAssignment.name, nonHomeroomCourses[2].name, "100 pts") Log.d(STEP_TAG, "Refresh the Schedule Page. Assert that the items are still displayed correctly.") schedulePage.scrollToPosition(0) schedulePage.refresh() - schedulePage.assertPageObjects() + sleep(3000) Log.d(STEP_TAG, "Assert that the current day of the calendar is titled as 'Today'.") schedulePage.assertDayHeaderShownByItemName(concatDayString(currentDateCalendar), schedulePage.getStringFromResource(R.string.today), schedulePage.getStringFromResource(R.string.today)) @@ -161,6 +161,7 @@ class ScheduleE2ETest : StudentTest() { Log.d(STEP_TAG, "Navigate back to Schedule Page and assert it is loaded.") Espresso.pressBack() + sleep(3000) schedulePage.assertPageObjects() } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDiscussionsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDiscussionsE2ETest.kt new file mode 100644 index 0000000000..37fad33e9d --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDiscussionsE2ETest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import androidx.test.espresso.Espresso +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.canvas.espresso.checkToastText +import com.instructure.dataseeding.api.DiscussionTopicsApi +import com.instructure.espresso.getDateInCanvasFormat +import com.instructure.student.R +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.ViewUtils +import com.instructure.student.ui.utils.openOverflowMenu +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test +import java.lang.Thread.sleep + +@HiltAndroidTest +class OfflineDiscussionsE2ETest : StudentTest() { + + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.E2E, SecondaryFeatureCategory.OFFLINE_MODE) + fun testOfflineDiscussionsE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG,"Seed a discussion topic for '${course.name}' course.") + val discussion1 = DiscussionTopicsApi.createDiscussion(course.id, teacher.token) + + Log.d(PREPARATION_TAG,"Seed another discussion topic for '${course.name}' course.") + val discussion2 = DiscussionTopicsApi.createDiscussion(course.id, teacher.token) + + Log.d(STEP_TAG,"Login with user: '${student.name}', login id: '${student.loginId}'.") + tokenLogin(student) + + Log.d(STEP_TAG,"Wait for the Dashboard Page to be rendered.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Select '${course.name}' course and navigate to Discussion List page.") + dashboardPage.selectCourse(course) + courseBrowserPage.selectDiscussions() + + Log.d(STEP_TAG,"Select '$discussion1' discussion topic and assert that there is no reply on the details page as well.") + discussionListPage.selectTopic(discussion1.title) + discussionDetailsPage.assertNoRepliesDisplayed() + + val replyMessage = "My reply" + Log.d(STEP_TAG,"Send a reply with text: '$replyMessage'.") + discussionDetailsPage.sendReply(replyMessage) + sleep(2000) // Allow some time for reply to propagate + + Log.d(STEP_TAG,"Assert the the previously sent reply '$replyMessage', is displayed on the details page.") + discussionDetailsPage.assertRepliesDisplayed() + + Log.d(STEP_TAG, "Navigate back to the Dashboard page.") + ViewUtils.pressBackButton(3) + + Log.d(STEP_TAG, "Open the '${course.name}' course's 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.clickCourseOverflowMenu(course.name, "Manage Offline Content") + + Log.d(STEP_TAG, "Expand '${course.name}' course.") + manageOfflineContentPage.expandCollapseItem(course.name) + + Log.d(STEP_TAG, "Select the 'Discussions' of '${course.name}' course for sync. Click on the 'Sync' button.") + manageOfflineContentPage.changeItemSelectionState("Discussions") + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Assert that the offline sync icon only displayed on the synced course's course card.") + dashboardPage.assertCourseOfflineSyncIconVisible(course.name) + device.waitForIdle() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + OfflineTestUtils.waitForNetworkToGoOffline(device) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") + OfflineTestUtils.assertOfflineIndicator() + + Log.d(STEP_TAG, "Select '${course.name}' course and open 'Announcements' menu.") + dashboardPage.selectCourse(course) + + Log.d(STEP_TAG,"Navigate to Discussion List Page.") + courseBrowserPage.selectDiscussions() + + Log.d(STEP_TAG, "Assert that both the '${discussion1.title}' and '${discussion2.title}' discussion are displayed on the Discussion List page.") + discussionListPage.assertTopicDisplayed(discussion1.title) + discussionListPage.assertTopicDisplayed(discussion2.title) + + Log.d(STEP_TAG,"Refresh the page. Assert that the previously sent reply has been counted, and there are no unread replies.") + discussionListPage.assertReplyCount(discussion1.title, 1) + discussionListPage.assertUnreadReplyCount(discussion1.title, 0) + + Log.d(STEP_TAG, "Assert that the due date is the current date (in the expected format).") + val currentDate = getDateInCanvasFormat() + discussionListPage.assertDueDate(discussion1.title, currentDate) + + Log.d(STEP_TAG, "Click on the Search (magnifying glass) icon and the '${discussion1.title}' discussion's title into the search input field.") + discussionListPage.searchable.clickOnSearchButton() + discussionListPage.searchable.typeToSearchBar(discussion1.title) + + Log.d(STEP_TAG, "Assert that only the '${discussion1.title}' discussion displayed as a search result and the other, '${discussion2.title}' discussion has not displayed.") + discussionListPage.assertTopicDisplayed(discussion1.title) + discussionListPage.assertTopicNotDisplayed(discussion2.title) + + Log.d(STEP_TAG, "Click on the 'Clear Search' (X) icon and assert that both of the discussion should be displayed again.") + discussionListPage.searchable.clickOnClearSearchButton() + discussionListPage.waitForDiscussionTopicToDisplay(discussion2.title) + discussionListPage.assertTopicDisplayed(discussion1.title) + + Log.d(STEP_TAG,"Select '${discussion1.title}' discussion and assert if the corresponding discussion title is displayed.") + discussionListPage.selectTopic(discussion1.title) + discussionDetailsPage.assertTitleText(discussion1.title) + + Log.d(STEP_TAG, "Try to click on the (main) 'Reply' button and assert that the 'No Internet Connection' dialog has displayed. Dismiss the dialog.") + discussionDetailsPage.clickReply() + OfflineTestUtils.assertNoInternetConnectionDialog() + OfflineTestUtils.dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Try to click on the (inner) 'Reply' button (so try to 'reply to a reply') and assert that the 'No Internet Connection' dialog has displayed. Dismiss the dialog.") + discussionDetailsPage.clickOnInnerReply() + OfflineTestUtils.assertNoInternetConnectionDialog() + OfflineTestUtils.dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG,"Navigate back to Discussion List Page.") + Espresso.pressBack() + + Log.d(STEP_TAG,"Select '${discussion2.title}' discussion and assert if the Discussion Details page is displayed and there is no reply for the discussion yet.") + discussionListPage.selectTopic(discussion2.title) + discussionDetailsPage.assertTitleText(discussion2.title) + discussionDetailsPage.assertNoRepliesDisplayed() + + Log.d(STEP_TAG, "Try to click on 'Add Bookmark' overflow menu and assert that the 'Functionality unavailable while offline' toast message is displayed.") + openOverflowMenu() + discussionDetailsPage.clickOnAddBookmarkMenu() + checkToastText(R.string.notAvailableOffline, activityRule.activity) + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt index 29eed58195..79f23cc8c2 100644 --- 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 @@ -105,7 +105,7 @@ class ScheduleInteractionTest : StudentTest() { goToScheduleTab(data) schedulePage.scrollToPosition(12) - schedulePage.assertMissingItemDisplayed(assignment1.name!!, courses[0].name, "10 pts") + schedulePage.assertMissingItemDisplayedInMissingItemSummary(assignment1.name!!, courses[0].name, "10 pts") } @Test diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt index 871d533d39..b001e424f2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt @@ -38,6 +38,7 @@ import com.instructure.canvas.espresso.stringContainsTextCaseInsensitive import com.instructure.canvas.espresso.waitForMatcherWithSleeps import com.instructure.canvasapi2.models.Assignment import com.instructure.dataseeding.model.SubmissionType +import com.instructure.espresso.ModuleItemInteractions import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertContainsText import com.instructure.espresso.assertDisplayed @@ -66,7 +67,7 @@ import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.anything import org.hamcrest.Matchers.not -open class AssignmentDetailsPage : BasePage(R.id.assignmentDetailsPage) { +open class AssignmentDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : BasePage(R.id.assignmentDetailsPage) { val toolbar by OnViewWithId(R.id.toolbar) val points by OnViewWithId(R.id.points) val date by OnViewWithId(R.id.dueDateTextView) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt index a7655e6675..63bc20c3ff 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionDetailsPage.kt @@ -23,7 +23,6 @@ import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast -import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement @@ -37,6 +36,7 @@ import com.instructure.canvas.espresso.withCustomConstraints import com.instructure.canvas.espresso.withElementRepeat import com.instructure.canvasapi2.models.DiscussionEntry import com.instructure.canvasapi2.models.DiscussionTopicHeader +import com.instructure.espresso.ModuleItemInteractions import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertDisplayed import com.instructure.espresso.assertGone @@ -47,6 +47,8 @@ import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.plus import com.instructure.espresso.page.waitForViewWithId import com.instructure.espresso.page.withAncestor +import com.instructure.espresso.page.withId +import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo import com.instructure.student.R import com.instructure.student.ui.utils.TypeInRCETextEditor @@ -55,7 +57,7 @@ import org.hamcrest.Matchers.containsString import org.junit.Assert.assertTrue -class DiscussionDetailsPage : BasePage(R.id.discussionDetailsPage) { +class DiscussionDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : BasePage(R.id.discussionDetailsPage) { private val discussionTopicTitle by OnViewWithId(R.id.discussionTopicTitle) private val replyButton by OnViewWithId(R.id.replyToDiscussionTopic) @@ -318,6 +320,16 @@ class DiscussionDetailsPage : BasePage(R.id.discussionDetailsPage) { onView(withId(R.id.pointsTextView)).assertNotDisplayed() } + fun clickOnInnerReply() { + onWebView(withId(R.id.contentWebView) + withAncestor(R.id.discussionRepliesWebViewWrapper)) + .withElement(findElement(Locator.XPATH, "//div[@class='reply_wrapper' and contains(@id, 'reply')]")) + .perform(webClick()) + } + + fun clickOnAddBookmarkMenu() { + onView(withText("Add Bookmark")).click() + } + private fun isUnreadIndicatorVisible(reply: DiscussionEntry): Boolean { return try { onWebView(withId(R.id.contentWebView) + withAncestor(R.id.discussionRepliesWebViewWrapper)) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionListPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionListPage.kt index 28f125e1a9..83a593b0a2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionListPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DiscussionListPage.kt @@ -16,7 +16,6 @@ */ package com.instructure.student.ui.pages -import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.platform.app.InstrumentationRegistry @@ -25,6 +24,7 @@ import com.instructure.canvas.espresso.explicitClick import com.instructure.canvas.espresso.scrollRecyclerView import com.instructure.canvas.espresso.waitForMatcherWithRefreshes import com.instructure.canvasapi2.models.DiscussionTopicHeader +import com.instructure.espresso.DoesNotExistAssertion import com.instructure.espresso.OnViewWithId import com.instructure.espresso.RecyclerViewItemCountAssertion import com.instructure.espresso.Searchable @@ -57,7 +57,6 @@ open class DiscussionListPage(val searchable: Searchable) : BasePage(R.id.discus fun waitForDiscussionTopicToDisplay(topicTitle: String) { val matcher = allOf(withText(topicTitle), withId(R.id.discussionTitle)) waitForView(matcher) - } fun assertTopicDisplayed(topicTitle: String) { @@ -67,7 +66,7 @@ open class DiscussionListPage(val searchable: Searchable) : BasePage(R.id.discus } fun assertTopicNotDisplayed(topicTitle: String?) { - onView(allOf(withText(topicTitle))).check(ViewAssertions.doesNotExist()) + onView(allOf(withText(topicTitle))).check(DoesNotExistAssertion(5)) } fun assertEmpty() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GoToQuizPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GoToQuizPage.kt new file mode 100644 index 0000000000..9d6cc3af2c --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/GoToQuizPage.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.pages + +import com.instructure.espresso.ModuleItemInteractions +import com.instructure.espresso.OnViewWithText +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.click +import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.plus +import com.instructure.espresso.page.withAncestor +import com.instructure.espresso.page.withId +import com.instructure.espresso.page.withText +import com.instructure.student.R + +class GoToQuizPage(val moduleItemInteractions: ModuleItemInteractions) : BasePage() { + + private val goToQuizButton by OnViewWithText(R.string.goToQuiz) + + fun clickGoToQuizButton() { + goToQuizButton.click() + } + + fun assertQuizTitle(expectedTitle: String) { + onView(withId(R.id.quizTitle) + withText(expectedTitle) + withAncestor(R.id.quizInfoContainer)).assertDisplayed() + } +} diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt index 959dee5ad9..bb24ae21e7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt @@ -126,7 +126,7 @@ class InboxConversationPage : BasePage(R.id.inboxConversationPage) { } fun assertNoSubjectDisplayed() { - onView(withId(R.id.subjectView) + withText(R.string.noSubject)).assertDisplayed() + onView(withId(R.id.subjectView) + withParent(withId(R.id.header)) + withText(R.string.noSubject)).assertDisplayed() } fun refresh() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt index 8626f8d2a0..f3fa937f0e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt @@ -300,4 +300,8 @@ class InboxPage : BasePage(R.id.inboxPage) { editToolbar.assertVisibility(visibility) } + fun assertConversationSubject(expectedSubject: String) { + onView(withId(R.id.subjectView) + withText(expectedSubject) + withAncestor(R.id.inboxRecyclerView)).assertDisplayed() + } + } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PageDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PageDetailsPage.kt new file mode 100644 index 0000000000..81b06aaf67 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PageDetailsPage.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.pages + +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches +import androidx.test.espresso.web.model.Atoms +import androidx.test.espresso.web.sugar.Web.onWebView +import com.instructure.espresso.ModuleItemInteractions +import com.instructure.espresso.page.BasePage +import com.instructure.student.R +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.containsString + +class PageDetailsPage(val moduleItemInteractions: ModuleItemInteractions) : BasePage() { + + fun webAssertPageUrl(pageUrl: String) { + onWebView(allOf(withId(R.id.contentWebView), isDisplayed())) + .check(webMatches(Atoms.getCurrentUrl(), containsString(pageUrl))) + } + +} \ 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 2e38a15188..2fc890500e 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 @@ -20,6 +20,7 @@ import android.view.View import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers.hasSibling import com.instructure.espresso.* import com.instructure.espresso.page.* import com.instructure.pandautils.binding.BindableViewHolder @@ -81,13 +82,13 @@ class SchedulePage : BasePage(R.id.schedulePage) { var i: Int = 0 while (true) { scrollToPosition(i) - Thread.sleep(500) + Thread.sleep(300) try { if(target == null) onView(withParent(itemId) + withText(itemName)).scrollTo() else onView(target + withText(itemName)).scrollTo() break } catch(e: NoMatchingViewException) { - i++ + i+=2 } } } @@ -100,16 +101,22 @@ class SchedulePage : BasePage(R.id.schedulePage) { waitForView(withAncestor(R.id.plannerItems) + withText(scheduleItemName)).assertDisplayed() } - fun assertMissingItemDisplayed(itemName: String, courseName: String, pointsPossible: String) { + fun assertMissingItemDisplayedOnPlannerItem(itemName: String, courseName: String, pointsPossible: String) { + val titleMatcher = withId(R.id.title) + withText(itemName) + val courseNameMatcher = withId(R.id.scheduleCourseHeaderText) + withText(courseName) + val pointsPossibleMatcher = withId(R.id.points) + withText(pointsPossible) + + onView(withId(R.id.plannerItems) + hasSibling(courseNameMatcher) + withDescendant(titleMatcher) + withDescendant(pointsPossibleMatcher) + withDescendant(withText(R.string.missingAssignment))) + .scrollTo() + .assertDisplayed() + } + + fun assertMissingItemDisplayedInMissingItemSummary(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) - ) + onView(withId(R.id.missingItemLayout) + withDescendant(courseNameMatcher) + withDescendant(titleMatcher) + withDescendant(pointsPossibleMatcher)) .scrollTo() .assertDisplayed() } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt index e72cd5bd6b..2bdebb30ac 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt @@ -35,6 +35,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.instructure.canvas.espresso.CanvasTest import com.instructure.espresso.InstructureActivityTestRule +import com.instructure.espresso.ModuleItemInteractions import com.instructure.espresso.Searchable import com.instructure.espresso.swipeRight import com.instructure.pandautils.utils.Const @@ -62,6 +63,7 @@ import com.instructure.student.ui.pages.ElementaryCoursePage import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.pages.FileListPage import com.instructure.student.ui.pages.FileUploadPage +import com.instructure.student.ui.pages.GoToQuizPage import com.instructure.student.ui.pages.GradesPage import com.instructure.student.ui.pages.GroupBrowserPage import com.instructure.student.ui.pages.HelpPage @@ -78,6 +80,7 @@ import com.instructure.student.ui.pages.ModuleProgressionPage import com.instructure.student.ui.pages.ModulesPage import com.instructure.student.ui.pages.NewMessagePage import com.instructure.student.ui.pages.NotificationPage +import com.instructure.student.ui.pages.PageDetailsPage import com.instructure.student.ui.pages.PageListPage import com.instructure.student.ui.pages.PairObserverPage import com.instructure.student.ui.pages.PandaAvatarPage @@ -150,7 +153,7 @@ abstract class StudentTest : CanvasTest() { */ val annotationCommentListPage = AnnotationCommentListPage() val announcementListPage = AnnouncementListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn)) - val assignmentDetailsPage = AssignmentDetailsPage() + val assignmentDetailsPage = AssignmentDetailsPage(ModuleItemInteractions(R.id.moduleName, R.id.next_item, R.id.prev_item)) val assignmentListPage = AssignmentListPage(Searchable(R.id.search, R.id.search_src_text)) val bookmarkPage = BookmarkPage() val calendarEventPage = CalendarEventPage() @@ -163,7 +166,7 @@ abstract class StudentTest : CanvasTest() { val courseGradesPage = CourseGradesPage() val dashboardPage = DashboardPage() val leftSideNavigationDrawerPage = LeftSideNavigationDrawerPage() - val discussionDetailsPage = DiscussionDetailsPage() + val discussionDetailsPage = DiscussionDetailsPage(ModuleItemInteractions(R.id.moduleName, R.id.next_item, R.id.prev_item)) val discussionListPage = DiscussionListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn)) val allCoursesPage = AllCoursesPage() val fileListPage = FileListPage(Searchable(R.id.search, R.id.queryInput, R.id.clearButton, R.id.backButton)) @@ -181,6 +184,7 @@ abstract class StudentTest : CanvasTest() { val newMessagePage = NewMessagePage() val notificationPage = NotificationPage() val pageListPage = PageListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn)) + val pageDetailsPage = PageDetailsPage(ModuleItemInteractions(R.id.moduleName, R.id.next_item, R.id.prev_item)) val pairObserverPage = PairObserverPage() val pandaAvatarPage = PandaAvatarPage() val peopleListPage = PeopleListPage() @@ -190,6 +194,7 @@ abstract class StudentTest : CanvasTest() { val qrLoginPage = QRLoginPage() val quizListPage = QuizListPage() val quizTakingPage = QuizTakingPage() + val goToQuizPage = GoToQuizPage(ModuleItemInteractions(R.id.moduleName, R.id.next_item, R.id.prev_item)) val remoteConfigSettingsPage = RemoteConfigSettingsPage() val settingsPage = SettingsPage() val submissionDetailsPage = SubmissionDetailsPage() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt index 3fc55b3592..02c6b4d5e4 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt @@ -23,6 +23,7 @@ import android.content.Intent import android.net.Uri import android.os.Environment import androidx.fragment.app.FragmentActivity +import androidx.test.espresso.Espresso import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId @@ -58,6 +59,10 @@ fun StudentTest.slowLogIn(enrollmentType: String = EnrollmentTypes.STUDENT_ENROL return user } +fun StudentTest.openOverflowMenu() { + Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) +} + fun seedDataForK5( teachers: Int = 0, tas: Int = 0, diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt index 00ea9846cc..603dc0c104 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt @@ -281,7 +281,7 @@ class AssignmentDetailsViewModel @Inject constructor( val assignmentState = AssignmentUtils2.getAssignmentState(assignment, assignment.submission, false) // Don't mark LTI assignments as missing when overdue as they usually won't have a real submission for it - val isMissing = assignment.submission?.missing.orDefault() || (assignment.turnInType != Assignment.TurnInType.EXTERNAL_TOOL + val isMissing = assignment.isMissing() || (assignment.turnInType != Assignment.TurnInType.EXTERNAL_TOOL && assignment.dueAt != null && assignmentState == AssignmentUtils2.ASSIGNMENT_STATE_MISSING) @@ -308,8 +308,11 @@ class AssignmentDetailsViewModel @Inject constructor( val submittedStatusIcon = if (assignment.isSubmitted) R.drawable.ic_complete_solid else R.drawable.ic_no // Submission Status under title - We only show Graded or nothing at all for PAPER/NONE - val submissionStatusVisible = assignmentState == AssignmentUtils2.ASSIGNMENT_STATE_GRADED - || (assignment.turnInType != Assignment.TurnInType.ON_PAPER && assignment.turnInType != Assignment.TurnInType.NONE) + val submissionStatusVisible = + assignmentState == AssignmentUtils2.ASSIGNMENT_STATE_GRADED + || assignmentState == AssignmentUtils2.ASSIGNMENT_STATE_MISSING + || assignmentState == AssignmentUtils2.ASSIGNMENT_STATE_GRADED_MISSING + || (assignment.turnInType != Assignment.TurnInType.ON_PAPER && assignment.turnInType != Assignment.TurnInType.NONE) if (assignment.isLocked) { val lockedMessage = if (assignment.lockInfo?.contextModule != null) { diff --git a/apps/student/src/main/java/com/instructure/student/features/discussion/details/DiscussionDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/features/discussion/details/DiscussionDetailsFragment.kt index e76d4dbabe..bcc9257472 100644 --- a/apps/student/src/main/java/com/instructure/student/features/discussion/details/DiscussionDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/discussion/details/DiscussionDetailsFragment.kt @@ -194,33 +194,29 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { //region Discussion Actions private fun viewAttachments(remoteFiles: List) { - if (repository.isOnline()) { - // Only one file can be attached to a discussion - val remoteFile = remoteFiles.firstOrNull() ?: return - - // Show lock message if file is locked - if (remoteFile.lockedForUser) { - if (remoteFile.lockExplanation.isValid()) { - Snackbar.make( - requireView(), - remoteFile.lockExplanation!!, - Snackbar.LENGTH_SHORT - ).show() - } else { - Snackbar.make( - requireView(), - R.string.fileCurrentlyLocked, - Snackbar.LENGTH_SHORT - ).show() - } + // Only one file can be attached to a discussion + val remoteFile = remoteFiles.firstOrNull() ?: return + + // Show lock message if file is locked + if (remoteFile.lockedForUser) { + if (remoteFile.lockExplanation.isValid()) { + Snackbar.make( + requireView(), + remoteFile.lockExplanation!!, + Snackbar.LENGTH_SHORT + ).show() + } else { + Snackbar.make( + requireView(), + R.string.fileCurrentlyLocked, + Snackbar.LENGTH_SHORT + ).show() } - - // Show attachment - val attachment = remoteFile.mapToAttachment() - openMedia(attachment.contentType, attachment.url, attachment.filename, canvasContext) - } else { - NoInternetConnectionDialog.show(requireFragmentManager()) } + + // Show attachment + val attachment = remoteFile.mapToAttachment() + openMedia(attachment.contentType, attachment.url, attachment.filename, canvasContext, localFile = attachment.isLocalFile) } private fun showReplyView(discussionEntryId: Long) { @@ -612,7 +608,8 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { canvasContext, discussionTopicHeader, discussionTopic!!.views, - discussionEntryId + discussionEntryId, + repository.isOnline() ) loadDiscussionTopicViews(html) diff --git a/apps/student/src/main/java/com/instructure/student/features/files/details/FileDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/features/files/details/FileDetailsFragment.kt index 4c4bfbbfe4..2a36489673 100644 --- a/apps/student/src/main/java/com/instructure/student/features/files/details/FileDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/files/details/FileDetailsFragment.kt @@ -96,6 +96,11 @@ class FileDetailsFragment : ParentFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_file_details, container, false) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.downloadButton.setVisible(repository.isOnline()) + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) getFileFolder() @@ -131,8 +136,6 @@ class FileDetailsFragment : ParentFragment() { requestPermissions(PermissionUtils.makeArray(PermissionUtils.WRITE_EXTERNAL_STORAGE), PermissionUtils.WRITE_FILE_PERMISSION_REQUEST_CODE) } } - - binding.downloadButton.setVisible(repository.isOnline()) } override fun onMediaLoadingStarted() { diff --git a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt index 553bb47c90..9aa6614bed 100644 --- a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt @@ -318,7 +318,7 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent val isUserFiles = canvasContext.type == CanvasContext.Type.USER if (recyclerAdapter == null) { - recyclerAdapter = FileListRecyclerAdapter(requireContext(), canvasContext, getFileMenuOptions(folder, canvasContext, fileListRepository.isOnline()), folder, adapterCallback, fileListRepository) + recyclerAdapter = FileListRecyclerAdapter(requireContext(), canvasContext, getFileMenuOptions(folder, canvasContext, fileListRepository.isOnline(), folder), folder, adapterCallback, fileListRepository) } configureRecyclerView(requireView(), requireContext(), recyclerAdapter!!, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) @@ -362,7 +362,7 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent val popup = PopupMenu(requireContext(), anchorView) popup.inflate(R.menu.file_folder_options) with(popup.menu) { - val options = getFileMenuOptions(item, canvasContext, fileListRepository.isOnline()) + val options = getFileMenuOptions(item, canvasContext, fileListRepository.isOnline(), folder) // Only show alternate-open option for PDF files findItem(R.id.openAlternate).isVisible = options.contains(FileMenuType.OPEN_IN_ALTERNATE) findItem(R.id.download).isVisible = options.contains(FileMenuType.DOWNLOAD) @@ -609,7 +609,7 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent /** * @return A list of possible actions the user is able to perform on the file/folder */ - fun getFileMenuOptions(fileFolder: FileFolder?, canvasContext: CanvasContext, isOnline: Boolean): List { + fun getFileMenuOptions(fileFolder: FileFolder?, canvasContext: CanvasContext, isOnline: Boolean, folder: FileFolder?): List { if (fileFolder == null) return emptyList() val options: MutableList = mutableListOf() @@ -617,7 +617,8 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent // We're in the user's files, they should have options in the options menu if (!fileFolder.isLockedForUser) { // File is not locked for this user - if (!fileFolder.forSubmissions) { + val forSubmission = if (!fileFolder.isFile) fileFolder.forSubmissions else folder?.forSubmissions ?: false + if (!forSubmission) { // File/folder is not for a submission, so we can rename/delete with(options) { add(FileMenuType.RENAME) diff --git a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListRecyclerAdapter.kt index 35f5dae3fd..29e867d69d 100644 --- a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListRecyclerAdapter.kt @@ -68,7 +68,7 @@ open class FileListRecyclerAdapter( } override fun bindHolder(item: FileFolder, holder: FileViewHolder, position: Int) { - holder.bind(item, contextColor, context, FileListFragment.getFileMenuOptions(item, canvasContext, fileListRepository.isOnline()), fileFolderCallback) + holder.bind(item, contextColor, context, FileListFragment.getFileMenuOptions(item, canvasContext, fileListRepository.isOnline(), folder), fileFolderCallback) } override fun createViewHolder(v: View, viewType: Int) = FileViewHolder(v) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt index 2ba2396090..9498e39d20 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt @@ -63,7 +63,7 @@ import com.instructure.student.databinding.CourseGridRecyclerRefreshLayoutBindin import com.instructure.student.databinding.FragmentCourseGridBinding import com.instructure.student.decorations.VerticalGridSpacingDecoration import com.instructure.student.dialog.ColorPickerDialog -import com.instructure.student.dialog.EditCourseNicknameDialog +import com.instructure.pandautils.dialogs.EditCourseNicknameDialog import com.instructure.student.events.CoreDataFinishedLoading import com.instructure.student.events.CourseColorOverlayToggledEvent import com.instructure.student.events.ShowGradesToggledEvent diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt index 0ffb0d1a1d..e223a0b533 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt @@ -168,12 +168,10 @@ class ToDoListFragment : ParentFragment() { when { toDo?.assignment != null -> { // Launch assignment details fragment. if (toDo.assignment!!.discussionTopicHeader != null) { - val groupTopic = toDo.assignment!!.discussionTopicHeader!!.groupTopicChildren.firstOrNull() - if (groupTopic == null) { // Launch discussion details fragment - RouteMatcher.route(requireActivity(), DiscussionRouterFragment.makeRoute(toDo.canvasContext!!, toDo.assignment!!.discussionTopicHeader!!)) - } else { // Launch discussion details fragment with the group - RouteMatcher.route(requireActivity(), DiscussionRouterFragment.makeRoute(CanvasContext.emptyGroupContext(groupTopic.groupId), groupTopic.id)) - } + RouteMatcher.route( + requireActivity(), + DiscussionRouterFragment.makeRoute(toDo.canvasContext!!, toDo.assignment!!.discussionTopicHeader!!) + ) } else { // Launch assignment details fragment. RouteMatcher.route(requireActivity(), AssignmentDetailsFragment.makeRoute(toDo.canvasContext!!, toDo.assignment!!.id)) diff --git a/apps/student/src/main/java/com/instructure/student/holders/GradeViewHolder.kt b/apps/student/src/main/java/com/instructure/student/holders/GradeViewHolder.kt index 2bd4e88500..4567300a2f 100644 --- a/apps/student/src/main/java/com/instructure/student/holders/GradeViewHolder.kt +++ b/apps/student/src/main/java/com/instructure/student/holders/GradeViewHolder.kt @@ -99,7 +99,7 @@ class GradeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { } date.setVisible(date.text.isNotBlank()) - if (assignment.isMissing() && !isEdit && assignment.submission?.grade == null) { + if (assignment.isMissing() && !isEdit) { submissionState.text = context.getString(R.string.missingAssignment) submissionState.setTextColor(ContextCompat.getColor(context, R.color.textDanger)) submissionState.setVisible() diff --git a/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/SubmissionDetailsUpdateTest.kt b/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/SubmissionDetailsUpdateTest.kt index dba488f7e2..0686b245e1 100644 --- a/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/SubmissionDetailsUpdateTest.kt +++ b/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/SubmissionDetailsUpdateTest.kt @@ -17,6 +17,7 @@ package com.instructure.student.test.assignment.details.submissionDetails import android.net.Uri import android.webkit.MimeTypeMap +import android.webkit.URLUtil import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.DataResult @@ -34,6 +35,7 @@ import com.spotify.mobius.test.NextMatchers.hasNothing import com.spotify.mobius.test.UpdateSpec import com.spotify.mobius.test.UpdateSpec.assertThatNext import io.mockk.* +import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test @@ -59,6 +61,14 @@ class SubmissionDetailsUpdateTest : Assert() { submission = Submission(id = 30L, attempt = 1L, assignmentId = assignment.id) initModel = SubmissionDetailsModel(assignmentId = assignment.id, canvasContext = course, isStudioEnabled = isStudioEnabled, assignmentEnhancementsEnabled = true) ltiTool = LTITool(url = "https://www.instructure.com") + + mockkStatic(URLUtil::class) + every { URLUtil.isNetworkUrl(any()) } returns true + } + + @After + fun tearDown() { + unmockkAll() } @Test diff --git a/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/commentTab/SubmissionCommentsUpdateTest.kt b/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/commentTab/SubmissionCommentsUpdateTest.kt index f153bc0b2b..22ed89ffa7 100644 --- a/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/commentTab/SubmissionCommentsUpdateTest.kt +++ b/apps/student/src/test/java/com/instructure/student/test/assignment/details/submissionDetails/commentTab/SubmissionCommentsUpdateTest.kt @@ -16,6 +16,7 @@ */ package com.instructure.student.test.assignment.details.submissionDetails.commentTab +import android.webkit.URLUtil import com.instructure.canvasapi2.models.* import com.instructure.student.mobius.assignmentDetails.submissionDetails.drawer.comments.SubmissionCommentsEffect import com.instructure.student.mobius.assignmentDetails.submissionDetails.drawer.comments.SubmissionCommentsEvent @@ -30,6 +31,10 @@ import com.spotify.mobius.test.NextMatchers.hasModel import com.spotify.mobius.test.NextMatchers.hasNoModel import com.spotify.mobius.test.UpdateSpec import com.spotify.mobius.test.UpdateSpec.assertThatNext +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test @@ -51,6 +56,14 @@ class SubmissionCommentsUpdateTest : Assert() { attemptId = 1, assignmentEnhancementsEnabled = true ) + + mockkStatic(URLUtil::class) + every { URLUtil.isNetworkUrl(any()) } returns false + } + + @After + fun teardown() { + unmockkAll() } @Test diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index ee6e4fa51d..d58c3fc40c 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -39,8 +39,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 63 - versionName = '1.28.1' + versionCode = 64 + versionName = '1.29.0' vectorDrawables.useSupportLibrary = true multiDexEnabled true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/ModuleListPageTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/ModuleListPageTest.kt index aa902d12ad..5bcdec9eab 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/ModuleListPageTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/ModuleListPageTest.kt @@ -32,7 +32,6 @@ import com.instructure.canvasapi2.models.Tab import com.instructure.dataseeding.util.Randomizer import com.instructure.teacher.R import com.instructure.teacher.ui.utils.TeacherComposeTest -import com.instructure.teacher.ui.utils.TeacherTest import com.instructure.teacher.ui.utils.openOverflowMenu import com.instructure.teacher.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -323,7 +322,7 @@ class ModuleListPageTest : TeacherComposeTest() { updateFilePermissionsPage.swipeUpBottomSheet() updateFilePermissionsPage.clickUnpublishRadioButton() - updateFilePermissionsPage.clickSaveButton() + updateFilePermissionsPage.clickUpdateButton() moduleListPage.assertModuleItemNotPublished(fileFolder.displayName.orEmpty()) } @@ -354,7 +353,7 @@ class ModuleListPageTest : TeacherComposeTest() { updateFilePermissionsPage.swipeUpBottomSheet() updateFilePermissionsPage.clickPublishRadioButton() - updateFilePermissionsPage.clickSaveButton() + updateFilePermissionsPage.clickUpdateButton() moduleListPage.assertModuleItemIsPublished(fileFolder.displayName.orEmpty()) } @@ -385,11 +384,36 @@ class ModuleListPageTest : TeacherComposeTest() { updateFilePermissionsPage.swipeUpBottomSheet() updateFilePermissionsPage.clickHideRadioButton() - updateFilePermissionsPage.clickSaveButton() + updateFilePermissionsPage.clickUpdateButton() moduleListPage.assertModuleItemHidden(fileFolder.displayName.orEmpty()) } + @Test + fun assertModuleItemDisabled() { + val data = goToModulesPage() + val module = data.courseModules.values.first().first() + val course = data.courses.values.first() + val assignment = data.addAssignment(courseId = data.courses.values.first().id) + data.addItemToModule( + course = course, + moduleId = module.id, + item = assignment, + published = true, + moduleContentDetails = ModuleContentDetails( + hidden = false, + locked = true + ), + unpublishable = false + ) + + moduleListPage.refresh() + + moduleListPage.clickItemOverflow(assignment.name.orEmpty()) + moduleListPage.assertSnackbarContainsText(assignment.name.orEmpty()) + } + + private fun goToModulesPage(publishedModuleCount: Int = 1, unpublishedModuleCount: Int = 0): MockCanvas { val data = MockCanvas.init(teacherCount = 1, courseCount = 1, favoriteCourseCount = 1) val course = data.courses.values.first() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderCommentsPageTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderCommentsPageTest.kt index cfaefe31a2..57634500cf 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderCommentsPageTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderCommentsPageTest.kt @@ -166,6 +166,7 @@ class SpeedGraderCommentsPageTest : TeacherTest() { // Allows Espresso to distinguish between this and the full name, which is elsewhere on the page authorName = student.shortName, authorPronouns = student.pronouns, + attempt = 1L, comment = "a comment", attachments = if(attachment == null) arrayListOf() else arrayListOf(attachment) ) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt index b0aaaa1a5a..daabbaaef9 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/ModulesE2ETest.kt @@ -9,19 +9,24 @@ import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.DiscussionTopicsApi +import com.instructure.dataseeding.api.FileFolderApi import com.instructure.dataseeding.api.ModulesApi import com.instructure.dataseeding.api.PagesApi import com.instructure.dataseeding.api.QuizzesApi +import com.instructure.dataseeding.api.SubmissionsApi +import com.instructure.dataseeding.model.FileUploadType import com.instructure.dataseeding.model.ModuleItemTypes import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 +import com.instructure.espresso.getCustomDateCalendar import com.instructure.teacher.R import com.instructure.teacher.ui.utils.TeacherComposeTest import com.instructure.teacher.ui.utils.openOverflowMenu import com.instructure.teacher.ui.utils.seedData import com.instructure.teacher.ui.utils.tokenLogin +import com.instructure.teacher.ui.utils.uploadTextFile import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @@ -203,11 +208,15 @@ class ModulesE2ETest : TeacherComposeTest() { Log.d(PREPARATION_TAG, "Seeding data.") val data = seedData(students = 1, teachers = 1, courses = 1) val teacher = data.teachersList[0] + val student = data.studentsList[0] val course = data.coursesList[0] Log.d(PREPARATION_TAG, "Seeding 'Text Entry' assignment for '${course.name}' course.") val assignment = AssignmentsApi.createAssignment(course.id, teacher.token, withDescription = true, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY), dueAt = 1.days.fromNow.iso8601) + Log.d(PREPARATION_TAG,"Submit '${assignment.name}' assignment for '${student.name}' student.") + SubmissionsApi.seedAssignmentSubmission(course.id, student.token, assignment.id, submissionSeedsList = listOf(SubmissionsApi.SubmissionSeedInfo(amount = 1, submissionType = SubmissionType.ONLINE_TEXT_ENTRY))) + Log.d(PREPARATION_TAG, "Seeding another 'Text Entry' assignment for '${course.name}' course.") val assignment2 = AssignmentsApi.createAssignment(course.id, teacher.token, withDescription = true, submissionTypes = listOf(SubmissionType.ONLINE_TEXT_ENTRY), dueAt = 1.days.fromNow.iso8601) @@ -261,6 +270,7 @@ class ModulesE2ETest : TeacherComposeTest() { moduleListPage.assertModuleIsDisplayed(module2.name) moduleListPage.assertModuleNotPublished(module2.name) moduleListPage.assertModuleItemIsPublished(assignment.name) + moduleListPage.assertModuleStatusIconAlpha(assignment.name, 0.5f) moduleListPage.assertModuleItemIsPublished(quiz.title) moduleListPage.assertModuleItemIsPublished(discussionTopic.title) moduleListPage.assertModuleItemNotPublished(testPage.title) @@ -303,14 +313,16 @@ class ModulesE2ETest : TeacherComposeTest() { progressPage.assertProgressPageTitle(R.string.allModulesAndItems) progressPage.clickDone() - Log.d(STEP_TAG, "Assert that the proper snack bar text is displayed and the '${module.name}' module and all of it's items became unpublished.") + Log.d(STEP_TAG, "Assert that the proper snack bar text is displayed and the '${module.name}' module and all of it's items (except '${assignment.name}' assignment) became unpublished.") moduleListPage.assertSnackbarText(R.string.allModulesAndAllItemsUnpublished) moduleListPage.assertModuleNotPublished(module.name) - moduleListPage.assertModuleItemNotPublished(assignment.name) moduleListPage.assertModuleItemNotPublished(quiz.title) moduleListPage.assertModuleItemNotPublished(testPage.title) moduleListPage.assertModuleItemNotPublished(discussionTopic.title) + Log.d(STEP_TAG, "Assert that the '${assignment.name}' assignment remained published because it's unpublishable since it has a submission already.") + moduleListPage.assertModuleItemIsPublished(assignment.name) + Log.d(STEP_TAG, "Assert that '${module2.name}' module and all of it's items became unpublished.") moduleListPage.assertModuleNotPublished(module2.name) moduleListPage.assertModuleItemNotPublished(assignment2.name) @@ -330,7 +342,7 @@ class ModulesE2ETest : TeacherComposeTest() { Log.d(STEP_TAG, "Assert that the proper snack bar text is displayed and only the '${module.name}' module became published, but it's items remaining unpublished.") moduleListPage.assertSnackbarText(R.string.onlyModulesPublished) moduleListPage.assertModuleIsPublished(module.name) - moduleListPage.assertModuleItemNotPublished(assignment.name) + moduleListPage.assertModuleItemIsPublished(assignment.name) moduleListPage.assertModuleItemNotPublished(quiz.title) moduleListPage.assertModuleItemNotPublished(testPage.title) moduleListPage.assertModuleItemNotPublished(discussionTopic.title) @@ -373,14 +385,16 @@ class ModulesE2ETest : TeacherComposeTest() { progressPage.assertProgressPageTitle(R.string.selectedModulesAndItems) progressPage.clickDone() - Log.d(STEP_TAG, "Assert that the proper snack bar text is displayed and the '${module.name}' module and all of it's items became unpublished.") + Log.d(STEP_TAG, "Assert that the proper snack bar text is displayed and the '${module.name}' module and all of it's items (except '${assignment.name}' assignment) became unpublished.") moduleListPage.assertSnackbarText(R.string.moduleAndAllItemsUnpublished) moduleListPage.assertModuleNotPublished(module.name) - moduleListPage.assertModuleItemNotPublished(assignment.name) moduleListPage.assertModuleItemNotPublished(quiz.title) moduleListPage.assertModuleItemNotPublished(testPage.title) moduleListPage.assertModuleItemNotPublished(discussionTopic.title) + Log.d(STEP_TAG, "Assert that the '${assignment.name}' assignment remained published because it's unpublishable since it has a submission already.") + moduleListPage.assertModuleItemIsPublished(assignment.name) + Log.d(STEP_TAG, "Click on '${module.name}' module overflow.") moduleListPage.clickItemOverflow(module.name) @@ -390,24 +404,15 @@ class ModulesE2ETest : TeacherComposeTest() { device.waitForWindowUpdate(null, 3000) device.waitForIdle() - Log.d(STEP_TAG, "Assert that only the '${module.name}' module became published, but it's items remaining unpublished.") + Log.d(STEP_TAG, "Assert that only the '${module.name}' module became published, but it's items (except '${assignment.name}' assignment) remaining unpublished.") moduleListPage.assertModuleIsPublished(module.name) - moduleListPage.assertModuleItemNotPublished(assignment.name) + moduleListPage.assertModuleItemIsPublished(assignment.name) moduleListPage.assertModuleItemNotPublished(quiz.title) moduleListPage.assertModuleItemNotPublished(testPage.title) moduleListPage.assertModuleItemNotPublished(discussionTopic.title) //Bottom layer - One module item - Log.d(STEP_TAG, "Click on '${assignment.name}' assignment's overflow menu and publish it. Confirm the publish via the publish dialog.") - moduleListPage.clickItemOverflow(assignment.name) - moduleListPage.clickOnText(R.string.publishModuleItemAction) - moduleListPage.clickOnText(R.string.publishDialogPositiveButton) - - Log.d(STEP_TAG, "Assert that the 'Item published' snack bar has displayed and the '${assignment.name}' assignment became published.") - moduleListPage.assertSnackbarText(R.string.moduleItemPublished) - moduleListPage.assertModuleItemIsPublished(assignment.name) - Log.d(STEP_TAG, "Click on '${quiz.title}' quiz's overflow menu and publish it. Confirm the publish via the publish dialog.") moduleListPage.clickItemOverflow(quiz.title) moduleListPage.clickOnText(R.string.publishModuleItemAction) @@ -435,14 +440,10 @@ class ModulesE2ETest : TeacherComposeTest() { moduleListPage.assertSnackbarText(R.string.moduleItemPublished) moduleListPage.assertModuleItemIsPublished(discussionTopic.title) - Log.d(STEP_TAG, "Click on '${assignment.name}' assignment's overflow menu and unpublish it. Confirm the unpublish via the unpublish dialog.") + Log.d(STEP_TAG, "Try to click on '${assignment.name}' assignment's overflow menu (in order to unpublish it). Assert that a snack bar with a proper text will be displayed that it cannot be unpublished since it has student submissions.") moduleListPage.clickItemOverflow(assignment.name) - moduleListPage.clickOnText(R.string.unpublishModuleItemAction) - moduleListPage.clickOnText(R.string.unpublishDialogPositiveButton) - - Log.d(STEP_TAG, "Assert that the 'Item unpublished' snack bar has displayed and the '${assignment.name}' assignment became unpublished.") - moduleListPage.assertSnackbarText(R.string.moduleItemUnpublished) - moduleListPage.assertModuleItemNotPublished(assignment.name) + moduleListPage.assertSnackbarContainsText(assignment.name) + moduleListPage.assertModuleItemIsPublished(assignment.name) Log.d(STEP_TAG, "Click on '${quiz.title}' quiz's overflow menu and unpublish it. Confirm the unpublish via the unpublish dialog.") moduleListPage.clickItemOverflow(quiz.title) @@ -472,4 +473,126 @@ class ModulesE2ETest : TeacherComposeTest() { moduleListPage.assertModuleItemNotPublished(discussionTopic.title) } + @E2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.E2E) + fun testFileModuleItemUpdateE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Seeding a module for '${course.name}' course. It starts as unpublished.") + val module = ModulesApi.createModule(course.id, teacher.token) + + Log.d(PREPARATION_TAG, "Get the root folder of the course.") + val courseRootFolder = FileFolderApi.getCourseRootFolder(course.id, teacher.token) + + Log.d(PREPARATION_TAG, "Create a (text) file within the root folder (so the 'Files' tab file list) of the '${course.name}' course.") + val testTextFile = uploadTextFile(courseRootFolder.id, token = teacher.token, fileUploadType = FileUploadType.COURSE_FILE) + + Log.d(PREPARATION_TAG, "Create another (text) file within the root folder (so the 'Files' tab file list) of the '${course.name}' course.") + val testTextFile2 = uploadTextFile(courseRootFolder.id, token = teacher.token, fileUploadType = FileUploadType.COURSE_FILE) + + Log.d(PREPARATION_TAG, "Associate '${testTextFile.fileName}' (course) file with module: '${module.id}'.") + ModulesApi.createModuleItem( + course.id, + teacher.token, + module.id, + moduleItemTitle = testTextFile.fileName, + moduleItemType = ModuleItemTypes.FILE.stringVal, + contentId = testTextFile.id.toString() + ) + + Log.d(PREPARATION_TAG, "Associate '${testTextFile2.fileName}' (course) file with module: '${module.id}'.") + ModulesApi.createModuleItem( + course.id, + teacher.token, + module.id, + moduleItemTitle = testTextFile2.fileName, + moduleItemType = ModuleItemTypes.FILE.stringVal, + contentId = testTextFile2.id.toString() + ) + + Log.d(STEP_TAG, "Login with user: '${teacher.name}', login id: '${teacher.loginId}'. Assert that '${course.name}' course is displayed on the Dashboard.") + tokenLogin(teacher) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open '${course.name}' course and navigate to Modules Page.") + dashboardPage.openCourse(course.name) + courseBrowserPage.openModulesTab() + + Log.d(STEP_TAG, "Assert that both, the '${testTextFile.fileName}' and '${testTextFile2.fileName}' files are published.") + moduleListPage.assertModuleItemIsPublished(testTextFile.fileName) + moduleListPage.assertModuleItemIsPublished(testTextFile2.fileName) + + Log.d(STEP_TAG, "Click on the 'more menu' of the '${testTextFile.fileName}' file.") + moduleListPage.clickItemOverflow(testTextFile.fileName) + + Log.d(STEP_TAG, "Assert that by default, on the Update File Permissions Page the 'Published' radio button is checked within the 'Availability' section.") + updateFilePermissionsPage.assertFilePublished() + + Log.d(STEP_TAG, "Assert that the 'Visibility' section radio buttons are enabled and the 'Inherit from course' radio button is checked by default within the 'Visibility' section.") + updateFilePermissionsPage.assertVisibilityEnabled() + updateFilePermissionsPage.assertFileVisibilityInherit() + + Log.d(STEP_TAG, "Click on the 'Unpublish' radio button and assert that the 'Visibility' section radio buttons became disabled.") + updateFilePermissionsPage.clickUnpublishRadioButton() + updateFilePermissionsPage.assertVisibilityDisabled() + + Log.d(STEP_TAG, "Click on the 'Update' button and assert on the Module List Page that the '${testTextFile.fileName}' file became unpublished.") + updateFilePermissionsPage.clickUpdateButton() + moduleListPage.assertModuleItemNotPublished(testTextFile.fileName) + + Log.d(STEP_TAG, "Click on the 'more menu' of the '${testTextFile.fileName}' file.") + moduleListPage.clickItemOverflow(testTextFile.fileName) + + Log.d(STEP_TAG, "Assert that the 'Unpublished' radio button is checked because of the previous modifications.") + updateFilePermissionsPage.assertFileUnpublished() + + Log.d(STEP_TAG, "Click on the 'Only available with link' (aka. 'Hide') radio button and click on the 'Update' button to save the changes.") + updateFilePermissionsPage.clickHideRadioButton() + updateFilePermissionsPage.clickUpdateButton() + + Log.d(STEP_TAG, "Assert that the '${testTextFile.fileName}' file module item became hidden.") + moduleListPage.assertModuleItemHidden(testTextFile.fileName) + + Log.d(STEP_TAG, "Click on the 'more menu' of the '${testTextFile.fileName}' file.") + moduleListPage.clickItemOverflow(testTextFile.fileName) + + Log.d(STEP_TAG, "Assert that the 'Hidden' radio button is checked because of the previous modifications.") + updateFilePermissionsPage.assertFileHidden() + + Log.d(STEP_TAG, "Click on the 'Schedule availability' (aka. 'Scheduled') radio button without setting any 'From' and 'Until' dates and click on the 'Update' button to save the changes.") + updateFilePermissionsPage.clickScheduleRadioButton() + updateFilePermissionsPage.clickUpdateButton() + + Log.d(STEP_TAG, "Assert that the '${testTextFile.fileName}' file is published since that is the expected behaviour if we does not select any dates for schedule.") + moduleListPage.assertModuleItemIsPublished(testTextFile.fileName) + + Log.d(STEP_TAG, "Click on the 'more menu' of the '${testTextFile.fileName}' file.") + moduleListPage.clickItemOverflow(testTextFile.fileName) + + Log.d(STEP_TAG, "Assert that by default, on the Update File Permissions Page the 'Published' radio button is checked within the 'Availability' section.") + updateFilePermissionsPage.assertFilePublished() + + Log.d(STEP_TAG, "Click on the 'Schedule availability' (aka. 'Scheduled') radio button, set some dates and click on the 'Update' button to save the changes.") + updateFilePermissionsPage.clickScheduleRadioButton() + + Log.d(PREPARATION_TAG, "Create a calendar with 3 days ago and another one with 3 days later.") + val unlockDateCalendar = getCustomDateCalendar(-3) + val lockDateCalendar = getCustomDateCalendar(3) + + Log.d(STEP_TAG, "Set the 'From' and 'Until' dates (and times) as well. These are coming from the calendars which has been previously created.") + updateFilePermissionsPage.setFromDateTime(unlockDateCalendar) + updateFilePermissionsPage.setUntilDateTime(lockDateCalendar) + + Log.d(STEP_TAG, "Click on the 'Update' button to save the changes.") + updateFilePermissionsPage.clickUpdateButton() + + Log.d(STEP_TAG, "Assert that the '${testTextFile.fileName}' file module item is scheduled.") + moduleListPage.assertModuleItemScheduled(testTextFile.fileName) + } + } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ModulesPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ModulesPage.kt index d872a7ad6d..8b1cd6c598 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ModulesPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ModulesPage.kt @@ -3,7 +3,9 @@ package com.instructure.teacher.ui.pages import androidx.annotation.StringRes import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.withChild +import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.espresso.RecyclerViewItemCountAssertion +import com.instructure.espresso.ViewAlphaAssertion import com.instructure.espresso.assertDisplayed import com.instructure.espresso.assertHasContentDescription import com.instructure.espresso.assertNotDisplayed @@ -39,8 +41,8 @@ class ModulesPage : BasePage() { } fun assertModuleNotPublished(moduleTitle: String) { - onView(withId(R.id.unpublishedIcon) + hasSibling(withId(R.id.moduleName) + withText(moduleTitle))).assertDisplayed() - onView(withId(R.id.publishedIcon) + hasSibling(withId(R.id.moduleName) + withText(moduleTitle))).assertNotDisplayed() + onView(withId(R.id.unpublishedIcon) + withParent(hasSibling(withId(R.id.moduleName) + withText(moduleTitle)))).assertDisplayed() + onView(withId(R.id.publishedIcon) + withParent(hasSibling(withId(R.id.moduleName) + withText(moduleTitle)))).assertNotDisplayed() } /** @@ -52,8 +54,8 @@ class ModulesPage : BasePage() { } fun assertModuleIsPublished(moduleTitle: String) { - onView(withId(R.id.unpublishedIcon) + hasSibling(withId(R.id.moduleName) + withText(moduleTitle))).assertNotDisplayed() - onView(withId(R.id.publishedIcon) + hasSibling(withId(R.id.moduleName) + withText(moduleTitle))).assertDisplayed() + onView(withId(R.id.unpublishedIcon) + withParent(hasSibling(withId(R.id.moduleName) + withText(moduleTitle)))).assertNotDisplayed() + onView(withId(R.id.publishedIcon) + withParent(hasSibling(withId(R.id.moduleName) + withText(moduleTitle)))).assertDisplayed() } /** @@ -112,6 +114,16 @@ class ModulesPage : BasePage() { ) } + /** + * Assert module status icon alpha value. + * + * @param moduleItemName The name of the module item. + * @param expectedAlphaValue The expected alpha (float) value. + */ + fun assertModuleStatusIconAlpha(moduleItemName: String, expectedAlphaValue: Float) { + onView(withId(R.id.moduleItemStatusIcon) + withParent(withId(R.id.publishActions) + hasSibling(withId(R.id.moduleItemTitle) + withText(moduleItemName)))).check(ViewAlphaAssertion(expectedAlphaValue)) + } + /** * Clicks on the collapse/expand icon. */ @@ -141,7 +153,7 @@ class ModulesPage : BasePage() { } fun clickItemOverflow(itemName: String) { - onView(withParent(withChild(withText(itemName))) + withId(R.id.overflow)).scrollTo().click() + onView(withParent(withChild(withText(itemName))) + withId(R.id.publishActions)).scrollTo().click() } fun assertModuleMenuItems() { @@ -166,6 +178,10 @@ class ModulesPage : BasePage() { onView(withId(com.google.android.material.R.id.snackbar_text) + withText(snackbarText)).assertDisplayed() } + fun assertSnackbarContainsText(snackbarText: String) { + onView(withId(com.google.android.material.R.id.snackbar_text) + containsTextCaseInsensitive(snackbarText)).assertDisplayed() + } + fun assertModuleItemHidden(moduleItemName: String) { onView(withAncestor(withChild(withText(moduleItemName))) + withId(R.id.moduleItemStatusIcon)).assertHasContentDescription( R.string.a11y_hidden diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ProgressPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ProgressPage.kt index 9a952b34aa..252f377e50 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ProgressPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/ProgressPage.kt @@ -33,7 +33,7 @@ class ProgressPage(private val composeTestRule: ComposeTestRule) : BasePage() { fun clickDone() { composeTestRule.waitForIdle() - composeTestRule.waitUntilExactlyOneExists(hasText("Done"), 10000) + composeTestRule.waitUntilExactlyOneExists(hasText("Done"), 20000) composeTestRule.onNodeWithText("Done").performClick() } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/UpdateFilePermissionsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/UpdateFilePermissionsPage.kt index 798d03d596..e3c9f2d060 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/UpdateFilePermissionsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/UpdateFilePermissionsPage.kt @@ -18,6 +18,13 @@ package com.instructure.teacher.ui.pages +import android.widget.DatePicker +import android.widget.NumberPicker +import android.widget.TimePicker +import androidx.databinding.adapters.TextViewBindingAdapter.setText +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.contrib.PickerActions +import androidx.test.espresso.matcher.ViewMatchers.withClassName import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertChecked import com.instructure.espresso.assertDisabled @@ -26,18 +33,27 @@ import com.instructure.espresso.assertEnabled import com.instructure.espresso.assertHasText import com.instructure.espresso.assertNotDisplayed import com.instructure.espresso.click +import com.instructure.espresso.getDateInCanvasFormat import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.onViewWithId import com.instructure.espresso.page.onViewWithText +import com.instructure.espresso.page.waitForViewWithClassName import com.instructure.espresso.page.waitForViewWithId +import com.instructure.espresso.page.withId +import com.instructure.espresso.replaceText import com.instructure.espresso.scrollTo import com.instructure.espresso.swipeUp +import com.instructure.espresso.typeText import com.instructure.teacher.R +import org.hamcrest.Matchers import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Date class UpdateFilePermissionsPage : BasePage() { - private val saveButton by OnViewWithId(R.id.updateButton) + private val updateButton by OnViewWithId(R.id.updateButton) private val publishRadioButton by OnViewWithId(R.id.publish) private val unpublishRadioButton by OnViewWithId(R.id.unpublish) private val hideRadioButton by OnViewWithId(R.id.hide) @@ -84,8 +100,8 @@ class UpdateFilePermissionsPage : BasePage() { publicRadioButton.assertChecked() } - fun clickSaveButton() { - saveButton.click() + fun clickUpdateButton() { + updateButton.click() } fun clickPublishRadioButton() { @@ -100,6 +116,10 @@ class UpdateFilePermissionsPage : BasePage() { waitForViewWithId(R.id.hide).click() } + fun clickScheduleRadioButton() { + waitForViewWithId(R.id.schedule).click() + } + fun assertScheduleLayoutDisplayed() { scheduleLayout.assertDisplayed() } @@ -144,4 +164,26 @@ class UpdateFilePermissionsPage : BasePage() { onViewWithText(R.string.edit_permissions).swipeUp() Thread.sleep(1000) } + + fun setFromDateTime(calendar: Calendar) { + availableFromDate.perform(click()) + waitForViewWithClassName(Matchers.equalTo(DatePicker::class.java.name)).perform(PickerActions.setDate(calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH))) + onViewWithId(android.R.id.button1).click() + availableFromTime.perform(click()) + onView(withClassName(Matchers.equalTo(TimePicker::class.java.name))) + .perform(PickerActions.setTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE))) + onViewWithId(android.R.id.button1).click() + } + + fun setUntilDateTime(calendar: Calendar) { + availableUntilDate.perform(click()) + waitForViewWithClassName(Matchers.equalTo(DatePicker::class.java.name)).perform(PickerActions.setDate(calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH))) + onViewWithId(android.R.id.button1).click() + availableUntilTime.perform(click()) + onView(withClassName(Matchers.equalTo(TimePicker::class.java.name))) + .perform(PickerActions.setTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE))) + onViewWithId(android.R.id.button1).click() + } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTestExtensions.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTestExtensions.kt index 37ecc53877..91337ad3f7 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTestExtensions.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherTestExtensions.kt @@ -279,7 +279,7 @@ fun TeacherTest.seedAssignmentSubmission( return SubmissionsApi.seedAssignmentSubmission(courseId, studentToken, assignmentId, commentSeeds, submissionSeeds) } -fun TeacherTest.uploadTextFile(courseId: Long, assignmentId: Long, token: String, fileUploadType: FileUploadType): AttachmentApiModel { +fun TeacherTest.uploadTextFile(courseId: Long, assignmentId: Long? = null, token: String, fileUploadType: FileUploadType): AttachmentApiModel { // Create the file val file = File( diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt index 0e550b3576..d801f2b45e 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt @@ -57,6 +57,7 @@ import com.instructure.loginapi.login.dialog.MasqueradingDialog import com.instructure.loginapi.login.tasks.LogoutTask import com.instructure.pandautils.activities.BasePresenterActivity import com.instructure.pandautils.binding.viewBinding +import com.instructure.pandautils.dialogs.EditCourseNicknameDialog import com.instructure.pandautils.dialogs.RatingDialog import com.instructure.pandautils.features.help.HelpDialogFragment import com.instructure.pandautils.features.inbox.list.InboxFragment @@ -73,7 +74,6 @@ import com.instructure.teacher.R import com.instructure.teacher.databinding.ActivityInitBinding import com.instructure.teacher.databinding.NavigationDrawerBinding import com.instructure.teacher.dialog.ColorPickerDialog -import com.instructure.teacher.dialog.EditCourseNicknameDialog import com.instructure.teacher.events.CourseUpdatedEvent import com.instructure.teacher.events.ToDoListUpdatedEvent import com.instructure.teacher.factory.InitActivityPresenterFactory diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/SpeedGraderActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/SpeedGraderActivity.kt index e637051f83..1a2dae9674 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/SpeedGraderActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/SpeedGraderActivity.kt @@ -57,6 +57,9 @@ import com.instructure.teacher.adapters.SubmissionContentAdapter import com.instructure.teacher.databinding.ActivitySpeedgraderBinding import com.instructure.teacher.events.AssignmentGradedEvent import com.instructure.teacher.factory.SpeedGraderPresenterFactory +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionRepository +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter import com.instructure.teacher.features.speedgrader.commentlibrary.CommentLibraryAction import com.instructure.teacher.features.speedgrader.commentlibrary.CommentLibraryFragment import com.instructure.teacher.features.speedgrader.commentlibrary.CommentLibraryViewModel @@ -75,12 +78,16 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.util.* +import javax.inject.Inject @PageView("courses/{courseId}/gradebook/speed_grader?assignment_id={assignmentId}") @ScreenView(SCREEN_VIEW_SPEED_GRADER) @AndroidEntryPoint class SpeedGraderActivity : BasePresenterActivity(), SpeedGraderView { + @Inject + lateinit var repository: AssignmentSubmissionRepository + private val binding by viewBinding(ActivitySpeedgraderBinding::inflate) /* These should be passed to the presenter factory and should not be directly referenced otherwise */ @@ -89,9 +96,15 @@ class SpeedGraderActivity : BasePresenterActivity by lazy { intent.extras!!.getParcelableArrayList(Const.SUBMISSION) ?: arrayListOf() } private val discussionTopicHeader: DiscussionTopicHeader? by lazy { intent.extras!!.getParcelable(Const.DISCUSSION_HEADER) } private val anonymousGrading: Boolean? by lazy { intent.extras?.getBoolean(Const.ANONYMOUS_GRADING) } + private val filter: SubmissionListFilter by lazy { + intent.extras!!.getSerializable( + FILTER + ) as? SubmissionListFilter + ?: SubmissionListFilter.ALL + } + private val filterValue: Double by lazy { intent.extras!!.getDouble(FILTER_VALUE) } private val initialSelection: Int by lazy { intent.extras!!.getInt(Const.SELECTED_ITEM, 0) } private var currentSelection = 0 @@ -124,9 +137,11 @@ class SpeedGraderActivity : BasePresenterActivity, selectedIdx: Int, anonymousGrading: Boolean? = null): Bundle { return Bundle().apply { putLong(Const.COURSE_ID, courseId) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/adapters/AssignmentAdapter.kt b/apps/teacher/src/main/java/com/instructure/teacher/adapters/AssignmentAdapter.kt index 0d494256f9..173f9534f2 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/adapters/AssignmentAdapter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/adapters/AssignmentAdapter.kt @@ -29,7 +29,7 @@ import com.instructure.teacher.databinding.AdapterAssignmentBinding import com.instructure.teacher.databinding.AdapterAssignmentGroupHeaderBinding import com.instructure.teacher.holders.AssignmentGroupHeaderViewHolder import com.instructure.teacher.holders.AssignmentViewHolder -import com.instructure.teacher.presenters.AssignmentListPresenter +import com.instructure.teacher.features.assignment.list.AssignmentListPresenter import com.instructure.teacher.viewinterface.AssignmentListView import instructure.androidblueprint.SyncExpandableRecyclerAdapter diff --git a/apps/teacher/src/main/java/com/instructure/teacher/adapters/GradeableStudentSubmissionAdapter.kt b/apps/teacher/src/main/java/com/instructure/teacher/adapters/GradeableStudentSubmissionAdapter.kt index cfc13e4e9c..4b769b4225 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/adapters/GradeableStudentSubmissionAdapter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/adapters/GradeableStudentSubmissionAdapter.kt @@ -24,7 +24,7 @@ import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.GradeableStudentSubmission import com.instructure.teacher.databinding.AdapterGradeableStudentSubmissionBinding import com.instructure.teacher.holders.GradeableStudentSubmissionViewHolder -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter import com.instructure.teacher.viewinterface.AssignmentSubmissionListView import instructure.androidblueprint.SyncRecyclerAdapter diff --git a/apps/teacher/src/main/java/com/instructure/teacher/dialog/EditCourseNicknameDialog.kt b/apps/teacher/src/main/java/com/instructure/teacher/dialog/EditCourseNicknameDialog.kt deleted file mode 100644 index 0c865c89dd..0000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/dialog/EditCourseNicknameDialog.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.instructure.teacher.dialog - -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -import android.app.Dialog -import android.os.Bundle -import android.view.View -import android.view.WindowManager -import android.view.inputmethod.EditorInfo -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatDialog -import androidx.appcompat.app.AppCompatDialogFragment -import androidx.appcompat.widget.AppCompatEditText -import androidx.fragment.app.FragmentManager -import com.instructure.canvasapi2.models.Course -import com.instructure.pandautils.analytics.SCREEN_VIEW_EDIT_COURSE_NICKNAME -import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.utils.* -import com.instructure.teacher.R -import java.util.Locale -import kotlin.properties.Delegates - -@ScreenView(SCREEN_VIEW_EDIT_COURSE_NICKNAME) -class EditCourseNicknameDialog : AppCompatDialogFragment() { - - private var mEditNicknameCallback: (String) -> Unit by Delegates.notNull() - - init { - retainInstance = true - } - - companion object { - fun getInstance(manager: FragmentManager, course: Course, callback: (String) -> Unit) : EditCourseNicknameDialog { - manager.dismissExisting() - val dialog = EditCourseNicknameDialog() - val args = Bundle() - args.putParcelable(Const.COURSE, course) - dialog.arguments = args - dialog.mEditNicknameCallback = callback - return dialog - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val course : Course = nonNullArgs.get(Const.COURSE) as Course - val view = View.inflate(requireActivity(), R.layout.dialog_course_nickname, null) - val editCourseNicknameEditText = view.findViewById(R.id.newCourseNickname) - editCourseNicknameEditText.setText(course.name) - ViewStyler.themeEditText(requireContext(), editCourseNicknameEditText, ThemePrefs.brandColor) - editCourseNicknameEditText.inputType = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS - editCourseNicknameEditText.selectAll() - - val nameDialog = AlertDialog.Builder(requireActivity()) - .setCancelable(true) - .setTitle(getString(R.string.edit_course_nickname)) - .setView(view) - .setPositiveButton(getString(android.R.string.ok).uppercase(Locale.getDefault())) { _, _ -> - mEditNicknameCallback(editCourseNicknameEditText.text.toString()) - } - .setNegativeButton(getString(android.R.string.cancel).uppercase(Locale.getDefault()), null) - .create() - nameDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) - - nameDialog.setOnShowListener { - nameDialog.getButton(AppCompatDialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.textButtonColor) - nameDialog.getButton(AppCompatDialog.BUTTON_NEGATIVE).setTextColor(ThemePrefs.textButtonColor) - } - return nameDialog - } - - override fun onDestroyView() { - // Fix for rotation bug - dialog?.let { if (retainInstance) it.setDismissMessage(null) } - super.onDestroyView() - } -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentDetailPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentDetailPresenterFactory.kt index 41e385eadf..704b4e6a97 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentDetailPresenterFactory.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentDetailPresenterFactory.kt @@ -17,7 +17,7 @@ package com.instructure.teacher.factory import com.instructure.canvasapi2.models.Assignment -import com.instructure.teacher.presenters.AssignmentDetailsPresenter +import com.instructure.teacher.features.assignment.details.AssignmentDetailsPresenter import com.instructure.teacher.viewinterface.AssignmentDetailsView import instructure.androidblueprint.PresenterFactory diff --git a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentListPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentListPresenterFactory.kt index 3e14ccb521..314917161c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentListPresenterFactory.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentListPresenterFactory.kt @@ -17,7 +17,7 @@ package com.instructure.teacher.factory import com.instructure.canvasapi2.models.CanvasContext -import com.instructure.teacher.presenters.AssignmentListPresenter +import com.instructure.teacher.features.assignment.list.AssignmentListPresenter import com.instructure.teacher.viewinterface.AssignmentListView import instructure.androidblueprint.PresenterFactory diff --git a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentSubmissionListPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentSubmissionListPresenterFactory.kt index cd10d97f5e..3f700b71bf 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentSubmissionListPresenterFactory.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/factory/AssignmentSubmissionListPresenterFactory.kt @@ -17,10 +17,17 @@ package com.instructure.teacher.factory import com.instructure.canvasapi2.models.Assignment -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionRepository +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter import com.instructure.teacher.viewinterface.AssignmentSubmissionListView import instructure.androidblueprint.PresenterFactory -class AssignmentSubmissionListPresenterFactory(private var mAssignment: Assignment, private var mFilter: AssignmentSubmissionListPresenter.SubmissionListFilter) : PresenterFactory { - override fun create(): AssignmentSubmissionListPresenter = AssignmentSubmissionListPresenter(mAssignment, mFilter) +class AssignmentSubmissionListPresenterFactory( + private var assignment: Assignment, + private var filter: SubmissionListFilter, + private val assignmentSubmissionRepository: AssignmentSubmissionRepository +) : PresenterFactory { + override fun create(): AssignmentSubmissionListPresenter = + AssignmentSubmissionListPresenter(assignment, filter, assignmentSubmissionRepository) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/factory/SpeedGraderPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/factory/SpeedGraderPresenterFactory.kt index b4405bb7fa..258bb126d5 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/factory/SpeedGraderPresenterFactory.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/factory/SpeedGraderPresenterFactory.kt @@ -17,18 +17,20 @@ package com.instructure.teacher.factory import com.instructure.canvasapi2.models.DiscussionTopicHeader -import com.instructure.canvasapi2.models.GradeableStudentSubmission +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionRepository +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter import com.instructure.teacher.presenters.SpeedGraderPresenter import com.instructure.teacher.viewinterface.SpeedGraderView import instructure.androidblueprint.PresenterFactory -import java.util.* class SpeedGraderPresenterFactory( private val courseId: Long, private val assignmentId: Long, - private val submissions: ArrayList, private val submissionId: Long, // Id used when we are coming from a push notification - private val discussionEntries: DiscussionTopicHeader? + private val discussionEntries: DiscussionTopicHeader?, + private val repository: AssignmentSubmissionRepository, + private val filter: SubmissionListFilter, + private val filterValue: Double ) : PresenterFactory { - override fun create() = SpeedGraderPresenter(courseId, assignmentId, submissions, submissionId, discussionEntries) + override fun create() = SpeedGraderPresenter(courseId, assignmentId, submissionId, discussionEntries, repository, filter, filterValue) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsFragment.kt similarity index 96% rename from apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentDetailsFragment.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsFragment.kt index 3960dd018a..d712f63cea 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsFragment.kt @@ -13,7 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.instructure.teacher.fragments +package com.instructure.teacher.features.assignment.details import android.os.Bundle import android.view.LayoutInflater @@ -62,8 +62,12 @@ import com.instructure.teacher.events.AssignmentGradedEvent import com.instructure.teacher.events.AssignmentUpdatedEvent import com.instructure.teacher.events.post import com.instructure.teacher.factory.AssignmentDetailPresenterFactory -import com.instructure.teacher.presenters.AssignmentDetailsPresenter -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter +import com.instructure.teacher.fragments.DueDatesFragment +import com.instructure.teacher.fragments.EditAssignmentDetailsFragment +import com.instructure.teacher.fragments.LtiLaunchFragment import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.getColorCompat import com.instructure.teacher.utils.setupBackButtonWithExpandCollapseAndBack @@ -390,17 +394,17 @@ class AssignmentDetailsFragment : BasePresenterFragment< } submissionsLayout.setOnClickListener { - navigateToSubmissions(course, assignment, AssignmentSubmissionListPresenter.SubmissionListFilter.ALL) + navigateToSubmissions(course, assignment, SubmissionListFilter.ALL) } donutGroup.viewAllSubmissions.onClick { submissionsLayout.performClick() } // Separate click listener for a11y donutGroup.gradedWrapper.setOnClickListener { - navigateToSubmissions(course, assignment, AssignmentSubmissionListPresenter.SubmissionListFilter.GRADED) + navigateToSubmissions(course, assignment, SubmissionListFilter.GRADED) } donutGroup.ungradedWrapper.setOnClickListener { - navigateToSubmissions(course, assignment, AssignmentSubmissionListPresenter.SubmissionListFilter.NOT_GRADED) + navigateToSubmissions(course, assignment, SubmissionListFilter.NOT_GRADED) } donutGroup.notSubmittedWrapper.setOnClickListener { - navigateToSubmissions(course, assignment, AssignmentSubmissionListPresenter.SubmissionListFilter.MISSING) + navigateToSubmissions(course, assignment, SubmissionListFilter.MISSING) } noDescriptionTextView.setOnClickListener { openEditPage(assignment) } @@ -425,7 +429,7 @@ class AssignmentDetailsFragment : BasePresenterFragment< } } - private fun navigateToSubmissions(course: Course, assignment: Assignment, filter: AssignmentSubmissionListPresenter.SubmissionListFilter) { + private fun navigateToSubmissions(course: Course, assignment: Assignment, filter: SubmissionListFilter) { val args = AssignmentSubmissionListFragment.makeBundle(assignment, filter) RouteMatcher.route(requireActivity(), Route(null, AssignmentSubmissionListFragment::class.java, course, args)) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentDetailsPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsPresenter.kt similarity index 98% rename from apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentDetailsPresenter.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsPresenter.kt index 2d06d7e18e..19529ba06b 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentDetailsPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/details/AssignmentDetailsPresenter.kt @@ -13,7 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.instructure.teacher.presenters +package com.instructure.teacher.features.assignment.details import com.instructure.canvasapi2.managers.AssignmentManager import com.instructure.canvasapi2.managers.SubmissionManager diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListFragment.kt similarity index 97% rename from apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentListFragment.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListFragment.kt index bf438e8fd3..aeef3a42dc 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListFragment.kt @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -package com.instructure.teacher.fragments +package com.instructure.teacher.features.assignment.list import android.os.Bundle import android.view.Gravity @@ -40,7 +40,9 @@ import com.instructure.teacher.adapters.AssignmentAdapter import com.instructure.teacher.databinding.FragmentAssignmentListBinding import com.instructure.teacher.events.AssignmentUpdatedEvent import com.instructure.teacher.factory.AssignmentListPresenterFactory -import com.instructure.teacher.presenters.AssignmentListPresenter +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment +import com.instructure.teacher.fragments.QuizDetailsFragment import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.RecyclerViewUtils import com.instructure.teacher.utils.setHeaderVisibilityListener diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentListPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListPresenter.kt similarity index 99% rename from apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentListPresenter.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListPresenter.kt index ab80e5851d..ae5c7adb17 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentListPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/AssignmentListPresenter.kt @@ -15,7 +15,7 @@ * */ -package com.instructure.teacher.presenters +package com.instructure.teacher.features.assignment.list import com.instructure.canvasapi2.StatusCallback import com.instructure.canvasapi2.managers.AssignmentManager diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentSubmissionListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListFragment.kt similarity index 79% rename from apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentSubmissionListFragment.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListFragment.kt index c6e78b5daa..675c01d47c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/AssignmentSubmissionListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListFragment.kt @@ -14,7 +14,7 @@ * along with this program. If not, see . * */ -package com.instructure.teacher.fragments +package com.instructure.teacher.features.assignment.submission import android.os.Bundle import android.view.MenuItem @@ -31,7 +31,15 @@ import com.instructure.pandautils.analytics.SCREEN_VIEW_ASSIGNMENT_SUBMISSION_LI import com.instructure.pandautils.analytics.ScreenView import com.instructure.pandautils.binding.viewBinding import com.instructure.pandautils.fragments.BaseSyncFragment -import com.instructure.pandautils.utils.* +import com.instructure.pandautils.utils.ParcelableArg +import com.instructure.pandautils.utils.SerializableArg +import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.ViewStyler +import com.instructure.pandautils.utils.backgroundColor +import com.instructure.pandautils.utils.isTablet +import com.instructure.pandautils.utils.setGone +import com.instructure.pandautils.utils.setVisible +import com.instructure.pandautils.utils.withArgs import com.instructure.teacher.R import com.instructure.teacher.activities.SpeedGraderActivity import com.instructure.teacher.adapters.GradeableStudentSubmissionAdapter @@ -44,18 +52,24 @@ import com.instructure.teacher.events.SubmissionCommentsUpdated import com.instructure.teacher.events.SubmissionFilterChangedEvent import com.instructure.teacher.factory.AssignmentSubmissionListPresenterFactory import com.instructure.teacher.features.postpolicies.ui.PostPolicyFragment +import com.instructure.teacher.fragments.AddMessageFragment import com.instructure.teacher.holders.GradeableStudentSubmissionViewHolder -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter.SubmissionListFilter import com.instructure.teacher.router.RouteMatcher -import com.instructure.teacher.utils.* +import com.instructure.teacher.utils.RecyclerViewUtils +import com.instructure.teacher.utils.setHeaderVisibilityListener +import com.instructure.teacher.utils.setupBackButtonAsBackPressedOnly +import com.instructure.teacher.utils.setupMenu +import com.instructure.teacher.utils.withRequireNetwork import com.instructure.teacher.view.QuizSubmissionGradedEvent import com.instructure.teacher.viewinterface.AssignmentSubmissionListView +import dagger.hilt.android.AndroidEntryPoint import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import javax.inject.Inject @ScreenView(SCREEN_VIEW_ASSIGNMENT_SUBMISSION_LIST) +@AndroidEntryPoint class AssignmentSubmissionListFragment : BaseSyncFragment< GradeableStudentSubmission, AssignmentSubmissionListPresenter, @@ -63,17 +77,20 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< GradeableStudentSubmissionViewHolder, GradeableStudentSubmissionAdapter>(), AssignmentSubmissionListView { + @Inject + lateinit var assignmentSubmissionRepository: AssignmentSubmissionRepository + private val binding by viewBinding(FragmentAssignmentSubmissionListBinding::bind) - private var mAssignment: Assignment by ParcelableArg(Assignment(), ASSIGNMENT) - private var mCourse: Course by ParcelableArg(Course()) + private var assignment: Assignment by ParcelableArg(Assignment(), ASSIGNMENT) + private var course: Course by ParcelableArg(Course()) private lateinit var mRecyclerView: RecyclerView - private var mFilter by SerializableArg(SubmissionListFilter.ALL, FILTER_TYPE) - private var mCanvasContextsSelected = ArrayList() + private var filter by SerializableArg(SubmissionListFilter.ALL, FILTER_TYPE) + private var canvasContextsSelected = ArrayList() - private var mNeedToForceNetwork = false + private var needToForceNetwork = false - private val mSubmissionFilters: Map by lazy { + private val submissionFilters: Map by lazy { sortedMapOf( Pair(SubmissionListFilter.ALL.ordinal, getString(R.string.all_submissions)), Pair(SubmissionListFilter.LATE.ordinal, getString(R.string.submitted_late)), @@ -87,7 +104,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< override fun layoutResId(): Int = R.layout.fragment_assignment_submission_list override val recyclerView: RecyclerView get() = binding.submissionsRecyclerView - override fun getPresenterFactory() = AssignmentSubmissionListPresenterFactory(mAssignment, mFilter) + override fun getPresenterFactory() = AssignmentSubmissionListPresenterFactory(assignment, filter, assignmentSubmissionRepository) override fun onCreateView(view: View) = Unit override fun onPresenterPrepared(presenter: AssignmentSubmissionListPresenter) = with(binding) { mRecyclerView = RecyclerViewUtils.buildRecyclerView(rootView, requireContext(), adapter, presenter, R.id.swipeRefreshLayout, @@ -111,8 +128,8 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< mRecyclerView.adapter = adapter } - presenter.refresh(mNeedToForceNetwork) - mNeedToForceNetwork = false + presenter.refresh(needToForceNetwork) + needToForceNetwork = false updateFilterTitle() binding.clearFilterTextView.setTextColor(ThemePrefs.textButtonColor) @@ -129,11 +146,18 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< } override fun createAdapter(): GradeableStudentSubmissionAdapter { - return GradeableStudentSubmissionAdapter(mAssignment, mCourse.id, requireContext(), presenter) { gradeableStudentSubmission -> + return GradeableStudentSubmissionAdapter(assignment, course.id, requireContext(), presenter) { gradeableStudentSubmission -> withRequireNetwork { val filteredSubmissions = (0 until presenter.data.size()).map { presenter.data[it] } val selectedIdx = filteredSubmissions.indexOf(gradeableStudentSubmission) - val bundle = SpeedGraderActivity.makeBundle(mCourse.id, mAssignment.id, filteredSubmissions, selectedIdx, mAssignment.anonymousGrading) + val bundle = SpeedGraderActivity.makeBundle( + course.id, + assignment.id, + selectedIdx, + assignment.anonymousGrading, + presenter.getFilter(), + presenter.getFilterPoints() + ) RouteMatcher.route(requireActivity(), Route(bundle, RouteContext.SPEED_GRADER)) } } @@ -151,7 +175,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< swipeRefreshLayout.isRefreshing = false // Theme the toolbar again since visibilities may have changed - ViewStyler.themeToolbarColored(requireActivity(), assignmentSubmissionListToolbar, mCourse.backgroundColor, requireContext().getColor(R.color.white)) + ViewStyler.themeToolbarColored(requireActivity(), assignmentSubmissionListToolbar, course.backgroundColor, requireContext().getColor(R.color.white)) updateStatuses() // Muted is now also set by not being in the new gradebook } @@ -170,13 +194,13 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< assignmentSubmissionListToolbar.setupBackButtonAsBackPressedOnly(this@AssignmentSubmissionListFragment) if(isTablet) { - assignmentSubmissionListToolbar.title = mAssignment.name + assignmentSubmissionListToolbar.title = assignment.name } else { assignmentSubmissionListToolbar.setNavigationIcon(R.drawable.ic_back_arrow) assignmentSubmissionListToolbar.title = getString(R.string.submissions) - assignmentSubmissionListToolbar.subtitle = mCourse.name + assignmentSubmissionListToolbar.subtitle = course.name } - ViewStyler.themeToolbarColored(requireActivity(), assignmentSubmissionListToolbar, mCourse.backgroundColor, requireContext().getColor(R.color.white)) + ViewStyler.themeToolbarColored(requireActivity(), assignmentSubmissionListToolbar, course.backgroundColor, requireContext().getColor(R.color.white)) ViewStyler.themeFAB(addMessage) } @@ -189,7 +213,12 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< } addMessage.setOnClickListener { - val args = AddMessageFragment.createBundle(presenter.getRecipients(), filterTitle.text.toString() + " " + getString(R.string.on) + " " + mAssignment.name, mCourse.contextId, false) + val args = AddMessageFragment.createBundle( + presenter.getRecipients(), + filterTitle.text.toString() + " " + getString(R.string.on) + " " + assignment.name, + course.contextId, + false + ) RouteMatcher.route(requireActivity(), Route(AddMessageFragment::class.java, null, args)) } } @@ -205,7 +234,9 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< when (presenter.getFilter()) { SubmissionListFilter.ALL -> { filterTitle.setText(R.string.all_submissions) - clearFilterTextView.setGone() + if (presenter.getSectionFilterText().isEmpty()) { + clearFilterTextView.setGone() + } } SubmissionListFilter.LATE -> filterTitle.setText(R.string.submitted_late) SubmissionListFilter.MISSING -> filterTitle.setText(R.string.havent_submitted_yet) @@ -230,15 +261,12 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< private fun setFilter(filterIndex: Int = -1, canvasContexts: ArrayList? = null) = with(binding) { canvasContexts?.let { - mCanvasContextsSelected = ArrayList() - mCanvasContextsSelected.addAll(canvasContexts) + canvasContextsSelected = ArrayList() + canvasContextsSelected.addAll(canvasContexts) presenter.setSections(canvasContexts) updateFilterTitle() - - filterTitle.text = filterTitle.text.toString().plus(presenter.getSectionFilterText()) - clearFilterTextView.setVisible() return } @@ -260,13 +288,13 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< updateFilterTitle() } SubmissionListFilter.BELOW_VALUE.ordinal -> { - FilterSubmissionByPointsDialog.getInstance(requireFragmentManager(), getString(R.string.scored_less_than), mAssignment.pointsPossible) { points -> + FilterSubmissionByPointsDialog.getInstance(requireFragmentManager(), getString(R.string.scored_less_than), assignment.pointsPossible) { points -> presenter.setFilter(SubmissionListFilter.BELOW_VALUE, points) updateFilterTitle() }.show(requireActivity().supportFragmentManager, FilterSubmissionByPointsDialog::class.java.simpleName) } SubmissionListFilter.ABOVE_VALUE.ordinal -> { - FilterSubmissionByPointsDialog.getInstance(requireFragmentManager(), getString(R.string.scored_more_than), mAssignment.pointsPossible) { points -> + FilterSubmissionByPointsDialog.getInstance(requireFragmentManager(), getString(R.string.scored_more_than), assignment.pointsPossible) { points -> presenter.setFilter(SubmissionListFilter.ABOVE_VALUE, points) updateFilterTitle() }.show(requireActivity().supportFragmentManager, FilterSubmissionByPointsDialog::class.java.simpleName) @@ -277,7 +305,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< val menuItemCallback: (MenuItem) -> Unit = { item -> when (item.itemId) { R.id.filterSubmissions -> { - val (keys, values) = mSubmissionFilters.toList().unzip() + val (keys, values) = submissionFilters.toList().unzip() val dialog = RadioButtonDialog.getInstance(requireActivity().supportFragmentManager, getString(R.string.filter_submissions), values as ArrayList, keys.indexOf(presenter.getFilter().ordinal)) { idx -> EventBus.getDefault().post(SubmissionFilterChangedEvent(keys[idx])) } @@ -286,18 +314,18 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< } R.id.filterBySection -> { //let the user select the course/group they want to see - PeopleListFilterDialog.getInstance(requireActivity().supportFragmentManager, presenter.getSectionListIds(), mCourse, false) { canvasContexts -> + PeopleListFilterDialog.getInstance(requireActivity().supportFragmentManager, presenter.getSectionListIds(), course, false) { canvasContexts -> EventBus.getDefault().post(SubmissionFilterChangedEvent(canvasContext = canvasContexts)) }.show(requireActivity().supportFragmentManager, PeopleListFilterDialog::class.java.simpleName) } R.id.menuPostPolicies -> { - RouteMatcher.route(requireActivity(), PostPolicyFragment.makeRoute(mCourse, mAssignment)) + RouteMatcher.route(requireActivity(), PostPolicyFragment.makeRoute(course, assignment)) } } } private fun updateStatuses() { - if (presenter.mAssignment.anonymousGrading) + if (presenter.assignment.anonymousGrading) binding.anonGradingStatusView.setVisible().text = getString(R.string.anonymousGradingLabel) } @@ -306,7 +334,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< fun onAssignmentGraded(event: AssignmentGradedEvent) { event.once(javaClass.simpleName) { //force network call on resume - if(presenter.mAssignment.id == it) mNeedToForceNetwork = true + if(presenter.assignment.id == it) needToForceNetwork = true } } @@ -315,7 +343,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< fun onQuizGraded(event: QuizSubmissionGradedEvent) { event.once(javaClass.simpleName) { // Force network call on resume - if (presenter.mAssignment.id == it.assignmentId) mNeedToForceNetwork = true + if (presenter.assignment.id == it.assignmentId) needToForceNetwork = true } } @@ -323,7 +351,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onSubmissionCommentUpdated(event: SubmissionCommentsUpdated) { event.once(AssignmentSubmissionListFragment::class.java.simpleName) { - mNeedToForceNetwork = true + needToForceNetwork = true } } @@ -338,7 +366,7 @@ class AssignmentSubmissionListFragment : BaseSyncFragment< @JvmStatic val FILTER_TYPE = "filter_type" fun newInstance(course: Course, args: Bundle) = AssignmentSubmissionListFragment().withArgs(args).apply { - mCourse = course + this.course = course } fun makeBundle(assignment: Assignment): Bundle { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListModule.kt new file mode 100644 index 0000000000..2fa5886f72 --- /dev/null +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListModule.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 - 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.assignment.submission + +import com.instructure.canvasapi2.apis.AssignmentAPI +import com.instructure.canvasapi2.apis.CourseAPI +import com.instructure.canvasapi2.apis.EnrollmentAPI +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(ActivityComponent::class) +class AssignmentSubmissionListModule { + + @Provides + fun provideAssignmentSubmissionListRepository( + assignmentApi: AssignmentAPI.AssignmentInterface, + enrollmentApi: EnrollmentAPI.EnrollmentInterface, + courseApi: CourseAPI.CoursesInterface + ): AssignmentSubmissionRepository { + return AssignmentSubmissionRepository(assignmentApi, enrollmentApi, courseApi) + } +} \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentSubmissionListPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListPresenter.kt similarity index 61% rename from apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentSubmissionListPresenter.kt rename to apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListPresenter.kt index 3669714b44..02c7eb8879 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/AssignmentSubmissionListPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionListPresenter.kt @@ -14,43 +14,41 @@ * along with this program. If not, see . * */ -package com.instructure.teacher.presenters - -import com.instructure.canvasapi2.managers.AssignmentManager -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.managers.EnrollmentManager -import com.instructure.canvasapi2.models.* +package com.instructure.teacher.features.assignment.submission + +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.canvasapi2.models.GradeableStudentSubmission +import com.instructure.canvasapi2.models.Group +import com.instructure.canvasapi2.models.GroupAssignee +import com.instructure.canvasapi2.models.Recipient +import com.instructure.canvasapi2.models.StudentAssignee +import com.instructure.canvasapi2.models.Submission +import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.intersectBy -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.awaitApis import com.instructure.canvasapi2.utils.weave.weave import com.instructure.pandautils.utils.AssignmentUtils2 import com.instructure.teacher.utils.getState import com.instructure.teacher.viewinterface.AssignmentSubmissionListView import instructure.androidblueprint.SyncPresenter import kotlinx.coroutines.Job -import java.util.* - -class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var mFilter: SubmissionListFilter) : SyncPresenter(GradeableStudentSubmission::class.java) { - - enum class SubmissionListFilter { - ALL, - LATE, - MISSING, - NOT_GRADED, - GRADED, - BELOW_VALUE, - ABOVE_VALUE - } +import java.util.Locale +import java.util.Random + +class AssignmentSubmissionListPresenter( + val assignment: Assignment, + private var filter: SubmissionListFilter, + private val assignmentSubmissionRepository: AssignmentSubmissionRepository +) : SyncPresenter(GradeableStudentSubmission::class.java) { private var apiCalls: Job? = null - private var mUnfilteredSubmissions: List = emptyList() - private var mFilteredSubmissions: List = emptyList() + private var unfilteredSubmissions: List = emptyList() + private var filteredSubmissions: List = emptyList() - private var mFilterValue: Double = 0.0 + private var filterValue: Double = 0.0 - private var mSectionsSelected = ArrayList() + private var sectionsSelected = ArrayList() @Suppress("EXPERIMENTAL_FEATURE_WARNING") override fun loadData(forceNetwork: Boolean) { @@ -58,7 +56,7 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var if (apiCalls?.isActive == true) return // Use existing data if we already have it. Unfiltered submissions should be cleared on refresh - if (!forceNetwork && mUnfilteredSubmissions.isNotEmpty()) { + if (!forceNetwork && unfilteredSubmissions.isNotEmpty()) { setFilteredData() return } @@ -67,44 +65,21 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var apiCalls = weave { try { viewCallback?.onRefreshStarted() - val (gradeableStudents, enrollments, submissions) = awaitApis, List, List>( - { AssignmentManager.getAllGradeableStudentsForAssignment(mAssignment.courseId, mAssignment.id, forceNetwork, it) }, - { EnrollmentManager.getAllEnrollmentsForCourse(mAssignment.courseId, null, forceNetwork, it) }, - { AssignmentManager.getAllSubmissionsForAssignment(mAssignment.courseId, mAssignment.id, forceNetwork, it) } - ) - - val enrollmentMap = enrollments.associateBy { it.user?.id } - val students = gradeableStudents.distinctBy { it.id }.map { - // Students need the enrollment info - var user = enrollmentMap[it.id]?.user - // Users can be enrolled in multiple sections, so we need to get all of them - user = user?.copy( - enrollments = user.enrollments + enrollments.filter { enrollment -> enrollment.userId == user?.id }, - isFakeStudent = it.isFakeStudent + unfilteredSubmissions = + assignmentSubmissionRepository.getGradeableStudentSubmissions( + assignment, + assignment.courseId, + forceNetwork ) - // Need to null out the user object to prevent infinite parcels - user?.enrollments?.forEach { it.user = null } - user - }.filterNotNull() - mUnfilteredSubmissions = if (mAssignment.groupCategoryId > 0 && !mAssignment.isGradeGroupsIndividually) { - val groups = awaitApi> { CourseManager.getGroupsForCourse(mAssignment.courseId, it, false) } - .filter { it.groupCategoryId == mAssignment.groupCategoryId } - makeGroupSubmissions(students, groups, submissions) - } else { - val submissionMap = submissions.associateBy { it.userId } - students.map { - GradeableStudentSubmission(StudentAssignee(it), submissionMap[it.id]) - } - } - setFilteredData() } catch (ignore: Throwable) { + ignore.printStackTrace() } } } fun setSections(sections: ArrayList) { - mSectionsSelected = sections + sectionsSelected = sections setFilteredData() } @@ -117,22 +92,22 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var */ fun getSectionListIds(): ArrayList { val contextIds = ArrayList() - mSectionsSelected.forEach { + sectionsSelected.forEach { contextIds.add(it.id) } return contextIds } fun getSectionFilterText() : String { - when (mSectionsSelected.isEmpty()) { + when (sectionsSelected.isEmpty()) { true -> return "" false -> { // get the title based on Section selected val title = StringBuilder() title.append(", ") - mSectionsSelected.forEachIndexed { index, canvasContext -> + sectionsSelected.forEachIndexed { index, canvasContext -> title.append(canvasContext.name) - if ((index + 1) < mSectionsSelected.size) { + if ((index + 1) < sectionsSelected.size) { title.append(", ") } } @@ -142,18 +117,18 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var } fun clearFilterList() { - mSectionsSelected.clear() + sectionsSelected.clear() } private fun setFilteredData() { - mFilteredSubmissions = mUnfilteredSubmissions.filter { - when (mFilter) { + filteredSubmissions = unfilteredSubmissions.filter { + when (filter) { SubmissionListFilter.ALL -> true - SubmissionListFilter.LATE -> it.submission?.let { mAssignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE) } ?: false - SubmissionListFilter.NOT_GRADED -> it.submission?.let { mAssignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED, AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE) || !it.isGradeMatchesCurrentSubmission } ?: false - SubmissionListFilter.GRADED -> it.submission?.let { mAssignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_GRADED, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_MISSING) && it.isGradeMatchesCurrentSubmission} ?: false - SubmissionListFilter.ABOVE_VALUE -> it.submission?.let { it.isGraded && it.score >= mFilterValue } ?: false - SubmissionListFilter.BELOW_VALUE -> it.submission?.let { it.isGraded && it.score < mFilterValue } ?: false + SubmissionListFilter.LATE -> it.submission?.let { assignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE) } ?: false + SubmissionListFilter.NOT_GRADED -> it.submission?.let { assignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED, AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE) || !it.isGradeMatchesCurrentSubmission } ?: false + SubmissionListFilter.GRADED -> it.submission?.let { assignment.getState(it, true) in listOf(AssignmentUtils2.ASSIGNMENT_STATE_GRADED, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_MISSING) && it.isGradeMatchesCurrentSubmission} ?: false + SubmissionListFilter.ABOVE_VALUE -> it.submission?.let { it.isGraded && it.score >= filterValue } ?: false + SubmissionListFilter.BELOW_VALUE -> it.submission?.let { it.isGraded && it.score < filterValue } ?: false // Filtering by ASSIGNMENT_STATE_MISSING here doesn't work because it assumes that the due date has already passed, which isn't necessarily the case when the teacher wants to see // which students haven't submitted yet SubmissionListFilter.MISSING -> it.submission?.workflowState == "unsubmitted" || it.submission == null @@ -161,17 +136,17 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var } // Shuffle if grading anonymously - if (mAssignment.anonymousGrading) mFilteredSubmissions = mFilteredSubmissions.shuffled(Random(1234)) + if (assignment.anonymousGrading) filteredSubmissions = filteredSubmissions.shuffled(Random(1234)) data.clear() // Filter by section if there is a section filter set - if (mSectionsSelected.isNotEmpty()) { + if (sectionsSelected.isNotEmpty()) { // get list of ids - val sectionIds = mSectionsSelected.map { it.id } + val sectionIds = sectionsSelected.map { it.id } - mFilteredSubmissions.forEach { submission -> + filteredSubmissions.forEach { submission -> sectionIds.forEach { section -> if (submission.assignee is StudentAssignee) { (submission.assignee as StudentAssignee).student.enrollments.forEach { @@ -184,7 +159,7 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var } } else { // No section filter, add all the submission filtered users - data.addOrUpdate(mFilteredSubmissions) + data.addOrUpdate(filteredSubmissions) } viewCallback?.onRefreshFinished() @@ -199,7 +174,7 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var override fun refresh(forceNetwork: Boolean) { clearData() - mUnfilteredSubmissions = emptyList() + unfilteredSubmissions = emptyList() loadData(forceNetwork) } @@ -207,17 +182,17 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var //In case this filter hasn't been shown yet, we'll need to show the loader so the user //doesn't see "no items" view first viewCallback?.onRefreshStarted() - mFilter = filter - mFilterValue = value + this.filter = filter + filterValue = value setFilteredData() } - fun getFilter() : SubmissionListFilter = mFilter + fun getFilter() : SubmissionListFilter = filter - fun getFilterPoints() : Double = mFilterValue + fun getFilterPoints() : Double = filterValue fun getRecipients() : List { - return mFilteredSubmissions.map { submission -> + return filteredSubmissions.map { submission -> when(val assignee = submission.assignee) { is StudentAssignee -> Recipient.from(assignee.student) is GroupAssignee -> Recipient.from(assignee.group) @@ -227,7 +202,7 @@ class AssignmentSubmissionListPresenter(val mAssignment: Assignment, private var override fun compare(item1: GradeableStudentSubmission, item2: GradeableStudentSubmission): Int { // Turns out we do need to sort them by sortable name, but not when anonymous grading is on - if (item1.assignee is StudentAssignee && item2.assignee is StudentAssignee && !mAssignment.anonymousGrading) { + if (item1.assignee is StudentAssignee && item2.assignee is StudentAssignee && !assignment.anonymousGrading) { return (item1.assignee as StudentAssignee).student.sortableName?.lowercase(Locale.getDefault()) ?.compareTo((item2.assignee as StudentAssignee).student.sortableName?.lowercase( Locale.getDefault() diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionRepository.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionRepository.kt new file mode 100644 index 0000000000..656bb7aecc --- /dev/null +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/AssignmentSubmissionRepository.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 - 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.assignment.submission + +import com.instructure.canvasapi2.apis.AssignmentAPI +import com.instructure.canvasapi2.apis.CourseAPI +import com.instructure.canvasapi2.apis.EnrollmentAPI +import com.instructure.canvasapi2.builders.RestParams +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.GradeableStudentSubmission +import com.instructure.canvasapi2.models.Group +import com.instructure.canvasapi2.models.GroupAssignee +import com.instructure.canvasapi2.models.StudentAssignee +import com.instructure.canvasapi2.models.Submission +import com.instructure.canvasapi2.models.User +import com.instructure.canvasapi2.utils.depaginate +import com.instructure.canvasapi2.utils.intersectBy + +class AssignmentSubmissionRepository( + private val assignmentApi: AssignmentAPI.AssignmentInterface, + private val enrollmentApi: EnrollmentAPI.EnrollmentInterface, + private val courseApi: CourseAPI.CoursesInterface +) { + + suspend fun getGradeableStudentSubmissions( + assignment: Assignment, + courseId: Long, + forceNetwork: Boolean + ): List { + val params = RestParams(isForceReadFromNetwork = forceNetwork, usePerPageQueryParam = true) + val gradeableStudents = assignmentApi.getFirstPageGradeableStudentsForAssignment( + courseId, + assignment.id, + params + ).depaginate { + assignmentApi.getNextPageGradeableStudents(it, params) + }.dataOrThrow + + val enrollments = + enrollmentApi.getFirstPageEnrollmentsForCourse(courseId, null, params).depaginate { + enrollmentApi.getNextPage(it, params) + }.dataOrThrow + + val submissions = + assignmentApi.getFirstPageSubmissionsForAssignment(courseId, assignment.id, params) + .depaginate { + assignmentApi.getNextPageSubmissions(it, params) + }.dataOrThrow + + val enrollmentMap = enrollments.associateBy { it.user?.id } + val students = gradeableStudents.distinctBy { it.id }.map { + var user = enrollmentMap[it.id]?.user + user = user?.copy( + enrollments = user.enrollments + enrollments.filter { enrollment -> enrollment.userId == user?.id }, + isFakeStudent = it.isFakeStudent + ) + user?.enrollments?.forEach { it.user = null } + user + }.filterNotNull() + + val allSubmissions = + if (assignment.groupCategoryId > 0 && !assignment.isGradeGroupsIndividually) { + val groups = courseApi.getFirstPageGroups(assignment.courseId, params) + .depaginate { courseApi.getNextPageGroups(it, params) }.dataOrThrow + .filter { it.groupCategoryId == assignment.groupCategoryId } + makeGroupSubmissions(students, groups, submissions) + } else { + val submissionMap = submissions.associateBy { it.userId } + students.map { + GradeableStudentSubmission(StudentAssignee(it), submissionMap[it.id]) + } + } + + return allSubmissions + } + + private fun makeGroupSubmissions( + students: List, + groups: List, + submissions: List + ): List { + val userMap = students.associateBy { it.id } + val groupAssignees = groups.map { GroupAssignee(it, it.users.map { userMap[it.id] ?: it }) } + + val individualIds = + students.map { it.id } - groupAssignees.flatMap { it.students.map { it.id } } + val individualAssignees = individualIds + .map { userMap[it] } + .filterNotNull() + .map { StudentAssignee(it) } + + val (groupedSubmissions, individualSubmissions) = submissions.partition { + (it.group?.id ?: 0L) != 0L + } + val studentSubmissionMap = individualSubmissions.associateBy { it.userId } + val groupSubmissionMap = groupedSubmissions + .groupBy { it.group!!.id } + .mapValues { + it.value.reduce { acc, submission -> + acc.submissionComments = + acc.submissionComments.intersectBy(submission.submissionComments) { + "${it.authorId}|${it.comment}|${it.createdAt?.time}" + } + acc + } + } + .toMap() + + val groupSubs = + groupAssignees.map { GradeableStudentSubmission(it, groupSubmissionMap[it.group.id]) } + val individualSubs = individualAssignees.map { + GradeableStudentSubmission( + it, + studentSubmissionMap[it.student.id] + ) + } + + return groupSubs + individualSubs + } +} \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFilter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFilter.kt new file mode 100644 index 0000000000..54c34722c3 --- /dev/null +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFilter.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 - 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.assignment.submission + +enum class SubmissionListFilter { + ALL, + LATE, + MISSING, + NOT_GRADED, + GRADED, + BELOW_VALUE, + ABOVE_VALUE +} \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/discussion/DiscussionsDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/discussion/DiscussionsDetailsFragment.kt index d78031c9c0..017718db54 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/discussion/DiscussionsDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/discussion/DiscussionsDetailsFragment.kt @@ -57,8 +57,10 @@ import com.instructure.teacher.dialog.NoInternetConnectionDialog import com.instructure.teacher.events.* import com.instructure.teacher.events.DiscussionEntryEvent import com.instructure.teacher.factory.DiscussionsDetailsPresenterFactory +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment import com.instructure.teacher.fragments.* -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter import com.instructure.teacher.presenters.DiscussionsDetailsPresenter import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.* @@ -377,21 +379,21 @@ class DiscussionsDetailsFragment : BasePresenterFragment< RouteMatcher.route(requireActivity(), Route(null, DueDatesFragment::class.java, canvasContext, args)) } submissionsLayout.setOnClickListener { - navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, AssignmentSubmissionListPresenter.SubmissionListFilter.ALL) + navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, SubmissionListFilter.ALL) } binding.donutGroup.viewAllSubmissions.onClick { submissionsLayout.performClick() } // Separate click listener for a11y binding.donutGroup.gradedWrapper.setOnClickListener { - navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, AssignmentSubmissionListPresenter.SubmissionListFilter.GRADED) + navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, SubmissionListFilter.GRADED) } binding.donutGroup.ungradedWrapper.setOnClickListener { - navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, AssignmentSubmissionListPresenter.SubmissionListFilter.NOT_GRADED) + navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, SubmissionListFilter.NOT_GRADED) } binding.donutGroup.notSubmittedWrapper.setOnClickListener { - navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, AssignmentSubmissionListPresenter.SubmissionListFilter.MISSING) + navigateToSubmissions(canvasContext, presenter.discussionTopicHeader.assignment!!, SubmissionListFilter.MISSING) } } - private fun navigateToSubmissions(context: CanvasContext, assignment: Assignment, filter: AssignmentSubmissionListPresenter.SubmissionListFilter) { + private fun navigateToSubmissions(context: CanvasContext, assignment: Assignment, filter: SubmissionListFilter) { val args = AssignmentSubmissionListFragment.makeBundle(assignment, filter) RouteMatcher.route(requireActivity(), Route(null, AssignmentSubmissionListFragment::class.java, context, args)) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListEffectHandler.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListEffectHandler.kt index 5b8591919c..6e54350cc9 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListEffectHandler.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListEffectHandler.kt @@ -85,7 +85,7 @@ class ModuleListEffectHandler( ) is ModuleListEffect.ShowSnackbar -> { - view?.showSnackbar(effect.message) + view?.showSnackbar(effect.message, effect.params) } is ModuleListEffect.UpdateFileModuleItem -> { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListModels.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListModels.kt index 7584ce52a6..4614f652db 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListModels.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListModels.kt @@ -61,6 +61,7 @@ sealed class ModuleListEvent { data class UpdateFileModuleItem(val fileId: Long, val contentDetails: ModuleContentDetails) : ModuleListEvent() object BulkUpdateCancelled : ModuleListEvent() + data class ShowSnackbar(@StringRes val message: Int, val params: Array = emptyArray()): ModuleListEvent() } sealed class ModuleListEffect { @@ -100,7 +101,25 @@ sealed class ModuleListEffect { val published: Boolean ) : ModuleListEffect() - data class ShowSnackbar(@StringRes val message: Int) : ModuleListEffect() + data class ShowSnackbar(@StringRes val message: Int, val params: Array = emptyArray()) : ModuleListEffect() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ShowSnackbar + + if (message != other.message) return false + if (!params.contentEquals(other.params)) return false + + return true + } + + override fun hashCode(): Int { + var result = message + result = 31 * result + params.contentHashCode() + return result + } + } data class UpdateFileModuleItem( val fileId: Long, diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListPresenter.kt index 369fa69bcc..0aca97e00a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListPresenter.kt @@ -27,7 +27,6 @@ import com.instructure.teacher.R import com.instructure.teacher.features.modules.list.ui.ModuleListItemData import com.instructure.teacher.features.modules.list.ui.ModuleListViewState import com.instructure.teacher.mobius.common.ui.Presenter -import kotlin.math.roundToInt object ModuleListPresenter : Presenter { @@ -135,7 +134,8 @@ object ModuleListPresenter : Presenter { isLoading = loading, type = tryOrNull { ModuleItem.Type.valueOf(item.type.orEmpty()) } ?: ModuleItem.Type.Assignment, contentDetails = item.moduleDetails, - contentId = item.contentId + contentId = item.contentId, + unpublishable = item.unpublishable ) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListUpdate.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListUpdate.kt index 2945615e33..d01a85f6fd 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListUpdate.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ModuleListUpdate.kt @@ -307,6 +307,11 @@ class ModuleListUpdate : UpdateInit { + val effect = ModuleListEffect.ShowSnackbar(event.message, event.params) + return Next.dispatch(setOf(effect)) + } } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListRecyclerAdapter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListRecyclerAdapter.kt index 5c1d808bf9..013ad27abd 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListRecyclerAdapter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListRecyclerAdapter.kt @@ -17,6 +17,7 @@ package com.instructure.teacher.features.modules.list.ui import android.content.Context +import androidx.annotation.StringRes import com.instructure.canvasapi2.models.ModuleContentDetails import com.instructure.teacher.adapters.GroupedRecyclerAdapter import com.instructure.teacher.adapters.ListItemCallback @@ -39,6 +40,8 @@ interface ModuleListCallback : ListItemCallback { fun publishModuleAndItems(moduleId: Long) fun unpublishModuleAndItems(moduleId: Long) fun updateFileModuleItem(fileId: Long, contentDetails: ModuleContentDetails) + + fun showSnackbar(@StringRes message: Int, params: Array) } class ModuleListRecyclerAdapter( diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListView.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListView.kt index ab97fbf527..f8c963585c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListView.kt @@ -115,6 +115,10 @@ class ModuleListView( ) } + override fun showSnackbar(@StringRes message: Int, params: Array) { + consumer?.accept(ModuleListEvent.ShowSnackbar(message, params)) + } + override fun updateModuleItem(itemId: Long, isPublished: Boolean) { val title = if (isPublished) R.string.publishDialogTitle else R.string.unpublishDialogTitle val message = @@ -239,8 +243,8 @@ class ModuleListView( .showThemed() } - fun showSnackbar(@StringRes message: Int) { - Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() + fun showSnackbar(@StringRes message: Int, params: Array = emptyArray()) { + Snackbar.make(binding.root, context.getString(message, *params), Snackbar.LENGTH_SHORT).show() } fun showUpdateFileDialog(fileId: Long, contentDetails: ModuleContentDetails) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListViewState.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListViewState.kt index a330540f1d..d38ce3ded6 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListViewState.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/ModuleListViewState.kt @@ -17,8 +17,6 @@ package com.instructure.teacher.features.modules.list.ui import androidx.annotation.ColorInt -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes import com.instructure.canvasapi2.models.ModuleContentDetails import com.instructure.canvasapi2.models.ModuleItem @@ -96,7 +94,9 @@ sealed class ModuleListItemData { val contentDetails: ModuleContentDetails? = null, - val contentId: Long? = null + val contentId: Long? = null, + + val unpublishable: Boolean = true ) : ModuleListItemData() } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/binders/ModuleListItemBinder.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/binders/ModuleListItemBinder.kt index d9f10c0484..754a9a8b8c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/binders/ModuleListItemBinder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/binders/ModuleListItemBinder.kt @@ -26,7 +26,6 @@ import com.instructure.canvasapi2.models.ModuleContentDetails import com.instructure.canvasapi2.models.ModuleItem import com.instructure.canvasapi2.utils.isValid import com.instructure.pandautils.binding.setTint -import com.instructure.pandautils.utils.getFragmentActivity import com.instructure.pandautils.utils.onClickWithRequireNetwork import com.instructure.pandautils.utils.setTextForVisibility import com.instructure.pandautils.utils.setVisible @@ -35,9 +34,9 @@ import com.instructure.teacher.adapters.ListItemBinder import com.instructure.teacher.databinding.AdapterModuleItemBinding import com.instructure.teacher.features.modules.list.ui.ModuleListCallback import com.instructure.teacher.features.modules.list.ui.ModuleListItemData -import com.instructure.teacher.features.modules.list.ui.file.UpdateFileDialogFragment -class ModuleListItemBinder : ListItemBinder() { +class ModuleListItemBinder : + ListItemBinder() { override val layoutResId = R.layout.adapter_module_item @@ -64,16 +63,27 @@ class ModuleListItemBinder : ListItemBinder { - icon = if (data.isPublished == true) R.drawable.ic_complete_solid else R.drawable.ic_no + icon = + if (data.isPublished == true) R.drawable.ic_complete_solid else R.drawable.ic_no tint = if (data.isPublished == true) R.color.textSuccess else R.color.textDark - contentDescription = if (data.isPublished == true) R.string.a11y_published else R.string.a11y_unpublished + contentDescription = + if (data.isPublished == true) R.string.a11y_published else R.string.a11y_unpublished } } @@ -145,7 +159,8 @@ class ModuleListItemBinder : ListItemBinder menu.add(0, 0, 0, R.string.unpublish) - false -> menu.add(0, 0, 0, R.string.publish) + false -> menu.add(0, 1, 1, R.string.publish) else -> { menu.add(0, 0, 0, R.string.unpublish) menu.add(0, 1, 1, R.string.publish) } } - overflow.contentDescription = it.context.getString(R.string.a11y_contentDescription_moduleOptions, item.title) + popup.setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + 0 -> { + callback.updateModuleItem(item.id, false) + true + } + + 1 -> { + callback.updateModuleItem(item.id, true) + true + } + + else -> false + } + } + + publishActions.contentDescription = it.context.getString(R.string.a11y_contentDescription_moduleOptions, item.title) popup.show() } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/file/UpdateFileDialogFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/file/UpdateFileDialogFragment.kt index ae28d0c29c..0ec264a5ea 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/file/UpdateFileDialogFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/list/ui/file/UpdateFileDialogFragment.kt @@ -113,6 +113,7 @@ class UpdateFileDialogFragment : BottomSheetDialogFragment() { setOnShowListener { val bottomSheet = findViewById(com.google.android.material.R.id.design_bottom_sheet) val behavior = BottomSheetBehavior.from(bottomSheet) + bottomSheet.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT behavior.skipCollapsed = true behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.peekHeight = 0 diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleItemAsset.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleItemAsset.kt index 8e165edd0e..acbb2019ee 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleItemAsset.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleItemAsset.kt @@ -20,7 +20,7 @@ package com.instructure.teacher.features.modules.progression import androidx.fragment.app.Fragment import com.instructure.interactions.router.RouterParams import com.instructure.pandautils.features.discussion.router.DiscussionRouterFragment -import com.instructure.teacher.fragments.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment import com.instructure.teacher.fragments.PageDetailsFragment import com.instructure.teacher.fragments.QuizDetailsFragment diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionFragment.kt index f37c697003..2a1dbca054 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionFragment.kt @@ -40,7 +40,7 @@ import com.instructure.teacher.R import com.instructure.teacher.databinding.FragmentModuleProgressionBinding import com.instructure.teacher.features.discussion.DiscussionsDetailsFragment import com.instructure.teacher.features.files.details.FileDetailsFragment -import com.instructure.teacher.fragments.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment import com.instructure.teacher.fragments.InternalWebViewFragment import com.instructure.teacher.fragments.PageDetailsFragment import com.instructure.teacher.fragments.QuizDetailsFragment @@ -89,6 +89,7 @@ class ModuleProgressionFragment : Fragment() { addOnPageChangeListener(object : SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { setupCarousel(data, position) + viewModel.setCurrentPosition(position) } }) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewModel.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewModel.kt index 93536f68cd..4238c96910 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewModel.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/modules/progression/ModuleProgressionViewModel.kt @@ -55,6 +55,8 @@ class ModuleProgressionViewModel @Inject constructor( get() = _events private val _events = MutableLiveData>() + private var currentPosition = -1 + fun loadData(canvasContext: CanvasContext, moduleItemIdParam: Long, assetType: String, assetId: String) { viewModelScope.tryLaunch { _state.postValue(ViewState.Loading) @@ -82,7 +84,13 @@ class ModuleProgressionViewModel @Inject constructor( .map { createModuleItemViewData(it, isDiscussionRedesignEnabled) to it } .filter { it.first != null } - val initialPosition = items.indexOfFirst { it.second.id == moduleItemId }.coerceAtLeast(0) + val position = if (currentPosition == -1) { + val initialPosition = items.indexOfFirst { it.second.id == moduleItemId }.coerceAtLeast(0) + currentPosition = initialPosition + initialPosition + } else { + currentPosition + } val moduleNames = items.map { item -> modules.find { item.second.moduleId == it.id }?.name.orEmpty() @@ -92,7 +100,7 @@ class ModuleProgressionViewModel @Inject constructor( ModuleProgressionViewData( items.map { it.first!! }, moduleNames, - initialPosition, + position, canvasContext.textAndIconColor ) ) @@ -114,4 +122,8 @@ class ModuleProgressionViewModel @Inject constructor( Type.File.name -> ModuleItemViewData.File(item.url.orEmpty()) else -> null } + + fun setCurrentPosition(position: Int) { + currentPosition = position + } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusView.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusView.kt index d087ac93a9..245cb36b8b 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusView.kt @@ -40,7 +40,7 @@ import com.instructure.teacher.events.SyllabusUpdatedEvent import com.instructure.teacher.features.calendar.event.CalendarEventFragment import com.instructure.teacher.features.syllabus.SyllabusEvent import com.instructure.teacher.features.syllabus.edit.EditSyllabusFragment -import com.instructure.teacher.fragments.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment import com.instructure.teacher.mobius.common.ui.MobiusView import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.setupBackButton diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CourseBrowserFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CourseBrowserFragment.kt index 9cc80b09c9..7383ac92c1 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CourseBrowserFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CourseBrowserFragment.kt @@ -46,6 +46,7 @@ import com.instructure.teacher.adapters.CourseBrowserAdapter import com.instructure.teacher.databinding.FragmentCourseBrowserBinding import com.instructure.teacher.events.CourseUpdatedEvent import com.instructure.teacher.factory.CourseBrowserPresenterFactory +import com.instructure.teacher.features.assignment.list.AssignmentListFragment import com.instructure.teacher.features.modules.list.ui.ModuleListFragment import com.instructure.teacher.features.syllabus.ui.SyllabusFragment import com.instructure.teacher.holders.CourseBrowserViewHolder diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizDetailsFragment.kt index e1a0ee1cb3..1229b4e251 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizDetailsFragment.kt @@ -60,7 +60,8 @@ import com.instructure.teacher.events.AssignmentUpdatedEvent import com.instructure.teacher.events.QuizUpdatedEvent import com.instructure.teacher.events.post import com.instructure.teacher.factory.QuizDetailsPresenterFactory -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter.SubmissionListFilter +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter import com.instructure.teacher.presenters.QuizDetailsPresenter import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.anonymousSubmissionsDisplayable diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/SpeedGraderPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/SpeedGraderPresenter.kt index d36779b0d0..99d0dfba3a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/SpeedGraderPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/SpeedGraderPresenter.kt @@ -17,10 +17,30 @@ package com.instructure.teacher.presenters import com.instructure.canvasapi2.apis.EnrollmentAPI -import com.instructure.canvasapi2.managers.* -import com.instructure.canvasapi2.models.* -import com.instructure.canvasapi2.utils.weave.* +import com.instructure.canvasapi2.managers.AssignmentManager +import com.instructure.canvasapi2.managers.CourseManager +import com.instructure.canvasapi2.managers.EnrollmentManager +import com.instructure.canvasapi2.managers.FeaturesManager +import com.instructure.canvasapi2.managers.SubmissionManager +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.Enrollment +import com.instructure.canvasapi2.models.GradeableStudentSubmission +import com.instructure.canvasapi2.models.StudentAssignee +import com.instructure.canvasapi2.models.Submission +import com.instructure.canvasapi2.models.User +import com.instructure.canvasapi2.utils.weave.awaitApi +import com.instructure.canvasapi2.utils.weave.awaitApis +import com.instructure.canvasapi2.utils.weave.catch +import com.instructure.canvasapi2.utils.weave.tryWeave +import com.instructure.canvasapi2.utils.weave.weave +import com.instructure.pandautils.utils.AssignmentUtils2 import com.instructure.teacher.events.SubmissionUpdatedEvent +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionRepository +import com.instructure.teacher.features.assignment.submission.SubmissionListFilter +import com.instructure.teacher.utils.getState import com.instructure.teacher.utils.transformForQuizGrading import com.instructure.teacher.viewinterface.SpeedGraderView import instructure.androidblueprint.Presenter @@ -28,15 +48,20 @@ import kotlinx.coroutines.Job import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import java.util.Locale class SpeedGraderPresenter( - private var courseId: Long, - private var assignmentId: Long, - private var submissions: List, - private var submissionId: Long, - private var discussion: DiscussionTopicHeader? + private var courseId: Long, + private var assignmentId: Long, + private var submissionId: Long, + private var discussion: DiscussionTopicHeader?, + private val repository: AssignmentSubmissionRepository, + private val filter: SubmissionListFilter, + private val filterValue: Double ) : Presenter { + private var submissions: List = emptyList() + private var mView: SpeedGraderView? = null private var mApiJob: Job? = null @@ -97,6 +122,24 @@ class SpeedGraderPresenter( ) course = data.first assignment = data.second + val allSubmissions = repository.getGradeableStudentSubmissions(assignment, courseId, false).sortedBy { + (it.assignee as? StudentAssignee)?.student?.sortableName?.lowercase( + Locale.getDefault()) + } + submissions = allSubmissions.filter { + when (filter) { + SubmissionListFilter.ALL -> true + SubmissionListFilter.LATE -> it.submission?.let { assignment.getState(it, true) in listOf( + AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE) } ?: false + SubmissionListFilter.NOT_GRADED -> it.submission?.let { assignment.getState(it, true) in listOf( + AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED, AssignmentUtils2.ASSIGNMENT_STATE_SUBMITTED_LATE) || !it.isGradeMatchesCurrentSubmission } ?: false + SubmissionListFilter.GRADED -> it.submission?.let { assignment.getState(it, true) in listOf( + AssignmentUtils2.ASSIGNMENT_STATE_GRADED, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_LATE, AssignmentUtils2.ASSIGNMENT_STATE_GRADED_MISSING) && it.isGradeMatchesCurrentSubmission} ?: false + SubmissionListFilter.ABOVE_VALUE -> it.submission?.let { it.isGraded && it.score >= filterValue } ?: false + SubmissionListFilter.BELOW_VALUE -> it.submission?.let { it.isGraded && it.score < filterValue } ?: false + SubmissionListFilter.MISSING -> it.submission?.workflowState == "unsubmitted" || it.submission == null + } + } if (submissionId > 0 && submissions.isEmpty()) { // We don't have all the data we need (we came from a push notification), get all the stuffs first diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/ToDoPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/ToDoPresenter.kt index 60c62fb729..dbdba4f217 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/ToDoPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/ToDoPresenter.kt @@ -22,7 +22,7 @@ import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.utils.weave.* import com.instructure.pandautils.utils.AssignmentUtils2 import com.instructure.teacher.events.ToDoListUpdatedEvent -import com.instructure.teacher.presenters.AssignmentSubmissionListPresenter.Companion.makeGroupSubmissions +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListPresenter.Companion.makeGroupSubmissions import com.instructure.teacher.utils.getState import com.instructure.teacher.viewinterface.ToDoView import instructure.androidblueprint.SyncPresenter diff --git a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt index de4ce204a4..9072e223d5 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt @@ -50,6 +50,9 @@ import com.instructure.teacher.PSPDFKit.AnnotationComments.AnnotationCommentList import com.instructure.teacher.R import com.instructure.teacher.activities.* import com.instructure.teacher.adapters.StudentContextFragment +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.list.AssignmentListFragment +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment import com.instructure.teacher.features.discussion.DiscussionsDetailsFragment import com.instructure.teacher.features.modules.list.ui.ModuleListFragment import com.instructure.teacher.features.modules.progression.ModuleProgressionFragment diff --git a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt index 6ce4b082db..b1b2dc9e9a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt @@ -17,6 +17,9 @@ import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.argsWithContext import com.instructure.teacher.PSPDFKit.AnnotationComments.AnnotationCommentListFragment import com.instructure.teacher.adapters.StudentContextFragment +import com.instructure.teacher.features.assignment.details.AssignmentDetailsFragment +import com.instructure.teacher.features.assignment.list.AssignmentListFragment +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionListFragment import com.instructure.teacher.features.calendar.event.CalendarEventFragment import com.instructure.teacher.features.discussion.DiscussionsDetailsFragment import com.instructure.teacher.features.files.search.FileSearchFragment diff --git a/apps/teacher/src/main/res/layout/adapter_module.xml b/apps/teacher/src/main/res/layout/adapter_module.xml index 74c226b877..971fdaf629 100644 --- a/apps/teacher/src/main/res/layout/adapter_module.xml +++ b/apps/teacher/src/main/res/layout/adapter_module.xml @@ -57,46 +57,55 @@ android:textStyle="bold" tools:text="This is for week 1: Origin of the Earth and the Problem with Complexity and Diversity" /> - + - + - + - + + + + diff --git a/apps/teacher/src/main/res/layout/adapter_module_item.xml b/apps/teacher/src/main/res/layout/adapter_module_item.xml index 58589b3f0d..1ec6800135 100644 --- a/apps/teacher/src/main/res/layout/adapter_module_item.xml +++ b/apps/teacher/src/main/res/layout/adapter_module_item.xml @@ -58,7 +58,7 @@ android:textColor="@color/textDarkest" android:textSize="16sp" app:layout_constraintBottom_toTopOf="@+id/moduleItemSubtitle" - app:layout_constraintEnd_toStartOf="@+id/statusWrapper" + app:layout_constraintEnd_toStartOf="@+id/publishActions" app:layout_constraintStart_toEndOf="@id/moduleItemIcon" app:layout_constraintTop_toTopOf="parent" tools:text="General Questions & Answers" /> @@ -72,7 +72,7 @@ android:maxLines="1" android:textColor="@color/textDark" app:layout_constraintBottom_toTopOf="@+id/moduleItemSubtitle2" - app:layout_constraintEnd_toStartOf="@+id/statusWrapper" + app:layout_constraintEnd_toStartOf="@+id/publishActions" app:layout_constraintStart_toEndOf="@id/moduleItemIcon" app:layout_constraintTop_toBottomOf="@id/moduleItemTitle" tools:text="Due Apr 25 at 11:59pm" /> @@ -86,18 +86,23 @@ android:maxLines="1" android:textColor="@color/textDark" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/statusWrapper" + app:layout_constraintEnd_toStartOf="@+id/publishActions" app:layout_constraintStart_toEndOf="@id/moduleItemIcon" app:layout_constraintTop_toBottomOf="@id/moduleItemSubtitle" tools:text="100 pts" /> - + - + diff --git a/apps/teacher/src/main/res/layout/adapter_module_sub_header.xml b/apps/teacher/src/main/res/layout/adapter_module_sub_header.xml index ab3f7db8a1..c862cae2c0 100644 --- a/apps/teacher/src/main/res/layout/adapter_module_sub_header.xml +++ b/apps/teacher/src/main/res/layout/adapter_module_sub_header.xml @@ -43,7 +43,7 @@ android:maxLines="1" android:textColor="@color/textDark" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/statusWrapper" + app:layout_constraintEnd_toStartOf="@+id/publishActions" app:layout_constraintStart_toEndOf="@id/moduleItemIndent" app:layout_constraintTop_toTopOf="parent" tools:text="SubHeader title" /> @@ -53,10 +53,15 @@ - + - + \ No newline at end of file diff --git a/apps/teacher/src/main/res/layout/dialog_course_nickname.xml b/apps/teacher/src/main/res/layout/dialog_course_nickname.xml deleted file mode 100644 index 1d4e8329db..0000000000 --- a/apps/teacher/src/main/res/layout/dialog_course_nickname.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/apps/teacher/src/main/res/layout/fragment_dialog_update_file.xml b/apps/teacher/src/main/res/layout/fragment_dialog_update_file.xml index 03074bbaa0..ab99ba3b71 100644 --- a/apps/teacher/src/main/res/layout/fragment_dialog_update_file.xml +++ b/apps/teacher/src/main/res/layout/fragment_dialog_update_file.xml @@ -43,7 +43,7 @@ + android:layout_height="56dp"> تغيير المستخدم عام إرسال تعليقات - التقييم على App Store سياسة الخصوصية اتفاقية ترخيص المستخدم شروط الاستخدام @@ -928,7 +927,6 @@ -%s نقطة -%s نقاط - قائمة مهام %d غير مقروءة إعدادات ملف التعريف تنزيل الملف… تم تنزيل الملف بنجاح. @@ -953,4 +951,5 @@ لا يوجد تقدير إعفاء تقدير مبالغ فيه بواسطة %s + لا يمكن إلغاء نشر %s في حالة وجود عمليات إرسال لطالب diff --git a/apps/teacher/src/main/res/values-b+da+DK+instk12/strings.xml b/apps/teacher/src/main/res/values-b+da+DK+instk12/strings.xml index abd699dd1f..c8bd24c125 100644 --- a/apps/teacher/src/main/res/values-b+da+DK+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+da+DK+instk12/strings.xml @@ -717,7 +717,6 @@ Skift bruger Generelt Send feedback - Vurder i App Store Datapolitik EULA Betingelser for brug @@ -874,7 +873,6 @@ -%s point -%s point - Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! @@ -899,4 +897,5 @@ Ingen vurdering Undskyld Givet for høj vurdering af %s + Kan ikke fjerne offentliggørelse af %s, hvis der er afleveringer fra elever diff --git a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml index e7180fe4be..04f123136e 100644 --- a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -715,7 +715,6 @@ Change User General Send Feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -872,7 +871,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. @@ -897,4 +895,5 @@ No grade Excuse Overgraded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml index 3d43fe160d..3de4b5e46a 100644 --- a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -715,7 +715,6 @@ Change user General Send feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -872,7 +871,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. @@ -897,4 +895,5 @@ No grade Excuse Over graded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-b+nb+NO+instk12/strings.xml b/apps/teacher/src/main/res/values-b+nb+NO+instk12/strings.xml index 0eba6238dc..b7605c5556 100644 --- a/apps/teacher/src/main/res/values-b+nb+NO+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+nb+NO+instk12/strings.xml @@ -313,7 +313,6 @@ Oppgaveliste Fagoversikt - Flere forfallsdatoer Alle @@ -718,7 +717,6 @@ Endre bruker Generelt Send tilbakemelding - Vurder på App Store Retningslinjer for personvern EULA brukervilkår @@ -875,7 +873,6 @@ -%s poeng -%s poeng - Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. @@ -901,4 +898,5 @@ Unnskyldning Overgradert av %s + Kan ikke avpublisere %s hvis det er elev innleveringer diff --git a/apps/teacher/src/main/res/values-b+sv+SE+instk12/strings.xml b/apps/teacher/src/main/res/values-b+sv+SE+instk12/strings.xml index eb7a5025ae..a7a2c3a26e 100644 --- a/apps/teacher/src/main/res/values-b+sv+SE+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+sv+SE+instk12/strings.xml @@ -717,7 +717,6 @@ Ändra användare Allmänt Skicka feedback - Bedöm på App Store Integritetspolicy EULA Användningsvillkor @@ -874,7 +873,6 @@ -%s poäng -%s poäng - Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. @@ -899,4 +897,5 @@ Ingen bedömning Ursäkta Överbedömd med %s + Det går inte att avpublicera %s om elevinlämningar finns diff --git a/apps/teacher/src/main/res/values-b+zh+HK/strings.xml b/apps/teacher/src/main/res/values-b+zh+HK/strings.xml index a00c82be81..8da217713f 100644 --- a/apps/teacher/src/main/res/values-b+zh+HK/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+HK/strings.xml @@ -703,7 +703,6 @@ 變更使用者 一般 發送回饋 - 前往 App Store 評分 《隱私政策》 EULA 《使用條款》 @@ -858,7 +857,6 @@ -%s 分 - 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 @@ -883,4 +881,5 @@ 無評分 豁免 已由%s重寫評分 + 如果有學生提交,則無法取消發佈 %s diff --git a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml index 1e1ac223c3..f0b0323f9f 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml @@ -703,7 +703,6 @@ 更改用户 一般 发送反馈 - 给应用商店评分 隐私政策 最终用户许可协议 使用条款 @@ -858,7 +857,6 @@ -%s 分 - 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 @@ -883,4 +881,5 @@ 无评分 原因 %s 评分过高 + 如果有学生提交文件,将无法取消发布%s diff --git a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml index a00c82be81..8da217713f 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml @@ -703,7 +703,6 @@ 變更使用者 一般 發送回饋 - 前往 App Store 評分 《隱私政策》 EULA 《使用條款》 @@ -858,7 +857,6 @@ -%s 分 - 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 @@ -883,4 +881,5 @@ 無評分 豁免 已由%s重寫評分 + 如果有學生提交,則無法取消發佈 %s diff --git a/apps/teacher/src/main/res/values-ca/strings.xml b/apps/teacher/src/main/res/values-ca/strings.xml index c73fe877fa..95d8a1e62e 100644 --- a/apps/teacher/src/main/res/values-ca/strings.xml +++ b/apps/teacher/src/main/res/values-ca/strings.xml @@ -313,7 +313,6 @@ Llista d\'activitats Temari - Dates de lliurament múltiples Tothom @@ -718,7 +717,6 @@ Canvia l\'usuari General Envia els comentaris - Puntuació a App Store Política de privacitat EULA Condicions d\'ús @@ -875,7 +873,6 @@ -%s punt -%s punts - La tasca pendent %d no s\'ha llegit Configuració del perfil S\'està baixant el fitxer… S\'ha baixat correctament el fitxer. @@ -901,4 +898,5 @@ Excusa Sobrequalificat per %s + No es pot anul·lar la publicació de %s si hi ha entregues d\'estudiants diff --git a/apps/teacher/src/main/res/values-cy/strings.xml b/apps/teacher/src/main/res/values-cy/strings.xml index a7f86304c6..677cf77b34 100644 --- a/apps/teacher/src/main/res/values-cy/strings.xml +++ b/apps/teacher/src/main/res/values-cy/strings.xml @@ -715,7 +715,6 @@ Newid Defnyddiwr Cyffredinol Anfon Adborth - Sgorio’r App Store Polisi Preifatrwydd EULA Telerau Defnyddio @@ -872,7 +871,6 @@ %s pwynt %s pwynt - I\'w gwneud %d heb eu darllen Gosodiadau Proffil Llwytho ffeil i lawr… Wedi llwyddo i lwytho ffeil i lawr. @@ -897,4 +895,5 @@ Dim gradd Esgusodi Wedi’i or-raddio gan %s + Does dim modd datgyhoeddi %s os oes gwaith wedi’i gyflwyno gan fyfyrwyr diff --git a/apps/teacher/src/main/res/values-da/strings.xml b/apps/teacher/src/main/res/values-da/strings.xml index 8045a7237c..12a59d8b40 100644 --- a/apps/teacher/src/main/res/values-da/strings.xml +++ b/apps/teacher/src/main/res/values-da/strings.xml @@ -715,7 +715,6 @@ Skift bruger Generelt Send feedback - Vurder i App Store Datapolitik EULA Betingelser for brug @@ -872,7 +871,6 @@ -%s point -%s point - Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! @@ -897,4 +895,5 @@ Ingen karakter Undskyld Givet for høj karakter af %s + Kan ikke fjerne offentliggørelse af %s, hvis der er afleveringer fra studerende diff --git a/apps/teacher/src/main/res/values-de/strings.xml b/apps/teacher/src/main/res/values-de/strings.xml index c32d43e9da..6433a86838 100644 --- a/apps/teacher/src/main/res/values-de/strings.xml +++ b/apps/teacher/src/main/res/values-de/strings.xml @@ -715,7 +715,6 @@ Benutzer*in wechseln Allgemein Feedback senden - Im App Store bewerten Datenschutzrichtlinien EULA Nutzungsbedingungen @@ -872,7 +871,6 @@ %s Punkt %s Punkte - %d Ungelesene erledigen Profileinstellungen Datei herunterladen… Datei erfolgreich heruntergeladen. @@ -897,4 +895,5 @@ Unbenotet Entschuldigund Überbenotet von %s + %s kann nicht unveröffentlicht werden, wenn Abgaben von Studierenden vorhanden sind diff --git a/apps/teacher/src/main/res/values-en-rAU/strings.xml b/apps/teacher/src/main/res/values-en-rAU/strings.xml index f76891b261..06b8105d60 100644 --- a/apps/teacher/src/main/res/values-en-rAU/strings.xml +++ b/apps/teacher/src/main/res/values-en-rAU/strings.xml @@ -715,7 +715,6 @@ Change User General Send Feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -872,7 +871,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. @@ -897,4 +895,5 @@ No mark Excuse Overmarked by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-en-rCA/strings.xml b/apps/teacher/src/main/res/values-en-rCA/strings.xml index e663c539c4..fbc2ea08f7 100644 --- a/apps/teacher/src/main/res/values-en-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCA/strings.xml @@ -718,7 +718,6 @@ Change User General Send Feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -875,7 +874,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. @@ -900,5 +898,6 @@ No grade Excuse Overgraded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-en-rCY/strings.xml b/apps/teacher/src/main/res/values-en-rCY/strings.xml index 3d43fe160d..3de4b5e46a 100644 --- a/apps/teacher/src/main/res/values-en-rCY/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCY/strings.xml @@ -715,7 +715,6 @@ Change user General Send feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -872,7 +871,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. @@ -897,4 +895,5 @@ No grade Excuse Over graded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-en-rGB/strings.xml b/apps/teacher/src/main/res/values-en-rGB/strings.xml index edcfd0f24c..11e86aaa5c 100644 --- a/apps/teacher/src/main/res/values-en-rGB/strings.xml +++ b/apps/teacher/src/main/res/values-en-rGB/strings.xml @@ -715,7 +715,6 @@ Change user General Send feedback - Rate on the App Store Privacy Policy EULA Terms of Use @@ -872,7 +871,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. @@ -897,4 +895,5 @@ No grade Excuse Over graded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/main/res/values-es-rES/strings.xml b/apps/teacher/src/main/res/values-es-rES/strings.xml index 50bfff9dbc..6c3403cd11 100644 --- a/apps/teacher/src/main/res/values-es-rES/strings.xml +++ b/apps/teacher/src/main/res/values-es-rES/strings.xml @@ -165,7 +165,7 @@ No hay mensajes No hay mensajes destacados - Toca \\"+\\" para crear una nueva conversación. + Toca \"+\" para crear una nueva conversación. Destaca mensajes tocando la estrella en el mensaje. Respuesta @@ -313,7 +313,6 @@ Lista de actividades Programa de la asignatura - Varias fechas de entrega Todos @@ -718,7 +717,6 @@ Cambiar usuario General Enviar comentarios - Califica en la App Store Política de privacidad EULA Términos de uso @@ -875,7 +873,6 @@ -%s punto -%s puntos - %d Tarea pendiente sin leer Configuración del perfil Descargando el archivo… Se ha descargado el archivo. @@ -901,4 +898,5 @@ Justificación Sobreevaluado por %s + No se puede cancelar la publicación de %s si hay entregas de estudiantes diff --git a/apps/teacher/src/main/res/values-es/strings.xml b/apps/teacher/src/main/res/values-es/strings.xml index 4c2f9a3fc3..79e3aad87c 100644 --- a/apps/teacher/src/main/res/values-es/strings.xml +++ b/apps/teacher/src/main/res/values-es/strings.xml @@ -716,7 +716,6 @@ Cambiar usuario General Enviar los comentarios - Califique en App Store Política de privacidad EULA Términos de uso @@ -873,7 +872,6 @@ -%s punto -%s puntos - Por hacer %d sin leer Configuración del perfil Descargando el archivo… Archivo descargado exitosamente. @@ -898,4 +896,5 @@ Sin calificación Justificación Sobrecalificado por %s + No se puede cancelar la publicación %s si hay entregas de los estudiantes diff --git a/apps/teacher/src/main/res/values-fi/strings.xml b/apps/teacher/src/main/res/values-fi/strings.xml index 496f5a72c2..e6852587ce 100644 --- a/apps/teacher/src/main/res/values-fi/strings.xml +++ b/apps/teacher/src/main/res/values-fi/strings.xml @@ -715,7 +715,6 @@ Change User (Vaihda käyttäjää) Yleinen Lähetä palautetta - Arvostele App Storessa Tietosuojakäytäntö EULA Käyttöehdot @@ -872,7 +871,6 @@ -%s piste -%s pistettä - Tehtävä %d lukematon Profiiliasetukset Tiedostoa ladataan… Tiedoston lataus onnistui. @@ -897,4 +895,5 @@ Ei arvosanaa Vapauta tehtävästä Yliarvosteltu, arvostelija %s + Julkaisua %s ei voida peruuttaa, jos opiskelijoiden tehtäväpalautuksia on olemassa diff --git a/apps/teacher/src/main/res/values-fr-rCA/strings.xml b/apps/teacher/src/main/res/values-fr-rCA/strings.xml index 8e4a5feca0..28b3d09aec 100644 --- a/apps/teacher/src/main/res/values-fr-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-fr-rCA/strings.xml @@ -715,7 +715,6 @@ Changer d\'utilisateur Général Envoyer un avis - Noter sur l\'App Store Politique de confidentialité CLUF Conditions d\'utilisation @@ -872,7 +871,6 @@ -%s point -%s points - À faire %d non lue Paramètres de profil Téléchargement du fichier … Fichiers téléchargé avec succès. @@ -897,4 +895,5 @@ Aucune note Exempté Surclassement par %s + Impossible d’annuler la publication %s s’il y a des envois d’étudiants diff --git a/apps/teacher/src/main/res/values-fr/strings.xml b/apps/teacher/src/main/res/values-fr/strings.xml index 488d38ab0c..c2959160c5 100644 --- a/apps/teacher/src/main/res/values-fr/strings.xml +++ b/apps/teacher/src/main/res/values-fr/strings.xml @@ -715,7 +715,6 @@ Changer d\'utilisateur Général Envoyer un avis - Évaluez sur l’App Store Politique de confidentialité CGU Conditions d\'utilisation @@ -872,7 +871,6 @@ -%s point -%s points - À faire %d non lu Paramètres de profil Téléchargement du fichier… Fichier téléchargé avec succès. @@ -897,4 +895,5 @@ Aucune note Dispense Surnoté par %s + Impossible de publier %s s\'il existe des soumissions effectuées par des élèves. diff --git a/apps/teacher/src/main/res/values-ht/strings.xml b/apps/teacher/src/main/res/values-ht/strings.xml index e0a6854a12..f3c0ba5e73 100644 --- a/apps/teacher/src/main/res/values-ht/strings.xml +++ b/apps/teacher/src/main/res/values-ht/strings.xml @@ -715,7 +715,6 @@ Chanje Itilizatè Jeneral Voye Kòmantè - Evalye nan App Store Politik Konfidansyalite EULA Kondisyon Itilizasyon @@ -872,7 +871,6 @@ -%s pwen -%s pwen yo - Pou fè %d poko li Paramèt Pwofi Telechajman fichye… Fichye telechaje kòrèkteman. @@ -897,4 +895,5 @@ Okenn nòt Eskiz Evalyasyon depase pa %s + Yo paka depibliye %s si elèv yo gentan fè soumisyon diff --git a/apps/teacher/src/main/res/values-id/strings.xml b/apps/teacher/src/main/res/values-id/strings.xml index 15ca1db9c2..8c4d99dfab 100644 --- a/apps/teacher/src/main/res/values-id/strings.xml +++ b/apps/teacher/src/main/res/values-id/strings.xml @@ -898,4 +898,5 @@ Dibolehkan Nilai lebih oleh %s + Tidak dapat menghapus terbitan %s jika ada penyerahan siswa diff --git a/apps/teacher/src/main/res/values-is/strings.xml b/apps/teacher/src/main/res/values-is/strings.xml index 274a6471af..b1413ea568 100644 --- a/apps/teacher/src/main/res/values-is/strings.xml +++ b/apps/teacher/src/main/res/values-is/strings.xml @@ -716,7 +716,6 @@ Breyta notanda Almennt Senda endurgjöf - Gefa einkunn á App Store Persónuverndarstefna EULA Notandaskilmálar @@ -873,7 +872,6 @@ -%s punktur -%s punktar - Verkefnalisti %d ólesin Uppsetningarstillingar Sæki skrá… Skrá var sótt. @@ -898,4 +896,5 @@ Engin einkunn Gefa undanþágu Of há einkunn gefin um %s + Ekki hægt að fela %s ef skil nemenda eru til staðar diff --git a/apps/teacher/src/main/res/values-it/strings.xml b/apps/teacher/src/main/res/values-it/strings.xml index 95f8fdad58..e352181d0a 100644 --- a/apps/teacher/src/main/res/values-it/strings.xml +++ b/apps/teacher/src/main/res/values-it/strings.xml @@ -716,7 +716,6 @@ Cambia utente Generale Invia feedback - Valuta su App Store Informativa sulla privacy EULA Termini di utilizzo @@ -873,7 +872,6 @@ -%s punto -%s punti - Elenco attività: %d da leggere Impostazioni profilo Download del file… Download del file completato. @@ -898,4 +896,5 @@ Nessun voto Giustifica Assegnato voto più alto da %s + Impossibile annullare la pubblicazione di %s se ci sono consegne degli studenti diff --git a/apps/teacher/src/main/res/values-ja/strings.xml b/apps/teacher/src/main/res/values-ja/strings.xml index 65996d9873..e15a2f8b82 100644 --- a/apps/teacher/src/main/res/values-ja/strings.xml +++ b/apps/teacher/src/main/res/values-ja/strings.xml @@ -173,7 +173,7 @@ アーカイブ 削除 メッセージ - アーカイブ解凍 + アーカイブの解除 メッセージオプション 未読としてマークする このメッセージのコピーを本当に削除しますか?この操作は元に戻すことはできません。 @@ -556,7 +556,7 @@ 保留中のレビュー 課題グループ: - 回答のシャッフル: + 解答をシャッフルする: 複数の試行: 制限時間: 保存するスコア: @@ -703,7 +703,6 @@ ユーザーを変更する 一般 フィードバックを送る - App Storeでレート プライバシーポリシー EULA 利用規約 @@ -858,7 +857,6 @@ -%s 点 - 未読の「するべきこと」%d プロファイル設定 ファイルをダウンロード中… ファイルのダウンロードに成功しました。 @@ -883,4 +881,5 @@ 評定なし 免除 採点が %s 過剰 + 受講生の提出がある場合は%sの公開を取り消すことはできません diff --git a/apps/teacher/src/main/res/values-mi/strings.xml b/apps/teacher/src/main/res/values-mi/strings.xml index 0891fbac4d..376f2bee5f 100644 --- a/apps/teacher/src/main/res/values-mi/strings.xml +++ b/apps/teacher/src/main/res/values-mi/strings.xml @@ -715,7 +715,6 @@ Huri Kaiwhakamahi Whānui Tuku Urupare - Auau i runga i te Taupānga Toa Kaupapahere Tūmataiti EULA Ngā ritenga whakamahi @@ -872,7 +871,6 @@ -%s koinga -%s ngā koinga - Hei mahi %d kaore i pānuitia Ngā Tautuhinga Kōtaha E tikiake ake ana kōnae… I pai te tikiake i te kōnae. @@ -897,4 +895,5 @@ Kaore he kōeke Whakawātea Kōeketia nui ma te %s + Kaore e taea te whakaputa %s mehemea he tukunga a nga tauira diff --git a/apps/teacher/src/main/res/values-ms/strings.xml b/apps/teacher/src/main/res/values-ms/strings.xml index fcec3a49c9..140e5026e7 100644 --- a/apps/teacher/src/main/res/values-ms/strings.xml +++ b/apps/teacher/src/main/res/values-ms/strings.xml @@ -717,7 +717,6 @@ Tukar Pengguna Umum Hantar Maklum Balas - Nilaikan di App Store Dasar Privasi EULA Terma Penggunaan @@ -899,4 +898,5 @@ Maafkan Digredkan Terlebih oleh %s + Tidak dapat menyahterbit %s jika terdapat serahan pelajar diff --git a/apps/teacher/src/main/res/values-nb/strings.xml b/apps/teacher/src/main/res/values-nb/strings.xml index 1fa082cfe1..7da1bbf506 100644 --- a/apps/teacher/src/main/res/values-nb/strings.xml +++ b/apps/teacher/src/main/res/values-nb/strings.xml @@ -313,7 +313,6 @@ Oppgaveliste Emneoversikt - Flere forfallsdatoer Alle @@ -718,7 +717,6 @@ Endre bruker Generelt Send tilbakemelding - Vurder på App Store Retningslinjer for personvern EULA brukervilkår @@ -875,7 +873,6 @@ -%s poeng -%s poeng - Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. @@ -901,4 +898,5 @@ Unnskyldning Overgradert av %s + Kan ikke avpublisere %s hvis det er innleveringer fra student diff --git a/apps/teacher/src/main/res/values-nl/strings.xml b/apps/teacher/src/main/res/values-nl/strings.xml index 0f7734ef68..2cbfe98971 100644 --- a/apps/teacher/src/main/res/values-nl/strings.xml +++ b/apps/teacher/src/main/res/values-nl/strings.xml @@ -715,7 +715,6 @@ Gebruiker wijzigen Algemeen Feedback versturen - Beoordelen in de App Store Privacy-beleid EULA Gebruiksvoorwaarden @@ -872,7 +871,6 @@ -%s punt -%s punten - Taken %d ongelezen Profielinstellingen Bestand downloaden… Bestand is gedownload. @@ -897,4 +895,5 @@ Geen cijfer Vrijstellen Te hoog beoordeeld door %s + Kan publicatie van %s niet ongedaan maken als er inleveringen van studenten zijn diff --git a/apps/teacher/src/main/res/values-pl/strings.xml b/apps/teacher/src/main/res/values-pl/strings.xml index 79921c3d6e..cd2281d04d 100644 --- a/apps/teacher/src/main/res/values-pl/strings.xml +++ b/apps/teacher/src/main/res/values-pl/strings.xml @@ -739,7 +739,6 @@ Zmień użytkownika Ogólne Wyślij informację zwrotną - Pobierz w sklepie App Store Polityka prywatności EULA Warunki korzystania @@ -900,7 +899,6 @@ -%s pkt -%s pkt - Lista zadań – nieprzeczytane %d Ustawienia profilu Pobieranie pliku… Pobrano plik. @@ -925,4 +923,5 @@ Brak oceny Usprawiedliwienie Zawyżona ocena od %s + Nie można cofnąć publikowania %s, jeśli występują przesłane materiały uczestnika diff --git a/apps/teacher/src/main/res/values-pt-rBR/strings.xml b/apps/teacher/src/main/res/values-pt-rBR/strings.xml index 49bd9f5111..7e43cf492b 100644 --- a/apps/teacher/src/main/res/values-pt-rBR/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rBR/strings.xml @@ -716,7 +716,6 @@ Trocar Usuário Geral Enviar Comentários - Avaliar na App Store Política de privacidade EULA Termos de Uso @@ -873,7 +872,6 @@ -%s ponto -%s pontos - %d da lista de tarefas não lido(s) Configurações do perfil Baixando arquivo… Arquivo baixado com sucesso. @@ -898,4 +896,5 @@ Sem nota Com licença Superado por %s + Não é possível remover a publicação de %s se houver trabalhos submetidos pelos alunos diff --git a/apps/teacher/src/main/res/values-pt-rPT/strings.xml b/apps/teacher/src/main/res/values-pt-rPT/strings.xml index e8255c1bf1..105d004522 100644 --- a/apps/teacher/src/main/res/values-pt-rPT/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rPT/strings.xml @@ -715,7 +715,6 @@ Mudar utilizador Geral Enviar Comentários - Taxa na App Store Política de Privacidade EULA Termos de uso @@ -872,7 +871,6 @@ -%s ponto -%s pontos - Para fazer %d não ler Configurações de perfil Download do ficheiro… Ficheiro baixado com sucesso. @@ -897,4 +895,5 @@ Sem classificação Desculpe Sobreclassificado por %s + Não é possível publicar %s se existirem envios de alunos diff --git a/apps/teacher/src/main/res/values-ru/strings.xml b/apps/teacher/src/main/res/values-ru/strings.xml index 3ed833e356..f9eefc587b 100644 --- a/apps/teacher/src/main/res/values-ru/strings.xml +++ b/apps/teacher/src/main/res/values-ru/strings.xml @@ -739,7 +739,6 @@ Сменить пользователя Общее Отправить отзыв - Оценить в App Store Политика конфиденциальности Лицензионный договор Условия использования @@ -900,7 +899,6 @@ -%s балла/баллов -%s балла/баллов - Выполнить непрочитанные %d Настройки профиля Загрузка файла… Файл успешно загружен. @@ -925,4 +923,5 @@ Без оценки Освободить Переоценил %s + Невозможно отменить публикацию %s, если есть отправленные студентами задания diff --git a/apps/teacher/src/main/res/values-sl/strings.xml b/apps/teacher/src/main/res/values-sl/strings.xml index 42d3901e5c..53ebe55cfe 100644 --- a/apps/teacher/src/main/res/values-sl/strings.xml +++ b/apps/teacher/src/main/res/values-sl/strings.xml @@ -715,7 +715,6 @@ Spremeni uporabnika Splošno Pošlji povratne informacije - Oceni v trgovini App Store Pravilnik o zasebnosti Licenčni pogoji za končnega uporabnika Pogoji uporabe @@ -872,7 +871,6 @@ – %s točka – %s točk - Seznam opravil, %d neprebranih Nastavitve profila Prenos datoteke… Datoteka je uspešno prenesena. @@ -897,4 +895,5 @@ Ni ocene Opravičilo Ponovno ocenil(a) %s + Preklic objave %s ni mogoč, če obstajajo oddaje študentov diff --git a/apps/teacher/src/main/res/values-sv/strings.xml b/apps/teacher/src/main/res/values-sv/strings.xml index bd1a7dab53..dbdb7be57a 100644 --- a/apps/teacher/src/main/res/values-sv/strings.xml +++ b/apps/teacher/src/main/res/values-sv/strings.xml @@ -716,7 +716,6 @@ Ändra användare Allmänt Skicka feedback - Bedöm på App Store Integritetspolicy EULA Användningsvillkor @@ -873,7 +872,6 @@ -%s poäng -%s poäng - Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. @@ -898,4 +896,5 @@ Inget omdöme Ursäkta Överbedömd med %s + Det går inte att avpublicera %s om studentinlämningar finns diff --git a/apps/teacher/src/main/res/values-th/strings.xml b/apps/teacher/src/main/res/values-th/strings.xml index 98a1168099..000cab0e4d 100644 --- a/apps/teacher/src/main/res/values-th/strings.xml +++ b/apps/teacher/src/main/res/values-th/strings.xml @@ -717,7 +717,6 @@ เปลี่ยนผู้ใช้ ทั่วไป ส่งความเห็น - ให้คะแนนใน App Store นโยบายความเป็นส่วนตัว EULA เงื่อนไขการใช้งาน @@ -874,7 +873,6 @@ -%s คะแนน -%s คะแนน - สิ่งที่ต้องทำ %d ที่ไม่ได้อ่าน ค่าปรับตั้งโพรไฟล์ กำลังดาวน์โหลดไฟล์… ดาวน์โหลดไฟล์เสร็จสิ้น @@ -900,4 +898,5 @@ ยกเว้น ให้เกรดเกินโดย %s + ไม่สามารถเลิกเผยแพร่ %s หากมีผลงานจัดส่งจากผู้เรียน diff --git a/apps/teacher/src/main/res/values-vi/strings.xml b/apps/teacher/src/main/res/values-vi/strings.xml index 9c37b925be..a45e8633f8 100644 --- a/apps/teacher/src/main/res/values-vi/strings.xml +++ b/apps/teacher/src/main/res/values-vi/strings.xml @@ -165,7 +165,7 @@ Không có tin nhắn Không Có Tin Nhắn Được Gắn Dấu Sao - Nhấn vào dấu \\"+\\" để tạo cuộc trò chuyện mới. + Nhấn vào dấu \"+\" để tạo cuộc trò chuyện mới. Gắn dấu sao bằng cách nhấn vào hình ngôi sao trong tin nhắn. Trả Lời @@ -313,7 +313,6 @@ Danh Sách Bài Tập Chương Trình Học - Nhiều Ngày Đến Hạn Mọi Người @@ -718,7 +717,6 @@ Thay Đổi Người Dùng Chung Gửi Ý Kiến Phản Hồi - Đánh giá trên App Store Chính Sách Quyền Riêng Tư EULA Điều Khoản Sử Dụng @@ -875,7 +873,6 @@ -%s điểm thành phần -%s điểm thành phần - Cần làm %d chưa đọc Cài Đặt Hồ Sơ Đang tải xuống tập tin… Đã tải xuống tập tin thành công. @@ -901,4 +898,5 @@ Xin Phép Được chấm điểm nhiều lần bởi %s + Không thể hủy công bố %s nếu có các bài nộp của sinh viên diff --git a/apps/teacher/src/main/res/values-zh/strings.xml b/apps/teacher/src/main/res/values-zh/strings.xml index 1e1ac223c3..f0b0323f9f 100644 --- a/apps/teacher/src/main/res/values-zh/strings.xml +++ b/apps/teacher/src/main/res/values-zh/strings.xml @@ -703,7 +703,6 @@ 更改用户 一般 发送反馈 - 给应用商店评分 隐私政策 最终用户许可协议 使用条款 @@ -858,7 +857,6 @@ -%s 分 - 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 @@ -883,4 +881,5 @@ 无评分 原因 %s 评分过高 + 如果有学生提交文件,将无法取消发布%s diff --git a/apps/teacher/src/main/res/values/strings.xml b/apps/teacher/src/main/res/values/strings.xml index 15abd0fa7c..964cd80ae9 100644 --- a/apps/teacher/src/main/res/values/strings.xml +++ b/apps/teacher/src/main/res/values/strings.xml @@ -898,5 +898,6 @@ No grade Excuse Overgraded by %s + Cannot unpublish %s if there are student submissions diff --git a/apps/teacher/src/test/java/com/instructure/teacher/unit/AssignmentListPresenterTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/unit/AssignmentListPresenterTest.kt index 469c8c4853..2627b22ad0 100644 --- a/apps/teacher/src/test/java/com/instructure/teacher/unit/AssignmentListPresenterTest.kt +++ b/apps/teacher/src/test/java/com/instructure/teacher/unit/AssignmentListPresenterTest.kt @@ -18,7 +18,7 @@ package com.instructure.teacher.unit import com.instructure.canvasapi2.models.Course import com.instructure.teacher.factory.AssignmentListPresenterFactory -import com.instructure.teacher.presenters.AssignmentListPresenter +import com.instructure.teacher.features.assignment.list.AssignmentListPresenter import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before diff --git a/apps/teacher/src/test/java/com/instructure/teacher/unit/features/assignment/submission/AssignmentSubmissionRepositoryTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/unit/features/assignment/submission/AssignmentSubmissionRepositoryTest.kt new file mode 100644 index 0000000000..75fb9ee390 --- /dev/null +++ b/apps/teacher/src/test/java/com/instructure/teacher/unit/features/assignment/submission/AssignmentSubmissionRepositoryTest.kt @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.instructure.teacher.unit.features.assignment.submission + +import com.instructure.canvasapi2.apis.AssignmentAPI +import com.instructure.canvasapi2.apis.CourseAPI +import com.instructure.canvasapi2.apis.EnrollmentAPI +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.Enrollment +import com.instructure.canvasapi2.models.GradeableStudent +import com.instructure.canvasapi2.models.GradeableStudentSubmission +import com.instructure.canvasapi2.models.Group +import com.instructure.canvasapi2.models.GroupAssignee +import com.instructure.canvasapi2.models.StudentAssignee +import com.instructure.canvasapi2.models.Submission +import com.instructure.canvasapi2.models.User +import com.instructure.canvasapi2.utils.DataResult +import com.instructure.canvasapi2.utils.LinkHeaders +import com.instructure.teacher.features.assignment.submission.AssignmentSubmissionRepository +import io.mockk.coEvery +import io.mockk.mockk +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import java.lang.IllegalStateException + +class AssignmentSubmissionRepositoryTest { + + private val courseApi: CourseAPI.CoursesInterface = mockk(relaxed = true) + private val enrollmentApi: EnrollmentAPI.EnrollmentInterface = mockk(relaxed = true) + private val assignmentApi: AssignmentAPI.AssignmentInterface = mockk(relaxed = true) + + private lateinit var repository: AssignmentSubmissionRepository + + @Before + fun setup() { + repository = AssignmentSubmissionRepository(assignmentApi, enrollmentApi, courseApi) + } + + @Test + fun `Create student submissions`() = runTest { + val users = listOf( + User(id = 1L), + User(id = 2L), + User(id = 3L), + User(id = 4L) + ) + val students = listOf( + GradeableStudent(id = 1L), + GradeableStudent(id = 2L), + GradeableStudent(id = 3L), + GradeableStudent(id = 4L) + ) + val enrollments = listOf( + Enrollment(id = 1L, userId = 1L, user = users[0]), + Enrollment(id = 2L, userId = 2L, user = users[1]), + Enrollment(id = 3L, userId = 3L, user = users[2]), + Enrollment(id = 4L, userId = 4L, user = users[3]) + ) + val submissions = listOf( + Submission(id = 1L, userId = 1L), + Submission(id = 2L, userId = 2L), + Submission(id = 3L, userId = 3L), + Submission(id = 4L, userId = 4L) + ) + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment(any(), any(), any()) + } returns DataResult.Success( + students.subList(0, 2), linkHeaders = LinkHeaders(nextUrl = "nextUrl") + ) + + coEvery { + assignmentApi.getNextPageGradeableStudents("nextUrl", any()) + } returns DataResult.Success( + students.subList(2, 4) + ) + + coEvery { + enrollmentApi.getFirstPageEnrollmentsForCourse( + any(), + any(), + any() + ) + } returns DataResult.Success( + enrollments.subList(0, 2), linkHeaders = LinkHeaders(nextUrl = "nextUrl") + ) + coEvery { enrollmentApi.getNextPage("nextUrl", any()) } returns DataResult.Success( + enrollments.subList(2, 4) + ) + coEvery { + assignmentApi.getFirstPageSubmissionsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Success( + submissions.subList(0, 2), linkHeaders = LinkHeaders(nextUrl = "nextUrl") + ) + coEvery { + assignmentApi.getNextPageSubmissions( + "nextUrl", + any() + ) + } returns DataResult.Success( + submissions.subList(2, 4) + ) + + val expected = listOf( + GradeableStudentSubmission( + assignee = StudentAssignee(users[0]), + submission = submissions[0] + ), + GradeableStudentSubmission( + assignee = StudentAssignee(users[1]), + submission = submissions[1] + ), + GradeableStudentSubmission( + assignee = StudentAssignee(users[2]), + submission = submissions[2] + ), + GradeableStudentSubmission( + assignee = StudentAssignee(users[3]), + submission = submissions[3] + ) + ) + val result = repository.getGradeableStudentSubmissions(Assignment(1L), 1L, false) + + assertEquals(expected, result) + } + + @Test + fun `Create group submissions`() = runTest { + val users = listOf( + User(id = 1L), + User(id = 2L), + User(id = 3L), + User(id = 4L) + ) + val students = listOf( + GradeableStudent(id = 1L), + GradeableStudent(id = 2L), + GradeableStudent(id = 3L), + GradeableStudent(id = 4L) + ) + val enrollments = listOf( + Enrollment(id = 1L, userId = 1L, user = users[0]), + Enrollment(id = 2L, userId = 2L, user = users[1]), + Enrollment(id = 3L, userId = 3L, user = users[2]), + Enrollment(id = 4L, userId = 4L, user = users[3]) + ) + val groups = listOf( + Group(id = 1L, groupCategoryId = 1L, users = users.subList(0, 2)), + Group(id = 2L, groupCategoryId = 1L, users = users.subList(2, 3)), + ) + val submissions = listOf( + Submission(id = 1L, group = groups[0]), + Submission(id = 2L, group = groups[1]), + Submission(id = 3L, userId = 4L) + ) + + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment(any(), any(), any()) + } returns DataResult.Success(students) + coEvery { + enrollmentApi.getFirstPageEnrollmentsForCourse(any(), any(), any()) + } returns DataResult.Success(enrollments) + coEvery { + assignmentApi.getFirstPageSubmissionsForAssignment(any(), any(), any()) + } returns DataResult.Success(submissions) + coEvery { + courseApi.getFirstPageGroups(any(), any()) + } returns DataResult.Success(groups) + + val expectedGroupSubmissions = listOf( + GradeableStudentSubmission( + assignee = GroupAssignee(groups[0], groups[0].users), + submission = submissions[0] + ), + GradeableStudentSubmission( + assignee = GroupAssignee(groups[1], groups[1].users), + submission = submissions[1] + ), + ) + val expectedIndividualSubmission = listOf( + GradeableStudentSubmission( + assignee = StudentAssignee(users[3]), + submission = submissions[2] + ) + ) + + val result = repository.getGradeableStudentSubmissions( + Assignment(1L, groupCategoryId = 1L), + 1L, + false + ) + + assertEquals(expectedGroupSubmissions + expectedIndividualSubmission, result) + } + + @Test(expected = IllegalStateException::class) + fun `Throw exception when getting students fails`() = runTest { + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Fail() + + repository.getGradeableStudentSubmissions(Assignment(1L), 1L, false) + } + + @Test(expected = IllegalStateException::class) + fun `Throw exception when getting enrollments fails`() = runTest { + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + enrollmentApi.getFirstPageEnrollmentsForCourse( + any(), + any(), + any() + ) + } returns DataResult.Fail() + + repository.getGradeableStudentSubmissions(Assignment(1L), 1L, false) + } + + @Test(expected = IllegalStateException::class) + fun `Throw exception when getting submissions fails`() = runTest { + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + enrollmentApi.getFirstPageEnrollmentsForCourse( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + assignmentApi.getFirstPageSubmissionsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Fail() + + repository.getGradeableStudentSubmissions(Assignment(1L), 1L, false) + } + + @Test(expected = IllegalStateException::class) + fun `Throw exception when getting groups fails`() = runTest { + coEvery { + assignmentApi.getFirstPageGradeableStudentsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + enrollmentApi.getFirstPageEnrollmentsForCourse( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + assignmentApi.getFirstPageSubmissionsForAssignment( + any(), + any(), + any() + ) + } returns DataResult.Success(emptyList()) + + coEvery { + courseApi.getFirstPageGroups( + any(), + any() + ) + } returns DataResult.Fail() + + repository.getGradeableStudentSubmissions(Assignment(1L, groupCategoryId = 1L), 1L, false) + } +} \ No newline at end of file diff --git a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListEffectHandlerTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListEffectHandlerTest.kt index c49520ad71..137f52ebe2 100644 --- a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListEffectHandlerTest.kt +++ b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListEffectHandlerTest.kt @@ -541,6 +541,15 @@ class ModuleListEffectHandlerTest : Assert() { confirmVerified(consumer) } + @Test + fun `ShowSnackbar with params calls showSnackbar on view`() { + val message = 123 + val params = arrayOf("param1", "param2") + connection.accept(ModuleListEffect.ShowSnackbar(message, params)) + verify(timeout = 100) { view.showSnackbar(message, params) } + confirmVerified(view) + } + private fun makeLinkHeader(nextUrl: String) = mapOf("Link" to """<$nextUrl>; rel="next"""").toHeaders() diff --git a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListUpdateTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListUpdateTest.kt index bd0cc4e2b9..223c874943 100644 --- a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListUpdateTest.kt +++ b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/list/ModuleListUpdateTest.kt @@ -914,5 +914,28 @@ class ModuleListUpdateTest : Assert() { ) } + @Test + fun `ShowSnackbar event emits ShowSnackbar effect`() { + val model = initModel.copy( + isLoading = false, + pageData = ModuleListPageData(DataResult.Success(emptyList()), false, "fakeUrl"), + modules = listOf( + ModuleObject(1L, items = listOf(ModuleItem(100L))), + ModuleObject(2L, items = listOf(ModuleItem(200L))) + ), + loadingModuleItemIds = setOf(1L) + ) + val params = arrayOf("param") + val snackbarEffect = ModuleListEffect.ShowSnackbar(R.string.error_unpublishable_module_item, params) + updateSpec + .given(model) + .whenEvent(ModuleListEvent.ShowSnackbar(R.string.error_unpublishable_module_item, params)) + .then( + assertThatNext( + matchesEffects(snackbarEffect) + ) + ) + } + } diff --git a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionViewModelTest.kt b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionViewModelTest.kt index 770e36351d..c4857d2dba 100644 --- a/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionViewModelTest.kt +++ b/apps/teacher/src/test/java/com/instructure/teacher/unit/modules/progression/ModuleProgressionViewModelTest.kt @@ -50,6 +50,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Assert +import org.junit.Assert.* import org.junit.Before import org.junit.Rule import org.junit.Test @@ -104,8 +105,8 @@ class ModuleProgressionViewModelTest { viewModel.loadData(Course(id = 1L), 1L, ModuleItemAsset.MODULE_ITEM.name, "") - Assert.assertEquals(ViewState.Error(expected), viewModel.state.value) - Assert.assertEquals(expected, (viewModel.state.value as? ViewState.Error)?.errorMessage) + assertEquals(ViewState.Error(expected), viewModel.state.value) + assertEquals(expected, (viewModel.state.value as? ViewState.Error)?.errorMessage) } @Test @@ -118,8 +119,8 @@ class ModuleProgressionViewModelTest { viewModel.loadData(Course(id = 1L), 1L, ModuleItemAsset.MODULE_ITEM.name, "") - Assert.assertEquals(ViewState.Success, viewModel.state.value) - Assert.assertEquals(expected, viewModel.data.value) + assertEquals(ViewState.Success, viewModel.state.value) + assertEquals(expected, viewModel.data.value) } @Test @@ -136,8 +137,8 @@ class ModuleProgressionViewModelTest { viewModel.loadData(Course(id = 1L), -1, ModuleItemAsset.ASSIGNMENT.name, "1") - Assert.assertEquals(ViewState.Success, viewModel.state.value) - Assert.assertEquals(expected, viewModel.data.value) + assertEquals(ViewState.Success, viewModel.state.value) + assertEquals(expected, viewModel.data.value) } @Test @@ -186,11 +187,11 @@ class ModuleProgressionViewModelTest { verify { Uri.parse("externalUri1") } verify { Uri.parse("externalUri2") } verify(exactly = 2) { mockUriBuilder.appendQueryParameter("display", "borderless") } - Assert.assertEquals(expected, viewModel.data.value) + assertEquals(expected, viewModel.data.value) } @Test - fun `Initial position calculated correctly`() { + fun `Position calculated correctly`() { coEvery { repository.getModulesWithItems(any()) } returns listOf( ModuleObject( id = 1L, name = "Module 1", items = listOf( @@ -208,8 +209,37 @@ class ModuleProgressionViewModelTest { viewModel.loadData(Course(id = 1L), 3L, ModuleItemAsset.MODULE_ITEM.name, "") - Assert.assertEquals(ViewState.Success, viewModel.state.value) - Assert.assertEquals(2, viewModel.data.value?.initialPosition) + assertEquals(ViewState.Success, viewModel.state.value) + assertEquals(2, viewModel.data.value?.initialPosition) + } + + @Test + fun `Position uses current position after reload when position was changed`() { + coEvery { repository.getModulesWithItems(any()) } returns listOf( + ModuleObject( + id = 1L, name = "Module 1", items = listOf( + ModuleItem(id = 1L, type = "Page", pageUrl = "pageUrl", moduleId = 1L), + ModuleItem(id = 2L, type = "Assignment", contentId = 1L, moduleId = 1L) + ) + ), + ModuleObject( + id = 2L, name = "Module 2", items = listOf( + ModuleItem(id = 3L, type = "Discussion", contentId = 2L, moduleId = 2L), + ModuleItem(id = 4L, type = "Quiz", contentId = 3L, moduleId = 2L) + ) + ) + ) + + viewModel.loadData(Course(id = 1L), 3L, ModuleItemAsset.MODULE_ITEM.name, "") + + assertEquals(ViewState.Success, viewModel.state.value) + assertEquals(2, viewModel.data.value?.initialPosition) + + viewModel.setCurrentPosition(3) + + viewModel.loadData(Course(id = 1L), 3L, ModuleItemAsset.MODULE_ITEM.name, "") + + assertEquals(3, viewModel.data.value?.initialPosition) } @Test @@ -220,6 +250,6 @@ class ModuleProgressionViewModelTest { viewModel.loadData(Course(id = 1L), -1, ModuleItemAsset.ASSIGNMENT.name, "1") - Assert.assertEquals(expected, viewModel.events.value?.peekContent()) + assertEquals(expected, viewModel.events.value?.peekContent()) } } 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 597435a46b..687671757e 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 @@ -1539,7 +1539,8 @@ fun MockCanvas.addItemToModule( item: Any, contentId: Long = 0, published: Boolean = true, - moduleContentDetails: ModuleContentDetails? = null + moduleContentDetails: ModuleContentDetails? = null, + unpublishable: Boolean = true ) : ModuleItem { // Placeholders for itemType and itemTitle values that we will compute below @@ -1603,7 +1604,8 @@ fun MockCanvas.addItemToModule( url = itemUrl, htmlUrl = itemUrl, contentId = contentId, - moduleDetails = moduleContentDetails + moduleDetails = moduleContentDetails, + unpublishable = unpublishable ) // Copy/update/replace the module diff --git a/automation/espresso/src/main/kotlin/com/instructure/espresso/TestingUtils.kt b/automation/espresso/src/main/kotlin/com/instructure/espresso/TestingUtils.kt index 42fe0e5770..3ef1601647 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/espresso/TestingUtils.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/espresso/TestingUtils.kt @@ -43,14 +43,23 @@ fun capitalizeFirstLetter(inputText: String): String { @RequiresApi(Build.VERSION_CODES.O) -fun getCurrentDateInCanvasFormat(): String { - val expectedDate = LocalDateTime.now() +fun getDateInCanvasFormat(date: LocalDateTime? = null): String { + val expectedDate = date ?: LocalDateTime.now() val monthString = capitalizeFirstLetter(expectedDate.month.name.take(3)) val dayString = expectedDate.dayOfMonth val yearString = expectedDate.year return "$monthString $dayString, $yearString" } +fun getCustomDateCalendar(dayDiffFromToday: Int): Calendar { + val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + cal.add(Calendar.DATE, dayDiffFromToday) + cal.set(Calendar.HOUR_OF_DAY, 10) + cal.set(Calendar.MINUTE, 1) + cal.set(Calendar.SECOND, 1) + return cal +} + fun retry( times: Int = 3, delay: Long = 1000, diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/AssignmentAPI.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/AssignmentAPI.kt index 5bc48d8b7e..b7a446261b 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/AssignmentAPI.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/AssignmentAPI.kt @@ -122,15 +122,31 @@ object AssignmentAPI { @GET("courses/{courseId}/assignments/{assignmentId}/gradeable_students") fun getFirstPageGradeableStudentsForAssignment(@Path("courseId") courseId: Long, @Path("assignmentId") assignmentId: Long): Call> + @GET("courses/{courseId}/assignments/{assignmentId}/gradeable_students") + suspend fun getFirstPageGradeableStudentsForAssignment( + @Path("courseId") courseId: Long, + @Path("assignmentId") assignmentId: Long, + @Tag restParams: RestParams + ): DataResult> + @GET fun getNextPageGradeableStudents(@Url nextUrl: String): Call> + @GET + suspend fun getNextPageGradeableStudents(@Url nextUrl: String, @Tag restParams: RestParams): DataResult> + @GET("courses/{courseId}/assignments/{assignmentId}/submissions?include[]=rubric_assessment&include[]=submission_history&include[]=submission_comments&include[]=group") fun getFirstPageSubmissionsForAssignment(@Path("courseId") courseId: Long, @Path("assignmentId") assignmentId: Long): Call> + @GET("courses/{courseId}/assignments/{assignmentId}/submissions?include[]=rubric_assessment&include[]=submission_history&include[]=submission_comments&include[]=group") + suspend fun getFirstPageSubmissionsForAssignment(@Path("courseId") courseId: Long, @Path("assignmentId") assignmentId: Long, @Tag params: RestParams): DataResult> + @GET fun getNextPageSubmissions(@Url nextUrl: String): Call> + @GET + suspend fun getNextPageSubmissions(@Url nextUrl: String, @Tag restParams: RestParams): DataResult> + @GET("courses/{courseId}/assignments?include[]=submission&include[]=rubric_assessment&needs_grading_count_by_section=true&override_assignment_dates=true&include[]=all_dates&include[]=overrides") fun getAssignments(@Path("courseId") courseId: Long): Call> 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 712e9d1d4d..59d5350152 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 @@ -136,9 +136,15 @@ object CourseAPI { @GET("courses/{courseId}/groups?include[]=users") fun getFirstPageGroups(@Path("courseId") courseId: Long): Call> + @GET("courses/{courseId}/groups?include[]=users") + suspend fun getFirstPageGroups(@Path("courseId") courseId: Long, @Tag restParams: RestParams): DataResult> + @GET fun getNextPageGroups(@Url nextUrl: String): Call> + @GET + suspend fun getNextPageGroups(@Url nextUrl: String, @Tag restParams: RestParams): DataResult> + @GET("courses/{courseId}/permissions") fun getCoursePermissions(@Path("courseId") courseId: Long, @Query("permissions[]") requestedPermissions: List): Call 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 b69242d366..14ceb9cc15 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 @@ -50,6 +50,12 @@ object EnrollmentAPI { @Path("courseId") courseId: Long, @Query("type[]") enrollmentType: String?): Call> + @GET("courses/{courseId}/enrollments?include[]=avatar_url&state[]=active") + suspend fun getFirstPageEnrollmentsForCourse( + @Path("courseId") courseId: Long, + @Query("type[]") enrollmentType: String?, + @Tag restParams: RestParams): DataResult> + @GET("courses/{courseId}/enrollments?userId") fun getFirstPageEnrollmentsForUserInCourse( @Path("courseId") courseId: Long, 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 4fa9e37b98..e6236d28bd 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 @@ -229,7 +229,7 @@ data class Assignment( override fun describeContents(): Int = 0 fun isMissing(): Boolean { - return !isSubmitted && dueDate?.before(Date()) ?: false + return submission?.missing == true || (!isSubmitted && dueDate?.before(Date()) ?: false && submission?.grade == null) } companion object { diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Attachment.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Attachment.kt index 14890338b7..065283bee4 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Attachment.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Attachment.kt @@ -17,7 +17,10 @@ package com.instructure.canvasapi2.models +import android.webkit.URLUtil import com.google.gson.annotations.SerializedName +import com.instructure.canvasapi2.utils.isValid +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import java.util.* @@ -40,4 +43,7 @@ data class Attachment( ) : CanvasModel() { override val comparisonDate get() = createdAt override val comparisonString get() = displayName + + @IgnoredOnParcel + val isLocalFile = url.isValid() && !URLUtil.isNetworkUrl(url) } diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/ModuleItem.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/ModuleItem.kt index a6da57f5d1..51439c03c4 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/ModuleItem.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/ModuleItem.kt @@ -17,7 +17,6 @@ package com.instructure.canvasapi2.models -import android.os.Parcelable import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize @@ -44,6 +43,7 @@ data class ModuleItem( val externalUrl: String? = null, @SerializedName("page_url") val pageUrl: String? = null, + val unpublishable: Boolean = true, @SerializedName("mastery_paths") var masteryPaths: MasteryPath? = null, // When we display the "Choose Assignment Group" when an assignment uses Mastery Paths we create a new row to display. diff --git a/libs/flutter_student_embed/lib/l10n/res/intl_ja.arb b/libs/flutter_student_embed/lib/l10n/res/intl_ja.arb index 9a29c1dad8..00e120e7c3 100644 --- a/libs/flutter_student_embed/lib/l10n/res/intl_ja.arb +++ b/libs/flutter_student_embed/lib/l10n/res/intl_ja.arb @@ -90,7 +90,7 @@ "placeholders_order": [], "placeholders": {} }, - "pointsPossible": "{points}の可能なポイント", + "pointsPossible": "配点 {points}", "@pointsPossible": { "description": "Screen reader label used for the points possible for an assignment, quiz, etc.", "type": "text", diff --git a/libs/pandares/src/main/res/values-ar/strings.xml b/libs/pandares/src/main/res/values-ar/strings.xml index 400a75f0e8..16dcf73223 100644 --- a/libs/pandares/src/main/res/values-ar/strings.xml +++ b/libs/pandares/src/main/res/values-ar/strings.xml @@ -1614,4 +1614,80 @@ محتوى مساق إضافي فشل تحديث ترتيب بطاقات لوحة المعلومات + نشر كل الوحدات النمطية والعناصر + نشر الوحدات النمطية فقط + إلغاء نشر كل الوحدات النمطية والعناصر + خيارات الوحدة النمطية + نشر الوحدة النمطية وكل العناصر + نشر الوحدة النمطية فقط + إلغاء نشر الوحدة النمطية وكل العناصر + خيارات الوحدة النمطية لـ %s + خيارات عنصر الوحدة النمطية لـ %s + نشر عنصر الوحدة النمطية + إلغاء نشر عنصر الوحدة النمطية + نشر؟ + سيؤدي هذا إلى إظهار الوحدة النمطية فقط أمام الطلاب. + سيؤدي هذا إلى إظهار الوحدة النمطية وكل العناصر أمام الطلاب. + إلغاء النشر؟ + سيؤدي هذا إلى إخفاء الوحدة النمطية وكل العناصر أمام الطلاب. + سيؤدي هذا إلى إخفاء هذا العنصر فقط أمام الطلاب. + سيؤدي هذا إلى إخفاء هذا العنصر فقط أمام الطلاب. + سيؤدي هذا إلى إظهار كل الوحدات النمطية والعناصر أمام الطلاب. + سيؤدي هذا إلى إظهار الوحدات النمطية فقط أمام الطلاب. + سيؤدي هذا إلى إخفاء كل الوحدات النمطية والعناصر أمام الطلاب. + متاح فقط لمن لديهم رابط + مدى توافر الجدول + تم نشر العنصر + تم إلغاء نشر العنصر + تم نشر الوحدة النمطية فقط + تم نشر الوحدة النمطية وكل العناصر + تم إلغاء نشر الوحدة النمطية وكل العناصر + تم نشر الوحدات النمطية فقط + تم نشر كل الوحدات النمطية وكل العناصر + تم إلغاء نشر كل الوحدات النمطية وكل العناصر + الاستيراد من المساق + أعضاء المساق + أعضاء المؤسسة التعليمية + عام + تحرير أذونات + تحديث + الإتاحة + الرؤية + متاح من + متاح حتى + من + حتى + التاريخ + الوقت + مسح تاريخ البدء + مسح تاريخ الانتهاء + قد تستغرق هذه العملية بضع دقائق. يمكنك إغلاق مربع الحوار أو الانتقال بعيدًا عن الصفحة أثناء هذه العملية. + الملاحظة + جميع الوحدات النمطية + كل الوحدات النمطية والعناصر + الوحدات النمطية والعناصر المحددة + الوحدات النمطية المحددة + جارٍ النشر + جارٍ إلغاء النشر + لن يتم عكس الوحدات والعناصر التي تمت معالجتها بالفعل إلى حالتها السابقة عند إيقاف العملية. + نجحت العملية! + تعذر التحديث + تم إلغاء التحديث + مخفي + مجدول + منشور + غير منشور + + %.0f من النقاط + %.0f نقطة + %.0f من النقاط + %.0f من النقاط + %.0f من النقاط + %.0f من النقاط + + نشر + إلغاء النشر + نشر + إلغاء النشر + تحديث diff --git a/libs/pandares/src/main/res/values-b+da+DK+instk12/strings.xml b/libs/pandares/src/main/res/values-b+da+DK+instk12/strings.xml index 2c8e219f1e..59dfd6ff1c 100644 --- a/libs/pandares/src/main/res/values-b+da+DK+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+da+DK+instk12/strings.xml @@ -1519,4 +1519,76 @@ Yderligere fagindhold Kunne ikke opdatere oversigtens kortordren + Offentliggør alle forløb og elementer + Offentliggør kun forløb + Fjern offentliggørelse af alle forløb og elementer + Forløb Indstillinger + Offentliggør forløb og alle elementer + Offentliggør kun forløb + Fjern offentliggørelse af forløb og alle elementer + Forløb indstillinger for %s + Forløbelementindstillinger for %s + Offentliggør forløbelement + Fjern offentliggørelse af forløbelement + Offentliggør? + Dette vil gøre kun forløbet synligt for eleverne. + Dette vil gøre forløbet og alle elementer synlige for eleverne. + Fjern offentliggørelse? + Dette vil gøre forløbet og alle elementer usynlige for eleverne. + Dette vil gøre kun dette element synligt for eleverne. + Dette vil gøre kun dette element usynligt for eleverne. + Dette vil gøre alle forløb og elementer synlige for eleverne. + Dette vil gøre kun forløb synlige for eleverne. + Dette vil gøre alle forløb og elementer usynlige for eleverne. + Kun tilgængelig med link + Planlæg tilgængelighed + Element offentliggjort + Elementet ikke offentliggjort + Kun forløb offentliggjort + Forløb og alle elementer offentliggjort + Forløb og alle elementer er ikke offentliggjort + Kun forløb offentliggjort + Alle forløb og alle elementer offentliggjort + Alle forløb og alle elementer ikke offentliggjort + Arv fra fag + Fagdeltagere + Institutionsmedlemmer + Offentligt + Rediger tilladelser + Opdatering + Tilgængelighed + Synlighed + Tilgængelig fra + Tilgængelig indtil + Fra + Indtil + Dato + Tidspunkt + Ryd fra-dato + Ryd indtil-dato + Denne process kan tage nogle minutter. Du kan lukke modalen eller navigere væk fra siden under denne proces. + Bemærk + Alle forløb + Alle forløb og elementer + Udvalgte forløb og elementer + Udvalgte forløb + Offentliggør + Fjerner offentliggørelse + Forløb og elementer, der allerede er blevet behandlet, vil ikke blive ført tilbage til deres tidligere tilstand, når processen afbrydes. + Succes! + Opdatering mislykkedes + Opdatering annulleret + Skjult + Planlagt + Offentliggjort + Ikke offentliggjort + + %.0f point + %.0f point + + Offentliggør + Annuller offentliggørelse + Offentliggør + Annuller offentliggørelse + Opdater 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 2db17bcd15..8d08f7e873 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 @@ -1519,4 +1519,76 @@ Additional subject content Failed to update Dashboard cards order + Publish all Modules and Items + Publish Modules only + Unpublish all Modules and Items + Module Options + Publish Module and all Items + Publish Module only + Unpublish Module and all Items + Module options for %s + Module item options for %s + Publish Module Item + Unpublish Module Item + Publish? + This will make only the module visible to students. + This will make the module and all items visible to students. + Unpublish? + This will make the module and all items invisible to students. + This will make only this item visible to students. + This will make only this item invisible to students. + This will make all modules and items visible to students. + This will make only the modules visible to students. + This will make all modules and items invisible to students. + Only available with link + Schedule availability + Item published + Item unpublished + Only Module published + Module and all Items published + Module and all Items unpublished + Only Modules published + All Modules and all Items published + All Modules and all Items unpublished + Inherit from Subject + Subject Members + Institution Members + Public + Edit Permissions + Update + Availability + Visibility + Available From + Available Until + From + Until + Date + Time + Clear From Date + Clear Until Date + This process could take a few minutes. You may close the modal or navigate away from the page during this process. + Note + All Modules + All Modules and Items + Selected Modules and Items + Selected Modules + Publishing + Unpublishing + Modules and items that have already been processed will not be reverted to their previous state when the process is discontinued. + Success! + Update failed + Update cancelled + Hidden + Scheduled + Published + Unpublished + + %.0f pt + %.0f pts + + Publish + Unpublish + Publish + Unpublish + Refresh diff --git a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml index 6eb60cea44..37e41c53e3 100644 --- a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -1519,4 +1519,76 @@ Additional module content Failed to update Dashboard cards order + Publish all Units and Items + Publish Units only + Unpublish all Units and Items + Unit Options + Publish Unit and all Items + Publish Unit only + Unpublish Unit and all Items + Unit options for %s + Unit item options for %s + Publish Unit Item + Unpublish Unit Item + Publish? + This will make only the unit visible to students. + This will make the unit and all items visible to students. + Unpublish? + This will make the unit and all items invisible to students. + This will make only this item visible to students. + This will make only this item invisible to students. + This will make all units and items visible to students. + This will make only the units visible to students. + This will make all units and items invisible to students. + Only available with link + Schedule availability + Item published + Item unpublished + Only Unit published + Unit and all Items published + Unit and all Items unpublished + Only Units published + All Units and all Items published + All Units and all Items unpublished + Inherit from Module + Module Members + Institution Members + Public + Edit Permissions + Update + Availability + Visibility + Available from + Available until + From + Until + Date + Time + Clear From Date + Clear Until Date + This process could take a few minutes. You may close the modal or navigate away from the page during this process. + Note + All Units + All Units and Items + Selected Units and Items + Selected Units + Publishing + Unpublishing + Units and items that have already been processed will not be reverted to their previous state when the process is discontinued. + Success! + Update failed + Update cancelled + Hidden + Scheduled + Published + Unpublished + + %.0f pt + %.0f pts + + Publish + Unpublish + Publish + Unpublish + Refresh diff --git a/libs/pandares/src/main/res/values-b+nb+NO+instk12/strings.xml b/libs/pandares/src/main/res/values-b+nb+NO+instk12/strings.xml index dd33e1e55a..a4f217d03b 100644 --- a/libs/pandares/src/main/res/values-b+nb+NO+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+nb+NO+instk12/strings.xml @@ -1520,4 +1520,76 @@ Ytterligere faginnhold Kunne ikke oppdatere rekkefølge på oversiktskort + Publiser alle moduler og elementer + Publiser kun moduler + Avpubliser alle moduler og elementer + Modulalternativer + Publiser modul og alle elementer + Publiser kun moduler + Avpubliser modul og alle elementer + Modulalternativer for %s + Modulelementalternativer for %s + Publiser modulelement + Avpubliser modulelement + Publisere? + Dette vil gjøre kun modulen synlig for elever. + Dette vil gjøre modulen og alle elementer synlige for elever. + Avpublisere? + Dette vil gjøre modulen og alle elementer usynlige for elever. + Dette vil gjøre kun dette elementet synlig for elever. + Dette vil gjøre kun dette elementet usynlig for elever. + Dette vil gjøre alle moduler og elementer synlige for elever. + Dette vil gjøre kun modulene synlig for elever. + Dette vil gjøre alle moduler og elementer usynlige for elever. + Kun tilgjengelig med lenke + Planlegg tilgjengelighet + Element publisert + Element avpublisert + Kun modul publisert + Modul og alle elementer publisert + Modul og alle elementer avpublisert + Alle moduler publisert + Alle moduler og alle elementer publisert + Alle moduler og alle elementer avpublisert + Arve fra fag + Fagmedlemmer + Institusjonsmedlemmer + Offentlig + Redigeringstillatelser + Oppdater + Tilgjengelighet + Synlighet + Tilgjengelig fra + Tilgjengelig frem til + Fra + Inntil + Dato + Tid + Slett fra dato + Slett til dato + Denne prosessen kan ta noen minutter. Du kan lukke modalen eller navigere bor fra siden under denne prosessen. + Merknad + Alle moduler + Alle moduler og elementer + Valgte moduler og elementer + Valgte moduler + Publiserer + Avpubliserer + Moduler og elementer som allerede er behandlet, vil ikke bli tilbakestilt til tidligere tilstand når prosessen avbrytes. + Vellykket! + Oppdatering mislyktes + Oppdatering avbrutt + Skjult + Planlagt + Publisert + Upublisert + + %.0f poeng + %.0f poeng + + Publiser + Avpubliser + Publiser + Avpubliser + Oppdater diff --git a/libs/pandares/src/main/res/values-b+sv+SE+instk12/strings.xml b/libs/pandares/src/main/res/values-b+sv+SE+instk12/strings.xml index ac8b92c870..65c115138e 100644 --- a/libs/pandares/src/main/res/values-b+sv+SE+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+sv+SE+instk12/strings.xml @@ -1519,4 +1519,76 @@ Extra kursinnehåll Det gick inte att uppdatera översiktens kortordning + Publicera alla moduler och objekt + Publicera endast moduler + Avpublicera alla moduler och objekt + Modulalternativ + Publicera modulen och alla objekt + Publicera endast modulen + Avpublicera modulen och alla objekt + Modulalternativ för %s + Modulobjektalternativ för %s + Publicera modulobjekt + Avpublicera modulobjekt + Publicera? + Detta gör modulen synlig endast för elever. + Detta gör modulen och alla objekt synliga för elever. + Avpublicera? + Detta döljer modulen och alla objekt för elever. + Detta gör endast detta objekt synligt för elever. + Detta döljer endast detta objekt för elever. + Detta gör alla moduler och objekt synliga för elever. + Detta gör endast modulerna synliga för elever. + Detta döljer alla moduler och objekt för elever. + Endast tillgängligt med länk + Schematillgänglighet + Objekt publicerat + Objekt avpublicerat + Endast modulen publicerades + Modulen och alla objekten publicerades + Modulen och alla objekten avpublicerades + Endast moduler publicerades + Alla moduler och objekt publicerades + Alla moduler och objekt avpublicerades + Ärv från kurs + Kursmedlem + Institutionens medlemmar + Offentlig + Redigera behörigheter + Uppdatera + Tillgänglighet + Synlighet + Tillgänglig från + Tillgänglig till + Från + Tills + Datum + Tid + Rensa från-datum + Rensa fram till-datum + Detta kan ta några minuter. Du kan stänga modalen eller navigera bort från sidan under det här förloppet. + Anteckning + Alla moduler + Alla moduler och objekt + Valda moduler och objekt + Valda moduler + Publicerar + Avpublicerar + Moduler som redan har bearbetats återställs inte till tidigare tillstånd är processen avslutas. + Framgång! + Uppdateringen misslyckades + Uppdateringen avbruten + Dold + Schemalagd + Publicerad + Ej offentliggjord + + %.0f poäng + %.0f poäng + + Publicera + Avpublicera + Publicera + Avpublicera + Uppdatera diff --git a/libs/pandares/src/main/res/values-b+zh+HK/strings.xml b/libs/pandares/src/main/res/values-b+zh+HK/strings.xml index 23ca780ba0..1ca7e8612d 100644 --- a/libs/pandares/src/main/res/values-b+zh+HK/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+HK/strings.xml @@ -1495,4 +1495,75 @@ 額外課程內容 無法更新控制面板卡片順序 + 發佈所有單元和項目 + 僅發佈單元 + 取消發佈所有單元和項目 + 單元選項 + 發佈單元和所有項目 + 僅發佈單元 + 取消發佈單元和所有項目 + %s 的單元選項 + %s 的單元項目選項 + 發佈單元項目 + 取消發佈單元項目 + 發佈嗎? + 這將使學生僅看到單元。 + 這將使學生看到單元和所有項目。 + 取消發佈嗎? + 這將使學生看不到單元和所有項目。 + 這將使學生僅看到此項目。 + 這將使學生僅看不到此項目。 + 這將使學生看到所有單元和項目。 + 這將使學生僅看到單元。 + 這將使學生看不到所有單元和項目。 + 只可以搭配連結使用 + 排程可用性 + 已發佈項目 + 已取消發佈項目 + 僅已發佈單元 + 已發佈單元和所有項目 + 已取消發佈單元和所有項目 + 僅已發佈單元 + 已發佈所有單元和所有項目 + 已取消發佈所有單元和所有項目 + 自課程繼承 + 學生 + 機構成員 + 公開 + 編輯權限 + 更新 + 可用期 + 可見度 + 可用起始日期 + 可用截止時間 + 開始時間 + 結束 + 日期 + 時間 + 清除開始日期 + 清除截止日期 + 此程序可能需要幾分鐘的時間。您可以在此處理期間關閉模態,或導覽遠離頁面。 + 注釋 + 所有單元 + 所有單元和項目 + 選取的單元和項目 + 選取的單元 + 正在發佈 + 取消發佈 + 中止處理時,已處理的單元和項目將不會回復到之前的狀態。 + 成功! + 更新失敗 + 已取消更新 + 已隱藏 + 已排程 + 已發佈 + 已取消發佈 + + %.0f 分 + + 發佈 + 取消發佈 + 發佈 + 取消發佈 + 重新整理 diff --git a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml index 0617c4530c..fd42ac2ee9 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml @@ -1495,4 +1495,75 @@ 更多课程内容 无法更新控制面板卡片顺序 + 发布所有模块和项目 + 仅发布模块 + 取消发布所有模块和项目 + 模块选项 + 发布模块和所有项目 + 仅发布模块 + 取消发布模块和所有项目 + %s 的模块选项 + %s 的模块项目选项 + 发布模块项目 + 取消发布模块项目 + 是否发布? + 这将仅使模块对学生可见。 + 这将使模块和所有项目对学生可见。 + 是否取消发布? + 这将使模块和所有项目对学生不可见。 + 这将仅使该项目对学生可见。 + 这将仅使该项目对学生不可见。 + 这将使所有模块和项目对学生可见。 + 这将仅使模块对学生可见。 + 这将使所有模块和项目对学生不可见。 + 仅提供链接 + 计划表可用性 + 已发布项目 + 已取消发布项目 + 已仅发布模块 + 已发布模块和所有项目 + 已取消发布模块和所有项目 + 已仅发布模块 + 已发布所有模块和所有项目 + 已取消发布所有模块和所有项目 + 从课程继承 + 课程成员 + 机构成员 + 公开 + 编辑权限 + 更新 + 可用性 + 可见性 + 开始日期 + 截止时间 + 开始日期 + 直至 + 日期 + 时间 + 清除开始日期 + 清除截止日期 + 该过程可能需要几分钟。在此期间,您可以关闭该模式或离开页面。 + + 所有模块 + 所有模块和项目 + 已选的模块和项目 + 已选的模块 + 正在发布 + 正在取消发布 + 停止处理后,已经处理的模块和项目不会退回到之前的状态。 + 成功! + 更新失败 + 更新已取消 + 隐藏 + 已计划 + 已发布 + 未发布 + + %.0f 分 + + 发布 + 取消发布 + 发布 + 取消发布 + 刷新 diff --git a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml index 23ca780ba0..1ca7e8612d 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml @@ -1495,4 +1495,75 @@ 額外課程內容 無法更新控制面板卡片順序 + 發佈所有單元和項目 + 僅發佈單元 + 取消發佈所有單元和項目 + 單元選項 + 發佈單元和所有項目 + 僅發佈單元 + 取消發佈單元和所有項目 + %s 的單元選項 + %s 的單元項目選項 + 發佈單元項目 + 取消發佈單元項目 + 發佈嗎? + 這將使學生僅看到單元。 + 這將使學生看到單元和所有項目。 + 取消發佈嗎? + 這將使學生看不到單元和所有項目。 + 這將使學生僅看到此項目。 + 這將使學生僅看不到此項目。 + 這將使學生看到所有單元和項目。 + 這將使學生僅看到單元。 + 這將使學生看不到所有單元和項目。 + 只可以搭配連結使用 + 排程可用性 + 已發佈項目 + 已取消發佈項目 + 僅已發佈單元 + 已發佈單元和所有項目 + 已取消發佈單元和所有項目 + 僅已發佈單元 + 已發佈所有單元和所有項目 + 已取消發佈所有單元和所有項目 + 自課程繼承 + 學生 + 機構成員 + 公開 + 編輯權限 + 更新 + 可用期 + 可見度 + 可用起始日期 + 可用截止時間 + 開始時間 + 結束 + 日期 + 時間 + 清除開始日期 + 清除截止日期 + 此程序可能需要幾分鐘的時間。您可以在此處理期間關閉模態,或導覽遠離頁面。 + 注釋 + 所有單元 + 所有單元和項目 + 選取的單元和項目 + 選取的單元 + 正在發佈 + 取消發佈 + 中止處理時,已處理的單元和項目將不會回復到之前的狀態。 + 成功! + 更新失敗 + 已取消更新 + 已隱藏 + 已排程 + 已發佈 + 已取消發佈 + + %.0f 分 + + 發佈 + 取消發佈 + 發佈 + 取消發佈 + 重新整理 diff --git a/libs/pandares/src/main/res/values-ca/strings.xml b/libs/pandares/src/main/res/values-ca/strings.xml index 4281c11c5d..97887e8e33 100644 --- a/libs/pandares/src/main/res/values-ca/strings.xml +++ b/libs/pandares/src/main/res/values-ca/strings.xml @@ -1520,4 +1520,76 @@ Contingut de l’assignatura addicional No s’ha pogut actualitzar la comanda de targetes del panell de control + Publica tots els continguts i elements + Publica només els continguts + Anul·la tots els continguts i elements + Opcions de contingut + Publica el contingut i tots els elements + Publica només el contingut + Anul·la la publicació del contingut i de tots els elements + Opcions de contingut per a %s + Opcions d’elements de contingut per a %s + Publica l’element de contingut + Anul·la la publicació de l\'element de contingut + Voleu publicar-ho? + D’aquesta manera, els estudiants només podran veure el contingut. + D’aquesta manera, els estudiants podran veure el contingut i tots els elements. + Voleu anul·lar-ne la publicació? + D’aquesta manera, el contingut i tots els elements seran invisibles per als estudiants. + D’aquesta manera, els estudiants només podran veure aquest element. + D’aquesta manera, només aquest element serà invisible per als estudiants. + D’aquesta manera, els estudiants podran veure tots els continguts i elements. + D’aquesta manera, els estudiants només podran veure els continguts. + D’aquesta manera, tots els continguts i elements seran invisibles per als estudiants. + Només està disponible a través d\'un enllaç + Planifica la disponibilitat + S’ha publicat l\'element + S’ha anul·lat la publicació de l\'element + Només s’ha publicat el contingut + S’han publicat el contingut i tots els elements + S’ha anul·lat la publicació del contingut i de tots els elements + Només s’han publicat els continguts + S’han publicat tots els continguts i elements + S’ha anul·lat la publicació de tots els continguts i elements + Hereta de l\'assignatura + Membres del curs + Membres de la institució + Públic + Edita els permisos + Actualitza + Disponibilitat + Visibilitat + Disponible des del + Disponible fins al + Des del + Fins al + Data + Hora + Esborra la data Des del + Esborra la data Fins al + Pot ser que aquest procés trigui uns minuts. Podeu tancar el modal o sortir de la pàgina durant aquest procés. + Nota + Tots els continguts + Tots els continguts i elements + Continguts i elements seleccionats + Continguts seleccionats + S\'està publicant + Se n\'està anul·lant la publicació + Els continguts i els elements que ja s’hagin processat no es revertiran al seu estat anterior un cop interromput el procés. + Operació correcta! + No s’ha pogut realitzar l’actualització + S’ha cancel·lat l’actualització + Ocult + S’ha planificat + S’ha publicat + Se n’ha anul·lat la publicació + + %.0f punt + %.0f punts + + Publica-ho + Anul·la’n la publicació + Publica-ho + Anul·la’n la publicació + Actualitza-ho diff --git a/libs/pandares/src/main/res/values-cy/strings.xml b/libs/pandares/src/main/res/values-cy/strings.xml index abcde4e9b9..d4936f0543 100644 --- a/libs/pandares/src/main/res/values-cy/strings.xml +++ b/libs/pandares/src/main/res/values-cy/strings.xml @@ -1519,4 +1519,76 @@ Cynnwys cwrs ychwanegol Wedi methu diweddaru trefn cardiau’r Dangosfwrdd + Cyhoeddi Pob Modiwl ac Eitem + Cyhoeddi Modiwlau’n Unig + Datgyhoeddi Pob Modiwl ac Eitem + Opsiynau Modiwl + Cyhoeddi Modiwl a phob eitem + Cyhoeddi Modiwl yn unig + Datgyhoeddi Modiwl a phob eitem + Opsiynau modiwl ar gyfer %s + Opsiynau eitemau modiwlau ar gyfer %s + Cyhoeddi Eitem Modiwl + Datgyhoeddi Eitem Modiwl + Cyhoeddi? + Bydd hyn yn gwneud dim ond y modiwl yn weladwy i fyfyrwyr. + Bydd hyn yn gwneud y modiwl a’r holl eitemau’n weladwy i fyfyrwyr. + Datgyhoeddi? + Bydd hyn yn gwneud y modiwl a’r holl eitemau’n anweladwy i fyfyrwyr. + Bydd hyn yn gwneud dim ond yr eitem hwn yn weladwy i fyfyrwyr. + Bydd hyn yn gwneud dim ond yr eitem hwn yn anweladwy i fyfyrwyr. + Bydd hyn yn gwneud pob modiwl ac eitem yn weladwy i fyfyrwyr. + Bydd hyn yn gwneud dim ond y modiwlau’n weladwy i fyfyrwyr. + Bydd hyn yn gwneud pob modiwl ac eitem yn anweladwy i fyfyrwyr. + Dim ond ar gael gyda dolen + Trefnu argaeledd + Eitem wedi’i gyhoeddi + Eitem wedi’i ddatgyhoeddi + Dim Ond Modiwl wedi’i gyhoeddi + Modiwl a phob eitem wedi’u cyhoeddi + Modiwl a phob eitem wedi’u datgyhoeddi + Dim Ond Modiwlau wedi’u cyhoeddi + Pob Modiwl a phob eitem wedi’u cyhoeddi + Pob Modiwl a phob eitem wedi’u datgyhoeddi + Etifeddu o Gwrs + Aelodau o’r Cwrs + Aelodau Sefydliad + Cyhoeddus + Golygu Hawliau + Diweddaru + Argaeledd + Gwelededd + Ar gael o + Ar gael tan + O + Tan + Dyddiad + Amser + Clirio O Ddyddiad + Clirio Tan Ddyddiad + Gall y broses gymryd munud neu ddau. Rhaid i chi gau’r is-ffenestr neu adael y dudalen yn ystod y broses hon. + Nodyn + Pob Modiwl + Pob Modiwl ac Eitem + Modiwlau ac Eitemau wedi’u Dewis + Modiwlau wedi’u Dewis + Wrthi’n cyhoeddi + Wrthi’n datgyhoeddi + Ni fydd modiwlau ac eitemau sydd eisoes wedi’u prosesu yn dychwelyd i’w cyflwr blaenorol pan fyddwch chi’n rhoi’r gorau i’r broses. + Wedi llwyddo! + Wedi methu diweddaru + Diweddariad wedi’i ganslo + Cudd + Wedi’i Drefnu + Wedi Cyhoeddi + Wedi dad-gyhoeddi + + %.0f pwynt + %.0f pwynt + + Cyhoeddi + Dad-gyhoeddi + Cyhoeddi + Dad-gyhoeddi + Adnewyddu diff --git a/libs/pandares/src/main/res/values-da/strings.xml b/libs/pandares/src/main/res/values-da/strings.xml index 1f2c7986a5..11eea0a456 100644 --- a/libs/pandares/src/main/res/values-da/strings.xml +++ b/libs/pandares/src/main/res/values-da/strings.xml @@ -1519,4 +1519,76 @@ Yderligere fagindhold Kunne ikke opdatere oversigtens kortordren + Offentliggør alle moduler og elementer + Offentliggør kun moduler + Fjern offentliggørelse af alle moduler og elementer + Modulindstillinger + Offentliggør modul og alle elementer + Offentliggør kun modul + Fjern offentliggørelse af modul og alle elementer + Modulindstillinger for %s + Modulelementindstillinger for %s + Offentliggør modulelement + Fjern offentliggørelse af modulelement + Offentliggør? + Dette vil gøre kun modulet synligt for studerende. + Dette vil gøre modulet og alle elementer synlige for studerende. + Fjern offentliggørelse? + Dette vil gøre modulet og alle elementer usynlige for studerende. + Dette vil gøre kun dette element synligt for studerende. + Dette vil gøre kun dette element usynligt for studerende. + Dette vil gøre alle moduler og elementer synlige for studerende. + Dette vil gøre kun modulerne synlige for studerende. + Dette vil gøre alle moduler og elementer usynlige for studerende. + Kun tilgængelig med link + Planlæg tilgængelighed + Element offentliggjort + Elementet ikke offentliggjort + Kun modul offentliggjort + Modul og alle elementer offentliggjort + Modul og alle elementer er ikke offentliggjort + Kun moduler offentliggjort + Alle moduler og alle elementer offentliggjort + Alle moduler og alle elementer ikke offentliggjort + Arv fra fag + Fagdeltagere + Institutionsmedlemmer + Offentligt + Rediger tilladelser + Opdatering + Tilgængelighed + Synlighed + Tilgængelig fra + Tilgængelig indtil + Fra + Indtil + Dato + Tidspunkt + Ryd fra-dato + Ryd indtil-dato + Denne process kan tage nogle minutter. Du kan lukke modalen eller navigere væk fra siden under denne proces. + Bemærk + Alle moduler + Alle moduler og elementer + Udvalgte moduler og elementer + Udvalgte moduler + Offentliggør + Fjerner offentliggørelse + Moduler og elementer, der allerede er blevet behandlet, vil ikke blive ført tilbage til deres tidligere tilstand, når processen afbrydes. + Succes! + Opdatering mislykkedes + Opdatering annulleret + Skjult + Planlagt + Offentliggjort + Ikke offentliggjort + + %.0f point + %.0f point + + Offentliggør + Annuller offentliggørelse + Offentliggør + Annuller offentliggørelse + Opdater diff --git a/libs/pandares/src/main/res/values-de/strings.xml b/libs/pandares/src/main/res/values-de/strings.xml index a0b12c826d..ca039d8e36 100644 --- a/libs/pandares/src/main/res/values-de/strings.xml +++ b/libs/pandares/src/main/res/values-de/strings.xml @@ -1519,4 +1519,76 @@ Zusätzliche Kursinhalte Reihenfolge der Dashboard-Karten konnte nicht aktualisiert werden + Alle Module und Elemente veröffentlichen + Nur Module veröffentlichen + Alle Module und Elemente unveröffentlichen + Modul-Optionen + Modul und alle Elemente veröffentlichen + Nur Modul veröffentlichen + Modul und alle Elemente unveröffentlichen + Modul-Optionen für %s + Modulelement-Optionen für %s + Modulelement veröffentlichen + Modulelement unveröffentlichen + Veröffentlichen? + Dadurch wird nur das Modul für Studierende sichtbar. + Dadurch werden das Modul und alle Elemente für Studierende sichtbar. + Unveröffentlichen? + Dadurch werden das Modul und alle Elemente für Studierende unsichtbar. + Dadurch nur dieses Element für Studierende sichtbar. + Dadurch nur dieses Element für Studierende unsichtbar. + Dadurch werden alle Module und Elemente für Studierende sichtbar. + Dadurch werden nur die Module für Studierende sichtbar. + Dadurch werden alle Module und Elemente für Studierende unsichtbar. + Nur verfügbar mit Link + Verfügbarkeit planen + Element veröffentlicht + Element unveröffentlicht + Nur Modul veröffentlicht + Modul und alle Elemente veröffentlicht + Modul und alle Elemente unveröffentlicht + Nur Module veröffentlicht + Alle Module und alle Elemente veröffentlicht + Alle Module und alle Elemente unveröffentlicht + Von Kurs vererben + Kursteilnehmer*innen + Mitglieder der Institution + Öffentlich + Berechtigungen bearbeiten + Aktualisieren + Verfügbarkeit + Sichtbarkeit + Verfügbar von + Verfügbar bis + Von + Bis + Datum + Zeit + Von-Datum löschen + Bis-Datum löschen + Dieser Prozess kann ein paar Minuten dauern. Sie können während dieses Vorgangs das Modalfenster schließen oder von der Seite weg navigieren. + Anmerkung + Alle Module + Alle Module und Elemente + Ausgewählte Module und Elemente + Ausgewählte Module + Wird veröffentlicht + Unveröffentlicht + Module und Elemente, die bereits verarbeitet wurden, werden nicht in den vorherigen Zustand zurückversetzt, wenn der Vorgang abgebrochen wird. + Erfolg! + Aktualisierung fehlgeschlagen + Update abgebrochen + Ausgeblendet + Geplant + Veröffentlicht + Nicht veröffentlicht + + %.0f Pkt. + %.0f Pkte. + + Veröffentlichen + Veröffentlichung rückgängig machen + Veröffentlichen + Veröffentlichung rückgängig machen + Aktualisieren 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 3afd34175a..c50400db4e 100644 --- a/libs/pandares/src/main/res/values-en-rAU/strings.xml +++ b/libs/pandares/src/main/res/values-en-rAU/strings.xml @@ -1519,4 +1519,76 @@ Additional course content Failed to update Dashboard cards order + Publish all Modules and Items + Publish Modules only + Unpublish all Modules and Items + Module Options + Publish Module and all Items + Publish Module only + Unpublish Module and all Items + Module options for %s + Module item options for %s + Publish Module Item + Unpublish Module Item + Publish? + This will make only the module visible to students. + This will make the module and all items visible to students. + Unpublish? + This will make the module and all items invisible to students. + This will make only this item visible to students. + This will make only this item invisible to students. + This will make all modules and items visible to students. + This will make only the modules visible to students. + This will make all modules and items invisible to students. + Only available with link + Schedule availability + Item published + Item unpublished + Only Module published + Module and all Items published + Module and all Items unpublished + Only Modules published + All Modules and all Items published + All Modules and all Items unpublished + Inherit from Course + Course Members + Institution Members + Public + Edit Permissions + Update + Availability + Visibility + Available From + Available Until + From + Until + Date + Time + Clear From Date + Clear Until Date + This process could take a few minutes. You may close the modal or navigate away from the page during this process. + Note + All Modules + All Modules and Items + Selected Modules and Items + Selected Modules + Publishing + Unpublishing + Modules and items that have already been processed will not be reverted to their previous state when the process is discontinued. + Success! + Update failed + Update cancelled + Hidden + Scheduled + Published + Unpublished + + %.0f pt + %.0f pts + + Publish + Unpublish + Publish + Unpublish + Refresh 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 6eb60cea44..37e41c53e3 100644 --- a/libs/pandares/src/main/res/values-en-rCY/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCY/strings.xml @@ -1519,4 +1519,76 @@ Additional module content Failed to update Dashboard cards order + Publish all Units and Items + Publish Units only + Unpublish all Units and Items + Unit Options + Publish Unit and all Items + Publish Unit only + Unpublish Unit and all Items + Unit options for %s + Unit item options for %s + Publish Unit Item + Unpublish Unit Item + Publish? + This will make only the unit visible to students. + This will make the unit and all items visible to students. + Unpublish? + This will make the unit and all items invisible to students. + This will make only this item visible to students. + This will make only this item invisible to students. + This will make all units and items visible to students. + This will make only the units visible to students. + This will make all units and items invisible to students. + Only available with link + Schedule availability + Item published + Item unpublished + Only Unit published + Unit and all Items published + Unit and all Items unpublished + Only Units published + All Units and all Items published + All Units and all Items unpublished + Inherit from Module + Module Members + Institution Members + Public + Edit Permissions + Update + Availability + Visibility + Available from + Available until + From + Until + Date + Time + Clear From Date + Clear Until Date + This process could take a few minutes. You may close the modal or navigate away from the page during this process. + Note + All Units + All Units and Items + Selected Units and Items + Selected Units + Publishing + Unpublishing + Units and items that have already been processed will not be reverted to their previous state when the process is discontinued. + Success! + Update failed + Update cancelled + Hidden + Scheduled + Published + Unpublished + + %.0f pt + %.0f pts + + Publish + Unpublish + Publish + Unpublish + Refresh 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 670d7f2794..19d4d34729 100644 --- a/libs/pandares/src/main/res/values-en-rGB/strings.xml +++ b/libs/pandares/src/main/res/values-en-rGB/strings.xml @@ -1519,4 +1519,76 @@ Additional course content Failed to update Dashboard cards order + Publish all Modules and Items + Publish Modules only + Unpublish all Modules and Items + Module Options + Publish Module and all Items + Publish Module only + Unpublish Module and all Items + Module options for %s + Module item options for %s + Publish Module Item + Unpublish Module Item + Publish? + This will make only the module visible to students. + This will make the module and all items visible to students. + Unpublish? + This will make the module and all items invisible to students. + This will make only this item visible to students. + This will make only this item invisible to students. + This will make all modules and items visible to students. + This will make only the modules visible to students. + This will make all modules and items invisible to students. + Only available with link + Schedule availability + Item published + Item unpublished + Only Module published + Module and all Items published + Module and all Items unpublished + Only Modules published + All Modules and all Items published + All Modules and all Items unpublished + Inherit from Course + Course Members + Institution Members + Public + Edit Permissions + Update + Availability + Visibility + Available from + Available until + From + Until + Date + Time + Clear From Date + Clear Until Date + This process could take a few minutes. You may close the modal or navigate away from the page during this process. + Note + All Modules + All Modules and Items + Selected Modules and Items + Selected Modules + Publishing + Unpublishing + Modules and items that have already been processed will not be reverted to their previous state when the process is discontinued. + Success! + Update failed + Update cancelled + Hidden + Scheduled + Published + Unpublished + + %.0f pt + %.0f pts + + Publish + Unpublish + Publish + Unpublish + Refresh diff --git a/libs/pandares/src/main/res/values-es-rES/strings.xml b/libs/pandares/src/main/res/values-es-rES/strings.xml index f9d488801c..fa8a3fbdfe 100644 --- a/libs/pandares/src/main/res/values-es-rES/strings.xml +++ b/libs/pandares/src/main/res/values-es-rES/strings.xml @@ -1521,4 +1521,76 @@ Contenido del curso adicional Error al actualizar el orden de las tarjetas en el cuadro de mando + Publicar todos los módulos e ítems + Publicar solo módulos + No publicar ni todos los módulos ni ítems + Opciones del módulo + Publicar módulo y todos los ítems + Publicar solo el módulo + No publicar ni el módulo ni todos los ítems + Opciones del módulo para %s + Opciones de los ítems del módulo para %s + Publicar el ítem del módulo + No publicar el ítem del módulo + ¿Publicar? + Esto hará que solo el módulo sea visible para los estudiantes. + Esto hará que el módulo y todos los ítems sean visibles para los estudiantes. + ¿No publicar? + Esto hará que el módulo y todos los ítems sean invisibles para los estudiantes. + Esto hará que solo este ítem sea visible para los estudiantes. + Esto hará que solo este ítem sea invisible para los estudiantes. + Esto hará que todos los módulos e ítems sean visibles para los estudiantes. + Esto hará que solo el módulo sea invisible para los estudiantes. + Esto hará que todos los módulos e ítems sean invisibles para los estudiantes. + Disponible solo con enlace + Programar la disponibilidad + Ítem publicado + Ítem no publicado + Solo módulo publicado + Módulo y todos los ítems publicados + Módulo y todos los ítems no publicados + Solo módulos publicados + Todos los módulos y todos los ítems publicados + Todos los módulos y todos los ítems no publicados + Heredar del curso + Miembros del curso + Miembros de la institución + Público + Editar permisos + Actualizar + Disponibilidad + Visibilidad + Disponible desde + Disponible hasta + Desde + Hasta + Fecha + Hora + Borrar fecha desde + Borrar fecha hasta + Esto podría tardar unos minutos. Puede cerrar el modal o salir de la página durante este proceso. + Nota + Todos los contenidos + Todos los módulos e ítems + Módulos e ítems seleccionados + Módulos seleccionados + Publicando + Cancelando publicación + Los módulos e ítems que ya se hayan procesado no volverán a su estado anterior cuando el proceso esté interrumpido. + Hecho + No se ha podido actualizar + Actualización cancelada + Oculto + Programado + Publicado + No publicado + + %.0f punto + %.0f puntos + + Publicar + Cancelar publicación + Publicar + Cancelar publicación + Actualizar diff --git a/libs/pandares/src/main/res/values-es/strings.xml b/libs/pandares/src/main/res/values-es/strings.xml index a3d2c4eecc..a54666b3b4 100644 --- a/libs/pandares/src/main/res/values-es/strings.xml +++ b/libs/pandares/src/main/res/values-es/strings.xml @@ -1519,4 +1519,76 @@ Contenido adicional del curso No se pudo actualizar el orden de las tarjetas del tablero + Publicar todos los módulos e ítems + Publicar solo los módulos + Cancelar publicación de todos los módulos e ítems + Opciones de módulo + Publicar el módulo y todos los ítems + Publicar solo el módulo + Cancelar publicación del módulo y todos los ítems + Opciones de módulo para %s + Opciones de ítems del módulo para %s + Publicar ítem del módulo + Cancelar la publicación del ítem del módulo + ¿Quieres publicarlo? + Los estudiantes podrán ver solo el módulo. + Los estudiantes no podrán ver el módulo ni ningún ítem. + ¿Quieres cancelar la publicación? + Los estudiantes no podrán ver ningún módulo ni ítem. + Los estudiantes podrán ver solo este ítem. + Los estudiantes no podrán ver solo este ítem. + Los estudiantes podrán ver los módulos e ítems. + Los estudiantes podrán ver solo los módulos. + Los estudiantes no podrán ver ningún módulo ni ítem. + Solo disponible con enlace + Disponibilidad en el planificador + Ítem publicado + Ítem no publicado + Solo se publicó el módulo + Se publicaron todos los módulos e ítems + No se publicó el módulo ni ningún ítem + Solo se publicaron los módulos + Se publicaron todos los módulos e ítems + No se publicaron los módulos ni los ítems + Heredar del curso + Miembros del curso + Miembros de la Institución + Público + Editar permisos + Actualizar + Disponibilidad + Visibilidad + Disponible desde + Disponible hasta + Desde + Hasta + Fecha + Hora + Reestablecer fecha de inicio + Reestablecer fecha de finalización + Este proceso puede tardar unos minutos. Puede cerrar el modal o navegar fuera de la página durante el proceso. + Nota + Todos los módulos + Todos los módulos e ítems + Seleccionar módulos e ítems + Seleccionar módulos + Industria editorial + Cancelando publicación + Si se interrumpe el proceso, los módulos y los ítems que ya hayan sido procesados no volverán a su estado previo. + ¡Éxito! + Se produjo un error al actualizar + Actualización cancelada + Oculto + Programado + Publicado + No publicado + + %.0f punto + %.0f puntos + + Publicar + Cancelar publicación + Publicar + Cancelar publicación + Actualizar diff --git a/libs/pandares/src/main/res/values-fi/strings.xml b/libs/pandares/src/main/res/values-fi/strings.xml index fcaf19fae0..cf05853adb 100644 --- a/libs/pandares/src/main/res/values-fi/strings.xml +++ b/libs/pandares/src/main/res/values-fi/strings.xml @@ -1519,4 +1519,76 @@ LIsäkurssin sisältö. Hallintapaneelin koontinäytön korttien päivittäminen epäonnistui + Julkaise kaikki moduulit ja kohteet + Julkaise vain moduulit + Peruuta kaikkien moduulien ja kohteiden julkaisu + Moduulin asetukset + Julkaise moduuli ja kaikki kohteet + Julkaise vain moduuli + Peruuta kaikkien moduulien ja kohteiden julkaisu + Moduulin asetukset kohteelle %s + Moduulikohteen asetukset kohteelle %s + Julkaise moduulin kohde + Peruuta moduulin kohteen julkaisu + Julkaistaanko? + Tämä tekee ainoastaan moduulin näkyväksi opiskelijoille. + Tämä tekee moduulin ja kaikki kohteet näkyviksi opiskelijoille.. + Peruutetaanko julkaisu? + Tämä tekee moduulin ja kaikki kohteet näkymättömiksi opiskelijoille. + Tämä tekee ainoastaan tämän kohteen näkyväksi opiskelijoille. + Tämä tekee ainoastaan tämän kohteen näkymättömäksi opiskelijoille. + Tämä tekee kaikki moduulit ja kohteet näkyviksi opiskelijoille. + Tämä tekee ainoastaan mooduulit näkyviksi opiskelijoille. + Tämä tekee kaikki moduulit ja kohteet näkymättömiksi opiskelijoille. + Saatavissa linkin kanssa + Aikataulun saatavissa olo + Kohde julkaistu + Kohteen julkaisu peruutettu + Vain moduuli julkaistu + Moduuli ja kohteet julkaistu + Moduulin ja kohteiden julkistetaminen kumottu + Kaikki moduuliy julkaistu + Kaikki moduulit ja kohteet julkaistu + Kaikkien moduulien ja kohteiden julkaisu peruttu + Peri kurssilta + Kurssin jäsenet + Laitoksen jäsenet + Julkinen + Muokkaa oikeuksia + Päivitä + Saatavuus + Näkyvyys + Saatavissa alkaen + Saatavissa tähän päivämäärään saakka + Lähettäjä + Asti + Päivämäärä + Aika + Tyhjennä tästä päivämäärästä lähtien + Tyhjennä tähän päivämäärään asti + Tämä prosessi voi kestää muutamia minuutteja. Voit sulkea modaalin tai siirtyä pois sivulta tämän prosessin aikana. + Huomautus + Kaikki moduulit + Kaikki moduulit ja kohteet + Valitut moduulit ja kohteet + Valitut moduulit + Julkaistaan + Julkaisua kumotaan + Moduulit ja kohteet, jotka on jo käsitelty ei palauteta aikaisempaan tilaan, kun käsittely keskeytetään. + Onnistui! + Päivitys epäonnistui + Päivitys peruutettu + Piilotettu + Aikataulutettu + Julkaistu + Julkaisematon + + %.0f piste + %.0f pistettä + + Julkaise + Peruuta julkaisu + Julkaise + Peruuta julkaisu + Päivitä 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 ebabfdafd4..b0cc5e2491 100644 --- a/libs/pandares/src/main/res/values-fr-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-fr-rCA/strings.xml @@ -1519,4 +1519,76 @@ Contenu supplémentaire de cours Échec de la mise à jour de la commande des cartes du tableau de bord + Publier tous les modules et éléments + Publier les modules uniquement + Annuler la publication de tous les modules et éléments + Options du module + Publier le module et tous les éléments + Publier le module uniquement + Annuler la publication du module et tous les éléments + Options de module pour %s + Options de l’élément de module pour %s + Publier un élément du module + Annuler la publication d’un élément du module + Publier? + Ainsi, seul le module sera visible par les étudiants. + Le module et tous ses éléments seront alors visibles par les étudiants. + Annuler la publication? + Cela rendra le module et tous les éléments invisibles pour les étudiants. + Seul cet élément sera visible par les étudiants. + Seul cet élément sera invisible pour les étudiants. + Tous les modules et éléments seront ainsi visibles par les étudiants. + Seuls les modules seront visibles par les étudiants. + Cela rendra tous les modules et éléments invisibles pour les étudiants. + Seulement disponible avec un lien + Programmer les disponibilités + Article publié + Article non publié + Seul le module est publié + Module et tous les éléments publiés + Module et tous les éléments non publiés + Uniquement les modules publiés + Tous les modules et tous les articles publiés + Tous les modules et tous les éléments ne sont pas publiés + Hériter depuis le cours + Membres du cours + Membres de l’institution + Public + Modifier les autorisations + Mettre à jour + Disponibilité + Visibilité + Disponible depuis + Disponible jusqu’à + De + Jusqu’au + Date + Heure + Effacer la date de début + Effacer la date d’échéance + Ce processus peut prendre quelques minutes. Vous pouvez fermer le modal ou quitter la page pendant ce processus. + Remarque + Tous les modules + Tous les modules et éléments + Modules et éléments sélectionnés + Modules sélectionnés + Publication + Annulation de la publication + Les modules et les éléments qui ont déjà été traités ne retrouveront pas leur état précédent lorsque le processus sera interrompu. + Succès! + Échec de la mise à jour + Mise à jour annulée + Masqué + Planifié + Publié + Non publié + + %.0f pt + %.0f pts + + Publier + Annuler la publication + Publier + Annuler la publication + Actualiser diff --git a/libs/pandares/src/main/res/values-fr/strings.xml b/libs/pandares/src/main/res/values-fr/strings.xml index bfecb2f221..e8781ce24a 100644 --- a/libs/pandares/src/main/res/values-fr/strings.xml +++ b/libs/pandares/src/main/res/values-fr/strings.xml @@ -1519,4 +1519,76 @@ Contenu de cours supplémentaire Impossible de mettre à jour l’ordre des cartes du tableau de bord + Publier tous les modules et les éléments + Publier les modules uniquement + Annuler la publication de tous les modules et des éléments + Options de module + Publier le module et tous les éléments + Publier le module uniquement + Annuler la publication du module et de tous les éléments + Options de module pour %s + Options d\'élément de module pour %s + Publier l\'élément de module + Annuler la publication de l\'élément de module + Publier ? + Seul le module sera visible pour les élèves. + Le module et tous les éléments seront visibles pour les élèves. + Annuler la publication ? + Le module et tous les éléments seront invisibles pour les élèves. + Seul cet élément sera visible pour les élèves. + Seul cet élément sera invisible pour les élèves. + Tous les modules et les éléments seront visibles pour les élèves. + Seuls les modules seront visibles pour les élèves. + Tous les modules et les éléments seront invisibles pour les élèves. + Accessible uniquement via un lien + Calendrier de disponibilités + Élément publié + Élément non publié + Module publié uniquement + Module et tous les éléments publiés + Module et tous les éléments non publiés + Modules publiés uniquement + Tous les modules et les éléments publiés + Tous les modules et les éléments non publiés + Hériter du cours + Membres du cours + Membres de l\'établissement + Public + Modifier les permissions + Mettre à jour + Disponibilité + Visibilité + Disponible depuis le + Disponible jusqu’au + Du + Au + Date + Temps + Effacer la date de début + Effacer la date de fin + Ce processus peut prendre quelques minutes. Vous pouvez fermer la fenêtre modale ou quitter la page pendant ce processus. + Remarque + Tous les modules + Tous les modules et les éléments + Les modules et les éléments sélectionnés + Les modules sélectionnés + Publication + Annulation de la publication + Les modules et éléments déjà traités ne seront pas ramenés à leur état initial si le processus est interrompu. + Réussite ! + Échec de la mise à jour + Mise à jour annulée + Masqué + Prévus + Publié + Non publié + + %.0f pt + %.0f pts + + Publier + Ne pas publier + Publier + Ne pas publier + Actualiser diff --git a/libs/pandares/src/main/res/values-ht/strings.xml b/libs/pandares/src/main/res/values-ht/strings.xml index 2a8d1c352c..f596c35e02 100644 --- a/libs/pandares/src/main/res/values-ht/strings.xml +++ b/libs/pandares/src/main/res/values-ht/strings.xml @@ -1519,4 +1519,76 @@ Plis kontni kou Echèk mizajou kòmann kat tablodbò a + Pibliye Tout Modil yo ak Atik yo + Pibliye Modil yo sèlman + Depibliye tout Modil yo ak Atik yo + Opsyon Modil yo + Pibliye Modil la ak tout Atik yo + Pibliye Modil sèlman + Depibliye Modil la ak tout Atik yo + Opsyon modil pou %s + Opsyon modil atik pou %s + Pibliye Modil Atik + Depibliye Modil Atik + Pibliye? + Sa ap rann sèlman modil la vizib pou etidyan yo. + Sa ap rann modil la ak tout atik yo vizib pou etidyan yo. + Depibliye + Sa ap rann modil la ak tout atik yo envizib pou etidyan yo. + Sa ap rann sèlman atik sa a vizib pou etidyan yo. + Sa ap rann sèlman atik sa a envizib pou etidyan yo. + Sa ap rann tout modil yo ak atik yo vizib pou etidyan yo. + Sa ap rann sèlman modil yo vizib pou etidyan yo. + Sa ap rann tout modil yo ak atik yo envizib pou etidyan yo. + Disponib sèlman ak lyen + Orè Disponib + Atik pibliye + Atik Depibliye + Modil ki pibliye sèlman + Modil ak tout Atik ki pibliye + Modil ak Tout Atik ki depibliye yo + Modil ki pibliye yo sèlman + Tout Modil ak tout Atik ki pibliye + Tout Modil ak tout Atik ki depibliye + Eritaj Kou + Manm Kou + Manm Enstitisyon + Piblik + Modifye Pèmisyon yo + Aktyalize + Disponibilite + Vizibilite + Disponib Depi + Disponib Jiska + De + Jiska + Dat + Tan + Efase Depi Dat + Efase Jiska Dat + Pwosesis sa a ka pran kèk minit. Ou ka fèmen fenèt modal la oswa kite paj la pandan pwosesis sa a. + Nòt + Tout Eleman + Tout Modil yo + Modil ak Atik Seleksyone yo + Modil Seleksyone yo + Pibliye + Pa Pibliye + Modil ak atik ki te deja trete yo pa p retounen nan eta yo te ye anvan an lè pwosesis la sipann. + Reyisi! + Mizajou echwe + Mizajou anile + Kache + Orè + Pibliye + Pa Pibliye + + %.0f pwen + %.0f pwen + + Pibliye + Pa Pibliye + Pibliye + Pa Pibliye + Aktyalize diff --git a/libs/pandares/src/main/res/values-id/strings.xml b/libs/pandares/src/main/res/values-id/strings.xml index 37180e7e64..24cf00da35 100644 --- a/libs/pandares/src/main/res/values-id/strings.xml +++ b/libs/pandares/src/main/res/values-id/strings.xml @@ -1522,4 +1522,76 @@ Konten kursus tambahan Gagal memperbarui urutan kartu Dashboard + Terbitkan semua Modul dan Item + Terbitkan Modul saja + Batalkan Penerbitan semua Modul dan Item + Opsi Modul + Terbitkan Modul dan semua Item + Terbitkan Modul saja + Batalkan Penerbitan Modul dan semua Item + Opsi modul untuk %s + Opsi item modul untuk %s + Terbitkan Item Modul + Batalkan Penerbitan Item Modul + Terbitkan? + Ini akan membuat hanya modul tampak oleh siswa. + Ini akan membuat modul dan semua item tampak oleh siswa. + Batalkan Penerbitan? + Ini akan membuat modul dan semua item tidak tampak oleh siswa. + Ini akan membuat hanya item tampak oleh siswa. + Ini akan membuat hanya item tidak tampak oleh siswa. + Ini akan membuat semua modul dan item tampak oleh siswa. + Ini akan membuat hanya modul tampak oleh siswa. + Ini akan membuat semua modul dan item tidak tampak oleh siswa. + Hanya tersedia dengan tautan + Ketersediaan jadwal + Item diterbitkan + Item batal diterbitkan + Hanya Modul diterbitkan + Modul dan semua item diterbitkan + Modul dan semua item batal diterbitkan + Hanya Modul diterbitkan + Semua Modul dan semua Item diterbitkan + Semua Modul dan semua Item batal diterbitkan + Warisi dari Kursus + Anggota Kursus + Anggota Institusi + Publik + Edit Izin + Pembaruan + Ketersediaan + Visibilitas + Tersedia Mulai + Tersedia Hingga + Dari + Hingga + Tanggal + Waktu + Kosongkan Tanggal Mulai + Kosongkan Tanggal Hingga + Proses ini dapat membutuhkan waktu beberapa menit. Anda dapat menutup modal atau menjauh dari halaman ini selama proses ini. + Catatan + Semua Modul + Semua Modul dan Item + Modul dan Item Pilihan + Modul Pilihan + Menerbitkan + Batal Diterbitkan + Modul dan item yang sudah diproses tidak akan dikembalikan ke kondisi sebelumnya ketika proses dihentikan. + Sukses! + Gagal memperbarui + Pembaruan dibatalkan + Tersembunyi + Dijadwalkan + Diterbitkan + Belum Diterbitkan + + %.0f poin + %.0f poin + + Terbitkan + Batalkan Terbit + Terbitkan + Batalkan Terbit + Muat Ulang diff --git a/libs/pandares/src/main/res/values-is/strings.xml b/libs/pandares/src/main/res/values-is/strings.xml index e72b1018dc..4c37c01555 100644 --- a/libs/pandares/src/main/res/values-is/strings.xml +++ b/libs/pandares/src/main/res/values-is/strings.xml @@ -1519,4 +1519,76 @@ Viðbótar námskeiðsefni Ekki tókst að uppfæra röð Skjáborðsspjalda + Birta allt námsefni og atriði + Birta eingöngu námsefni + Fela allt námsefni og atriði + Valkostir námsefnis + Birta námsefni og öll atriði + Birta eingöngu námsefni + Fela námsefni og öll atriði + Valkostir námsefnis fyrir %s + Valkostir námsefnisatriðis fyrir %s + Birta námsefnisatriði + Fela námsefnisatriði + Birta? + Þetta mun gera aðeins námsefnið sýnilegt nemendum. + Þetta mun gera námsefnið og öll atriði sýnileg nemendum. + Fela? + Þetta mun gera námsefnið og öll atriði ósýnileg nemendum. + Þetta mun aðeins gera þetta atriði sýnilegt nemendum. + Þetta mun aðeins gera þetta atriði ósýnilegt nemendum. + Þetta mun gera allt námsefni og atriði sýnileg nemendum. + Þetta mun aðeins gera námsefnið sýnilegar nemendum. + Þetta mun gera allt námsefni og atriði ósýnileg nemendum. + Eingöngu tiltækt með tengli + Áætla tiltækileika + Atriði birt + Atriði falið + Aðeins námsefni birt + Námsefni og öll atriði birt + Námsefni og öll atriði óbirt + Aðeins námsefni birt + Allt námsefni og öll atriði birt + Allt námsefni og öll atriði falin + Erft frá námskeiði + Meðlimir námskeiðs + Meðlimir stofnunar + Opinbert + Breyta aðgangsheimildum + Uppfæra + Tiltækileiki + Sýnileiki + Tiltækt frá + Tiltækt til + Frá + Þar til + Dagsetning + Tími + Hreinsa frá dagsetningu + Hreinsa til dagsetningu + Þetta ferli getur tekið nokkrar mínútur. Þú getur lokað staðfestingarglugganum eða farið af síðunni meðan á þessu ferli stendur. + Athugasemd + Allt námsefni + Allt námsefni og atriði + Valið námsefni og atriði + Valið námsefni + Birtir + Felur + Námsefni og atriði sem þegar hafa verið unnin verða ekki færð í fyrra ástand þegar ferlinu er hætt. + Tókst! + Uppfærsla mistókst + Hætt við uppfærslu + Falið + Tímasett + Birt + Falið + + %.0f punktur + %.0f punktar + + Birta + Fela + Birta + Fela + Glæða diff --git a/libs/pandares/src/main/res/values-it/strings.xml b/libs/pandares/src/main/res/values-it/strings.xml index d2718c0d31..4b3ad2d5fb 100644 --- a/libs/pandares/src/main/res/values-it/strings.xml +++ b/libs/pandares/src/main/res/values-it/strings.xml @@ -1519,4 +1519,76 @@ Altro contenuto del corso Impossibile aggiornare l’ordine delle schede dashboard + Pubblica tutti i moduli e gli elementi + Pubblica solo i moduli + Non pubblicare tutti i moduli e gli elementi + Opzioni modulo + Pubblica il modulo e tutti gli elementi + Pubblica solo il modulo + Non pubblicare il modulo e tutti gli elementi + Opzioni modulo per %s + Opzioni elemento modulo per %s + Pubblica elemento modulo + Non pubblicare elemento modulo + Pubblicare? + Tale operazione renderà visibile solo il modulo agli studenti. + Tale operazione renderà visibile il modulo e tutti gli elementi agli studenti. + Annullare pubblicazione? + Tale operazione renderà invisibile il modulo e tutti gli elementi agli studenti. + Tale operazione renderà visibile solo questo elemento agli studenti. + Tale operazione renderà invisibile solo questo elemento agli studenti. + Tale operazione renderà visibili tutti i moduli e gli elementi agli studenti. + Tale operazione renderà visibili solo i moduli agli studenti. + Tale operazione renderà invisibile tutti i moduli e gli elementi agli studenti. + Disponibile solo con link + Pianifica disponibilità + Elemento pubblicato + Elemento non pubblicato + Pubblicato solo modulo + Modulo e tutti gli elementi pubblicati + Modulo e tutti gli elementi non pubblicati + Pubblicati solo i moduli + Tutti i moduli e tutti gli elementi pubblicati + Tutti i moduli e tutti gli elementi non pubblicati + Eredita da corso + Membri del corso + Membri dell’istituto + Pubblico + Modifica Permessi + Aggiorna + Disponibilità + Visibilità + Disponibile da + Disponibile fino a + Da + Fino a + Data + Ora + Data di inizio cancellazione + Data di fine cancellazione + Questo processo potrebbe richiedere alcuni minuti. Durante questo processo è possibile chiudere il modale o uscire dalla pagina. + Nota + Tutti i moduli + Tutti i moduli e gli elementi + Moduli ed elementi selezionati + Moduli selezionati + Pubblicazione + Annullamento pubblicazione + Quando il processo viene interrotto, i moduli e gli elementi già elaborati non saranno riportati al loro stato precedente. + Operazione riuscita. + Aggiornamento non riuscito + Aggiornamento annullato + Nascosto + Programmato + Pubblicato + Non pubblicato + + %.0f pt + %.0f pt. + + Pubblica + Annulla pubblicazione + Pubblica + Annulla pubblicazione + Aggiorna diff --git a/libs/pandares/src/main/res/values-ja/strings.xml b/libs/pandares/src/main/res/values-ja/strings.xml index 5de5425ac7..428f0982fd 100644 --- a/libs/pandares/src/main/res/values-ja/strings.xml +++ b/libs/pandares/src/main/res/values-ja/strings.xml @@ -388,7 +388,7 @@ メッセージオプション 転送 すべてに返信 - アーカイブ解凍 + アーカイブの解除 このメッセージのコピーを本当に削除しますか?この操作は元に戻すことはできません。 この会話のコピーを本当に削除しますか?この操作は元に戻すことはできません。 この操作を実行できません。接続を確認して、もう一度お試しください。 @@ -1370,7 +1370,7 @@ 削除 アーカイブ - アーカイブ解凍 + アーカイブの解除 既読にする 未読にする スター @@ -1495,4 +1495,75 @@ 追加のコースコンテンツ ダッシュボードのカードの順番を更新できませんでした + すべてのモジュールと項目を公開する + モジュールのみ公開する + すべてのモジュールと項目の公開を解除する + モジュールオプション + モジュールとすべての項目を公開する + モジュールのみ公開する + モジュールとすべての項目の公開を解除する + %sモジュールオプション + %sモジュール項目オプション + モジュール項目を公開する + モジュール項目を非公開にする + 公開しますか? + これはモジュールのみを受講者に公開します。 + これはモジュールおよびすべての項目を受講者に公開します。 + 非公開にしますか? + モジュールとすべての項目を受講者に非公開にします。 + この項目のみ受講者に見えるようにします。 + これはこの項目のみを受講者に公開します。 + これはすべてのモジュールおよび項目を受講者に公開します。 + これはモジュールのみを受講者に公開します。 + これはすべてのモジュールと項目を受講者に非公開にします。 + リンクを知っている受講者のみ利用可能 + 期間を設定する + 項目が公開 + 項目が非公開 + モジュールのみ公開 + モジュールと全項目が公開 + モジュールと全項目が非公開 + モジュールのみ公開 + すべてのモジュールとすべての項目が公開 + すべてのモジュールとすべての項目が非公開 + コースから継承 + コースメンバー + 教育機関メンバー + 公開 + 承認を編集 + 更新 + 利用可能性 + 可視性 + 開始日時 + 終了日時 + From(~から) + 利用終了日時 + 日付 + 制限時間 + クリア開始日 + クリア終了日 + この処理には数分かかることがあります。この処理の間、モーダルを閉じたり、ページから移動したりできます。 + + すべてのモジュール + すべてのモジュールと項目 + モジュールおよび項目を選択 + モジュールを選択 + 公開 + 公開取り消し中 + すでに処理されているモジュールや項目は、処理を中止しても以前の状態には戻りません。 + 成功! + 更新失敗 + 更新がキャンセルされました。 + 非表示 + スケジュール済み + 公開済み + 非公開 + + %.0f点 + + 公開 + 非公開 + 公開 + 非公開 + 更新 diff --git a/libs/pandares/src/main/res/values-mi/strings.xml b/libs/pandares/src/main/res/values-mi/strings.xml index ac24ae565b..dbc81d51fe 100644 --- a/libs/pandares/src/main/res/values-mi/strings.xml +++ b/libs/pandares/src/main/res/values-mi/strings.xml @@ -1519,4 +1519,76 @@ Ihirangi akoranga taapiri I rahua te whakahōu i te raupapa kāri Papatohu + Whakaputa i ngā Kōwae me ngā Tūemi katoa + Whakaputa Kōwae anake + Wete whakaputa i ngā Kōwae me ngā Tūemi katoa + Ngā Kōwhiringa Kōwae + Whakaputa Kōwae me ngā Tūemi Katoa + Whakaputa Kōwae anake + Wete whakaputa i te Kōwae me ngā Tūemi katoa + Ngā kōwhiringa kōwae mō %s + Ngā kōwhiringa tūemi kōwae mō %s + Whakaputa Tūemi Kōwae + Wete whakaputa i te Tūemi Kōwae + Whakaputa? + Mā tēnei anake e kitea ai te kōwae ki ngā ākonga. + Mā tēnei ka kitea te kōwae me ngā tūemi katoa e kitea ana e ngā ākonga. + Weteputa? + Mā tēnei ka noho te kōwae me ngā tūemi katoa kāore e kitea ana e ngā ākonga. + Mā tēnei anake e kitea ai tēnei tūemi ki ngā ākonga. + Mā tēnei anake e mahi i tēnei tūemi kāore e kitea ana e ngā ākonga. + Mā tēnei ka kitea ngā kōwae me ngā tūemi katoa ki ngā ākonga. + Mā tēnei anake e kitea ai ngā kōwae ki ngā ākonga. + Mā tēnei ka kitea ngā kōwae me ngā tūemi katoa ki ngā ākonga. + E waatea ana me te hono + Whakaritea te waate + Tūemi i whakaputaina + Kāore i whakaputaina te tūemi + Kōwae anake i whakaputaina + Kōwae me ngā Tūemi katoa i whakaputaina + Kōwae me ngā Tūemi katoa kāore anō kia whakaputaina + Ngā Kōwae anake i whakaputaina + Ngā Kōwae Katoa me ngā Tūemi katoa i whakaputaina + Ngā Kōwae Katoa me ngā Tūemi katoa kāore i whakaputaina + Tukua mai i te Akoranga + Nga Mema Akoranga + Nga Mema Whare + tūmatanui + Whakatika Whakaaetanga + Whakahou + Wātea + Aritanga + Wātea Mai + Wātea Kia + + Tae noa ki + + + Ūkui Mai i te Rā + Ūkui tae noa ki te Rā + He meneti torutoru pea tenei tukanga. Ka taea e koe te kati i te matapihi aratau ka waiho ranei i te wharangi i tenei mahi. + Tuhipoka + Ngā kōwae katoa + Ngā Kōwae me ngā Tūemi Katoa + Ngā Kōwae me ngā Tūemi kua tīpakohia + Ngā Kōwae kua Tīpakohia + Whakaputa ana + Wete whakaputa ana + Ko nga waahanga me nga mea kua oti te tukatuka e kore e hoki ki to ratau ahuatanga o mua ka haukotia te tukanga. + Angitu! + I rahua te whakahou + Kua whakakorea te whakahōu + Huna + Kua Raupapatia + I whakaputaina + Kaore ii whakaputaina + + %.0f koinga + %.0f ngā koinga + + Whakaputa + Kaore i tāngia + Whakaputa + Kaore i tāngia + Whakahouhia diff --git a/libs/pandares/src/main/res/values-ms/strings.xml b/libs/pandares/src/main/res/values-ms/strings.xml index b832724d45..0e53a37475 100644 --- a/libs/pandares/src/main/res/values-ms/strings.xml +++ b/libs/pandares/src/main/res/values-ms/strings.xml @@ -1525,4 +1525,76 @@ Kandungan kursus tambahan Gagal untuk mengemas kini susunan kad Papan Pemuka + Terbitkan semua Modul dan Item + Terbitkan Modul sahaja + Nyahterbit semua Modul dan Item + Pilihan Modul + Terbitkan Modul dan semua Item + Terbitkan Modul sahaja + Nyahterbit Modul dan semua Item + Pilihan Modul untuk %s + Pilihan item Modul untuk %s + Terbitkan Item Modul + Nyahterbit Item Modul + Terbitkan? + Ini akan menjadikan modul ini sahaja kelihatan kepada pelajar. + Ini akan menjadikan modul ini dan semua item kelihatan kepada pelajar. + Nyahterbit? + Ini akan menjadikan modul ini dan semua item tidak kelihatan kepada pelajar. + Ini akan menjadikan item ini sahaja kelihatan kepada pelajar. + Ini akan menjadikan item ini sahaja tidak kelihatan kepada pelajar. + Ini akan menjadikan semua modul dan item kelihatan kepada pelajar. + Ini akan menjadikan modul-modul ini sahaja kelihatan kepada pelajar. + Ini akan menjadikan semua modul dan item ini tidak kelihatan kepada pelajar. + Hanya tersedia dengan pautan + Jadualkan ketersediaan + Item diterbitkan + Item dinyahterbit + Hanya Modul diterbitkan + Modul dan semua Item diterbitkan + Modul dan semua Item dinyahterbit + Hanya Modul diterbitkan + Semua Modul dan semua Item diterbitkan + Semua Modul dan semua Item dinyahterbit + Warisi daripada Kursus + Ahli Kursus + Ahli Institusi + Umum + Edit Keizinan + Kemas kini + Ketersediaan + Kebolehlihatan + Tersedia Dari + Tersedia Hingga + Dari + Sehingga + Tarikh + Masa + Kosongkan dari Tarikh + Kosong Sehingga Tarikh + Proses ini mungkin mengambil masa beberapa minit. Anda boleh menutup modal atau menavigasi keluar daripada halaman semasa proses ini. + Nota + Semua Modul + Semua Modul dan Item + Modul dan Item terpilih + Modul terpilih + Menerbitkan + Menyahterbit + Modul dan item yang telah diproses tidak akan ditukar kembali kepada keadaan sebelumnya apabila proses dihentikan. + Berjaya! + Kemas Kini gagal + Kemas kini dibatalkan + Tersembunyi. + Dijadualkan + Telah diterbitkan + Dinyahterbit + + %.0f mata + %.0f mata + + Terbitkan + Nyahterbit + Terbitkan + Nyahterbit + Segar semula diff --git a/libs/pandares/src/main/res/values-nb/strings.xml b/libs/pandares/src/main/res/values-nb/strings.xml index 2103ae6842..49f959e434 100644 --- a/libs/pandares/src/main/res/values-nb/strings.xml +++ b/libs/pandares/src/main/res/values-nb/strings.xml @@ -1520,4 +1520,76 @@ Ytterligere emneinnhold Kunne ikke oppdatere rekkefølge på Dashbord-kort + Publiser alle moduler og elementer + Publiser kun moduler + Avpubliser alle moduler og elementer + Modulalternativer + Publiser modul og alle elementer + Publiser kun moduler + Avpubliser modul og alle elementer + Modulalternativer for %s + Modulelementalternativer for %s + Publiser modulelement + Avpubliser modulelement + Publisere? + Dette vil gjøre kun modulen synlig for studenter. + Dette vil gjøre modulen og alle elementer synlige for studenter. + Avpublisere? + Dette vil gjøre modulen og alle elementer usynlige for studenter. + Dette vil gjøre kun dette elementet synlig for studenter. + Dette vil gjøre kun dette elementet usynlig for studenter. + Dette vil gjøre alle moduler og elementer synlige for studenter. + Dette vil gjøre kun modulene synlig for studenter. + Dette vil gjøre alle moduler og elementer usynlige for studenter. + Kun tilgjengelig med lenke + Planlegg tilgjengelighet + Element publisert + Element avpublisert + Kun modul publisert + Modul og alle elementer publisert + Modul og alle elementer avpublisert + Alle moduler publisert + Alle moduler og alle elementer publisert + Alle moduler og alle elementer avpublisert + Arve fra emne + Emnemedlemmer + Institusjonsmedlemmer + Offentlig + Redigeringstillatelser + Oppdater + Tilgjengelighet + Synlighet + Tilgjengelig fra + Tilgjengelig frem til + Fra + Inntil + Dato + Tid + Slett fra dato + Slett til dato + Denne prosessen kan ta noen minutter. Du kan lukke modalen eller navigere bor fra siden under denne prosessen. + Merknad + Alle moduler + Alle moduler og elementer + Valgte moduler og elementer + Valgte moduler + Publiserer + Avpubliserer + Moduler og elementer som allerede er behandlet, vil ikke bli tilbakestilt til tidligere tilstand når prosessen avbrytes. + Vellykket! + Oppdatering mislyktes + Oppdatering avbrutt + Skjult + Planlagt + Publisert + Upublisert + + %.0f poeng + %.0f poeng + + Publiser + Avpubliser + Publiser + Avpubliser + Oppdater diff --git a/libs/pandares/src/main/res/values-nl/strings.xml b/libs/pandares/src/main/res/values-nl/strings.xml index a71a87122c..8a798ac782 100644 --- a/libs/pandares/src/main/res/values-nl/strings.xml +++ b/libs/pandares/src/main/res/values-nl/strings.xml @@ -1519,4 +1519,76 @@ Aanvullende cursusinhoud Kan de volgorde van Dashboard-kaarten niet wijzigen + Alle modules en items publiceren + Alleen modules publiceren + Publicatie van alle modules en items ongedaan maken + Moduleopties + Module en alle items publiceren + Alleen module publiceren + Publicatie van module en alle items ongedaan maken + Moduleopties voor %s + Module-itemopties voor %s + Module-item publiceren + Publicatie van module-item ongedaan maken + Publiceren? + Hiermee wordt alleen de module zichtbaar voor studenten. + Hiermee worden de module en alle items zichtbaar voor studenten. + Publicatie ongedaan maken? + Hiermee worden de module en alle items onzichtbaar voor studenten. + Hiermee wordt alleen dit item zichtbaar voor studenten. + Hiermee wordt alleen dit item onzichtbaar voor studenten. + Hiermee worden alle modules en items zichtbaar voor studenten. + Hiermee worden alleen de modules zichtbaar voor studenten. + Hiermee worden alle modules en items onzichtbaar voor studenten. + Alleen beschikbaar met koppeling + Beschikbaarheid van schema + Item gepubliceerd + Publicatie van item ongedaan gemaakt + Alleen module gepubliceerd + Module en alle items gepubliceerd + Publicatie van module en alle items ongedaan gemaakt + Alleen modules gepubliceerd + Alle modules en items gepubliceerd + Publicatie van alle modules en items ongedaan gemaakt + Overnemen uit cursus + Cursusdeelnemers + Leden van de organisatie + Openbaar + Machtigingen bewerken + Bijwerken + Beschikbaarheid + Zichtbaarheid + Beschikbaar vanaf + Beschikbaar tot + Van + Tot + Datum + Tijd + Datum Van wissen + Datum Tot wissen + Dit proces kan een paar minuten duren. Je kunt tijdens dit proces de modal sluiten of de pagina verlaten. + Opmerking + Alle modules + Alle modules en items + Geselecteerde modules en items + Geselecteerde modules + Bezig met publiceren + Publicatie ongedaan maken + Modules en items die al zijn verwerkt, worden niet naar hun eerdere status teruggezet wanneer het proces wordt stopgezet. + Gelukt! + Bijwerken mislukt + Bijwerken geannuleerd + Verborgen + Gepland + Gepubliceerd + Niet-gepubliceerd + + %.0f pt + %.0f punten + + Publiceren + Publicatie ongedaan maken + Publiceren + Publicatie ongedaan maken + Vernieuwen diff --git a/libs/pandares/src/main/res/values-pl/strings.xml b/libs/pandares/src/main/res/values-pl/strings.xml index 3506aef4af..8d30608d7d 100644 --- a/libs/pandares/src/main/res/values-pl/strings.xml +++ b/libs/pandares/src/main/res/values-pl/strings.xml @@ -1567,4 +1567,78 @@ Dodatkowa zawartość kursu Nie udało się zaktualizować kolejności kart pulpitu nawigacyjnego + Publikuj wszystkie moduły i elementy + Publikuj tylko moduły + Cofnij publikację wszystkich modułów i elementów + Opcje modułu + Publikuj moduł i wszystkie elementy + Publikuj tylko moduł + Cofnij publikację modułu i wszystkich elementów + Opcje modułu dla %s + Opcje elementów modułu dla %s + Publikuj element modułu + Cofnij publikację elementu modułu + Publikować? + Dzięki temu tylko moduły będą widoczne dla uczestników. + Dzięki temu ten moduł i wszystkie elementy będą widoczne dla uczestników. + Cofnąć publikację? + Dzięki temu ten moduł i wszystkie elementy będą niewidoczne dla uczestników. + Dzięki temu tylko ten element będzie widoczny dla uczestników. + Dzięki temu tylko ten element będzie niewidoczny dla uczestników. + Dzięki temu wszystkie moduły i elementy będą widoczne dla uczestników. + Dzięki temu tylko moduły będą widoczne dla uczestników. + Dzięki temu wszystkie moduły i elementy będą niewidoczne dla uczestników. + Dostępne tylko z łączem + Zaplanuj dostępność + Opublikowano element + Cofnięto publikację elementu + Opublikowano tylko moduł + Opublikowano moduł i wszystkie elementy + Cofnięto publikację modułu i wszystkich elementów + Opublikowano tylko moduły + Opublikowano wszystkie moduły i elementy + Cofnięto publikację wszystkich modułów i elementów + Odziedzicz z kursu + Uczestnicy kursu + Członkowie instytucji + Publiczny + Edytuj uprawnienia + Zaktualizuj + Dostępność + Widoczność + Dostępne od + Dostępne do + Z + Do + Data + Czas + Wyczyść od dnia + Wyczyść do dnia + Proces ten może zająć kilka minut. W trakcie tego procesu można zamknąć okno dialogowe lub przejść poza stronę. + Uwaga + Wszystkie moduły + Wszystkie moduły i elementy + Wybrano wszystkie moduły i elementy + Wybrano moduły + Publikowanie + Cofanie publikacji + Przetworzone moduły i elementy nie zostaną przywrócone do poprzedniego stanu, jeśli proces zostanie przerwany. + Zakończono powodzeniem! + Aktualizacja nie powiodła się + Anulowano aktualizacje + Ukryte + Zaplanowane + Opublikowane + Nieopublikowane + + %.0f pkt + %.0f pkt + %.0f pkt + %.0f pkt + + Publikuj + Cofnij publikowanie + Publikuj + Cofnij publikowanie + Odśwież 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 fc76fee792..602ae0fcc2 100644 --- a/libs/pandares/src/main/res/values-pt-rBR/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rBR/strings.xml @@ -1519,4 +1519,76 @@ Conteúdo adicional do curso Falha ao atualizar a ordem dos cartões do Painel + Publicar todos os módulos e itens + Publicar somente módulos + Cancelar a publicação de todos os módulos e itens + Opções de módulo + Publicar o módulo e todos os itens + Somente publicar módulo + Cancelar a publicação do módulo e de todos os itens + Opções de módulo para %s + Opções de itens do módulo para %s + Publicar item do módulo + Cancelar a publicação do item do módulo + Publicar? + Isso fará com que apenas o módulo fique visível para os alunos. + Isso tornará o módulo e todos os itens visíveis para os alunos. + Cancelar a publicação? + Isso fará com que o módulo e todos os itens fiquem invisíveis para os alunos. + Isso fará com que apenas esse item fique visível para os alunos. + Isso fará com que apenas esse item fique invisível para os alunos. + Isso fará com que todos os módulos e itens fiquem visíveis para os alunos. + Isso fará com que apenas os módulos fiquem visíveis para os alunos. + Isso fará com que todos os módulos e itens fiquem invisíveis para os alunos. + Disponível apenas com link + Disponibilidade de agendamento + Item publicado + Item não publicado + Somente o módulo publicado + Módulo e todos os itens publicados + Módulo e todos os itens não publicados + Somente módulos publicados + Todos os módulos e todos os itens publicados + Todos os módulos e todos os itens não publicados + Herdar do curso + Membros do curso + Membros da instituição + Público + Editar permissões + Atualizar + Disponibilidade + Visibilidade + Disponível de + Disponível até + De + Até + Data + Tempo + Limpar da data + Limpar até a data + Esse processo pode levar alguns minutos. Você pode fechar o modal ou sair da página durante esse processo. + Observação + Todos os módulos + Todos os módulos e itens + Módulos e itens selecionados + Módulos selecionados + Publicando + Cancelando publicação + Os módulos e itens que já foram processados não serão revertidos ao estado anterior quando o processo for interrompido. + Sucesso! + Atualização falhou + Atualização cancelada + Oculto + Agendado + Publicado(as) + Não publicado + + %.0f pt + %.0f pts + + Publicar + Despublicar + Publicar + Despublicar + Atualizar 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 7bad8df237..9237cd7e84 100644 --- a/libs/pandares/src/main/res/values-pt-rPT/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rPT/strings.xml @@ -1519,4 +1519,76 @@ Conteúdo adicional da disciplina Falha ao atualizar a ordem dos cartões do Painel + Publicar todos os Módulos e Itens + Publicar apenas módulos + Cancelar a publicação de todos os módulos e itens + Opções de módulo + Publicar módulo e todos os itens + Publicar apenas o módulo + Cancelar a publicação do módulo e de todos os itens + Opções de módulo para %s + Opções de item do módulo para %s + Publicar item do módulo + Cancelar a publicação do item do módulo + Publicar? + Isto fará com que apenas o módulo seja visível para os alunos. + Isto tornará o módulo e todos os itens visíveis para os alunos. + Cancelar publicação? + Isto tornará o módulo e todos os itens invisíveis para os alunos. + Isto tornará apenas este item visível para os alunos. + Isto tornará apenas este item invisível para os alunos. + Isto tornará todos os módulos e itens visíveis para os alunos. + Isto fará com que apenas os módulos sejam visíveis para os alunos. + Isto tornará todos os módulos e itens invisíveis para os alunos. + Apenas disponível com ligação + Disponibilidade de horários + Item publicado + Item não publicado + Apenas o módulo publicado + Módulo e todos os itens publicados + Módulo e todos os itens não publicados + Apenas módulos publicados + Todos os módulos e todos os itens publicados + Todos os módulos e todos os itens não publicados + Herdar da Disciplina + Membros da Disciplina + Membros da instituição + Público + Editar permissões + Atualizar + Disponibilidade + Visibilidade + Disponível de + Disponível até + De + Até + Data + Hora + Limpar da data + Limpar até à data + Este processo pode demorar alguns minutos. Podes fechar o modal ou navegar para fora da página durante este processo. + Observação + Todos os módulos + Todos os módulos e itens + Módulos e itens seleccionados + Módulos seleccionados + Publicando + Não publicar + Os módulos e itens que já tenham sido processados não serão revertidos para o seu estado anterior quando o processo for interrompido. + Bem sucedido! + Atualização falhada + Atualização cancelada + Ocultar + Agendado + Publicado + Não publicado + + %.0f pt + %.0f pts + + Publicar + Anular publicação + Publicar + Anular publicação + Atualizar diff --git a/libs/pandares/src/main/res/values-ru/strings.xml b/libs/pandares/src/main/res/values-ru/strings.xml index 0dd4d9f812..132f92eebf 100644 --- a/libs/pandares/src/main/res/values-ru/strings.xml +++ b/libs/pandares/src/main/res/values-ru/strings.xml @@ -1567,4 +1567,78 @@ Дополнительный контент курса Не удалось обновить порядок карт на информационной панели + Опубликовать все модули и элементы + Опубликовать только модули + Отменить публикацию всех модулей и элементов + Параметры модуля + Опубликовать модуль и все элементы + Опубликовать только модуль + Отменить публикацию модуля и всех элементов + Параметры модуля для %s + Параметры элемента модуля для %s + Опубликовать элемент модуля + Отменить публикацию элемента модуля + Опубликовать? + После этого учащимся будет виден только модуль. + После этого учащимся будут видны модуль и все элементы. + Отменить публикацию? + После этого учащимся не будут видны модуль и все элементы. + После этого учащимся будет виден только этот элемент. + После этого учащимся не будет виден только этот элемент. + После этого учащимся будут видны все модули и все элементы. + После этого учащимся будут видны только модули. + После этого учащимся не будут видны все модули и все элементы. + Доступно только по ссылке + Доступность расписания + Элемент опубликован + Отменена публикация элемента + Опубликован только модуль + Опубликованы модуль и все элементы + Отменена публикация модуля и всех элементов + Опубликованы только модули + Опубликованы все модули и все элементы + Публикация всех модулей и всех элементов отменена + Наследование из курса + Участники курса + Члены организации + Общедоступно + Редактировать разрешения + Обновить + Доступность + Доступность + Доступно с + Доступно до + С + До + Дата + Время + Очистить с даты + Очистить до даты + Этот процесс может занять несколько минут. Во время этой процедуры вы можете закрыть модальное окно или перейти на другую страницу. + Примечание + Все модули + Все модули и элементы + Выбранные модули и элементы + Выбранные модули + Выполняется публикация + Выполняется отмена публикации + Модули и элементы, которые уже были обработаны, не будут возвращены в прежнее состояние при остановке процедуры. + Успешно! + Сбой обновления + Обновление отменено + Скрыто + Запланировано + Опубликовано + Не опубликовано + + %.0f баллов + %.0f баллов + %.0f баллов + %.0f баллов + + Опубликовать + Отменить публикацию + Опубликовать + Отменить публикацию + Обновить diff --git a/libs/pandares/src/main/res/values-sl/strings.xml b/libs/pandares/src/main/res/values-sl/strings.xml index d59c7cf222..181be9c3d9 100644 --- a/libs/pandares/src/main/res/values-sl/strings.xml +++ b/libs/pandares/src/main/res/values-sl/strings.xml @@ -1519,4 +1519,76 @@ Dodatna vsebina predmeta Vrstnega reda kartic nadzorne plošče ni bilo mogoče posodobiti + Objavi vse module in elemente + Objavi samo module + Prekliči objavo vseh modulov in elementov + Možnosti modula + Objavi modul in vse elemente + Objavi samo modul + Prekliči objavo modula in vseh elementov + Možnosti modula za %s + Možnosti elementa modula za %s + Objavi element modula + Prekliči objavo elementa modula + Želite izvesti objavo? + S tem bo študentom postal viden le modul. + S tem bo modul in vsi elementi študentom postali vidni. + Želite preklicati objavo? + S tem bo modul in vsi elementi študentom postali nevidni. + S tem bo študentom postal viden le ta element. + S tem bo študentom postal neviden le ta element. + S tem bodo vsi moduli in predmeti študentom postali vidni. + S tem bodo študentom postali vidni le moduli. + S tem bodo vsi moduli in predmeti študentom postali nevidni. + Na voljo samo s povezavo + Razporedi razpoložljivost + Element je objavljen + Objava elementa je preklicana + Objavljen je samo modul + Objavljen je modul in vsi elementi + Objava modula in vseh elementov je preklicana + Objavljeni so samo moduli + Objavljeni so vsi moduli in vsi elementi + Objava vseh modulov in vseh elementov je preklicana + Podeduj od predmeta + Člani predmeta + Člani ustanove + Javno dostopno + Urejanje dovoljenj + Posodobi + Razpoložljivost + Vidnost + Na voljo od + Na voljo do + Pošiljatelj + Do + Datum + Čas + Počisti od datuma + Počisti do datuma + Ta proces lahko traja nekaj minut. Med tem postopkom lahko zaprete modalno okno ali zapustite stran. + Opomba + Vsi moduli + Vsi moduli in elementi + Izbrani moduli in elementi + Izbrani moduli + Poteka objavljanje + Poteka preklic objave + Moduli in elementi, ki so bili že obdelani, po prekinitvi procesa ne bodo povrnjeni v predhodno stanje. + Uspešno! + Posodobitev ni uspela + Posodobitev je preklicana + Skrito + Načrtovano + Objavljeno + Neobjavljeno + + %.0f točka + %.0f točk + + Objavi + Ne objavi + Objavi + Ne objavi + Osveži diff --git a/libs/pandares/src/main/res/values-sv/strings.xml b/libs/pandares/src/main/res/values-sv/strings.xml index 19363df17a..0697d7a83b 100644 --- a/libs/pandares/src/main/res/values-sv/strings.xml +++ b/libs/pandares/src/main/res/values-sv/strings.xml @@ -1519,4 +1519,76 @@ Extra kursinnehåll Det gick inte att uppdatera översiktens kortordning + Publicera alla moduler och objekt + Publicera endast moduler + Avpublicera alla moduler och objekt + Modulalternativ + Publicera modulen och alla objekt + Publicera endast modulen + Avpublicera modulen och alla objekt + Modulalternativ för %s + Modulobjektalternativ för %s + Publicera modulobjekt + Avpublicera modulobjekt + Publicera? + Detta gör modulen synlig endast för studenter. + Detta gör modulen och alla objekt synliga för studenter. + Avpublicera? + Detta döljer modulen och alla objekt för studenter. + Detta gör endast detta objekt synligt för studenter. + Detta döljer endast detta objekt för studenter. + Detta gör alla moduler och objekt synliga för studenter. + Detta gör endast modulerna synliga för studenter. + Detta döljer alla moduler och objekt för studenter. + Endast tillgängligt med länk + Schematillgänglighet + Objekt publicerat + Objekt avpublicerat + Endast modulen publicerades + Modulen och alla objekten publicerades + Modulen och alla objekten avpublicerades + Endast moduler publicerades + Alla moduler och objekt publicerades + Alla moduler och objekt avpublicerades + Ärv från kurs + Kursmedlem + Lärosätets medlemmar + Offentlig + Redigera behörigheter + Uppdatera + Tillgänglighet + Synlighet + Tillgänglig från + Tillgänglig till + Från + Tills + Datum + Tid + Rensa från-datum + Rensa fram till-datum + Detta kan ta några minuter. Du kan stänga modalen eller navigera bort från sidan under det här förloppet. + Anteckning + Alla moduler + Alla moduler och objekt + Valda moduler och objekt + Valda moduler + Publicerar + Avpublicerar + Moduler som redan har bearbetats återställs inte till tidigare tillstånd är processen avslutas. + Framgång! + Uppdateringen misslyckades + Uppdateringen avbruten + Dold + Schemalagd + Publicerad + Ej offentliggjord + + %.0f poäng + %.0f poäng + + Publicera + Avpublicera + Publicera + Avpublicera + Uppdatera diff --git a/libs/pandares/src/main/res/values-th/strings.xml b/libs/pandares/src/main/res/values-th/strings.xml index a3282f2149..fb92e26dfd 100644 --- a/libs/pandares/src/main/res/values-th/strings.xml +++ b/libs/pandares/src/main/res/values-th/strings.xml @@ -1519,4 +1519,76 @@ เนื้อหาบทเรียนเพิ่มเติม ไม่สามารถอัพเดตลำดับการ์ดแผงข้อมูล + เผยแพร่หน่วยการเรียนทั้งหมดและรายการที่เกี่ยวข้อง + เผยแพร่เฉพาะหน่วยการเรียน + เลิกเผยแพร่หน่วยการเรียนทั้งหมดและรายการที่เกี่ยวข้อง + ตัวเลือกหน่วยการเรียน + เผยแพร่หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมด + เผยแพร่เฉพาะหน่วยการเรียน + เลิกเผยแพร่หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมด + ตัวเลือกหน่วยการเรียนสำหรับ %s + ตัวเลือกรายการในหน่วยการเรียนสำหรับ %s + เผยแพร่รายการในหน่วยการเรียน + เลิกเผยแพร่รายการในหน่วยการเรียน + เผยแพร่หรือไม่ + ขั้นตอนนี้จะทำให้หน่วยการเรียนเปิดแสดงเฉพาะกับผู้เรียนเท่านั้น + ขั้นตอนนี้จะทำให้หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมดถูกแสดงผลให้แก่ผู้เรียน + เลิกเผยแพร่หรือไม่ + ขั้นตอนนี้จะทำให้หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมดถูกปิดการแสดงผลสำหรับผู้เรียน + ขั้นตอนนี้จะทำให้รายการนี้ถูกแสดงผลให้แก่ผู้เรียนเท่านั้น + ขั้นตอนนี้จะทำให้รายการนี้ถูกปิดการแสดงผลสำหรับผู้เรียนเท่านั้น + ขั้นตอนนี้จะทำให้หน่วยการเรียนและรายการทั้งหมดถูกแสดงผลให้แก่ผู้เรียน + ขั้นตอนนี้จะทำให้หน่วยการเรียนเปิดแสดงเฉพาะกับผู้เรียนเท่านั้น + ขั้นตอนนี้จะทำให้หน่วยการเรียนและรายการทั้งหมดถูกปิดการแสดงผลสำหรับผู้เรียน + จัดไว้ให้พร้อมลิงค์เท่านั้น + นัดหมายเวลาที่พร้อม + เผยแพร่รายการแล้ว + เลิกเผยแพร่รายการแล้ว + เฉพาะหน่วยการเรียนที่มีการเผยแพร่ + เผยแพร่หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมดแล้ว + เลิกเผยแพร่หน่วยการเรียนที่เกี่ยวข้องและรายการทั้งหมดแล้ว + เฉพาะหน่วยการเรียนที่มีการเผยแพร่ + เผยแพร่หน่วยการเรียนทั้งหมดและรายการทั้งหมดแล้ว + เลิกเผยแพร่หน่วยการเรียนทั้งหมดและรายการทั้งหมดแล้ว + รับช่วงจากบทเรียน (Course) + สมาชิกบทเรียน + สมาชิกของสถาบัน + สาธารณะ + แก้ไขสิทธิ์อนุญาต + อัพเดต + ความพร้อม + การแสดงผล + ใช้ได้จาก + ใช้ได้จนถึง + จาก + จนถึง + วันที่ + เวลา + ล้างวันที่ จาก + ล้างวันที่ ถึง + กระบวนการนี้อาจต้องใช้เวลาสักครู่ คุณสามารถปิดโมดัลหรือออกจากเพจได้ระหว่างกระบวนการนี้ + หมายเหตุ + หน่วยการเรียนทั้งหมด + หน่วยการเรียนทั้งหมดและรายการที่เกี่ยวข้อง + หน่วยการเรียนและรายการที่เลือก + หน่วยการเรียนที่เลือก + กำลังเผยแพร่ + กำลังเลิกเผยแพร่ + หน่วยการเรียนและรายการที่ดำเนินการไปแล้วจะไม่กลับคืนเป็นสถานะก่อนหน้าเมื่อมีการยุติการดำเนินการ + เสร็จสิ้น! + อัพเดตล้มเหลว + การอัพเดตถูกยกเลิก + ซ่อนไว้ + กำหนดเวลาแล้ว + เผยแพร่แล้ว + ไม่ได้เผยแพร่ + + %.0f คะแนน + %.0f คะแนน + + เผยแพร่ + เลิกเผยแพร่ + เผยแพร่ + เลิกเผยแพร่ + รีเฟรช diff --git a/libs/pandares/src/main/res/values-vi/strings.xml b/libs/pandares/src/main/res/values-vi/strings.xml index e73a2afbb2..bbf0bf4f13 100644 --- a/libs/pandares/src/main/res/values-vi/strings.xml +++ b/libs/pandares/src/main/res/values-vi/strings.xml @@ -1520,4 +1520,76 @@ Nội dung khóa học bổ sung Không thể cập nhật thứ tự thẻ Bảng Điều Khiển + Phát hành tất cả các Học Phần và Mục + Chỉ phát hành Các Học Phần + Hủy phát hành tất cả các Học Phần và Mục + Tùy Chọn Học Phần + Phát Hành Học Phần và tất cả Các Mục + Chỉ phát hành Học Phần + Hủy phát hành Học Phần và tất cả Các Mục + Tùy chọn học phần cho %s + Tùy chọn mục học phần cho %s + Phát Hành Mục Học Phần + Hủy Phát Hành Mục Học Phần + Phát hành? + Chức năng này sẽ chỉ hiển thị học phần cho sinh viên. + Chức năng này sẽ hiển thị học phần và tất cả các mục cho sinh viên. + Hủy phát hành? + Chức năng này sẽ ẩn học phần và tất cả các mục đối với sinh viên. + Chức năng này sẽ chỉ hiển thị mục này cho sinh viên. + Chức năng này sẽ chỉ ẩn mục này đối với sinh viên. + Chức năng này sẽ hiển thị tất cả các học phần và mục cho sinh viên. + Chức năng này sẽ chỉ hiển thị các học phần cho sinh viên. + Chức năng này sẽ ẩn tất cả các học phần và mục đối với sinh viên. + Chỉ khả dụng với liên kết + Đặt lịch độ khả dụng + Mục đã phát hành + Mục đã hủy phát hành + Riêng Học Phần đã phát hành + Học Phần và tất cả Các Mục đã phát hành + Học Phần và tất cả Các Mục đã hủy phát hành + Riêng Các Học Phần đã phát hành + Tất cả Các Học Phần và tất cả Các Mục đã phát hành + Tất cả Các Học Phần và tất cả Các Mục đã hủy phát hành + Kế thừa từ Khóa Học + Các Thành Viên Khóa Học + Các Thành Viên Của Tổ Chức + Công Khai + Quyền Chỉnh Sửa + Cập Nhật + Độ Khả Dụng + Hiển Thị + Khả Dụng Từ + Khả Dụng Đến + Từ + Đến + Ngày + Thời Gian + Xóa Từ Ngày + Xóa Đến Ngày + Quá trình này có thể mất vài phút. Bạn có thể đóng phương thức hoặc điều hướng khỏi trang trong quá trình này. + Ghi chú + Tất Cả Các Học Phần + Tất Cả Các Học Phần và Mục + Các Học Phần và Mục Đã Chọn + Các Học Phần Đã Chọn + Đang phát hành + Đang hủy phát hành + Các học phần và mục đã được xử lý sẽ không được hoàn nguyên về trạng thái trước đó khi ngừng quá trình. + Thành công! + Cập nhật không thành công + Đã hủy bỏ cập nhật + Ẩn + Đã Đặt Lịch + Đã phát hành + Chưa Phát Hành + + %.0f điểm + %.0f điểm + + Phát Hành + Hủy Phát Hành + Phát Hành + Hủy Phát Hành + Làm Mới diff --git a/libs/pandares/src/main/res/values-zh/strings.xml b/libs/pandares/src/main/res/values-zh/strings.xml index 0617c4530c..fd42ac2ee9 100644 --- a/libs/pandares/src/main/res/values-zh/strings.xml +++ b/libs/pandares/src/main/res/values-zh/strings.xml @@ -1495,4 +1495,75 @@ 更多课程内容 无法更新控制面板卡片顺序 + 发布所有模块和项目 + 仅发布模块 + 取消发布所有模块和项目 + 模块选项 + 发布模块和所有项目 + 仅发布模块 + 取消发布模块和所有项目 + %s 的模块选项 + %s 的模块项目选项 + 发布模块项目 + 取消发布模块项目 + 是否发布? + 这将仅使模块对学生可见。 + 这将使模块和所有项目对学生可见。 + 是否取消发布? + 这将使模块和所有项目对学生不可见。 + 这将仅使该项目对学生可见。 + 这将仅使该项目对学生不可见。 + 这将使所有模块和项目对学生可见。 + 这将仅使模块对学生可见。 + 这将使所有模块和项目对学生不可见。 + 仅提供链接 + 计划表可用性 + 已发布项目 + 已取消发布项目 + 已仅发布模块 + 已发布模块和所有项目 + 已取消发布模块和所有项目 + 已仅发布模块 + 已发布所有模块和所有项目 + 已取消发布所有模块和所有项目 + 从课程继承 + 课程成员 + 机构成员 + 公开 + 编辑权限 + 更新 + 可用性 + 可见性 + 开始日期 + 截止时间 + 开始日期 + 直至 + 日期 + 时间 + 清除开始日期 + 清除截止日期 + 该过程可能需要几分钟。在此期间,您可以关闭该模式或离开页面。 + + 所有模块 + 所有模块和项目 + 已选的模块和项目 + 已选的模块 + 正在发布 + 正在取消发布 + 停止处理后,已经处理的模块和项目不会退回到之前的状态。 + 成功! + 更新失败 + 更新已取消 + 隐藏 + 已计划 + 已发布 + 未发布 + + %.0f 分 + + 发布 + 取消发布 + 发布 + 取消发布 + 刷新 diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index c665eb497f..8bbe1eddf6 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1499,7 +1499,7 @@ Offline Content Sync Failed Cancel Sync? It will stop offline content sync. You can do it again later. - One or more files failed to sync. Check your internet connection and retry to submit. + One or more items failed to sync. Please check your internet connection and retry syncing. Download starting Courses cannot be added to favorites offline. All Courses diff --git a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDaoTest.kt b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDaoTest.kt new file mode 100644 index 0000000000..8692631aa9 --- /dev/null +++ b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDaoTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.instructure.pandautils.room.offline.daos + +import android.content.Context +import android.database.sqlite.SQLiteConstraintException +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.DiscussionParticipant +import com.instructure.canvasapi2.models.DiscussionTopicHeader +import com.instructure.canvasapi2.models.RemoteFile +import com.instructure.pandautils.room.offline.OfflineDatabase +import com.instructure.pandautils.room.offline.entities.CourseEntity +import com.instructure.pandautils.room.offline.entities.DiscussionParticipantEntity +import com.instructure.pandautils.room.offline.entities.DiscussionTopicHeaderEntity +import com.instructure.pandautils.room.offline.entities.DiscussionTopicPermissionEntity +import com.instructure.pandautils.room.offline.entities.DiscussionTopicRemoteFileEntity +import com.instructure.pandautils.room.offline.entities.RemoteFileEntity +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class DiscussionTopicRemoteFileDaoTest { + + private lateinit var db: OfflineDatabase + private lateinit var remoteFileDao: RemoteFileDao + private lateinit var discussionTopicHeaderDao: DiscussionTopicHeaderDao + private lateinit var discussionTopicRemoteFileDao: DiscussionTopicRemoteFileDao + + @Before + fun setUp() = runTest { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, OfflineDatabase::class.java).build() + remoteFileDao = db.remoteFileDao() + discussionTopicRemoteFileDao = db.discussionTopicRemoteFileDao() + discussionTopicHeaderDao = db.discussionTopicHeaderDao() + + db.courseDao().insert(CourseEntity(Course(1L))) + } + + @After + fun tearDown() { + db.close() + } + + @Test + fun testFindByDiscussionId() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + val discussionTopicEntity = DiscussionTopicHeaderEntity(DiscussionTopicHeader(2L), 1L) + discussionTopicHeaderDao.insert(discussionTopicEntity) + remoteFileDao.insert(remoteFileEntity) + + val discussionTopicRemoteFileEntity = DiscussionTopicRemoteFileEntity(discussionTopicEntity.id, remoteFileEntity.id) + discussionTopicRemoteFileDao.insert(discussionTopicRemoteFileEntity) + val result = discussionTopicRemoteFileDao.findByDiscussionId(discussionTopicEntity.id) + assertEquals(listOf(discussionTopicRemoteFileEntity), result) + } + + @Test(expected = SQLiteConstraintException::class) + fun testInsertDiscussionForeignKey() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + remoteFileDao.insert(remoteFileEntity) + + val discussionTopicRemoteFileEntity = DiscussionTopicRemoteFileEntity(1L, remoteFileEntity.id) + discussionTopicRemoteFileDao.insert(discussionTopicRemoteFileEntity) + } + + @Test(expected = SQLiteConstraintException::class) + fun testInsertRemoteFileForeignKey() = runTest { + val discussionTopicEntity = DiscussionTopicHeaderEntity(DiscussionTopicHeader(1L), 1L) + discussionTopicHeaderDao.insert(discussionTopicEntity) + + val discussionTopicRemoteFileEntity = DiscussionTopicRemoteFileEntity(discussionTopicEntity.id, 1L) + discussionTopicRemoteFileDao.insert(discussionTopicRemoteFileEntity) + } + + @Test + fun testRemoteFileCascade() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + remoteFileDao.insert(remoteFileEntity) + + val discussionTopicEntity = DiscussionTopicHeaderEntity(DiscussionTopicHeader(2L), 1L) + discussionTopicHeaderDao.insert(discussionTopicEntity) + + val discussionTopicRemoteFileEntity = DiscussionTopicRemoteFileEntity(discussionTopicEntity.id, remoteFileEntity.id) + discussionTopicRemoteFileDao.insert(discussionTopicRemoteFileEntity) + + remoteFileDao.delete(remoteFileEntity) + val result = discussionTopicRemoteFileDao.findByDiscussionId(discussionTopicEntity.id) + assertEquals(emptyList(), result) + } + + @Test + fun testDiscussionTopicCascade() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + remoteFileDao.insert(remoteFileEntity) + + val discussionTopicEntity = DiscussionTopicHeaderEntity(DiscussionTopicHeader(2L), 1L) + discussionTopicHeaderDao.insert(discussionTopicEntity) + + val discussionTopicRemoteFileEntity = DiscussionTopicRemoteFileEntity(discussionTopicEntity.id, remoteFileEntity.id) + discussionTopicRemoteFileDao.insert(discussionTopicRemoteFileEntity) + + discussionTopicHeaderDao.delete(discussionTopicEntity) + val result = discussionTopicRemoteFileDao.findByDiscussionId(discussionTopicEntity.id) + assertEquals(emptyList(), result) + } + +} \ No newline at end of file diff --git a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/RemoteFileDaoTest.kt b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/RemoteFileDaoTest.kt new file mode 100644 index 0000000000..c27b9a24da --- /dev/null +++ b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/RemoteFileDaoTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.instructure.pandautils.room.offline.daos + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.instructure.canvasapi2.models.RemoteFile +import com.instructure.pandautils.room.offline.OfflineDatabase +import com.instructure.pandautils.room.offline.entities.RemoteFileEntity +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class RemoteFileDaoTest { + + private lateinit var db: OfflineDatabase + private lateinit var remoteFileDao: RemoteFileDao + + @Before + fun setUp() { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, OfflineDatabase::class.java).build() + remoteFileDao = db.remoteFileDao() + } + + @After + fun tearDown() { + db.close() + } + + @Test + fun testInsertReplace() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + val updated = remoteFileEntity.copy(displayName = "new name") + remoteFileDao.insert(remoteFileEntity) + remoteFileDao.insert(updated) + val result = remoteFileDao.findById(1) + assertEquals(updated, result) + } + + @Test + fun testInsertAllReplace() = runTest { + val remoteFileEntity1 = RemoteFileEntity(RemoteFile(1L)) + val remoteFileEntity2 = RemoteFileEntity(RemoteFile(2L)) + val updated = remoteFileEntity1.copy(displayName = "new name") + remoteFileDao.insertAll(listOf(remoteFileEntity1, remoteFileEntity2)) + remoteFileDao.insertAll(listOf(updated)) + val result1 = remoteFileDao.findById(1) + val result2 = remoteFileDao.findById(2) + assertEquals(updated, result1) + assertEquals(remoteFileEntity2, result2) + } + + @Test + fun testFindById() = runTest { + val remoteFileEntity = RemoteFileEntity(RemoteFile(1L)) + val remoteFileEntity2 = RemoteFileEntity(RemoteFile(2L)) + remoteFileDao.insertAll(listOf(remoteFileEntity, remoteFileEntity2)) + val result = remoteFileDao.findById(1) + assertEquals(remoteFileEntity, result) + } + +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/di/OfflineModule.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/di/OfflineModule.kt index 6cbed10131..2f4b27eb49 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/di/OfflineModule.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/di/OfflineModule.kt @@ -282,9 +282,20 @@ class OfflineModule { discussionTopicHeaderDao: DiscussionTopicHeaderDao, discussionParticipantDao: DiscussionParticipantDao, discussionTopicPermissionDao: DiscussionTopicPermissionDao, + remoteFileDao: RemoteFileDao, + localFileDao: LocalFileDao, + discussionTopicRemoteFileDao: DiscussionTopicRemoteFileDao, offlineDatabase: OfflineDatabase, ): DiscussionTopicHeaderFacade { - return DiscussionTopicHeaderFacade(discussionTopicHeaderDao, discussionParticipantDao, discussionTopicPermissionDao, offlineDatabase) + return DiscussionTopicHeaderFacade( + discussionTopicHeaderDao, + discussionParticipantDao, + discussionTopicPermissionDao, + remoteFileDao, + localFileDao, + discussionTopicRemoteFileDao, + offlineDatabase + ) } @Provides @@ -532,4 +543,16 @@ class OfflineModule { ): OfflineSyncHelper { return OfflineSyncHelper(workManager, syncSettingsFacade, apiPrefs) } + + @Provides + fun provideRemoteFileDao( + appDatabase: OfflineDatabase + ): RemoteFileDao { + return appDatabase.remoteFileDao() + } + + @Provides + fun provideDiscussionTopicRemoteFileDao(appDatabase: OfflineDatabase): DiscussionTopicRemoteFileDao { + return appDatabase.discussionTopicRemoteFileDao() + } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/dialog/EditCourseNicknameDialog.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/dialogs/EditCourseNicknameDialog.kt similarity index 85% rename from apps/student/src/main/java/com/instructure/student/dialog/EditCourseNicknameDialog.kt rename to libs/pandautils/src/main/java/com/instructure/pandautils/dialogs/EditCourseNicknameDialog.kt index 32f6d89a17..96a72202cb 100644 --- a/apps/student/src/main/java/com/instructure/student/dialog/EditCourseNicknameDialog.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/dialogs/EditCourseNicknameDialog.kt @@ -1,4 +1,4 @@ -package com.instructure.student.dialog +package com.instructure.pandautils.dialogs /* * Copyright (C) 2017 - present Instructure, Inc. @@ -27,10 +27,10 @@ import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.widget.AppCompatEditText import androidx.fragment.app.FragmentManager import com.instructure.canvasapi2.models.Course +import com.instructure.pandautils.R import com.instructure.pandautils.analytics.SCREEN_VIEW_EDIT_COURSE_NICKNAME import com.instructure.pandautils.analytics.ScreenView import com.instructure.pandautils.utils.* -import com.instructure.student.R import java.util.Locale import kotlin.properties.Delegates @@ -75,6 +75,16 @@ class EditCourseNicknameDialog : AppCompatDialogFragment() { .create() nameDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + editCourseNicknameEditText.onTextChanged { + if (course.originalName == null && it.isEmpty()) { + nameDialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled = false + nameDialog.getButton(Dialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.increaseAlpha(ThemePrefs.textButtonColor, 128)) + } else { + nameDialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled = true + nameDialog.getButton(Dialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.textButtonColor) + } + } + nameDialog.setOnShowListener { nameDialog.getButton(AppCompatDialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.textButtonColor) nameDialog.getButton(AppCompatDialog.BUTTON_NEGATIVE).setTextColor(ThemePrefs.textButtonColor) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/discussions/DiscussionUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/discussions/DiscussionUtils.kt index fe22c90bf1..9f7f974584 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/discussions/DiscussionUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/discussions/DiscussionUtils.kt @@ -248,7 +248,8 @@ object DiscussionUtils { canvasContext: CanvasContext, discussionTopicHeader: DiscussionTopicHeader, discussionEntries: List, - startEntryId: Long): String { + startEntryId: Long, + isOnline: Boolean = true): String { val builder = StringBuilder() val brandColor = ThemePrefs.brandColor @@ -276,7 +277,7 @@ object DiscussionUtils { fun buildEntry(discussionEntry: DiscussionEntry, depth: Int) { val isViewableEnd = (depth == maxDepth && discussionEntry.totalChildren > 0) builder.append(build(context, isTablet, canvasContext, discussionTopicHeader, discussionEntry, converter, - template, makeAvatarForWebView(context, discussionEntry), depth, isViewableEnd, brandColor, likeColor, + template, makeAvatarForWebView(context, discussionEntry, isOnline), depth, isViewableEnd, brandColor, likeColor, likeImage, replyButtonWidth)) if (depth < maxDepth) discussionEntry.replies?.forEach { buildEntry(it, depth + 1) } } @@ -475,8 +476,8 @@ object DiscussionUtils { * If the avatar is valid then returns an empty string. Otherwise... * Returns an avatar bitmap converted into a base64 string for webviews. */ - private fun makeAvatarForWebView(context: Context, discussionEntry: DiscussionEntry): String { - if (discussionEntry.author != null && ProfileUtils.shouldLoadAltAvatarImage(discussionEntry.author!!.avatarImageUrl)) { + private fun makeAvatarForWebView(context: Context, discussionEntry: DiscussionEntry, isOnline: Boolean): String { + if (!isOnline || discussionEntry.author != null && ProfileUtils.shouldLoadAltAvatarImage(discussionEntry.author!!.avatarImageUrl)) { val avatarBitmap = ProfileUtils.getInitialsAvatarBitMap( context, discussionEntry.author!!.displayName!!, Color.TRANSPARENT, diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt index 4799e6bf9f..b5c511c861 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt @@ -42,6 +42,7 @@ import com.instructure.pandautils.utils.orDefault import com.instructure.pandautils.utils.toJson import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import java.lang.IllegalStateException import java.util.* @HiltWorker @@ -127,7 +128,7 @@ class FileUploadWorker @AssistedInject constructor( updateSubmissionComplete(notificationId) attemptId = it.attempt Result.success() - } ?: Result.retry() + } ?: throw IllegalStateException("Failed to attach file to submission") } ACTION_MESSAGE_ATTACHMENTS -> { updateNotificationComplete(notificationId) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxEntryItemCreator.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxEntryItemCreator.kt index ce40905fb8..7d2137370d 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxEntryItemCreator.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxEntryItemCreator.kt @@ -40,7 +40,7 @@ class InboxEntryItemCreator(private val context: Context, private val apiPrefs: conversation.id, createAvatarData(conversation), createMessageTitle(conversation), - conversation.subject ?: "", + conversation.subject.takeIf { it?.isNotBlank() == true } ?: context.getString(R.string.noSubject), conversation.lastMessagePreview ?: "", createDateText(conversation), conversation.workflowState == Conversation.WorkflowState.UNREAD, diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxFragment.kt index 6934e27622..18cc9cd62a 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/list/InboxFragment.kt @@ -324,6 +324,8 @@ class InboxFragment : Fragment(), NavigationCallbacks, FragmentInteractions { ViewStyler.themeToolbarColored(requireActivity(), binding.editToolbar, ThemePrefs.primaryColor, ThemePrefs.primaryTextColor) binding.toolbarWrapper.setBackgroundColor(ThemePrefs.primaryColor) binding.addMessage.backgroundTintList = ViewStyler.makeColorStateListForButton() + binding.scopeFilterText.setTextColor(ThemePrefs.textButtonColor) + binding.scopeFilterIcon.setColorFilter(ThemePrefs.textButtonColor) inboxRouter.attachNavigationIcon(binding.toolbar) } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/CourseSync.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/CourseSync.kt index aa2b4fea03..cd9bed2f7d 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/CourseSync.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/CourseSync.kt @@ -59,6 +59,7 @@ import com.instructure.pandautils.room.offline.entities.CourseSyncProgressEntity import com.instructure.pandautils.room.offline.entities.CourseSyncSettingsEntity import com.instructure.pandautils.room.offline.entities.FileFolderEntity import com.instructure.pandautils.room.offline.entities.QuizEntity +import com.instructure.pandautils.room.offline.entities.RemoteFileEntity import com.instructure.pandautils.room.offline.facade.AssignmentFacade import com.instructure.pandautils.room.offline.facade.ConferenceFacade import com.instructure.pandautils.room.offline.facade.CourseFacade @@ -150,7 +151,11 @@ class CourseSync( listOf(contentDeferred, filesDeferred).awaitAll() } - fileSync.syncAdditionalFiles(courseSettings, additionalFileIdsToSync[courseId].orEmpty(), externalFilesToSync[courseId].orEmpty()) + fileSync.syncAdditionalFiles( + courseSettings, + additionalFileIdsToSync[courseId].orEmpty(), + externalFilesToSync[courseId].orEmpty(), + ) val progress = courseSyncProgressDao.findByCourseId(courseId) progress @@ -411,6 +416,9 @@ class CourseSync( discussions.forEach { it.message = parseHtmlContent(it.message, courseId) + it.attachments.forEach { + additionalFileIdsToSync[courseId] = additionalFileIdsToSync[courseId].orEmpty() + it.id + } } discussionTopicHeaderFacade.insertDiscussions(discussions, courseId, false) @@ -433,6 +441,9 @@ class CourseSync( announcements.forEach { it.message = parseHtmlContent(it.message, courseId) + it.attachments.forEach { + additionalFileIdsToSync[courseId] = additionalFileIdsToSync[courseId].orEmpty() + it.id + } } discussionTopicHeaderFacade.insertDiscussions(announcements, courseId, true) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt index eb80412b8b..920c6f0367 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt @@ -79,7 +79,6 @@ class OfflineSyncHelper( val syncSettings = syncSettingsFacade.getSyncSettings() val constraints = Constraints.Builder() .setRequiredNetworkType(if (syncSettings.wifiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) .build() val inputData = Data.Builder() .putLongArray(COURSE_IDS, courseIds.toLongArray()) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/OfflineDatabase.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/OfflineDatabase.kt index b455e6f507..3b2d610ee9 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/OfflineDatabase.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/OfflineDatabase.kt @@ -43,6 +43,7 @@ import com.instructure.pandautils.room.offline.daos.DiscussionParticipantDao import com.instructure.pandautils.room.offline.daos.DiscussionTopicDao import com.instructure.pandautils.room.offline.daos.DiscussionTopicHeaderDao import com.instructure.pandautils.room.offline.daos.DiscussionTopicPermissionDao +import com.instructure.pandautils.room.offline.daos.DiscussionTopicRemoteFileDao import com.instructure.pandautils.room.offline.daos.EditDashboardItemDao import com.instructure.pandautils.room.offline.daos.EnrollmentDao import com.instructure.pandautils.room.offline.daos.FileFolderDao @@ -66,6 +67,7 @@ import com.instructure.pandautils.room.offline.daos.ModuleObjectDao import com.instructure.pandautils.room.offline.daos.PageDao import com.instructure.pandautils.room.offline.daos.PlannerOverrideDao import com.instructure.pandautils.room.offline.daos.QuizDao +import com.instructure.pandautils.room.offline.daos.RemoteFileDao import com.instructure.pandautils.room.offline.daos.RubricCriterionAssessmentDao import com.instructure.pandautils.room.offline.daos.RubricCriterionDao import com.instructure.pandautils.room.offline.daos.RubricCriterionRatingDao @@ -341,4 +343,8 @@ abstract class OfflineDatabase : RoomDatabase() { abstract fun discussionEntryDao(): DiscussionEntryDao abstract fun discussionTopicPermissionDao(): DiscussionTopicPermissionDao + + abstract fun remoteFileDao(): RemoteFileDao + + abstract fun discussionTopicRemoteFileDao(): DiscussionTopicRemoteFileDao } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDao.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDao.kt new file mode 100644 index 0000000000..3f911a20fb --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/DiscussionTopicRemoteFileDao.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.instructure.pandautils.room.offline.daos + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.instructure.pandautils.room.offline.entities.DiscussionTopicRemoteFileEntity + +@Dao +interface DiscussionTopicRemoteFileDao { + + @Insert + suspend fun insert(entity: DiscussionTopicRemoteFileEntity) + + @Insert + suspend fun insertAll(entities: List) + + @Query("SELECT * FROM DiscussionTopicRemoteFileEntity WHERE discussionId = :discussionId") + suspend fun findByDiscussionId(discussionId: Long): List +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/RemoteFileDao.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/RemoteFileDao.kt new file mode 100644 index 0000000000..0d6afbd1f2 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/daos/RemoteFileDao.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.instructure.pandautils.room.offline.daos + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.instructure.pandautils.room.offline.entities.RemoteFileEntity + +@Dao +interface RemoteFileDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(entity: RemoteFileEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(entities: List) + + @Delete + suspend fun delete(entity: RemoteFileEntity) + + @Query("SELECT * FROM RemoteFileEntity WHERE id = :id") + suspend fun findById(id: Long): RemoteFileEntity? +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicHeaderEntity.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicHeaderEntity.kt index 5eea233e1d..4b26468d14 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicHeaderEntity.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicHeaderEntity.kt @@ -24,6 +24,7 @@ import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.DiscussionParticipant import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.models.DiscussionTopicPermission +import com.instructure.canvasapi2.models.RemoteFile import com.instructure.pandautils.utils.orDefault import java.util.Date @@ -126,7 +127,8 @@ data class DiscussionTopicHeaderEntity( fun toApiModel( author: DiscussionParticipant? = null, assignment: Assignment? = null, - permissions: DiscussionTopicPermission? = null + permissions: DiscussionTopicPermission? = null, + attachments: List ) = DiscussionTopicHeader( id = id, discussionType = discussionType, @@ -151,8 +153,7 @@ data class DiscussionTopicHeaderEntity( groupCategoryId = groupCategoryId, announcement = announcement, groupTopicChildren = emptyList(), - //TODO - attachments = mutableListOf(), + attachments = attachments.toMutableList(), //TODO permissions = permissions, assignment = assignment, diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/RemoteFileEntity.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/RemoteFileEntity.kt index 87d0980a4e..cca851c4a9 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/RemoteFileEntity.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/RemoteFileEntity.kt @@ -19,6 +19,7 @@ package com.instructure.pandautils.room.offline.entities import androidx.room.Entity import androidx.room.PrimaryKey +import com.instructure.canvasapi2.models.RemoteFile @Entity data class RemoteFileEntity( @@ -42,4 +43,48 @@ data class RemoteFileEntity( val lockedForUser: Boolean, val previewUrl: String?, val lockExplanation: String? -) \ No newline at end of file +) { + constructor(remoteFile: RemoteFile) : this( + id = remoteFile.id, + folderId = remoteFile.folderId, + displayName = remoteFile.displayName, + fileName = remoteFile.fileName, + contentType = remoteFile.contentType, + url = remoteFile.url, + size = remoteFile.size, + createdAt = remoteFile.createdAt, + updatedAt = remoteFile.updatedAt, + unlockAt = remoteFile.unlockAt, + locked = remoteFile.locked, + hidden = remoteFile.hidden, + lockAt = remoteFile.lockAt, + hiddenForUser = remoteFile.hiddenForUser, + thumbnailUrl = remoteFile.thumbnailUrl, + modifiedAt = remoteFile.modifiedAt, + lockedForUser = remoteFile.lockedForUser, + previewUrl = remoteFile.previewUrl, + lockExplanation = remoteFile.lockExplanation + ) + + fun toApiModel() = RemoteFile( + id = id, + folderId = folderId, + displayName = displayName, + fileName = fileName, + contentType = contentType, + url = url, + size = size, + createdAt = createdAt, + updatedAt = updatedAt, + unlockAt = unlockAt, + locked = locked, + hidden = hidden, + lockAt = lockAt, + hiddenForUser = hiddenForUser, + thumbnailUrl = thumbnailUrl, + modifiedAt = modifiedAt, + lockedForUser = lockedForUser, + previewUrl = previewUrl, + lockExplanation = lockExplanation + ) +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/facade/DiscussionTopicHeaderFacade.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/facade/DiscussionTopicHeaderFacade.kt index ffba0e901f..d2c6fcb53a 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/facade/DiscussionTopicHeaderFacade.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/facade/DiscussionTopicHeaderFacade.kt @@ -23,20 +23,32 @@ import com.instructure.pandautils.room.offline.OfflineDatabase import com.instructure.pandautils.room.offline.daos.DiscussionParticipantDao import com.instructure.pandautils.room.offline.daos.DiscussionTopicHeaderDao import com.instructure.pandautils.room.offline.daos.DiscussionTopicPermissionDao +import com.instructure.pandautils.room.offline.daos.DiscussionTopicRemoteFileDao +import com.instructure.pandautils.room.offline.daos.LocalFileDao +import com.instructure.pandautils.room.offline.daos.RemoteFileDao import com.instructure.pandautils.room.offline.entities.DiscussionParticipantEntity import com.instructure.pandautils.room.offline.entities.DiscussionTopicHeaderEntity import com.instructure.pandautils.room.offline.entities.DiscussionTopicPermissionEntity +import com.instructure.pandautils.room.offline.entities.DiscussionTopicRemoteFileEntity +import com.instructure.pandautils.room.offline.entities.RemoteFileEntity class DiscussionTopicHeaderFacade( private val discussionTopicHeaderDao: DiscussionTopicHeaderDao, private val discussionParticipantDao: DiscussionParticipantDao, private val discussionTopicPermissionDao: DiscussionTopicPermissionDao, + private val remoteFileDao: RemoteFileDao, + private val localFileDao: LocalFileDao, + private val discussionTopicRemoteFileDao: DiscussionTopicRemoteFileDao, private val offlineDatabase: OfflineDatabase ) { suspend fun insertDiscussion(discussionTopicHeader: DiscussionTopicHeader, courseId: Long): Long { discussionTopicHeader.author?.let { discussionParticipantDao.insert(DiscussionParticipantEntity(it)) } val discussionTopicHeaderId = discussionTopicHeaderDao.insert(DiscussionTopicHeaderEntity(discussionTopicHeader, courseId, null)) val permissionId = discussionTopicHeader.permissions?.let { discussionTopicPermissionDao.insert(DiscussionTopicPermissionEntity(it, discussionTopicHeaderId)) } + val attachments = discussionTopicHeader.attachments.map { RemoteFileEntity(it) } + val connectionEntities = attachments.map { DiscussionTopicRemoteFileEntity(discussionTopicHeaderId, it.id) } + remoteFileDao.insertAll(attachments) + discussionTopicRemoteFileDao.insertAll(connectionEntities) discussionTopicHeaderDao.update(DiscussionTopicHeaderEntity(discussionTopicHeader.copy(id = discussionTopicHeaderId), courseId, permissionId)) return discussionTopicHeaderId } @@ -51,15 +63,23 @@ class DiscussionTopicHeaderFacade( discussionParticipantDao.upsertAll(authors) - val discussionEntities = - discussionTopicHeaders.mapIndexed { index, discussionTopicHeader -> - DiscussionTopicHeaderEntity( - discussionTopicHeader, - courseId, - null - ) - } + val discussionEntities = mutableListOf() + val attachmentEntities = mutableListOf() + val discussionRemoteFileEntities = mutableListOf() + + discussionTopicHeaders.forEach { discussion -> + val entity = DiscussionTopicHeaderEntity(discussion, courseId, null) + val attachments = discussion.attachments.map { RemoteFileEntity(it) } + val connectionEntites = attachments.map { DiscussionTopicRemoteFileEntity(entity.id, it.id) } + + discussionEntities.add(entity) + attachmentEntities.addAll(attachments) + discussionRemoteFileEntities.addAll(connectionEntites) + } + discussionTopicHeaderDao.insertAll(discussionEntities) + remoteFileDao.insertAll(attachmentEntities) + discussionTopicRemoteFileDao.insertAll(discussionRemoteFileEntities) val permissionIds = discussionTopicHeaders.mapIndexed { index, discussionTopicHeader -> discussionTopicHeader.permissions?.let { @@ -76,9 +96,7 @@ class DiscussionTopicHeaderFacade( suspend fun getDiscussionsForCourse(courseId: Long): List { return discussionTopicHeaderDao.findAllDiscussionsForCourse(courseId) .map { discussionTopic -> - val authorEntity = discussionTopic.authorId?.let { discussionParticipantDao.findById(it) } - val permission = discussionTopicPermissionDao.findByDiscussionTopicHeaderId(discussionTopic.id) - discussionTopic.toApiModel(author = authorEntity?.toApiModel(), permissions = permission?.toApiModel()) + createDiscussionApiModel(discussionTopic) } } @@ -97,8 +115,18 @@ class DiscussionTopicHeaderFacade( } private suspend fun createDiscussionApiModel(discussionTopicHeaderEntity: DiscussionTopicHeaderEntity): DiscussionTopicHeader { - val authorEntity = discussionTopicHeaderEntity.authorId?.let { discussionParticipantDao.findById(it) } - val permission = discussionTopicPermissionDao.findByDiscussionTopicHeaderId(discussionTopicHeaderEntity.id) - return discussionTopicHeaderEntity.toApiModel(authorEntity?.toApiModel(), permissions = permission?.toApiModel()) + val authorEntity = + discussionTopicHeaderEntity.authorId?.let { discussionParticipantDao.findById(it) } + val permission = + discussionTopicPermissionDao.findByDiscussionTopicHeaderId(discussionTopicHeaderEntity.id) + val attachments = discussionTopicRemoteFileDao.findByDiscussionId( + discussionTopicHeaderEntity.id + ).mapNotNull { remoteFileDao.findById(it.remoteFileId) } + .map { + val path = localFileDao.findById(it.id)?.path + it.copy(url = path) + } + .map { it.toApiModel() } + return discussionTopicHeaderEntity.toApiModel(authorEntity?.toApiModel(), permissions = permission?.toApiModel(), attachments = attachments) } } \ No newline at end of file diff --git a/apps/student/src/main/res/layout/dialog_course_nickname.xml b/libs/pandautils/src/main/res/layout/dialog_course_nickname.xml similarity index 100% rename from apps/student/src/main/res/layout/dialog_course_nickname.xml rename to libs/pandautils/src/main/res/layout/dialog_course_nickname.xml diff --git a/libs/pandautils/src/main/res/layout/fragment_inbox.xml b/libs/pandautils/src/main/res/layout/fragment_inbox.xml index 43947bd07b..cc82d34096 100644 --- a/libs/pandautils/src/main/res/layout/fragment_inbox.xml +++ b/libs/pandautils/src/main/res/layout/fragment_inbox.xml @@ -157,6 +157,7 @@ tools:text="Inbox" />