diff --git a/apps/build.gradle b/apps/build.gradle index a0b3f6aaa..e74b6b143 100644 --- a/apps/build.gradle +++ b/apps/build.gradle @@ -45,13 +45,6 @@ allprojects { } mavenCentral() maven { url 'https://jitpack.io' } - maven { - credentials { - username pspdfMavenUser - password pspdfMavenPass - } - url 'https://customers.pspdfkit.com/maven/' - } maven { url "https://maven.google.com/" } } } diff --git a/apps/buildSrc/src/main/java/GlobalDependencies.kt b/apps/buildSrc/src/main/java/GlobalDependencies.kt index ad676509b..25aa35d53 100644 --- a/apps/buildSrc/src/main/java/GlobalDependencies.kt +++ b/apps/buildSrc/src/main/java/GlobalDependencies.kt @@ -24,7 +24,6 @@ object Versions { /* Others */ const val APOLLO = "4.1.1" - const val PSPDFKIT = "2024.8.0" const val PHOTO_VIEW = "2.3.0" const val MOBIUS = "1.2.1" const val HILT = "2.52" @@ -127,7 +126,6 @@ object Libs { const val COMPOSE_VIEW_MODEL = "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.LIFECYCLE}" /* Media and content handling */ - const val PSPDFKIT = "com.pspdfkit:pspdfkit:${Versions.PSPDFKIT}" const val EXOPLAYER = "com.google.android.exoplayer:exoplayer:2.18.5" // This is deprecated, we should migrate to https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide const val MEDIA3 = "androidx.media3:media3-exoplayer:${Versions.MEDIA3}" const val MEDIA3_UI = "androidx.media3:media3-ui:${Versions.MEDIA3}" diff --git a/apps/parent/proguard-rules.txt b/apps/parent/proguard-rules.txt index bde42b751..7e5a37dfe 100644 --- a/apps/parent/proguard-rules.txt +++ b/apps/parent/proguard-rules.txt @@ -187,16 +187,6 @@ -dontwarn okhttp3.** -keep class okhttp3.** { *; } -# PSPDFKit --keep class com.pspdfkit.** { *; } --dontwarn com.pspdfkit.** --dontwarn sun.misc.** --dontwarn android.view.WindowInsets --dontwarn android.graphics.Insets --dontwarn edu.umd.cs.findbugs.annotations.SuppressWarnings --keepnames class io.reactivex.android.schedulers.AndroidSchedulers --keepnames class io.reactivex.Observable - # RxJava -keep class rx.internal.util.unsafe.** { *; } diff --git a/apps/parent/src/main/java/com/instructure/parentapp/util/AppManager.kt b/apps/parent/src/main/java/com/instructure/parentapp/util/AppManager.kt index 0efb1d333..bbb9fb82a 100644 --- a/apps/parent/src/main/java/com/instructure/parentapp/util/AppManager.kt +++ b/apps/parent/src/main/java/com/instructure/parentapp/util/AppManager.kt @@ -59,6 +59,6 @@ class AppManager : BaseAppManager() { private fun initPendo() { val options = Pendo.PendoOptions.Builder().setJetpackComposeBeta(true).build() - Pendo.setup(this, BuildConfig.PENDO_TOKEN, options, null) +// Pendo.setup(this, BuildConfig.PENDO_TOKEN, options, null) } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt index 4c8d933d2..72d47e1de 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt @@ -127,55 +127,6 @@ class PdfInteractionTest : StudentComposeTest() { fileListPage.assertPdfPreviewDisplayed() } - @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) - fun testAnnotations_openPdfsInPSPDFKitFromLinksInAssignment() { - // Annotation toolbar icon needs to be present, this link is specific to assignment details, as that was the advertised use case - val data = MockCanvas.init( - studentCount = 1, - courseCount = 1 - ) - - val course = data.courses.values.first() - val student = data.students[0] - val token = data.tokenFor(student)!! - data.addAssignmentsToGroups(course) - tokenLogin(data.domain, token, student) - routeTo("courses/${course.id}/assignments", data.domain) - - val fileId = data.newItemId() - val url = """https://mock-data.instructure.com/courses/${course.id}/files/${fileId}/download?""" - - data.addFileToCourse( - courseId = course.id, - displayName = pdfFileName, - contentType = "application/pdf", - fileId = fileId, - url = url - ) - - val uniqueFileName = OpenMediaAsyncTaskLoader.makeFilenameUnique(pdfFileName, url, fileId.toString()) - - cacheFile(student.id.toString(), uniqueFileName) - - val pdfUrlElementId = "testLinkElement" - val assignmentDescriptionHtml = """pdf baby!!!""" - - val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD), description = assignmentDescriptionHtml) - - assignmentListPage.refreshAssignmentList() - assignmentListPage.clickAssignment(assignment) - assignmentDetailsPage.assertAssignmentDetails(assignment) - - // Scroll to the description, as it will likely be offscreen for landscape tests - assignmentDetailsPage.scrollToAssignmentDescription() - - // Click the url in the description to load the pdf - Web.onWebView(withId(R.id.contentWebView)) - .withElement(DriverAtoms.findElement(Locator.ID, pdfUrlElementId)) - .perform(DriverAtoms.webClick()) - fileListPage.assertPdfPreviewDisplayed() - } private fun getToCourse(): MockCanvas { val data = MockCanvas.init( diff --git a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListFragment.kt b/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListFragment.kt deleted file mode 100644 index 591773fb5..000000000 --- a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListFragment.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2018 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ package com.instructure.student.AnnotationComments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatDialog -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.DefaultItemAnimator -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.instructure.annotations.AnnotationDialogs.AnnotationCommentDialog -import com.instructure.annotations.createCommentReplyAnnotation -import com.instructure.annotations.generateAnnotationId -import com.instructure.canvasapi2.managers.CanvaDocsManager -import com.instructure.canvasapi2.models.ApiValues -import com.instructure.canvasapi2.models.DocSession -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.canvasapi2.utils.weave.weave -import com.instructure.interactions.router.Route -import com.instructure.pandautils.analytics.SCREEN_VIEW_ANNOTATION_COMMENT_LIST -import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.binding.viewBinding -import com.instructure.pandautils.utils.* -import com.instructure.student.R -import com.instructure.student.databinding.FragmentAnnotationCommentListBinding -import com.instructure.student.fragment.ParentFragment -import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.PdfStudentSubmissionView -import kotlinx.coroutines.Job -import okhttp3.ResponseBody -import org.greenrobot.eventbus.EventBus -import java.util.* - -@ScreenView(SCREEN_VIEW_ANNOTATION_COMMENT_LIST) -class AnnotationCommentListFragment : ParentFragment() { - - private val binding by viewBinding(FragmentAnnotationCommentListBinding::bind) - - private var annotations by ParcelableArrayListArg() - private var assigneeId by LongArg() - private var docSession by ParcelableArg() - private var apiValues by ParcelableArg() - private var headAnnotationId by StringArg() - private var canComment by BooleanArg(true, CAN_COMMENT) - - private var recyclerAdapter: AnnotationCommentListRecyclerAdapter? = null - - private var sendCommentJob: Job? = null - private var editCommentJob: Job? = null - private var deleteCommentJob: Job? = null - - override fun title() = getString(R.string.comments) - - override fun applyTheme() { - with (binding) { - toolbar.title = title() - toolbar.setupAsCloseButton(this@AnnotationCommentListFragment) - ViewStyler.themeToolbarLight(requireActivity(), toolbar) - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = - layoutInflater.inflate(R.layout.fragment_annotation_comment_list, container, false) - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - recyclerAdapter = AnnotationCommentListRecyclerAdapter(requireContext(), docSession, { annotation, position -> - AnnotationCommentDialog.getInstance(requireFragmentManager(), annotation.contents ?: "", requireContext().getString(R.string.editComment)) { cancelled, text -> - if(!cancelled) { - annotation.contents = text - editComment(annotation, position) - } - }.show(requireFragmentManager(), AnnotationCommentDialog::class.java.simpleName) - }, { annotation, position -> - val builder = AlertDialog.Builder(requireContext()) - //we want to show a different title for the root comment - builder.setTitle(R.string.deleteComment) - builder.setMessage(if(position == 0) R.string.deleteHeadCommentConfirmation else R.string.deleteCommentConfirmation) - builder.setPositiveButton(getString(R.string.delete).uppercase(Locale.getDefault())) { _, _ -> - deleteComment(annotation, position) - } - builder.setNegativeButton(getString(R.string.cancel).uppercase(Locale.getDefault()), null) - val dialog = builder.create() - dialog.setOnShowListener { - dialog.getButton(AppCompatDialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.textButtonColor) - dialog.getButton(AppCompatDialog.BUTTON_NEGATIVE).setTextColor(ThemePrefs.textButtonColor) - } - dialog.show() - }) - - configureRecyclerView() - applyTheme() - setupCommentInput() - - if(recyclerAdapter?.size() == 0) { - recyclerAdapter?.addAll(annotations) - } - } - - override fun onDestroyView() { - super.onDestroyView() - sendCommentJob?.cancel() - editCommentJob?.cancel() - deleteCommentJob?.cancel() - } - - fun configureRecyclerView() { - val layoutManager = LinearLayoutManager(requireContext()) - layoutManager.orientation = RecyclerView.VERTICAL - binding.annotationCommentsRecyclerView.apply { - this.layoutManager = layoutManager - itemAnimator = DefaultItemAnimator() - adapter = recyclerAdapter - } - } - - private fun setupCommentInput() = with(binding) { - // We want users with read permission to still be able to create and respond to comments. - if(docSession.annotationMetadata?.canRead() == false || !canComment) { - commentInputContainer.setVisible(false) - } else { - sendCommentButton.imageTintList = ViewStyler.generateColorStateList( - intArrayOf(-android.R.attr.state_enabled) to ContextCompat.getColor(requireContext(), R.color.textDark), - intArrayOf() to ThemePrefs.textButtonColor - ) - - sendCommentButton.isEnabled = false - commentEditText.onTextChanged { sendCommentButton.isEnabled = it.isNotBlank() } - sendCommentButton.onClickWithRequireNetwork { - sendComment(commentEditText.text.toString()) - } - } - } - - private fun showSendingStatus() = with(binding) { - sendCommentButton.setInvisible() - sendingProgressBar.setVisible() - sendingProgressBar.announceForAccessibility(getString(R.string.sendingSimple)) - sendingErrorTextView.setGone() - commentEditText.isEnabled = false - } - - private fun hideSendingStatus(success: Boolean) = with(binding) { - sendingProgressBar.setGone() - sendCommentButton.setVisible() - commentEditText.isEnabled = true - if (success) { - commentEditText.setText("") - commentEditText.hideKeyboard() - } else { - sendingErrorTextView.setVisible() - } - } - - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - private fun sendComment(comment: String) { - sendCommentJob = weave { - try { - showSendingStatus() - //first we need to find the root comment - val rootComment = annotations.firstOrNull() - if (rootComment != null) { - val newCommentReply = awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, generateAnnotationId(), createCommentReplyAnnotation(comment, headAnnotationId, apiValues.documentId, ApiPrefs.user?.id.toString(), rootComment.page), apiValues.canvaDocsDomain, it) } - // The put request doesn't return this property, so we need to set it to true - newCommentReply.isEditable = true - recyclerAdapter?.add(newCommentReply) //ALSO, add it to the UI - hideSendingStatus(true) - } else { - hideSendingStatus(false) - } - } catch (e: Throwable) { - hideSendingStatus(false) - } - } - } - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - private fun editComment(annotation: CanvaDocAnnotation, position: Int) { - editCommentJob = tryWeave { - awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, annotation.annotationId, annotation, apiValues.canvaDocsDomain, it) } - // Update the UI - recyclerAdapter?.add(annotation) - recyclerAdapter?.notifyItemChanged(position) - } catch { - hideSendingStatus(false) - } - } - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - private fun deleteComment(annotation: CanvaDocAnnotation, position: Int) { - deleteCommentJob = tryWeave { - awaitApi { CanvaDocsManager.deleteAnnotation(apiValues.sessionId, annotation.annotationId, apiValues.canvaDocsDomain, it) } - if(annotation.annotationId == annotations.firstOrNull()?.annotationId) { - //this is the root comment, deleting this deletes the entire thread - headAnnotationDeleted() - } else { - recyclerAdapter?.remove(annotation) - recyclerAdapter?.notifyItemChanged(position) - } - } catch { - hideSendingStatus(false) - } - } - - private fun headAnnotationDeleted() { - activity?.onBackPressed() - } - - companion object { - private const val ANNOTATIONS = "annotations" - private const val ASSIGNEE_ID = "assigneeId" - private const val DOC_SESSION = "docSession" - private const val API_VALUES = "apiValues" - private const val HEAD_ANNOTATION_ID = "headAnnotationId" - private const val CAN_COMMENT = "canComment" - - fun newInstance(bundle: Bundle) = AnnotationCommentListFragment().apply { arguments = bundle } - - fun makeRoute(annotations: ArrayList, headAnnotationId: String, docSession: DocSession, apiValues: ApiValues, assigneeId: Long, canComment: Boolean): Route { - val args = makeBundle(annotations, headAnnotationId, docSession, apiValues, assigneeId, canComment) - - return Route(null, AnnotationCommentListFragment::class.java, null, args) - } - - fun validRoute(route: Route): Boolean { - return route.arguments.containsKey(ANNOTATIONS) - && route.arguments.containsKey(HEAD_ANNOTATION_ID) - && route.arguments.containsKey(DOC_SESSION) - && route.arguments.containsKey(API_VALUES) - && route.arguments.containsKey(ASSIGNEE_ID) - } - - fun newInstance(route: Route): AnnotationCommentListFragment? { - if (!validRoute(route)) return null - return AnnotationCommentListFragment().withArgs(route.arguments) - } - - fun makeBundle(annotations: ArrayList, headAnnotationId: String, docSession: DocSession, apiValues: ApiValues, assigneeId: Long, canComment: Boolean): Bundle { - val args = Bundle() - args.putParcelableArrayList(ANNOTATIONS, annotations) - args.putLong(ASSIGNEE_ID, assigneeId) - args.putParcelable(DOC_SESSION, docSession) - args.putParcelable(API_VALUES, apiValues) - args.putString(HEAD_ANNOTATION_ID, headAnnotationId) - args.putBoolean(CAN_COMMENT, canComment) - return args - } - } -} diff --git a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListRecyclerAdapter.kt deleted file mode 100644 index fae9684f7..000000000 --- a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListRecyclerAdapter.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2018 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.student.AnnotationComments - -import android.content.Context -import android.view.View -import com.instructure.canvasapi2.models.DocSession -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.student.adapter.BaseListRecyclerAdapter - -class AnnotationCommentListRecyclerAdapter( - context: Context, - private val docSession: DocSession, - private val editCallback: (CanvaDocAnnotation, Int) -> Unit, - private val deleteCallback: (CanvaDocAnnotation, Int) -> Unit -) : BaseListRecyclerAdapter(context, CanvaDocAnnotation::class.java) { - - init { - itemCallback = object : ItemComparableCallback() { - override fun compare(item1: CanvaDocAnnotation, item2: CanvaDocAnnotation): Int { - val createdAt1 = item1.createdAt - val createdAt2 = item2.createdAt - - return if(createdAt1 != null && createdAt2 != null) { - createdAt1.compareTo(createdAt2) - } else if(createdAt1 != null && createdAt2 == null) { - 1 - } else { - -1 - } - } - } - } - - override fun createViewHolder(v: View, viewType: Int) = AnnotationCommentViewHolder(v) - override fun itemLayoutResId(viewType: Int) = AnnotationCommentViewHolder.holderRes - override fun bindHolder(model: CanvaDocAnnotation, holder: AnnotationCommentViewHolder, position: Int) { - val canDelete = docSession.annotationMetadata?.canManage() ?: false - || (docSession.annotationMetadata?.canWrite() == true && model.userId == docSession.annotationMetadata?.userId) - val canEdit = (docSession.annotationMetadata?.canWrite() == true - && model.userId == docSession.annotationMetadata?.userId) - holder.bind(model, canEdit, canDelete, editCallback, deleteCallback) - } -} diff --git a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentViewHolder.kt b/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentViewHolder.kt deleted file mode 100644 index 03aca5ef8..000000000 --- a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentViewHolder.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2018 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.student.AnnotationComments - -import android.view.Gravity -import android.view.View -import androidx.appcompat.widget.PopupMenu -import androidx.recyclerview.widget.RecyclerView -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.canvasapi2.utils.DateHelper -import com.instructure.pandautils.utils.onClick -import com.instructure.pandautils.utils.setGone -import com.instructure.pandautils.utils.setVisible -import com.instructure.student.R -import com.instructure.student.databinding.ViewholderAnnotationCommentBinding - -class AnnotationCommentViewHolder(view: View) : RecyclerView.ViewHolder(view) { - companion object { - const val holderRes = R.layout.viewholder_annotation_comment - } - - fun bind(annotation: CanvaDocAnnotation, canEdit: Boolean, canDelete: Boolean, editCallback: (CanvaDocAnnotation, Int) -> Unit, deleteCallback: (CanvaDocAnnotation, Int) -> Unit) = with(ViewholderAnnotationCommentBinding.bind(itemView)) { - commentAuthorTextView.text = annotation.userName - commentDateTextView.text = DateHelper.getMonthDayAtTime(root.context, DateHelper.stringToDateWithMillis(annotation.createdAt), root.context.getString(R.string.at)) - commentContentsTextView.text = annotation.contents - - commentEditIcon.setVisible((canEdit || canDelete) && !annotation.deleted) - - if(annotation.deleted) { - commentRemovedLabel.setVisible() - val date = DateHelper.getMonthDayAtTime(root.context, DateHelper.stringToDateWithMillis(annotation.deletedAt), root.context.getString(R.string.at)) - commentRemovedLabel.text = root.resources.getString(R.string.removedComment, date, annotation.deletedBy) - } else { - commentRemovedLabel.setGone() - } - - commentEditIcon.onClick { - val popup = PopupMenu(root.context, it, Gravity.TOP, 0, - R.style.Base_Widget_AppCompat_PopupMenu_Overflow) - popup.inflate(R.menu.menu_edit_annotation_comment) - if(!canEdit) popup.menu.removeItem(R.id.edit) - if(!canDelete) popup.menu.removeItem(R.id.delete) - popup.setOnMenuItemClickListener { - when(it.itemId) { - R.id.edit -> { - editCallback(annotation, adapterPosition) - } - R.id.delete -> { deleteCallback(annotation, adapterPosition) } - } - true - } - popup.show() - } - } -} diff --git a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt index 0052ea522..ccfc547bd 100644 --- a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt +++ b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt @@ -27,7 +27,6 @@ import com.instructure.pandautils.features.settings.inboxsignature.InboxSignatur import com.instructure.pandautils.features.smartsearch.SmartSearchFragment import com.instructure.pandautils.fragments.RemoteConfigParamsFragment import com.instructure.pandautils.utils.Const -import com.instructure.student.AnnotationComments.AnnotationCommentListFragment import com.instructure.student.activity.NothingToSeeHereFragment import com.instructure.student.features.coursebrowser.CourseBrowserFragment import com.instructure.student.features.discussion.details.DiscussionDetailsFragment @@ -168,7 +167,6 @@ object RouteResolver { cls.isA() -> UrlSubmissionUploadFragment.newInstance(route) cls.isA() -> PickerSubmissionUploadFragment.newInstance(route) cls.isA() -> UploadStatusSubmissionFragment.newInstance(route) - cls.isA() -> AnnotationCommentListFragment.newInstance(route) cls.isA() -> NothingToSeeHereFragment.newInstance() cls.isA() -> AnnotationSubmissionUploadFragment.newInstance(route) cls.isA() -> PushNotificationPreferencesFragment.newInstance() diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index 39fdece2c..6f39146fc 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -58,7 +58,6 @@ android { /* Add private data */ PrivateData.merge(project, "teacher") - buildConfigField "String", "PSPDFKIT_LICENSE_KEY", "\"$pspdfkitLicenseKey\"" } bundle { diff --git a/apps/teacher/proguard-rules.txt b/apps/teacher/proguard-rules.txt index 0157f1151..0d8b350e7 100644 --- a/apps/teacher/proguard-rules.txt +++ b/apps/teacher/proguard-rules.txt @@ -191,16 +191,6 @@ -dontwarn okhttp3.** -keep class okhttp3.** { *; } -# PSPDFKit --keep class com.pspdfkit.** { *; } --dontwarn com.pspdfkit.** --dontwarn sun.misc.** --dontwarn android.view.WindowInsets --dontwarn android.graphics.Insets --dontwarn edu.umd.cs.findbugs.annotations.SuppressWarnings --keepnames class io.reactivex.android.schedulers.AndroidSchedulers --keepnames class io.reactivex.Observable - # RxJava -keep class rx.internal.util.unsafe.** { *; } diff --git a/apps/teacher/src/main/AndroidManifest.xml b/apps/teacher/src/main/AndroidManifest.xml index b947410ae..55ab3ed2a 100644 --- a/apps/teacher/src/main/AndroidManifest.xml +++ b/apps/teacher/src/main/AndroidManifest.xml @@ -194,13 +194,6 @@ android:name=".activities.InternalWebViewActivity" android:theme="@style/AppTheme.NoActionBar" /> - - diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListAdapter.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListAdapter.kt deleted file mode 100644 index 93df1bea4..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListAdapter.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import android.content.Context -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.viewbinding.ViewBinding -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.teacher.databinding.AdapterAnnotationCommentBinding -import com.instructure.pandautils.blueprint.ListRecyclerAdapter - -class AnnotationCommentListAdapter( - context: Context, - presenter: AnnotationCommentListPresenter, - private val editCallback: (CanvaDocAnnotation, Int) -> Unit, - private val deleteCallback: (CanvaDocAnnotation, Int) -> Unit -) : ListRecyclerAdapter(context, presenter) { - - override fun createViewHolder(binding: ViewBinding, viewType: Int) = AnnotationCommentViewHolder(binding as AdapterAnnotationCommentBinding) - - override fun bindingInflater(viewType: Int): (LayoutInflater, ViewGroup, Boolean) -> ViewBinding = AdapterAnnotationCommentBinding::inflate - - override fun bindHolder(model: CanvaDocAnnotation, holder: AnnotationCommentViewHolder, position: Int) { - val annotationCommentPresenter = presenter as AnnotationCommentListPresenter - val canDelete = annotationCommentPresenter.docSession.annotationMetadata?.canManage() == true - || (annotationCommentPresenter.docSession.annotationMetadata?.canWrite() == true - && model.userId == annotationCommentPresenter.docSession.annotationMetadata?.userId) - val canEdit = annotationCommentPresenter.docSession.annotationMetadata?.canWrite() == true - && model.userId == (annotationCommentPresenter.docSession.annotationMetadata?.userId) - - holder.bind(model, canEdit, canDelete, editCallback, deleteCallback) - } -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListFragment.kt deleted file mode 100644 index 7521e4049..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListFragment.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import android.os.Bundle -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatDialog -import androidx.recyclerview.widget.DefaultItemAnimator -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.instructure.annotations.AnnotationDialogs.AnnotationCommentDialog -import com.instructure.canvasapi2.models.ApiValues -import com.instructure.canvasapi2.models.DocSession -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.pandautils.binding.viewBinding -import com.instructure.pandautils.fragments.BaseListFragment -import com.instructure.pandautils.utils.* -import com.instructure.teacher.R -import com.instructure.teacher.databinding.FragmentAnnotationCommentListBinding -import com.instructure.teacher.utils.getColorCompat -import com.instructure.teacher.utils.setupBackButton -import java.util.* - -class AnnotationCommentListFragment : BaseListFragment< - CanvaDocAnnotation, - AnnotationCommentListPresenter, - AnnotationCommentListView, - AnnotationCommentViewHolder, - AnnotationCommentListAdapter>(), AnnotationCommentListView { - - private val binding by viewBinding(FragmentAnnotationCommentListBinding::bind) - - private var mAnnotationList by ParcelableArrayListArg() - private var mAssigneeId by LongArg() - private var mDocSession by ParcelableArg() - private var mApiValues by ParcelableArg() - private var mHeadAnnotationId by StringArg() - - override fun createAdapter(): AnnotationCommentListAdapter { - return AnnotationCommentListAdapter(requireContext(), presenter, { annotation, position -> - AnnotationCommentDialog.getInstance(requireFragmentManager(), annotation.contents ?: "", getString(R.string.editComment)) { cancelled, text -> - if(!cancelled) { - annotation.contents = text - presenter.editComment(annotation, position) - } - }.show(requireFragmentManager(), AnnotationCommentDialog::class.java.simpleName) - }, { annotation, position -> - val builder = AlertDialog.Builder(requireContext()) - //we want to show a different title for the head annotation - builder.setTitle(if(position == 0) R.string.deleteAnnotation else R.string.deleteComment) - builder.setMessage(if(position == 0) R.string.deleteHeadCommentConfirmation else R.string.deleteCommentConfirmation) - builder.setPositiveButton(getString(R.string.delete).uppercase(Locale.getDefault())) { _, _ -> - presenter.deleteComment(annotation, position) - } - builder.setNegativeButton(getString(R.string.cancel).uppercase(Locale.getDefault()), null) - val dialog = builder.create() - dialog.setOnShowListener { - dialog.getButton(AppCompatDialog.BUTTON_POSITIVE).setTextColor(ThemePrefs.textButtonColor) - dialog.getButton(AppCompatDialog.BUTTON_NEGATIVE).setTextColor(ThemePrefs.textButtonColor) - } - dialog.show() - }) - } - - override val recyclerView: RecyclerView get() = binding.annotationCommentsRecyclerView - override fun layoutResId() = R.layout.fragment_annotation_comment_list - override fun checkIfEmpty() {} // we don't display this view if its empty, so no need to check - override fun onRefreshFinished() {} - override fun onRefreshStarted() {} - override fun getPresenterFactory() = AnnotationCommentListPresenterFactory(mAnnotationList, mDocSession, mApiValues, mAssigneeId, mHeadAnnotationId) - - override fun onPresenterPrepared(presenter: AnnotationCommentListPresenter) { - val layoutManager = LinearLayoutManager(requireContext()) - layoutManager.orientation = RecyclerView.VERTICAL - recyclerView.layoutManager = layoutManager - recyclerView.itemAnimator = DefaultItemAnimator() - recyclerView.adapter = adapter - } - - override fun onReadySetGo(presenter: AnnotationCommentListPresenter) { - setupToolbar() - presenter.loadData(false) - setupCommentInput() - } - - fun setupToolbar() { - binding.toolbar.title = getString(R.string.sg_tab_comments) - binding.toolbar.setupBackButton(this) - ViewStyler.themeToolbarLight(requireActivity(), binding.toolbar) - ViewStyler.setToolbarElevationSmall(requireContext(), binding.toolbar) - } - - private fun setupCommentInput() { - // We only want to enable comments if the user has write permissions or greater - if(presenter.docSession.annotationMetadata?.canWrite() != true) { - binding.commentInputContainer.setVisible(false) - } else { - binding.commentInputContainer.setVisible(true) - binding.sendCommentButton.imageTintList = ViewStyler.generateColorStateList( - intArrayOf(-android.R.attr.state_enabled) to requireContext().getColorCompat(R.color.textDark), - intArrayOf() to ThemePrefs.textButtonColor - ) - - binding.sendCommentButton.isEnabled = false - binding.commentEditText.onTextChanged { binding.sendCommentButton.isEnabled = it.isNotBlank() } - binding.sendCommentButton.onClickWithRequireNetwork { - presenter.sendComment(binding.commentEditText.text.toString()) - } - } - } - - override fun showSendingStatus() { - binding.sendCommentButton.setInvisible() - binding.sendingProgressBar.setVisible() - binding.sendingProgressBar.announceForAccessibility(getString(R.string.sendingSimple)) - binding.sendingErrorTextView.setGone() - binding.commentEditText.isEnabled = false - } - - override fun hideSendingStatus(success: Boolean) { - binding.sendingProgressBar.setGone() - binding.sendCommentButton.setVisible() - binding.commentEditText.isEnabled = true - if (success) { - binding.commentEditText.setText("") - binding.commentEditText.hideKeyboard() - } else { - binding.sendingErrorTextView.setVisible() - } - } - - override fun notifyItemChanged(position: Int) { - adapter.notifyItemChanged(position) - } - - override fun headAnnotationDeleted() { - requireActivity().onBackPressed() - } - - companion object { - @JvmStatic val ANNOTATIONS = "mAnnotationList" - @JvmStatic val ASSIGNEE_ID = "mAssigneeId" - @JvmStatic val DOC_SESSION = "mDocSession" - @JvmStatic val API_VALUES = "mApiValues" - @JvmStatic val HEAD_ANNOTATION_ID = "mHeadAnnotationId" - - fun newInstance(bundle: Bundle) = AnnotationCommentListFragment().apply { arguments = bundle } - - fun makeBundle(annotations: ArrayList, headAnnotationId: String, docSession: DocSession, apiValues: ApiValues, assigneeId: Long): Bundle { - val args = Bundle() - args.putParcelableArrayList(ANNOTATIONS, annotations) - args.putLong(ASSIGNEE_ID, assigneeId) - args.putParcelable(DOC_SESSION, docSession) - args.putParcelable(API_VALUES, apiValues) - args.putString(HEAD_ANNOTATION_ID, headAnnotationId) - return args - } - } -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenter.kt deleted file mode 100644 index a0e890e09..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenter.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import com.instructure.annotations.* -import com.instructure.canvasapi2.managers.CanvaDocsManager -import com.instructure.canvasapi2.models.ApiValues -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.canvasapi2.models.DocSession -import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.canvasapi2.utils.weave.weave -import com.instructure.teacher.view.* -import com.instructure.pandautils.blueprint.ListPresenter -import kotlinx.coroutines.Job -import okhttp3.ResponseBody -import org.greenrobot.eventbus.EventBus - -class AnnotationCommentListPresenter(val annotations: ArrayList, - val docSession: DocSession, - val apiValues: ApiValues, - val assigneeId: Long, - val headAnnotationId: String) : ListPresenter(CanvaDocAnnotation::class.java) { - - private var mSendCommentJob: Job? = null - private var mEditCommentJob: Job? = null - private var mDeleteCommentJob: Job? = null - - override fun getItemId(item: CanvaDocAnnotation): Long { - return item.annotationId.hashCode().toLong() - } - - override fun loadData(forceNetwork: Boolean) { - //this prevents deleted items from reloading on rotation - if(data.size() == 0) { - data.addAll(annotations) - } - } - - override fun refresh(forceNetwork: Boolean) {} - - override fun onDestroyed() { - mSendCommentJob?.cancel() - mEditCommentJob?.cancel() - mDeleteCommentJob?.cancel() - EventBus.getDefault().post( - AnnotationCommentDeleteAcknowledged( - annotations.filter { it.deleted && it.deleteAcknowledged.isNullOrEmpty() }, - assigneeId)) - super.onDestroyed() - } - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - fun sendComment(comment: String) { - mSendCommentJob = weave { - try { - viewCallback?.showSendingStatus() - //first we need to find the root comment - val rootComment = data[0] - if (rootComment != null) { - val newCommentReply = awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, generateAnnotationId(), createCommentReplyAnnotation(comment, headAnnotationId, apiValues.documentId, ApiPrefs.user?.id.toString(), rootComment.page), apiValues.canvaDocsDomain, it) } - EventBus.getDefault().post(AnnotationCommentAdded(newCommentReply, assigneeId)) - - // The put request doesn't return this property, so we need to set it to true - newCommentReply.isEditable = true - data.add(newCommentReply) //ALSO, add it to the UI - viewCallback?.hideSendingStatus(true) - } else { - viewCallback?.hideSendingStatus(false) - } - } catch (e: Throwable) { - viewCallback?.hideSendingStatus(false) - } - } - } - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - fun editComment(annotation: CanvaDocAnnotation, position: Int) { - mEditCommentJob = tryWeave { - awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, annotation.annotationId, annotation, apiValues.canvaDocsDomain, it) } - EventBus.getDefault().post(AnnotationCommentEdited(annotation, assigneeId)) - - //ALSO, add it to the UI - data.addOrUpdate(annotation) - viewCallback?.notifyItemChanged(position) - } catch { - viewCallback?.hideSendingStatus(false) - } - } - - @Suppress("EXPERIMENTAL_FEATURE_WARNING") - fun deleteComment(annotation: CanvaDocAnnotation, position: Int) { - mDeleteCommentJob = tryWeave { - awaitApi { CanvaDocsManager.deleteAnnotation(apiValues.sessionId, annotation.annotationId, apiValues.canvaDocsDomain, it) } - if(annotation.annotationId == data[0]?.annotationId) { - //this is the root comment, deleting this deletes the entire thread - EventBus.getDefault().post(AnnotationCommentDeleted(annotation, true, assigneeId)) - viewCallback?.headAnnotationDeleted() - } else { - EventBus.getDefault().post(AnnotationCommentDeleted(annotation, false, assigneeId)) - data.remove(annotation) - viewCallback?.notifyItemChanged(position) - } - } catch { - viewCallback?.hideSendingStatus(false) - } - } - - override fun compare(item1: CanvaDocAnnotation, item2: CanvaDocAnnotation): Int { - val createdAt1 = item1.createdAt - val createdAt2 = item2.createdAt - - return if(createdAt1 != null && createdAt2 != null) { - createdAt1.compareTo(createdAt2) - } else if(createdAt1 != null && createdAt2 == null) { - 1 - } else { - -1 - } - } -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenterFactory.kt deleted file mode 100644 index 994f69b3e..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListPresenterFactory.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import com.instructure.canvasapi2.models.ApiValues -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.canvasapi2.models.DocSession -import com.instructure.pandautils.blueprint.PresenterFactory - -class AnnotationCommentListPresenterFactory( - val annotations: ArrayList, - val docSession: DocSession, - val apiValues: ApiValues, - val assigneeId: Long, - val headAnnotationId: String): PresenterFactory { - override fun create() = AnnotationCommentListPresenter(annotations, docSession, apiValues, assigneeId, headAnnotationId) -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListView.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListView.kt deleted file mode 100644 index 0a67a5e80..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentListView.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.pandautils.blueprint.ListManager - -interface AnnotationCommentListView : ListManager { - fun showSendingStatus() - fun hideSendingStatus(success: Boolean) - fun notifyItemChanged(position: Int) - fun headAnnotationDeleted() -} - diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt deleted file mode 100644 index 03c4108c2..000000000 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package com.instructure.teacher.PSPDFKit.AnnotationComments - -import android.view.Gravity -import androidx.appcompat.widget.PopupMenu -import androidx.recyclerview.widget.RecyclerView -import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation -import com.instructure.canvasapi2.utils.DateHelper -import com.instructure.pandautils.utils.onClick -import com.instructure.pandautils.utils.setGone -import com.instructure.pandautils.utils.setVisible -import com.instructure.teacher.R -import com.instructure.teacher.databinding.AdapterAnnotationCommentBinding - -class AnnotationCommentViewHolder(private val binding: AdapterAnnotationCommentBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind( - annotation: CanvaDocAnnotation, - canEdit: Boolean, - canDelete: Boolean, - editCallback: (CanvaDocAnnotation, Int) -> Unit, - deleteCallback: (CanvaDocAnnotation, Int) -> Unit - ) = with(binding) { - val context = binding.root.context - commentAuthorTextView.text = annotation.userName - commentDateTextView.text = DateHelper.getMonthDayAtTime(context, DateHelper.stringToDateWithMillis(annotation.createdAt), context.getString(R.string.at)) - commentContentsTextView.text = annotation.contents - - commentEditIcon.setVisible((canEdit || canDelete) && !annotation.deleted) - - if(annotation.deleted) { - commentRemovedLabel.setVisible() - val date = DateHelper.getMonthDayAtTime(context, DateHelper.stringToDateWithMillis(annotation.deletedAt), context.getString(R.string.at)) - commentRemovedLabel.text = context.resources.getString(R.string.removedComment, date, annotation.deletedBy) - } else { - commentRemovedLabel.setGone() - } - - commentEditIcon.onClick { - val popup = PopupMenu(context, it, Gravity.TOP, 0, com.google.android.material.R.style.Widget_AppCompat_PopupMenu_Overflow) - popup.inflate(R.menu.menu_edit_annotation_comment) - if(!canEdit) popup.menu.removeItem(R.id.edit) - if(!canDelete) popup.menu.removeItem(R.id.delete) - popup.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.edit -> editCallback(annotation, adapterPosition) - R.id.delete -> deleteCallback(annotation, adapterPosition) - } - true - } - popup.show() - } - } -} 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 e453e6296..ea1922a43 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 @@ -83,11 +83,7 @@ import com.instructure.teacher.utils.isTalkbackEnabled import com.instructure.teacher.utils.setupBackButton import com.instructure.teacher.utils.setupMenu import com.instructure.teacher.utils.toast -import com.instructure.teacher.view.AudioPermissionGrantedEvent -import com.instructure.teacher.view.TabSelectedEvent -import com.instructure.teacher.view.VideoPermissionGrantedEvent import com.instructure.teacher.viewinterface.SpeedGraderView -import com.pspdfkit.preferences.PSPDFKitPreferences import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay import org.greenrobot.eventbus.EventBus @@ -165,10 +161,6 @@ class SpeedGraderActivity : BasePresenterActivity { if(grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { - EventBus.getDefault().post(VideoPermissionGrantedEvent(assigneeId)) +// EventBus.getDefault().post(VideoPermissionGrantedEvent(assigneeId)) } } - permissions.contains(PermissionUtils.RECORD_AUDIO) -> EventBus.getDefault().post(AudioPermissionGrantedEvent(assigneeId)) +// permissions.contains(PermissionUtils.RECORD_AUDIO) -> EventBus.getDefault().post(AudioPermissionGrantedEvent(assigneeId)) } } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/dialog/SGAddMediaCommentDialog.kt b/apps/teacher/src/main/java/com/instructure/teacher/dialog/SGAddMediaCommentDialog.kt index 6795c7136..fd7fddd3c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/dialog/SGAddMediaCommentDialog.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/dialog/SGAddMediaCommentDialog.kt @@ -28,7 +28,6 @@ import com.instructure.pandautils.utils.* import com.instructure.teacher.R import com.instructure.teacher.activities.SpeedGraderActivity import com.instructure.teacher.databinding.DialogSgAddAttachmentCommentBinding -import com.instructure.teacher.view.MediaCommentDialogClosedEvent import org.greenrobot.eventbus.EventBus class SGAddMediaCommentDialog : BaseCanvasAppCompatDialogFragment() { @@ -77,7 +76,6 @@ class SGAddMediaCommentDialog : BaseCanvasAppCompatDialogFragment() { override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) - EventBus.getDefault().post(MediaCommentDialogClosedEvent()) } companion object { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFragment.kt index 7dbae3a6e..092674566 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/assignment/submission/SubmissionListFragment.kt @@ -49,7 +49,6 @@ import com.instructure.teacher.events.SubmissionCommentsUpdated import com.instructure.teacher.events.SubmissionFilterChangedEvent import com.instructure.teacher.features.postpolicies.ui.PostPolicyFragment import com.instructure.teacher.router.RouteMatcher -import com.instructure.teacher.view.QuizSubmissionGradedEvent import dagger.hilt.android.AndroidEntryPoint import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -147,12 +146,6 @@ class SubmissionListFragment : BaseCanvasFragment() { viewModel.uiState.value.actionHandler(SubmissionListAction.Refresh) } - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) - fun onQuizGraded(event: QuizSubmissionGradedEvent) { - viewModel.uiState.value.actionHandler(SubmissionListAction.Refresh) - } - @Suppress("unused") @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onSubmissionCommentUpdated(event: SubmissionCommentsUpdated) { 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 1fec4a9df..b209051e0 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 @@ -77,7 +77,6 @@ import com.instructure.teacher.utils.setupBackButtonWithExpandCollapseAndBack import com.instructure.teacher.utils.setupMenu import com.instructure.teacher.utils.shuffleAnswersDisplayable import com.instructure.teacher.utils.updateToolbarExpandCollapseIcon -import com.instructure.teacher.view.QuizSubmissionGradedEvent import com.instructure.teacher.viewinterface.QuizDetailsView import kotlinx.coroutines.Job import org.greenrobot.eventbus.EventBus @@ -518,14 +517,6 @@ class QuizDetailsFragment : BasePresenterFragment< } } - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) - fun onQuizGraded(event: QuizSubmissionGradedEvent) { - event.once(javaClass.simpleName) { - if (presenter.mQuiz.assignmentId == it.assignmentId) needToForceNetwork = true - } - } - companion object { @JvmStatic val QUIZ_ID = "quiz_details_quiz_id" @JvmStatic val QUIZ = "quiz_details_quiz" diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizListFragment.kt index 6aad4ebde..edb9fdfdc 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/QuizListFragment.kt @@ -50,7 +50,6 @@ import com.instructure.teacher.presenters.QuizListPresenter import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.RecyclerViewUtils import com.instructure.teacher.utils.setupBackButton -import com.instructure.teacher.view.QuizSubmissionGradedEvent import com.instructure.teacher.viewinterface.QuizListView import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -192,12 +191,6 @@ class QuizListFragment : BaseExpandableSyncFragment< } } - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) - fun onQuizGraded(event: QuizSubmissionGradedEvent) { - event.once(javaClass.simpleName) { needToForceNetwork = true } - } - override fun onHandleBackPressed() = binding.quizListToolbar.closeSearch() companion object { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt index 22fc3b92d..ac2990fa2 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt @@ -80,10 +80,6 @@ import com.instructure.teacher.presenters.SpeedGraderCommentsPresenter import com.instructure.teacher.utils.RecyclerViewUtils import com.instructure.teacher.utils.getColorCompat import com.instructure.teacher.utils.view -import com.instructure.teacher.view.CommentTextFocusedEvent -import com.instructure.teacher.view.MediaCommentDialogClosedEvent -import com.instructure.teacher.view.SubmissionSelectedEvent -import com.instructure.teacher.view.UploadMediaCommentEvent import com.instructure.teacher.viewinterface.SpeedGraderCommentsView import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -221,13 +217,6 @@ class SpeedGraderCommentsFragment : BaseListFragment - if (hasFocus) { - (requireActivity() as SpeedGraderActivity).openCommentLibrary(mSubmissionId) - EventBus.getDefault().post(CommentTextFocusedEvent(mAssignee.id)) - } - } - addAttachment.onClick { (requireActivity() as SpeedGraderActivity).closeCommentLibrary() SGAddMediaCommentDialog.show(requireActivity().supportFragmentManager, @@ -268,18 +257,6 @@ class SpeedGraderCommentsFragment : BaseListFragment { SubmissionManager.getSingleSubmission(mCourseId, mAssignmentId, mStudentId, it, true) } updatedSubmission.transformForQuizGrading() - EventBus.getDefault().postSticky(QuizSubmissionGradedEvent(updatedSubmission)) +// EventBus.getDefault().postSticky(QuizSubmissionGradedEvent(updatedSubmission)) SubmissionUpdatedEvent(updatedSubmission).post() } catch (e: StatusCallbackError) { toast(R.string.error_saving_quiz) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewMediaFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewMediaFragment.kt index 9c5ca056b..7d45a9cef 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewMediaFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewMediaFragment.kt @@ -46,7 +46,6 @@ import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.setupBackButtonWithExpandCollapseAndBack import com.instructure.teacher.utils.setupMenu import com.instructure.teacher.utils.updateToolbarExpandCollapseIcon -import com.instructure.teacher.view.MediaContent import org.greenrobot.eventbus.EventBus @ScreenView(SCREEN_VIEW_VIEW_MEDIA) @@ -213,7 +212,7 @@ class ViewMediaFragment : BaseCanvasFragment(), ShareableFile { companion object { - fun newInstance(media: MediaContent) = newInstance(media.uri, media.thumbnailUrl, media.contentType!!, media.displayName) +// fun newInstance(media: MediaContent) = newInstance(media.uri, media.thumbnailUrl, media.contentType!!, media.displayName) fun newInstance( uri: Uri, diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewPdfFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewPdfFragment.kt index 03b646cde..6daae172c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewPdfFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/ViewPdfFragment.kt @@ -36,9 +36,6 @@ import com.instructure.teacher.presenters.ViewPdfFragmentPresenter import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.* import com.instructure.teacher.viewinterface.ViewPdfFragmentView -import com.pspdfkit.configuration.PdfConfiguration -import com.pspdfkit.configuration.page.PageScrollDirection -import com.pspdfkit.ui.PdfFragment import com.instructure.pandautils.blueprint.PresenterFragment import org.greenrobot.eventbus.EventBus @@ -52,8 +49,6 @@ class ViewPdfFragment : PresenterFragment fragment = ViewUnsupportedFileFragment.newInstance(route.arguments) ChooseRecipientsFragment::class.java.isAssignableFrom(cls) -> fragment = ChooseRecipientsFragment.newInstance(route.arguments) SpeedGraderQuizWebViewFragment::class.java.isAssignableFrom(cls) -> fragment = SpeedGraderQuizWebViewFragment.newInstance(route.arguments) - AnnotationCommentListFragment::class.java.isAssignableFrom(cls) -> fragment = AnnotationCommentListFragment.newInstance(route.arguments) CreateDiscussionWebViewFragment::class.java.isAssignableFrom(cls) -> fragment = CreateDiscussionWebViewFragment.newInstance(route) SettingsFragment::class.java.isAssignableFrom(cls) -> fragment = SettingsFragment.newInstance(route) ProfileEditFragment::class.java.isAssignableFrom(cls) -> fragment = ProfileEditFragment.newInstance(route.arguments) 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 4f9155ff3..87650a607 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 @@ -26,7 +26,6 @@ import com.instructure.pandautils.fragments.HtmlContentFragment import com.instructure.pandautils.fragments.RemoteConfigParamsFragment 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.submission.SubmissionListFragment @@ -181,8 +180,6 @@ object RouteResolver { fragment = ChooseRecipientsFragment.newInstance(route.arguments) } else if (SpeedGraderQuizWebViewFragment::class.java.isAssignableFrom(cls)) { fragment = SpeedGraderQuizWebViewFragment.newInstance(route.arguments) - } else if (AnnotationCommentListFragment::class.java.isAssignableFrom(cls)) { - fragment = AnnotationCommentListFragment.newInstance(route.arguments) } else if (SettingsFragment::class.java.isAssignableFrom(cls)) { fragment = SettingsFragment.newInstance(route) } else if (ProfileEditFragment::class.java.isAssignableFrom(cls)) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt index cbded212d..8bada7635 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt @@ -40,10 +40,6 @@ import com.instructure.teacher.BuildConfig import com.instructure.teacher.R import com.instructure.teacher.activities.InitActivity import com.instructure.teacher.tasks.TeacherLogoutTask -import com.pspdfkit.PSPDFKit -import com.pspdfkit.exceptions.InvalidPSPDFKitLicenseException -import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException -import com.pspdfkit.initialization.InitializationOptions abstract class BaseAppManager : com.instructure.canvasapi2.AppManager() { @@ -75,14 +71,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager() { ColorKeeper.defaultColor = getColorCompat(R.color.textDarkest) - try { - PSPDFKit.initialize(this, InitializationOptions(licenseKey = BuildConfig.PSPDFKIT_LICENSE_KEY)) - } catch (e: PSPDFKitInitializationFailedException) { - Logger.e("Current device is not compatible with PSPDFKIT!") - } catch (e: InvalidPSPDFKitLicenseException) { - Logger.e("Invalid or Trial PSPDFKIT License!") - } - MasqueradeHelper.masqueradeLogoutTask = Runnable { TeacherLogoutTask( LogoutTask.Type.LOGOUT, diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/ModelExtensions.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/ModelExtensions.kt index 5a070472b..66e8b4734 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/ModelExtensions.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/ModelExtensions.kt @@ -37,7 +37,6 @@ import com.instructure.teacher.fragments.ViewImageFragment import com.instructure.teacher.fragments.ViewPdfFragment import com.instructure.teacher.fragments.ViewUnsupportedFileFragment import com.instructure.teacher.router.RouteMatcher -import com.pspdfkit.ui.PdfFragment // region Attachment extensions @@ -81,7 +80,6 @@ fun viewMedia( when { // PDF contentType == "application/pdf" -> { - PdfFragment() val bundle = ViewPdfFragment.newInstance(url ?: "", toolbarColor, editableFile).nonNullArgs if (fullScreen) { RouteMatcher.route(activity, Route(ViewPdfFragment::class.java, null, bundle)) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/CommentSubmissionView.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/CommentSubmissionView.kt index a19c4f827..b20009630 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/view/CommentSubmissionView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/view/CommentSubmissionView.kt @@ -92,7 +92,7 @@ class CommentSubmissionView(context: Context, val submission: Submission) : Line binding.subtitleTextView.text = subtitle } - binding.root.onClick { EventBus.getDefault().post(SubmissionSelectedEvent(submission)) } +// binding.root.onClick { EventBus.getDefault().post(SubmissionSelectedEvent(submission)) } addView(binding.root) } @@ -118,8 +118,8 @@ class CommentSubmissionView(context: Context, val submission: Submission) : Line binding.titleTextView.text = attachment.displayName binding.subtitleTextView.text = Formatter.formatFileSize(context, attachment.size) binding.root.onClick { - EventBus.getDefault().post(SubmissionSelectedEvent(submission)) - EventBus.getDefault().post(SubmissionFileSelectedEvent(submission.id, attachment)) +// EventBus.getDefault().post(SubmissionSelectedEvent(submission)) +// EventBus.getDefault().post(SubmissionFileSelectedEvent(submission.id, attachment)) } (binding.root.layoutParams as LayoutParams).topMargin = context.DP(4).toInt() addView(binding.root) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/view/SubmissionContentView.kt b/apps/teacher/src/main/java/com/instructure/teacher/view/SubmissionContentView.kt index 049b2f6ab..a696abb33 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/view/SubmissionContentView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/view/SubmissionContentView.kt @@ -68,6 +68,7 @@ import com.instructure.canvasapi2.utils.Logger import com.instructure.canvasapi2.utils.Pronouns import com.instructure.canvasapi2.utils.exhaustive import com.instructure.canvasapi2.utils.validOrNull +import com.instructure.canvasapi2.utils.weave.WeaveCoroutine import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch import com.instructure.canvasapi2.utils.weave.tryWeave @@ -96,7 +97,6 @@ import com.instructure.pandautils.views.ExpandCollapseAnimation import com.instructure.pandautils.views.ProgressiveCanvasLoadingView import com.instructure.pandautils.views.RecordingMediaType import com.instructure.pandautils.views.ViewPagerNonSwipeable -import com.instructure.teacher.PSPDFKit.AnnotationComments.AnnotationCommentListFragment import com.instructure.teacher.R import com.instructure.teacher.activities.SpeedGraderActivity import com.instructure.teacher.adapters.StudentContextFragment @@ -125,10 +125,6 @@ import com.instructure.teacher.utils.isTablet import com.instructure.teacher.utils.setAnonymousAvatar import com.instructure.teacher.utils.setupMenu import com.instructure.teacher.utils.transformForQuizGrading -import com.pspdfkit.ui.PdfFragment -import com.pspdfkit.ui.inspector.PropertyInspectorCoordinatorLayout -import com.pspdfkit.ui.special_mode.manager.AnnotationManager -import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout import com.sothree.slidinguppanel.SlidingUpPanelLayout import kotlinx.coroutines.Job import okhttp3.ResponseBody @@ -146,21 +142,12 @@ class SubmissionContentView( private val studentSubmission: GradeableStudentSubmission, private val assignment: Assignment, private val course: Course, - var initialTabIndex: Int = 0 -) : PdfSubmissionView(context, courseId = course.id), AnnotationManager.OnAnnotationCreationModeChangeListener, AnnotationManager.OnAnnotationEditingModeChangeListener { + var initialTabIndex: Int = 0, +) : PdfSubmissionView(context, courseId = course.id) { + override lateinit var pdfContentJob: WeaveCoroutine private val binding: ViewSubmissionContentBinding - override val annotationToolbarLayout: ToolbarCoordinatorLayout - get() = binding.annotationToolbarLayout - override val inspectorCoordinatorLayout: PropertyInspectorCoordinatorLayout - get() = binding.inspectorCoordinatorLayout - override val commentsButton: ImageView - get() = binding.commentsButton - override val loadingContainer: FrameLayout - get() = binding.loadingContainer - override val progressBar: ProgressiveCanvasLoadingView - get() = binding.speedGraderProgressBar override val progressColor: Int get() = R.color.login_teacherAppTheme @@ -175,7 +162,14 @@ class SubmissionContentView( private var isCleanedUp = false private val activity: SpeedGraderActivity get() = context as SpeedGraderActivity - private val gradeFragment by lazy { SpeedGraderGradeFragment.newInstance(rootSubmission, assignment, course, assignee) } + private val gradeFragment by lazy { + SpeedGraderGradeFragment.newInstance( + rootSubmission, + assignment, + course, + assignee + ) + } val hasUnsavedChanges: Boolean get() = gradeFragment.hasUnsavedChanges @@ -183,52 +177,6 @@ class SubmissionContentView( private var selectedSubmission: Submission? = null private var assignmentEnhancementsEnabled = false - override fun showNoInternetDialog() { - NoInternetConnectionDialog.show(supportFragmentManager) - } - - override fun disableViewPager() { - activity.disableViewPager() - } - - override fun enableViewPager() { - activity.enableViewPager() - } - - override fun setIsCurrentlyAnnotating(boolean: Boolean) { - activity.isCurrentlyAnnotating = boolean - } - - override fun showFileError() { - showMessageFragment(R.string.error_loading_files) - } - - override fun showAnnotationComments(commentList: ArrayList, headAnnotationId: String, docSession: DocSession, apiValues: ApiValues) { - val bundle = AnnotationCommentListFragment.makeBundle(commentList, headAnnotationId, docSession, apiValues, assignee.id) - //if isTablet, we need to prevent the sliding panel from moving opening all the way with the keyboard - if(context.isTablet) { - setIsCurrentlyAnnotating(true) - } - RouteMatcher.route(activity as FragmentActivity, Route(AnnotationCommentListFragment::class.java, null, bundle)) - } - - @SuppressLint("CommitTransaction") - override fun setFragment(fragment: Fragment) { - if (!isCleanedUp && isAttachedToWindow) supportFragmentManager.beginTransaction().replace(containerId, fragment).commitNowAllowingStateLoss() - - //if we can share the content with another app, show the share icon - binding.speedGraderToolbar.menu.findItem(R.id.menu_share).apply { - isVisible = fragment is ShareableFile || fragment is PdfFragment - iconTintList = ContextCompat.getColorStateList(context, R.color.textDarkest) - } - } - - override fun removeContentFragment() { - val contentFragment = supportFragmentManager.findFragmentById(containerId) - if (contentFragment != null) { - supportFragmentManager.beginTransaction().remove(contentFragment).commitAllowingStateLoss() - } - } //region view lifecycle init { @@ -236,18 +184,10 @@ class SubmissionContentView( setLoading(true) - //if we have anonymous peer reviews we don't want the teacher to be able to annotate - if (assignment.isPeerReviews && assignment.anonymousPeerReviews) { - annotationToolbarLayout.setGone() - inspectorCoordinatorLayout.setGone() - } - containerId = View.generateViewId() binding.content.id = containerId bottomViewPager = binding.bottomViewPager.apply { id = View.generateViewId() } - initializeSubmissionView() - if (isAccessibilityEnabled(context)) { binding.slidingUpPanelLayout?.anchorPoint = 1.0f } @@ -309,12 +249,25 @@ class SubmissionContentView( initJob = tryWeave { if (!studentSubmission.isCached) { // Determine if the logged in user is an Observer - val enrollments = awaitApi> { EnrollmentManager.getObserveeEnrollments(true, it) } + val enrollments = awaitApi> { + EnrollmentManager.getObserveeEnrollments( + true, + it + ) + } val isObserver = enrollments.any { it.isObserver } if (isObserver) { // Get the first observee associated with this course val observee = enrollments.first { it.courseId == course.id } - studentSubmission.submission = awaitApi { SubmissionManager.getSingleSubmission(course.id, assignment.id, studentSubmission.assigneeId, it, true) } + studentSubmission.submission = awaitApi { + SubmissionManager.getSingleSubmission( + course.id, + assignment.id, + studentSubmission.assigneeId, + it, + true + ) + } } else { // Get the user's submission normally studentSubmission.submission = awaitApi { @@ -327,8 +280,10 @@ class SubmissionContentView( ) } } - val featureFlags = FeaturesManager.getEnabledFeaturesForCourseAsync(course.id, true).await().dataOrNull - assignmentEnhancementsEnabled = featureFlags?.contains("assignments_2_student").orDefault() + val featureFlags = FeaturesManager.getEnabledFeaturesForCourseAsync(course.id, true) + .await().dataOrNull + assignmentEnhancementsEnabled = + featureFlags?.contains("assignments_2_student").orDefault() studentSubmission.isCached = true } setup() @@ -348,9 +303,9 @@ class SubmissionContentView( if (SubmissionType.ONLINE_QUIZ in assignment.getSubmissionTypes()) rootSubmission?.transformForQuizGrading() setupToolbar(assignee) setupDueDate(assignment) - setupSubmissionVersions(rootSubmission?.submissionHistory?.filterNotNull()?.filter { it.attempt > 0 }) + setupSubmissionVersions( + rootSubmission?.submissionHistory?.filterNotNull()?.filter { it.attempt > 0 }) setSubmission(rootSubmission) - setupBottomSheetViewPager(course) setupSlidingPanel() //we must set up the sliding panel prior to registering to the event EventBus.getDefault().register(this) @@ -360,7 +315,8 @@ class SubmissionContentView( private fun setupDueDate(assignment: Assignment) = with(binding) { if (assignment.dueDate != null) { dueDateTextView?.setVisible(true) - dueDateTextView?.text = DateHelper.getDateAtTimeString(context, R.string.due_dateTime, assignment.dueDate) + dueDateTextView?.text = + DateHelper.getDateAtTimeString(context, R.string.due_dateTime, assignment.dueDate) } } @@ -372,45 +328,9 @@ class SubmissionContentView( EventBus.getDefault().unregister(this) } - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) = with(binding) { - // Resize sliding panel and content, don't if keyboard based annotations are active or selected - if (oldh > 0 && oldh != h && !activity.isCurrentlyAnnotating && pdfFragment?.selectedAnnotations?.isEmpty() != false) { - val newState = when { - context.isTablet -> slidingUpPanelLayout?.panelState ?: SlidingUpPanelLayout.PanelState.ANCHORED - h < oldh -> SlidingUpPanelLayout.PanelState.EXPANDED - else -> SlidingUpPanelLayout.PanelState.ANCHORED - } - - if (newState != SlidingUpPanelLayout.PanelState.DRAGGING) { - slidingUpPanelLayout?.panelState = newState - } - - // Have to post here as we wait for contentRoot height to settle - contentRoot.post { - val slideOffset = when (newState) { - SlidingUpPanelLayout.PanelState.EXPANDED -> 1f - SlidingUpPanelLayout.PanelState.ANCHORED -> 0.5f - else -> 0f - } - - val maxHeight = contentRoot.height - val adjustedHeight = Math.abs(maxHeight * slideOffset).toInt() - - if (slideOffset >= 0.50F) { //Prevents resizing views when not necessary - this@SubmissionContentView.bottomViewPager.layoutParams?.height = adjustedHeight - this@SubmissionContentView.bottomViewPager.requestLayout() - } else if (slideOffset <= 0.50F) { - contentWrapper?.layoutParams?.height = Math.abs(maxHeight - adjustedHeight) - contentWrapper?.requestLayout() - } - } - } - } - @SuppressLint("CommitTransaction") fun performCleanup() { isCleanedUp = true - getCurrentFragment()?.let { supportFragmentManager.beginTransaction().remove(it).commit() } } //endregion @@ -423,22 +343,23 @@ class SubmissionContentView( submission?.submissionType == null -> NoSubmissionContent assignment.getState(submission) == AssignmentUtils2.ASSIGNMENT_STATE_MISSING || assignment.getState(submission) == AssignmentUtils2.ASSIGNMENT_STATE_GRADED_MISSING -> NoSubmissionContent + else -> when (Assignment.getSubmissionTypeFromAPIString(submission.submissionType!!)) { - // LTI submission + // LTI submission SubmissionType.BASIC_LTI_LAUNCH -> ExternalToolContent( - submission.previewUrl.validOrNull() ?: assignment.url.validOrNull() - ?: assignment.htmlUrl ?: "" + submission.previewUrl.validOrNull() ?: assignment.url.validOrNull() + ?: assignment.htmlUrl ?: "" ) - // Text submission + // Text submission SubmissionType.ONLINE_TEXT_ENTRY -> TextContent(submission.body ?: "") - // Media submission + // Media submission SubmissionType.MEDIA_RECORDING -> submission.mediaComment?.let { MediaContent( - uri = Uri.parse(it.url), - contentType = it.contentType ?: "", + uri = Uri.parse(it.url), + contentType = it.contentType ?: "", displayName = it.displayName ) } ?: UnsupportedContent @@ -447,7 +368,10 @@ class SubmissionContentView( SubmissionType.ONLINE_UPLOAD -> getAttachmentContent(submission.attachments[0]) // URL Submission - SubmissionType.ONLINE_URL -> UrlContent(submission.url!!, submission.attachments.firstOrNull()?.url) + SubmissionType.ONLINE_URL -> UrlContent( + submission.url!!, + submission.attachments.firstOrNull()?.url + ) // Quiz Submission SubmissionType.ONLINE_QUIZ -> handleQuizSubmissionType(submission) @@ -463,9 +387,6 @@ class SubmissionContentView( else -> UnsupportedContent } } - (bottomViewPager.adapter as? BottomSheetPagerAdapter)?.refreshFilesTabCount(submission?.attachments?.size - ?: 0) - setGradeableContent(content) } private fun handleQuizSubmissionType(submission: Submission): GradeableContent { @@ -488,67 +409,81 @@ class SubmissionContentView( val fileExtension = attachment.filename?.substringAfterLast(".") ?: "" type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension) ?: MimeTypeMap.getFileExtensionFromUrl(attachment.url) - ?: type + ?: type } return when { - type == "application/pdf" || (attachment.previewUrl?.contains(Const.CANVADOC) ?: false) -> { + type == "application/pdf" || (attachment.previewUrl?.contains(Const.CANVADOC) + ?: false) -> { if (attachment.previewUrl?.contains(Const.CANVADOC) == true) { PdfContent(attachment.previewUrl ?: "") } else { PdfContent(attachment.url ?: "") } } + type.startsWith("audio") || type.startsWith("video") -> with(attachment) { MediaContent( - uri = Uri.parse(url), - thumbnailUrl = thumbnailUrl, - contentType = contentType, - displayName = displayName + uri = Uri.parse(url), + thumbnailUrl = thumbnailUrl, + contentType = contentType, + displayName = displayName ) } + type.startsWith("image") -> ImageContent(attachment.url ?: "", attachment.contentType!!) else -> OtherAttachmentContent(attachment) } } - private fun setAttachmentContent(attachment: Attachment) { - setGradeableContent(getAttachmentContent(attachment)) - } - - private fun setupSubmissionVersions(unsortedSubmissions: List?) = with(binding.submissionVersionsSpinner) { - if (unsortedSubmissions.isNullOrEmpty()) { - setGone() - } else { - unsortedSubmissions.sortedByDescending { it.submittedAt }.let { submissions -> - val itemViewModels = submissions.mapIndexed { index, submission -> - AssignmentDetailsAttemptItemViewModel( - AssignmentDetailsAttemptViewData( - context.getString(R.string.attempt, unsortedSubmissions.size - index), - submission.submittedAt?.let { getFormattedAttemptDate(it) }.orEmpty() + private fun setupSubmissionVersions(unsortedSubmissions: List?) = + with(binding.submissionVersionsSpinner) { + if (unsortedSubmissions.isNullOrEmpty()) { + setGone() + } else { + unsortedSubmissions.sortedByDescending { it.submittedAt }.let { submissions -> + val itemViewModels = submissions.mapIndexed { index, submission -> + AssignmentDetailsAttemptItemViewModel( + AssignmentDetailsAttemptViewData( + context.getString( + R.string.attempt, + unsortedSubmissions.size - index + ), + submission.submittedAt?.let { getFormattedAttemptDate(it) } + .orEmpty() + ) ) + } + adapter = BindableSpinnerAdapter( + context, + R.layout.item_submission_attempt_spinner, + itemViewModels ) - } - adapter = BindableSpinnerAdapter(context, R.layout.item_submission_attempt_spinner, itemViewModels) - setSelection(0, false) - onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) = Unit - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - EventBus.getDefault().post(SubmissionSelectedEvent(submissions[position])) + setSelection(0, false) + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + EventBus.getDefault() + .post(SubmissionSelectedEvent(submissions[position])) + } } - } - if (submissions.size > 1) { - setVisible() - } else { - setGone() - binding.attemptView?.apply { - itemViewModel = itemViewModels.firstOrNull() - root.setVisible() + if (submissions.size > 1) { + setVisible() + } else { + setGone() + binding.attemptView?.apply { + itemViewModel = itemViewModels.firstOrNull() + root.setVisible() + } } } } } - } private fun getFormattedAttemptDate(date: Date): String = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, @@ -577,7 +512,6 @@ class SubmissionContentView( } } - speedGraderToolbar.setupMenu(R.menu.menu_share_file, menuItemCallback) speedGraderToolbar.foregroundGravity = Gravity.CENTER_VERTICAL ViewStyler.setToolbarElevationSmall(context, speedGraderToolbar) @@ -585,11 +519,18 @@ class SubmissionContentView( assignment.anonymousGrading -> userImageView.setAnonymousAvatar() assignee is GroupAssignee -> userImageView.setImageResource(assignee.iconRes) assignee is StudentAssignee -> { - ProfileUtils.loadAvatarForUser(userImageView, assignee.student.name, assignee.student.avatarUrl) + ProfileUtils.loadAvatarForUser( + userImageView, + assignee.student.name, + assignee.student.avatarUrl + ) userImageView.setupAvatarA11y(assignee.name) userImageView.onClick { val bundle = StudentContextFragment.makeBundle(assignee.id, course.id) - RouteMatcher.route(activity as FragmentActivity, Route(StudentContextFragment::class.java, null, bundle)) + RouteMatcher.route( + activity as FragmentActivity, + Route(StudentContextFragment::class.java, null, bundle) + ) } } } @@ -597,19 +538,6 @@ class SubmissionContentView( if (assignee is GroupAssignee && !assignment.anonymousGrading) setupGroupMemberList(assignee) } - private val menuItemCallback: (MenuItem) -> Unit = { item -> - when (item.itemId) { - R.id.menu_share -> { - (getCurrentFragment() as? ShareableFile)?.viewExternally() - - //pdfs are a different type of fragment - if (pdfFragment != null) { - pdfFragment?.document?.documentSource?.fileUri?.viewExternally(context, "application/pdf") - } - } - } - } - private fun setupGroupMemberList(assignee: GroupAssignee) = with(binding) { assigneeWrapperView.onClick { val popup = ListPopupWindow(context) @@ -617,7 +545,8 @@ class SubmissionContentView( popup.setAdapter(object : ArrayAdapter(context, 0, assignee.students) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val user = getItem(position) - val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.adapter_speed_grader_group_member, parent, false) + val view = convertView ?: LayoutInflater.from(context) + .inflate(R.layout.adapter_speed_grader_group_member, parent, false) val memberAvatarView = view.findViewById(R.id.memberAvatarView) ProfileUtils.loadAvatarForUser(memberAvatarView, user?.name, user?.avatarUrl) val memberNameView = view.findViewById(R.id.memberNameView) @@ -629,139 +558,71 @@ class SubmissionContentView( popup.verticalOffset = -assigneeWrapperView.height popup.isModal = true // For a11y popup.setOnItemClickListener { _, _, position, _ -> - val bundle = StudentContextFragment.makeBundle(assignee.students[position].id, course.id) - RouteMatcher.route(activity as FragmentActivity, Route(StudentContextFragment::class.java, null, bundle)) + val bundle = + StudentContextFragment.makeBundle(assignee.students[position].id, course.id) + RouteMatcher.route( + activity as FragmentActivity, + Route(StudentContextFragment::class.java, null, bundle) + ) popup.dismiss() } popup.show() } } - private fun setGradeableContent(content: GradeableContent) = with(binding) { - // Handle the existing PdfFragment if there is one - val currentFragment = getCurrentFragment() - if (currentFragment is PdfFragment) { - // Unregister listeners for the existing fragment - unregisterPdfFragmentListeners() - } - - topDivider?.setVisible(!(content is PdfContent)) - - when (content) { - is PdfContent -> { - if(content.url.contains("canvadoc")) { - if(slidingUpPanelLayout?.panelState == SlidingUpPanelLayout.PanelState.ANCHORED) { - // Attempt to reset the sliding panel to collapsed, so we don't render the pdf at anchored size - slidingUpPanelLayout.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED - } - handlePdfContent(content.url) - } else { - showMessageFragment(R.string.pdfError) - } - } - is NoSubmissionContent -> when (assignee) { - is StudentAssignee -> showMessageFragment(R.string.noSubmission, R.string.noSubmissionTeacher) - is GroupAssignee -> showMessageFragment(R.string.speedgrader_group_no_submissions) - } - is UnsupportedContent -> showMessageFragment(R.string.speedgrader_unsupported_type) - is UrlContent -> setFragment(SpeedGraderUrlSubmissionFragment.newInstance(content.url, content.previewUrl)) - is QuizContent -> setFragment(SpeedGraderQuizSubmissionFragment.newInstance(content)) - is OtherAttachmentContent -> with(content.attachment) { - setFragment(ViewUnsupportedFileFragment.newInstance( - uri = Uri.parse(url), - displayName = displayName ?: filename ?: "", - contentType = contentType ?: "", - previewUri = thumbnailUrl?.let { Uri.parse(it) }, - fallbackIcon = iconRes - )) - } - is TextContent -> setFragment(SpeedGraderTextSubmissionFragment.newInstance(content.text)) - is MediaContent -> setFragment(ViewMediaFragment.newInstance(content)) - is ImageContent -> load(content.url) { setFragment(ViewImageFragment.newInstance(content.url, it, content.contentType, false)) } - is NoneContent -> showMessageFragment(R.string.speedGraderNoneMessage) - is ExternalToolContent -> setFragment(SpeedGraderLtiSubmissionFragment.newInstance(content)) - is OnPaperContent -> showMessageFragment(R.string.speedGraderOnPaperMessage) - is DiscussionContent -> setFragment(SimpleWebViewFragment.newInstance(content.previewUrl!!)) - is AnonymousSubmissionContent -> showMessageFragment(R.string.speedGraderAnonymousSubmissionMessage) - is StudentAnnotationContent -> { - studentAnnotationJob = tryWeave { - val canvaDocSession = CanvaDocsManager.createCanvaDocSessionAsync( - content.submissionId, - content.attempt.toString() - ).await().dataOrThrow - handlePdfContent(canvaDocSession.canvadocsSessionUrl ?: "") - } catch { - toast(R.string.errorLoadingSubmission) - } - } - }.exhaustive - } - - private fun showMessageFragment(@StringRes stringRes: Int) = showMessageFragment(resources.getString(stringRes)) + private fun showMessageFragment(@StringRes stringRes: Int) = + showMessageFragment(resources.getString(stringRes)) private fun showMessageFragment(@StringRes titleRes: Int, @StringRes messageRes: Int) = - showMessageFragment(resources.getString(titleRes), resources.getString(messageRes)) + showMessageFragment(resources.getString(titleRes), resources.getString(messageRes)) private fun showMessageFragment(message: String) { val fragment = SpeedGraderEmptyFragment.newInstance(message = message) - setFragment(fragment) } private fun showMessageFragment(title: String, message: String) { val fragment = SpeedGraderEmptyFragment.newInstance(title = title, message = message) - setFragment(fragment) - } - - private fun getCurrentFragment(): Fragment? { - return supportFragmentManager.findFragmentById(containerId) - } - - override fun attachDocListener() { - if (!(assignment.anonymousPeerReviews && assignment.isPeerReviews)) { - // We don't need to do annotations if there are anonymous peer reviews - if(docSession.annotationMetadata?.canWrite() == true) { - if ((context as Activity).isTablet) - pdfFragment?.setInsets(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 68f, context.resources.displayMetrics).toInt(), 0, 0) - else - pdfFragment?.setInsets(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, context.resources.displayMetrics).toInt(), 0, 0) - } - super.attachDocListener() - } else { - pdfFragment?.setInsets(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, context.resources.displayMetrics).toInt(), 0, 0) - } } private fun setupSlidingPanel() = with(binding) { - slidingUpPanelLayout?.addPanelSlideListener(object : SlidingUpPanelLayout.PanelSlideListener { + slidingUpPanelLayout?.addPanelSlideListener(object : + SlidingUpPanelLayout.PanelSlideListener { override fun onPanelSlide(panel: View?, slideOffset: Float) { adjustPanelHeights(slideOffset) } - override fun onPanelStateChanged(panel: View?, - previousState: SlidingUpPanelLayout.PanelState?, - newState: SlidingUpPanelLayout.PanelState?) { + override fun onPanelStateChanged( + panel: View?, + previousState: SlidingUpPanelLayout.PanelState?, + newState: SlidingUpPanelLayout.PanelState? + ) { if (newState != previousState) { // We don't want to update for all states, just these three when (newState) { SlidingUpPanelLayout.PanelState.ANCHORED -> { submissionVersionsSpinner.isClickable = true postPanelEvent(newState, 0.5f) - contentRoot.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + contentRoot.importantForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } + SlidingUpPanelLayout.PanelState.EXPANDED -> { submissionVersionsSpinner.isClickable = false postPanelEvent(newState, 1.0f) - contentRoot.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + contentRoot.importantForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } + SlidingUpPanelLayout.PanelState.COLLAPSED -> { submissionVersionsSpinner.isClickable = true //fix for rotating when the panel is collapsed - pdfFragment?.notifyLayoutChanged() postPanelEvent(newState, 0.0f) - contentRoot.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES + contentRoot.importantForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_YES } + else -> {} } } @@ -787,73 +648,21 @@ class SubmissionContentView( } } - private fun setupBottomSheetViewPager(course: Course) = with(binding) { - this@SubmissionContentView.bottomViewPager.offscreenPageLimit = 2 - this@SubmissionContentView.bottomViewPager.adapter = BottomSheetPagerAdapter.Holder(supportFragmentManager) - .add(gradeFragment) - .add(SpeedGraderCommentsFragment.newInstance( - rootSubmission, - assignee, - this@SubmissionContentView.course.id, - assignment.id, - assignment.groupCategoryId > 0 && assignee is GroupAssignee, - assignment.anonymousGrading, - assignmentEnhancementsEnabled - )) - .add(SpeedGraderFilesFragment.newInstance(rootSubmission)) - .setFileCount(rootSubmission?.attachments?.size ?: 0) - .set() - - this@SubmissionContentView.bottomViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - - override fun onPageSelected(position: Int) {} - - override fun onPageScrollStateChanged(state: Int) { - if (state == ViewPager.SCROLL_STATE_IDLE) { - - } - } - }) - - bottomTabLayout.setupWithViewPager(this@SubmissionContentView.bottomViewPager) - bottomTabLayout.setSelectedTabIndicatorColor(course.color) - bottomTabLayout.setTabTextColors(ContextCompat.getColor(context, R.color.textDarkest), course.color) - bottomTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabReselected(tab: TabLayout.Tab?) { - if (slidingUpPanelLayout?.panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) { - slidingUpPanelLayout.panelState = SlidingUpPanelLayout.PanelState.ANCHORED - } - } - - override fun onTabUnselected(tab: TabLayout.Tab?) {} - override fun onTabSelected(tab: TabLayout.Tab?) { - EventBus.getDefault().post(TabSelectedEvent(tab?.position ?: 0)) - if (slidingUpPanelLayout?.panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) { - slidingUpPanelLayout.panelState = SlidingUpPanelLayout.PanelState.ANCHORED - } - } - }) - - val spacing = resources.getDimensionPixelOffset(R.dimen.speedgrader_tab_spacing) - - for (i in 0 until bottomTabLayout.tabCount) { - val tab = (bottomTabLayout.getChildAt(0) as ViewGroup).getChildAt(i) - val params = tab.layoutParams as MarginLayoutParams - params.setMargins(spacing, 0, spacing, 0) - tab.requestLayout() - } - - this@SubmissionContentView.bottomViewPager.currentItem = initialTabIndex - } - private fun showVideoCommentDialog() = with(binding) { activity.disableViewPager() floatingRecordingView.setContentType(RecordingMediaType.Video) floatingRecordingView.startVideoView() floatingRecordingView.recordingCallback = { it?.let { - EventBus.getDefault().post(UploadMediaCommentEvent(it, assignment.id, assignment.courseId, assignee.id, selectedSubmission?.attempt)) + EventBus.getDefault().post( + UploadMediaCommentEvent( + it, + assignment.id, + assignment.courseId, + assignee.id, + selectedSubmission?.attempt + ) + ) } } floatingRecordingView.stoppedCallback = { @@ -861,7 +670,12 @@ class SubmissionContentView( EventBus.getDefault().post(MediaCommentDialogClosedEvent()) } floatingRecordingView.replayCallback = { - val bundle = BaseViewMediaActivity.makeBundle(it, "video", context.getString(R.string.videoCommentReplay), true) + val bundle = BaseViewMediaActivity.makeBundle( + it, + "video", + context.getString(R.string.videoCommentReplay), + true + ) RouteMatcher.route(activity as FragmentActivity, Route(bundle, RouteContext.MEDIA)) } } @@ -876,223 +690,86 @@ class SubmissionContentView( } floatingRecordingView.recordingCallback = { it?.let { - EventBus.getDefault().post(UploadMediaCommentEvent(it, assignment.id, assignment.courseId, assignee.id, selectedSubmission?.attempt)) - } - } - } - - private fun getAudioPermission() { - ActivityCompat.requestPermissions((context as SpeedGraderActivity), arrayOf(PermissionUtils.RECORD_AUDIO), PermissionUtils.PERMISSION_REQUEST_CODE) - } - - private fun getVideoPermission() { - ActivityCompat.requestPermissions((context as SpeedGraderActivity), arrayOf(PermissionUtils.CAMERA, PermissionUtils.RECORD_AUDIO), PermissionUtils.PERMISSION_REQUEST_CODE) - } - //endregion - - private class BottomSheetPagerAdapter internal constructor(fm: FragmentManager, fragments: ArrayList, var fileCount: Int = 0) : FragmentPagerAdapter(fm) { - - private var fragments = ArrayList() - - init { - this.fragments = fragments - } - - override fun getItem(position: Int) = fragments[position] - - override fun getCount() = fragments.size - - override fun getPageTitle(position: Int) = when (position) { - 0 -> ContextKeeper.appContext.getString(R.string.sg_tab_grade).uppercase(Locale.getDefault()) - 1 -> ContextKeeper.appContext.getString(R.string.sg_tab_comments).uppercase(Locale.getDefault()) - 2 -> ContextKeeper.appContext.getString(R.string.sg_tab_files_w_counter, fileCount).uppercase(Locale.getDefault()) - else -> "" - } - - fun refreshFilesTabCount(fileCount: Int) { - this.fileCount = fileCount - notifyDataSetChanged() - } - - internal class Holder(private val manager: FragmentManager) { - - private val fragments = ArrayList() - private var fileCount: Int = 0 - - fun add(f: Fragment): Holder { - fragments.add(f) - return this - } - - fun setFileCount(fileCount: Int): Holder { - this.fileCount = fileCount - return this - } - - fun set() = BottomSheetPagerAdapter(manager, fragments, fileCount) - } - - override fun finishUpdate(container: ViewGroup) { - // Workaround for known issue in the support library - try { - super.finishUpdate(container) - } catch (nullPointerException: NullPointerException) { - } - } - } - - //endregion - - //region event bus subscriptions - @Subscribe(threadMode = ThreadMode.MAIN) - fun onSwitchSubmission(event: SubmissionSelectedEvent) { - //close the annotations toolbar so it can be associated with new document - pdfFragment?.exitCurrentlyActiveMode() - if (event.submission?.id == rootSubmission?.id) setSubmission(event.submission) - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onSwitchAttachment(event: SubmissionFileSelectedEvent) { - if (event.submissionId == rootSubmission?.id) { - //close the annotations toolbar so it can be associated with new document - pdfFragment?.exitCurrentlyActiveMode() - setAttachmentContent(event.attachment) - } - } - - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - fun onAnchorChanged(event: SlidingPanelAnchorEvent) { - binding.slidingUpPanelLayout?.panelState = event.anchorPosition - //If we try to adjust the panels before contentRoot's height is determined, things don't work - //This post works because we setup the panel before registering to the event - binding.contentRoot.post { adjustPanelHeights(event.offset) } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onCommentTextFocused(event: CommentTextFocusedEvent) { - if (event.assigneeId == assignee.id) { - activity.isCurrentlyAnnotating = false - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onAnnotationCommentAdded(event: AnnotationCommentAdded) { - if (event.assigneeId == assignee.id) { - //add the comment to the hashmap - commentRepliesHashMap[event.annotation.inReplyTo]?.add(event.annotation) - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onAnnotationCommentEdited(event: AnnotationCommentEdited) { - if (event.assigneeId == assignee.id) { - //update the annotation in the hashmap - commentRepliesHashMap[event.annotation.inReplyTo]?.find { it.annotationId == event.annotation.annotationId }?.contents = event.annotation.contents - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onAnnotationCommentDeleted(event: AnnotationCommentDeleted) { - if (event.assigneeId == assignee.id) { - if (event.isHeadAnnotation) { - //we need to delete the entire list of comments from the hashmap - commentRepliesHashMap.remove(event.annotation.inReplyTo) - pdfFragment?.selectedAnnotations?.get(0)?.contents = "" - noteHinter?.notifyDrawablesChanged() - } else { - //otherwise just remove the comment - commentRepliesHashMap[event.annotation.inReplyTo]?.remove(event.annotation) - } - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onAnnotationCommentDeleteAcknowledged(event: AnnotationCommentDeleteAcknowledged) { - if (event.assigneeId == assignee.id) { - deleteJob = tryWeave { - for(annotation in event.annotationList) { - awaitApi { CanvaDocsManager.deleteAnnotation(apiValues.sessionId, annotation.annotationId, apiValues.canvaDocsDomain, it) } - commentRepliesHashMap[annotation.inReplyTo]?.remove(annotation) - } - } catch { - Logger.d("There was an error acknowledging the delete!") + EventBus.getDefault().post( + UploadMediaCommentEvent( + it, + assignment.id, + assignment.courseId, + assignee.id, + selectedSubmission?.attempt + ) + ) } } } - @Subscribe - fun onTabSelected(event: TabSelectedEvent) { - bottomViewPager.currentItem = event.selectedTabIdx - } - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onAudioPermissionGranted(event: AudioPermissionGrantedEvent) { - if (compareAssignees(event.assigneeId)) - showAudioCommentDialog() - } - - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onVideoPermissionGranted(event: VideoPermissionGrantedEvent) { - if (compareAssignees(event.assigneeId)) - showVideoCommentDialog() - } - - private fun compareAssignees(eventAssigneeId: Long): Boolean{ - return if(assignee is GroupAssignee) { - (assignee as GroupAssignee).group.users.any { it.id == eventAssigneeId } - } else { - return eventAssigneeId == assignee.id - } - } - //endregion -} - -class SubmissionSelectedEvent(val submission: Submission?) -class SubmissionFileSelectedEvent(val submissionId: Long, val attachment: Attachment) -class QuizSubmissionGradedEvent(submission: Submission) : RationedBusEvent(submission) -class SlidingPanelAnchorEvent(val anchorPosition: SlidingUpPanelLayout.PanelState, val offset: Float) -class CommentTextFocusedEvent(val assigneeId: Long) -class AnnotationCommentAdded(val annotation: CanvaDocAnnotation, val assigneeId: Long) -class AnnotationCommentEdited(val annotation: CanvaDocAnnotation, val assigneeId: Long) -class AnnotationCommentDeleted(val annotation: CanvaDocAnnotation, val isHeadAnnotation: Boolean, val assigneeId: Long) -class AnnotationCommentDeleteAcknowledged(val annotationList: List, val assigneeId: Long) -class TabSelectedEvent(val selectedTabIdx: Int) -class UploadMediaCommentEvent(val file: File, val assignmentId: Long, val courseId: Long, val assigneeId: Long, val attemptId: Long?) - - -sealed class GradeableContent -object NoSubmissionContent : GradeableContent() -object NoneContent : GradeableContent() -class ExternalToolContent(val url: String) : GradeableContent() -object OnPaperContent : GradeableContent() -object UnsupportedContent : GradeableContent() -class OtherAttachmentContent(val attachment: Attachment) : GradeableContent() -class PdfContent(val url: String) : GradeableContent() -class TextContent(val text: String) : GradeableContent() -class ImageContent(val url: String, val contentType: String) : GradeableContent() -class UrlContent(val url: String, val previewUrl: String?) : GradeableContent() -class DiscussionContent(val previewUrl: String?) : GradeableContent() -class StudentAnnotationContent(val submissionId: Long, val attempt: Long) : GradeableContent() -object AnonymousSubmissionContent : GradeableContent() - -class MediaCommentDialogClosedEvent -class AudioPermissionGrantedEvent(val assigneeId: Long) -class VideoPermissionGrantedEvent(val assigneeId: Long) - - -class QuizContent( - val courseId: Long, - val assignmentId: Long, - val studentId: Long, - val url: String, - val pendingReview: Boolean -) : GradeableContent() - -class MediaContent( + class SubmissionSelectedEvent(val submission: Submission?) + class SubmissionFileSelectedEvent(val submissionId: Long, val attachment: Attachment) + class QuizSubmissionGradedEvent(submission: Submission) : + RationedBusEvent(submission) + + class SlidingPanelAnchorEvent( + val anchorPosition: SlidingUpPanelLayout.PanelState, + val offset: Float + ) + + class CommentTextFocusedEvent(val assigneeId: Long) + class AnnotationCommentAdded(val annotation: CanvaDocAnnotation, val assigneeId: Long) + class AnnotationCommentEdited(val annotation: CanvaDocAnnotation, val assigneeId: Long) + class AnnotationCommentDeleted( + val annotation: CanvaDocAnnotation, + val isHeadAnnotation: Boolean, + val assigneeId: Long + ) + + class AnnotationCommentDeleteAcknowledged( + val annotationList: List, + val assigneeId: Long + ) + + class TabSelectedEvent(val selectedTabIdx: Int) + class UploadMediaCommentEvent( + val file: File, + val assignmentId: Long, + val courseId: Long, + val assigneeId: Long, + val attemptId: Long? + ) + + + sealed class GradeableContent + object NoSubmissionContent : GradeableContent() + object NoneContent : GradeableContent() + class ExternalToolContent(val url: String) : GradeableContent() + object OnPaperContent : GradeableContent() + object UnsupportedContent : GradeableContent() + class OtherAttachmentContent(val attachment: Attachment) : GradeableContent() + class PdfContent(val url: String) : GradeableContent() + class TextContent(val text: String) : GradeableContent() + class ImageContent(val url: String, val contentType: String) : GradeableContent() + class UrlContent(val url: String, val previewUrl: String?) : GradeableContent() + class DiscussionContent(val previewUrl: String?) : GradeableContent() + class StudentAnnotationContent(val submissionId: Long, val attempt: Long) : GradeableContent() + object AnonymousSubmissionContent : GradeableContent() + + class MediaCommentDialogClosedEvent + class AudioPermissionGrantedEvent(val assigneeId: Long) + class VideoPermissionGrantedEvent(val assigneeId: Long) + + + class QuizContent( + val courseId: Long, + val assignmentId: Long, + val studentId: Long, + val url: String, + val pendingReview: Boolean + ) : GradeableContent() + + class MediaContent( val uri: Uri, val contentType: String? = null, val thumbnailUrl: String? = null, val displayName: String? = null -) : GradeableContent() + ) : GradeableContent() +} diff --git a/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml b/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml index 6162ffc5f..6092d2297 100644 --- a/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml +++ b/apps/teacher/src/main/res/layout-sw760dp/view_submission_content.xml @@ -220,16 +220,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/teacher/src/main/res/values-v35/styles.xml b/apps/teacher/src/main/res/values-v35/styles.xml index 32375bbaf..36ff67fec 100644 --- a/apps/teacher/src/main/res/values-v35/styles.xml +++ b/apps/teacher/src/main/res/values-v35/styles.xml @@ -14,7 +14,6 @@ @color/backgroundLightest @style/Widget.ActionButton.Overflow - @color/backgroundLight @style/DatePickerStyle true diff --git a/apps/teacher/src/main/res/values/styles.xml b/apps/teacher/src/main/res/values/styles.xml index 3875d44d3..021f94e52 100644 --- a/apps/teacher/src/main/res/values/styles.xml +++ b/apps/teacher/src/main/res/values/styles.xml @@ -28,70 +28,9 @@ @style/NoGifEditText @color/backgroundLightest @style/Widget.ActionButton.Overflow - @color/backgroundLight @style/DatePickerStyle - - - - - - - - - - - - - - - -