diff --git a/.gitignore b/.gitignore
index ddeb227f3..83d5f3b6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,5 @@ fastlane/.idea
apps/captures
libs/DocumentScanner/build/
+apps/buildSrc/src/main/java/candroid.keystore
+open_source_data/student/google-services.json
diff --git a/.gitmodules b/.gitmodules
index 06b7c3fb6..d13a3c5f9 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "apps/mobile-offline-downloader-android"]
path = apps/mobile-offline-downloader-android
url = https://github.com/2uinc/mobile-offline-downloader-android.git
+ branch = main
diff --git a/apps/buildSrc/src/main/java/OfflineDependencies.kt b/apps/buildSrc/src/main/java/OfflineDependencies.kt
index f273c6922..781da524f 100644
--- a/apps/buildSrc/src/main/java/OfflineDependencies.kt
+++ b/apps/buildSrc/src/main/java/OfflineDependencies.kt
@@ -1,6 +1,6 @@
object OfflineDependencies {
- const val versionCode = 10
- const val versionName = "1.5"
+ const val versionCode = 15
+ const val versionName = "1.9"
const val constraintLayout = "2.1.0"
const val appCompat = "1.5.0"
const val recyclerView = "1.2.1"
diff --git a/apps/mobile-offline-downloader-android b/apps/mobile-offline-downloader-android
index 0282231d7..b0882937c 160000
--- a/apps/mobile-offline-downloader-android
+++ b/apps/mobile-offline-downloader-android
@@ -1 +1 @@
-Subproject commit 0282231d77782a5738d3d87a12568402533bdb44
+Subproject commit b0882937c27056276c5933ceb53775c334432ea5
diff --git a/apps/settings.gradle b/apps/settings.gradle
index 255c7d14b..b220898ad 100644
--- a/apps/settings.gradle
+++ b/apps/settings.gradle
@@ -1,7 +1,5 @@
/* Top-level project modules */
include ':student'
-include ':teacher'
-include ':parent'
/* Library modules */
include ':annotations'
diff --git a/apps/student/build.gradle b/apps/student/build.gradle
index 185936cf5..1d0d6f50e 100644
--- a/apps/student/build.gradle
+++ b/apps/student/build.gradle
@@ -51,7 +51,6 @@ android {
/* Add private data */
PrivateData.merge(project, "student")
buildConfigField "String", "TRANSLATION_TAGS", "\"${LocaleScanner.getAvailableLanguageTags(project)}\""
- buildConfigField "String", "PSPDFKIT_LICENSE_KEY", "\"$pspdfkitLicenseKey\""
testBuildType = "debug"
diff --git a/apps/student/proguard-rules.txt b/apps/student/proguard-rules.txt
index 1b35a6619..02ff2a7a8 100644
--- a/apps/student/proguard-rules.txt
+++ b/apps/student/proguard-rules.txt
@@ -193,16 +193,6 @@
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okhttp3.**
-#PSPDFKit
--keep class com.pspdfkit.** { *; }
--dontwarn sun.misc.**
--dontwarn edu.umd.cs.findbugs.annotations.SuppressWarnings
--keepnames class io.reactivex.android.schedulers.AndroidSchedulers
--keepnames class io.reactivex.Observable
--dontwarn com.pspdfkit.**
--dontwarn android.view.WindowInsets
--dontwarn android.graphics.Insets
-
# Keep third party lib class names. PSPDFKit relies on those class names to be present.
-keepnames class rx.android.schedulers.AndroidSchedulers
-keepnames class rx.Observable
diff --git a/apps/student/src/main/AndroidManifest.xml b/apps/student/src/main/AndroidManifest.xml
index 0d0f633e3..a584e1694 100644
--- a/apps/student/src/main/AndroidManifest.xml
+++ b/apps/student/src/main/AndroidManifest.xml
@@ -155,11 +155,6 @@
android:foregroundServiceType="dataSync"
android:exported="false" />
-
-
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
index e9eaaac3c..591773fb5 100644
--- a/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListFragment.kt
+++ b/apps/student/src/main/java/com/instructure/student/AnnotationComments/AnnotationCommentListFragment.kt
@@ -122,10 +122,6 @@ class AnnotationCommentListFragment : ParentFragment() {
sendCommentJob?.cancel()
editCommentJob?.cancel()
deleteCommentJob?.cancel()
- EventBus.getDefault().post(
- PdfStudentSubmissionView.AnnotationCommentDeleteAcknowledged(
- annotations.filter { it.deleted && it.deleteAcknowledged.isNullOrEmpty() },
- assigneeId))
}
fun configureRecyclerView() {
@@ -186,7 +182,6 @@ class AnnotationCommentListFragment : ParentFragment() {
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) }
- EventBus.getDefault().post(PdfStudentSubmissionView.AnnotationCommentAdded(newCommentReply, assigneeId))
// 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
@@ -204,7 +199,6 @@ class AnnotationCommentListFragment : ParentFragment() {
private fun editComment(annotation: CanvaDocAnnotation, position: Int) {
editCommentJob = tryWeave {
awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, annotation.annotationId, annotation, apiValues.canvaDocsDomain, it) }
- EventBus.getDefault().post(PdfStudentSubmissionView.AnnotationCommentEdited(annotation, assigneeId))
// Update the UI
recyclerAdapter?.add(annotation)
recyclerAdapter?.notifyItemChanged(position)
@@ -219,10 +213,8 @@ class AnnotationCommentListFragment : ParentFragment() {
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
- EventBus.getDefault().post(PdfStudentSubmissionView.AnnotationCommentDeleted(annotation, true, assigneeId))
headAnnotationDeleted()
} else {
- EventBus.getDefault().post(PdfStudentSubmissionView.AnnotationCommentDeleted(annotation, false, assigneeId))
recyclerAdapter?.remove(annotation)
recyclerAdapter?.notifyItemChanged(position)
}
diff --git a/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt
deleted file mode 100644
index f29384eb1..000000000
--- a/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2017 - present Instructure, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package com.instructure.student.activity
-
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.util.LayoutDirection
-import android.util.TypedValue
-import android.view.Menu
-import android.view.MenuItem
-import androidx.annotation.ColorInt
-import androidx.core.text.TextUtilsCompat
-import com.instructure.annotations.CanvasPdfMenuGrouping
-import com.instructure.loginapi.login.dialog.NoInternetConnectionDialog
-import com.instructure.pandautils.analytics.SCREEN_VIEW_PSPDFKIT
-import com.instructure.pandautils.analytics.ScreenView
-import com.instructure.pandautils.features.shareextension.ShareFileSubmissionTarget
-import com.instructure.pandautils.utils.Const
-import com.instructure.pandautils.utils.NetworkStateProvider
-import com.instructure.pandautils.utils.ViewStyler
-import com.instructure.student.R
-import com.instructure.student.features.shareextension.StudentShareExtensionActivity
-import com.pspdfkit.annotations.AnnotationType
-import com.pspdfkit.document.processor.PdfProcessorTask
-import com.pspdfkit.document.sharing.DefaultDocumentSharingController
-import com.pspdfkit.document.sharing.DocumentSharingIntentHelper
-import com.pspdfkit.document.sharing.DocumentSharingManager
-import com.pspdfkit.document.sharing.SharingOptions
-import com.pspdfkit.ui.PdfActivity
-import com.pspdfkit.ui.toolbar.AnnotationCreationToolbar
-import com.pspdfkit.ui.toolbar.ContextualToolbar
-import com.pspdfkit.ui.toolbar.ContextualToolbarMenuItem
-import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.File
-import java.util.EnumSet
-import java.util.Locale
-import javax.inject.Inject
-
-@ScreenView(SCREEN_VIEW_PSPDFKIT)
-@AndroidEntryPoint
-class CandroidPSPDFActivity : PdfActivity(), ToolbarCoordinatorLayout.OnContextualToolbarLifecycleListener {
- override fun onDisplayContextualToolbar(p0: ContextualToolbar<*>) {}
- override fun onRemoveContextualToolbar(p0: ContextualToolbar<*>) {}
-
- private var menuItems: List? = null
-
- private val submissionTarget by lazy { intent?.extras?.getParcelable(Const.SUBMISSION_TARGET) }
-
- @Inject
- lateinit var networkStateProvider: NetworkStateProvider
-
- override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) {
- if(toolbar is AnnotationCreationToolbar) {
- toolbar.setMenuItemGroupingRule(CanvasPdfMenuGrouping(this))
- toolbar.layoutParams = ToolbarCoordinatorLayout.LayoutParams(
- ToolbarCoordinatorLayout.LayoutParams.Position.TOP, EnumSet.of(ToolbarCoordinatorLayout.LayoutParams.Position.TOP))
- }
-
- // We need to reverse the order of the menu items for RTL, once PSPDFKit supports RTL this can be deleted
- if (menuItems == null) {
- menuItems = toolbar.menuItems
- if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == LayoutDirection.RTL) {
- for (item in menuItems!!) {
- if (item.position == ContextualToolbarMenuItem.Position.START) {
- item.position = ContextualToolbarMenuItem.Position.END
- } else {
- item.position = ContextualToolbarMenuItem.Position.START
- }
- }
- if(toolbar.closeButton.position == ContextualToolbarMenuItem.Position.START) {
- toolbar.closeButton.position = ContextualToolbarMenuItem.Position.END
- } else {
- toolbar.closeButton.position = ContextualToolbarMenuItem.Position.START
- }
- }
- } else {
- toolbar.menuItems = menuItems as List
- }
- }
-
-
- override fun onDestroy() {
- val path = filesDir.path + intent.data?.path?.replace("/files", "")
- document?.let {
- val annotations = it.annotationProvider.getAllAnnotationsOfType(
- EnumSet.allOf(AnnotationType::class.java)
- )
- if (annotations.isEmpty() && networkStateProvider.isOnline()) {
- val file = File(path)
- if (file.exists()) {
- file.delete()
- }
- }
- }
-
- super.onDestroy()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setOnContextualToolbarLifecycleListener(this)
-
- val typedValue = TypedValue()
- theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
- @ColorInt val color = typedValue.data
- ViewStyler.setStatusBarDark(this, color)
- }
-
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- super.onCreateOptionsMenu(menu)
- menuInflater.inflate(R.menu.pspdf_activity_menu, menu)
- if (submissionTarget != null) {
- // If targeted for submission, change the menu item title from "Upload to Canvas" to "Submit Assignment"
- val item = menu?.findItem(R.id.upload_item)
- item?.title = getString(R.string.submitAssignment)
- }
- return true
- }
-
- override fun onGetShowAsAction(menuItemId: Int, defaultShowAsAction: Int): Int {
- return if (menuItemId != R.id.upload_item) MenuItem.SHOW_AS_ACTION_ALWAYS else super.onGetShowAsAction(menuItemId, defaultShowAsAction)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- super.onOptionsItemSelected(item)
- return if (item.itemId == R.id.upload_item) {
- uploadDocumentToCanvas()
- true
- } else {
- false
- }
- }
-
- private fun uploadDocumentToCanvas() {
- if (document != null) {
- if (networkStateProvider.isOnline()) {
- DocumentSharingManager.shareDocument(
- CandroidDocumentSharingController(this, submissionTarget),
- document!!,
- SharingOptions(PdfProcessorTask.AnnotationProcessingMode.FLATTEN))
- } else {
- NoInternetConnectionDialog.show(supportFragmentManager)
- }
- }
- }
-
-
- private inner class CandroidDocumentSharingController(
- private val mContext: Context,
- private val submissionTarget: ShareFileSubmissionTarget?
- ) : DefaultDocumentSharingController(mContext) {
-
- override fun onDocumentPrepared(shareUri: Uri) {
- val intent = Intent(mContext, StudentShareExtensionActivity::class.java)
- intent.type = DocumentSharingIntentHelper.MIME_TYPE_PDF
- intent.putExtra(Intent.EXTRA_STREAM, shareUri)
- intent.putExtra(Const.SUBMISSION_TARGET, submissionTarget)
- intent.action = Intent.ACTION_SEND
- mContext.startActivity(intent)
- }
- }
-}
diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/details/StudentAssignmentDetailsSubmissionHandler.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/details/StudentAssignmentDetailsSubmissionHandler.kt
index 82fba4c1f..fe064efdc 100644
--- a/apps/student/src/main/java/com/instructure/student/features/assignments/details/StudentAssignmentDetailsSubmissionHandler.kt
+++ b/apps/student/src/main/java/com/instructure/student/features/assignments/details/StudentAssignmentDetailsSubmissionHandler.kt
@@ -134,7 +134,7 @@ class StudentAssignmentDetailsSubmissionHandler(
}
if (isUploading && submission.errorFlag) {
data.value?.attempts = attempts?.toMutableList()?.apply {
- if (isNotEmpty()) removeFirst()
+ if (isNotEmpty()) removeAt(0)
add(0, AssignmentDetailsAttemptItemViewModel(
AssignmentDetailsAttemptViewData(
resources.getString(R.string.attempt, attempts.size),
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionUploadFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionUploadFragment.kt
index d1d16c0c4..2a6b3ea17 100644
--- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionUploadFragment.kt
+++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionUploadFragment.kt
@@ -71,8 +71,6 @@ class AnnotationSubmissionUploadFragment : BaseCanvasFragment() {
PdfStudentSubmissionView(
activity = requireActivity(),
pdfUrl = it,
- fragmentManager = childFragmentManager,
- studentAnnotationSubmit = true,
courseId = canvasContext.id
)
)
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/AnnotationSubmissionViewFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/AnnotationSubmissionViewFragment.kt
index 559db7aa9..613795aaf 100644
--- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/AnnotationSubmissionViewFragment.kt
+++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/AnnotationSubmissionViewFragment.kt
@@ -53,9 +53,7 @@ class AnnotationSubmissionViewFragment : BaseCanvasFragment() {
PdfStudentSubmissionView(
requireActivity(),
it,
- courseId,
- childFragmentManager,
- studentAnnotationView = true,
+ courseId
)
)
}
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfStudentSubmissionView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfStudentSubmissionView.kt
index c3243b460..7eae6c4da 100644
--- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfStudentSubmissionView.kt
+++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfStudentSubmissionView.kt
@@ -17,136 +17,40 @@
package com.instructure.student.mobius.assignmentDetails.submissionDetails.content
import android.annotation.SuppressLint
-import android.graphics.Color
-import android.util.TypedValue
-import android.view.Gravity
import android.view.LayoutInflater
-import android.widget.FrameLayout
-import android.widget.ImageView
-import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
-import androidx.fragment.app.FragmentManager
import com.instructure.annotations.PdfSubmissionView
-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.Analytics
-import com.instructure.canvasapi2.utils.AnalyticsEventConstants
-import com.instructure.canvasapi2.utils.ApiPrefs
-import com.instructure.canvasapi2.utils.Logger
-import com.instructure.canvasapi2.utils.weave.awaitApi
-import com.instructure.canvasapi2.utils.weave.catch
-import com.instructure.canvasapi2.utils.weave.tryWeave
-import com.instructure.loginapi.login.dialog.NoInternetConnectionDialog
-import com.instructure.pandautils.utils.onClick
-import com.instructure.pandautils.utils.setGone
+import com.instructure.canvasapi2.utils.weave.WeaveCoroutine
+import com.instructure.pandautils.utils.bind
import com.instructure.pandautils.utils.setVisible
-import com.instructure.pandautils.views.ProgressiveCanvasLoadingView
-import com.instructure.student.AnnotationComments.AnnotationCommentListFragment
import com.instructure.student.R
import com.instructure.student.databinding.ViewPdfStudentSubmissionBinding
-import com.instructure.student.router.RouteMatcher
-import com.pspdfkit.preferences.PSPDFKitPreferences
-import com.pspdfkit.ui.inspector.PropertyInspectorCoordinatorLayout
-import com.pspdfkit.ui.special_mode.manager.AnnotationManager
-import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import okhttp3.ResponseBody
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
+import kotlinx.coroutines.launch
@SuppressLint("ViewConstructor")
class PdfStudentSubmissionView(
private val activity: FragmentActivity,
private val pdfUrl: String,
private val courseId: Long,
- private val fragmentManager: FragmentManager,
- private val studentAnnotationSubmit: Boolean = false,
- private val studentAnnotationView: Boolean = false,
+ private val studentAnnotationView: Boolean = false
+
) : PdfSubmissionView(
activity, studentAnnotationView, courseId
-), AnnotationManager.OnAnnotationCreationModeChangeListener, AnnotationManager.OnAnnotationEditingModeChangeListener {
+) {
private val binding: ViewPdfStudentSubmissionBinding
- private var initJob: Job? = null
- private var deleteJob: Job? = null
+ override lateinit var pdfContentJob: WeaveCoroutine
- 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.progressBar
+ private var initJob: Job? = null
override val progressColor: Int
get() = R.color.login_studentAppTheme
- override fun disableViewPager() {}
- override fun enableViewPager() {}
- override fun setIsCurrentlyAnnotating(boolean: Boolean) {}
-
- override fun showAnnotationComments(
- commentList: ArrayList,
- headAnnotationId: String,
- docSession: DocSession,
- apiValues: ApiValues
- ) {
- if (isAttachedToWindow) RouteMatcher.route(
- activity,
- AnnotationCommentListFragment.makeRoute(commentList, headAnnotationId, docSession, apiValues, ApiPrefs.user!!.id, !studentAnnotationView)
- )
- }
-
- override fun showFileError() {
- binding.loadingView.setGone()
- binding.retryLoadingContainer.setVisible()
- binding.retryLoadingButton.onClick {
- setLoading(true)
- setup()
- }
- }
-
- override fun configureCommentView(commentsButton: ImageView) {
- // If we are making annotations position the comments button as we would position in the teacher.
- if (studentAnnotationSubmit) {
- super.configureCommentView(commentsButton)
- return
- }
-
- //we want to offset the comment button by the height of the action bar
- val marginDp = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 12f, context.resources.displayMetrics)
- val layoutParams = commentsButton.layoutParams as LayoutParams
- commentsButton.drawable.setTint(Color.WHITE)
- layoutParams.gravity = Gravity.END or Gravity.TOP
- layoutParams.topMargin = marginDp.toInt()
- layoutParams.rightMargin = marginDp.toInt()
-
- commentsButton.onClick {
- openComments()
- }
- }
-
- override fun logOnAnnotationSelectedAnalytics() {
- Analytics.logEvent(AnalyticsEventConstants.SUBMISSION_ANNOTATION_SELECTED)
- }
-
- override fun showNoInternetDialog() {
- NoInternetConnectionDialog.show(fragmentManager)
- }
-
init {
- if (!PSPDFKitPreferences.get(getContext()).isAnnotationCreatorSet) {
- PSPDFKitPreferences.get(getContext()).setAnnotationCreator(ApiPrefs.user?.name)
- }
-
binding = ViewPdfStudentSubmissionBinding.inflate(LayoutInflater.from(context), this, true)
-
setLoading(true)
}
@@ -157,27 +61,23 @@ class PdfStudentSubmissionView(
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- //we must set up the sliding panel prior to registering to the event
- EventBus.getDefault().register(this)
-
setup()
}
- override fun attachDocListener() {
- // We need to add this flag, because we want to show the toolbar in the student annotation, but hide when
- // we open an already submitted file submission with a teacher's annotations.
- if (!studentAnnotationSubmit) {
- // Modify the session data permissions to make sure students can't annotate already submitted assignments
- if (docSession.annotationMetadata?.canWrite() == true) {
- docSession.annotationMetadata?.permissions = "read"
- }
- // Default is to have top inset, remove this since there will be no toolbar
- pdfFragment?.setInsets(0, 0, 0, 0)
- }
- super.attachDocListener()
+ override fun onFileInitialized() {
+ binding.openExternallyButton.setText(R.string.utils_openWithAnotherApp)
+ binding.openExternallyButton.isEnabled = true
}
fun setup() {
+
+ binding.openExternallyButton.setText(R.string.downloadingInProgress)
+ binding.openExternallyButton.isEnabled = false
+
+ binding.openExternallyButton.setOnClickListener {
+ openPdf()
+ }
+
handlePdfContent(pdfUrl)
setLoading(false)
}
@@ -185,69 +85,5 @@ class PdfStudentSubmissionView(
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
initJob?.cancel()
- EventBus.getDefault().unregister(this)
- }
-
-
- @SuppressLint("CommitTransaction")
- override fun setFragment(fragment: Fragment) {
- if (isAttachedToWindow) fragmentManager.beginTransaction().replace(binding.content.id, fragment).commitNowAllowingStateLoss()
- }
-
- override fun removeContentFragment() {
- val contentFragment = fragmentManager.findFragmentById(binding.content.id)
- if (contentFragment != null) {
- fragmentManager.beginTransaction().remove(contentFragment).commitAllowingStateLoss()
- }
}
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onAnnotationCommentAdded(event: AnnotationCommentAdded) {
- if (event.assigneeId == ApiPrefs.user!!.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 == ApiPrefs.user!!.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 == ApiPrefs.user!!.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 == ApiPrefs.user!!.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!")
- }
- }
- }
-
- 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)
}
\ No newline at end of file
diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfSubmissionViewFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfSubmissionViewFragment.kt
index 8f1a121ce..b07b77ce3 100644
--- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfSubmissionViewFragment.kt
+++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/content/PdfSubmissionViewFragment.kt
@@ -30,7 +30,7 @@ class PdfSubmissionViewFragment : BaseCanvasFragment() {
private var courseId by LongArg()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return PdfStudentSubmissionView(requireActivity(), pdfUrl, courseId, childFragmentManager)
+ return PdfStudentSubmissionView(requireActivity(), pdfUrl, courseId)
}
companion object {
diff --git a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt
index ce30a75ca..1052fdb39 100644
--- a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt
+++ b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt
@@ -35,10 +35,6 @@ import com.instructure.pandautils.utils.ThemePrefs
import com.instructure.student.BuildConfig
import com.instructure.student.R
import com.instructure.student.activity.NavigationActivity
-import com.pspdfkit.PSPDFKit
-import com.pspdfkit.exceptions.InvalidPSPDFKitLicenseException
-import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException
-import com.pspdfkit.initialization.InitializationOptions
import com.zynksoftware.documentscanner.ui.DocumentScanner
abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling {
@@ -61,8 +57,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti
// Hold off on initializing this until we emit the user properties.
RemoteConfigUtils.initialize()
- //initPSPDFKit()
-
initDocumentScanning()
if (BuildConfig.DEBUG) {
@@ -110,16 +104,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti
}
- private fun initPSPDFKit() {
- 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!")
- }
- }
-
private fun initDocumentScanning() {
DocumentScanner.init(this)
}
diff --git a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt
index 73fb0b106..5eb0b0b5b 100644
--- a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt
+++ b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt
@@ -20,20 +20,9 @@ package com.instructure.student.util
import android.content.Context
import android.net.Uri
import androidx.annotation.IntegerRes
-import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader
import com.instructure.student.R
-import com.instructure.student.activity.CandroidPSPDFActivity
import com.instructure.pandautils.features.shareextension.ShareFileSubmissionTarget
-import com.pspdfkit.PSPDFKit
-import com.pspdfkit.annotations.AnnotationType
-import com.pspdfkit.configuration.activity.PdfActivityConfiguration
-import com.pspdfkit.configuration.activity.ThumbnailBarMode
-import com.pspdfkit.configuration.page.PageFitMode
-import com.pspdfkit.configuration.page.PageScrollDirection
-import com.pspdfkit.preferences.PSPDFKitPreferences
-import com.pspdfkit.ui.PdfActivityIntentBuilder
-import com.pspdfkit.ui.special_mode.controller.AnnotationTool
object FileUtils {
@@ -43,68 +32,7 @@ object FileUtils {
context: Context,
submissionTarget: ShareFileSubmissionTarget? = null
) {
- val annotationCreationList = listOf(
- AnnotationTool.INK,
- AnnotationTool.HIGHLIGHT,
- AnnotationTool.STRIKEOUT,
- AnnotationTool.SQUARE,
- AnnotationTool.NOTE,
- AnnotationTool.FREETEXT,
- AnnotationTool.ERASER
- )
-
- val annotationEditList = listOf(
- AnnotationType.INK,
- AnnotationType.HIGHLIGHT,
- AnnotationType.STRIKEOUT,
- AnnotationType.SQUARE,
- AnnotationType.NOTE,
- AnnotationType.FREETEXT,
- AnnotationType.NONE // Wee need this to enable the eraser
- )
- if (!PSPDFKitPreferences.get(context).isAnnotationCreatorSet) {
- PSPDFKitPreferences.get(context).setAnnotationCreator(ApiPrefs.user?.shortName.orEmpty())
- }
-
- val pspdfActivityConfiguration: PdfActivityConfiguration
-
- if (loadedMedia.isSubmission) {
- // We don't want to allow users to edit for submission viewing
- pspdfActivityConfiguration = PdfActivityConfiguration.Builder(context)
- .scrollDirection(PageScrollDirection.HORIZONTAL)
- .showThumbnailGrid()
- .setThumbnailBarMode(ThumbnailBarMode.THUMBNAIL_BAR_MODE_PINNED)
- .disableAnnotationEditing()
- .disableAnnotationList()
- .disableDocumentEditor()
- .fitMode(PageFitMode.FIT_TO_WIDTH)
- .build()
- } else {
- // Standard behavior
- pspdfActivityConfiguration = PdfActivityConfiguration.Builder(context)
- .scrollDirection(PageScrollDirection.HORIZONTAL)
- .showThumbnailGrid()
- .setDocumentInfoViewSeparated(false)
- .enableDocumentEditor()
- .enabledAnnotationTools(annotationCreationList)
- .editableAnnotationTypes(annotationEditList)
- .fitMode(PageFitMode.FIT_TO_WIDTH)
- .build()
- }
-
- if (PSPDFKit.isOpenableUri(context, uri)) {
- val intent = PdfActivityIntentBuilder
- .fromUri(context, uri)
- .configuration(pspdfActivityConfiguration)
- .activityClass(CandroidPSPDFActivity::class.java)
- .build()
- intent.putExtra(com.instructure.pandautils.utils.Const.SUBMISSION_TARGET, submissionTarget)
- context.startActivity(intent)
- } else {
- //If we still can't open this PDF, we will then attempt to pass it off to the user's pdfviewer
- context.startActivity(loadedMedia.intent)
- }
-
+ context.startActivity(loadedMedia.intent)
}
@IntegerRes
@@ -120,4 +48,6 @@ object FileUtils {
}
}
}
+
+
}
diff --git a/apps/student/src/main/res/layout/view_pdf_student_submission.xml b/apps/student/src/main/res/layout/view_pdf_student_submission.xml
index 0fae96121..e6487ce64 100644
--- a/apps/student/src/main/res/layout/view_pdf_student_submission.xml
+++ b/apps/student/src/main/res/layout/view_pdf_student_submission.xml
@@ -29,89 +29,66 @@
app:pclv_indeterminate="true"
app:pclv_override_color="@color/login_studentAppTheme"/>
-
+ android:fillViewport="true"
+ android:visibility="gone"
+ android:scrollbars="none">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ android:layout_marginTop="24dp"
+ android:textColor="@color/textDarkest"
+ android:textSize="20sp"
+ tools:text="sample_file_name.png"
+ android:focusable="true"
+ android:importantForAccessibility="yes"/>
+
+
+
+
-
+
-
+
diff --git a/apps/student/src/main/res/values-ldrtl/styles.xml b/apps/student/src/main/res/values-ldrtl/styles.xml
deleted file mode 100644
index 4f0fa3ecf..000000000
--- a/apps/student/src/main/res/values-ldrtl/styles.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/student/src/main/res/values-night/styles.xml b/apps/student/src/main/res/values-night/styles.xml
deleted file mode 100644
index eb2fb7e72..000000000
--- a/apps/student/src/main/res/values-night/styles.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/student/src/main/res/values-v35/themes_canvastheme.xml b/apps/student/src/main/res/values-v35/themes_canvastheme.xml
index 6b29d72e3..84ec534da 100644
--- a/apps/student/src/main/res/values-v35/themes_canvastheme.xml
+++ b/apps/student/src/main/res/values-v35/themes_canvastheme.xml
@@ -18,18 +18,6 @@
- @style/AppBottomSheetDialogTheme
- @style/NoGifEditText
- @style/NoGifEditText
- - @style/PropertyInspector
- - @style/ContextualToolbarStyle
- -
- @style/AnnotationCreationToolbarIconsStyle
-
- -
- @style/AnnotationEditingToolbarIconsStyle
-
- - @style/StampPickerStyle
- - @style/ModalDialogStyle
- - @style/AnnotationNoteHinter
- - @color/backgroundLight
- @style/DatePickerStyle
- true
diff --git a/apps/student/src/main/res/values/styles.xml b/apps/student/src/main/res/values/styles.xml
index 0bc8f4b60..9ebf34b80 100644
--- a/apps/student/src/main/res/values/styles.xml
+++ b/apps/student/src/main/res/values/styles.xml
@@ -36,70 +36,6 @@
- false
- @null
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/student/src/test/java/com/instructure/student/test/util/ModuleUtilityTest.kt b/apps/student/src/test/java/com/instructure/student/test/util/ModuleUtilityTest.kt
index d4c052a9e..b440a65a5 100644
--- a/apps/student/src/test/java/com/instructure/student/test/util/ModuleUtilityTest.kt
+++ b/apps/student/src/test/java/com/instructure/student/test/util/ModuleUtilityTest.kt
@@ -1,358 +1,358 @@
-/*
- * Copyright (C) 2017 - present Instructure, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.instructure.student.test.util
-
-import android.content.Context
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.instructure.canvasapi2.models.Course
-import com.instructure.canvasapi2.models.ModuleItem
-import com.instructure.canvasapi2.models.ModuleObject
-import com.instructure.canvasapi2.models.Tab
-import com.instructure.pandautils.features.assignments.details.AssignmentDetailsFragment
-import com.instructure.pandautils.features.discussion.details.DiscussionDetailsWebViewFragment
-import com.instructure.student.features.discussion.details.DiscussionDetailsFragment
-import com.instructure.student.features.files.details.FileDetailsFragment
-import com.instructure.student.features.modules.progression.ModuleQuizDecider
-import com.instructure.student.features.modules.progression.NotAvailableOfflineFragment
-import com.instructure.student.features.modules.util.ModuleUtility
-import com.instructure.student.features.pages.details.PageDetailsFragment
-import com.instructure.student.fragment.InternalWebviewFragment
-import com.instructure.student.util.Const
-import io.mockk.mockk
-import junit.framework.TestCase
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ModuleUtilityTest : TestCase() {
-
- private val context = mockk(relaxed = true)
-
- @Test
- fun testGetFragment_file() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "File",
- url = url
- )
-
- var moduleObject: ModuleObject? = ModuleObject(
- id = 1234
- )
-
- val course = Course()
- val expectedUrl = "courses/222/assignments/123456789"
-
- var expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putString(Const.FILE_URL, expectedUrl)
- expectedBundle.putInt(Const.FILE_ID, 0)
- expectedBundle.putLong(Const.ITEM_ID, moduleItem.id)
- expectedBundle.putParcelable(com.instructure.pandautils.utils.Const.MODULE_OBJECT, moduleObject)
-
-
- var parentFragment = callGetFragment(moduleItem, course, moduleObject)
- assertNotNull(parentFragment)
- assertEquals(FileDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
-
- // Test module object is null
- moduleObject = null
- expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putString(Const.FILE_URL, expectedUrl)
- expectedBundle.putInt(Const.FILE_ID, 0)
- parentFragment = callGetFragment(moduleItem, course, moduleObject)
- assertNotNull(parentFragment)
- assertEquals(FileDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
-
- }
-
- @Test
- fun testGetFragment_fileOfflineNotAvailable() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "File",
- url = url
- )
-
- val moduleObject: ModuleObject = ModuleObject(
- id = 1234
- )
-
- val course = Course()
-
- val filDetailsFragment = callGetFragment(moduleItem, course, moduleObject, isOnline = false)
- assertNotNull(filDetailsFragment)
- assertEquals(NotAvailableOfflineFragment::class.java, filDetailsFragment!!.javaClass)
- }
-
- @Test
- fun testGetFragment_page() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/pages/hello-world"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Page",
- url = url,
- title = "hello-world"
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putString(PageDetailsFragment.PAGE_NAME, "hello-world")
- expectedBundle.putBoolean(PageDetailsFragment.NAVIGATED_FROM_MODULES, false)
-
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(PageDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_assignment() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Assignment",
- url = url
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
- expectedBundle.putLong(Const.ASSIGNMENT_ID, 123456789)
-
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_assignment_offlineSynced() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Assignment",
- url = url
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
- expectedBundle.putLong(Const.ASSIGNMENT_ID, 123456789)
-
- val parentFragment = callGetFragment(moduleItem, course, null, isOnline = false, tabs = setOf(Tab.ASSIGNMENTS_ID))
- assertNotNull(parentFragment)
- assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_assignment_offlineNotSynced() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Assignment",
- url = url
- )
-
- val course = Course()
-
- val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
- assertNotNull(fragment)
- assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
- }
-
- @Test
- fun testGetFragment_assignmentShardId() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/12345~6789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Assignment",
- url = url
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
- expectedBundle.putLong(Const.ASSIGNMENT_ID, 123450000000006789)
-
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_assignmentSubmissionShardId() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/12345~6789/submissions"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Assignment",
- url = url
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
- expectedBundle.putLong(Const.ASSIGNMENT_ID, 123450000000006789)
-
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_externalurl_externaltool() {
- val url = "https://instructure.com"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "ExternalUrl",
- title = "Hello",
- htmlUrl = url
-
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putString(Const.INTERNAL_URL, "https://instructure.com?display=borderless")
- expectedBundle.putString(Const.ACTION_BAR_TITLE, "Hello")
- expectedBundle.putBoolean(Const.AUTHENTICATE, true)
- expectedBundle.putBoolean(com.instructure.pandautils.utils.Const.IS_EXTERNAL_TOOL, true)
- expectedBundle.putBoolean(com.instructure.pandautils.utils.Const.IS_UNSUPPORTED_FEATURE, true)
-
- var parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(InternalWebviewFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- // test external tool type
-
- val moduleItem2 = moduleItem.copy(type = "ExternalTool")
- parentFragment = callGetFragment(moduleItem2, course, null)
- assertNotNull(parentFragment)
- assertEquals(InternalWebviewFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_externalTool_offline() {
- val url = "https://instructure.com"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "ExternalUrl",
- title = "Hello",
- htmlUrl = url
-
- )
-
- val course = Course()
-
- val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
- assertNotNull(fragment)
- assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
- }
-
- @Test
- fun testGetFragment_subheader() {
- val moduleItem = ModuleItem(
- type = "SubHeader"
- )
-
- val course = Course()
- val parentFragment = callGetFragment(moduleItem, course, null)
- TestCase.assertNull(parentFragment)
- }
-
- @Test
- fun testGetFragment_quiz() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/quizzes/123456789"
- val htmlUrl = "https://mobile.canvas.net/courses/222/quizzes/123456789"
- val apiUrl = "courses/222/quizzes/123456789"
-
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Quiz",
- url = url,
- htmlUrl = htmlUrl,
- contentId = 55
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putString(Const.URL, htmlUrl)
- expectedBundle.putString(Const.API_URL, apiUrl)
- expectedBundle.putLong(Const.ID, 55)
-
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(ModuleQuizDecider::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_discussion() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/discussion_topics/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Discussion",
- url = url
- )
-
- val course = Course()
- val expectedBundle = Bundle()
- expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
- expectedBundle.putLong(DiscussionDetailsFragment.DISCUSSION_TOPIC_HEADER_ID, 123456789)
- val parentFragment = callGetFragment(moduleItem, course, null)
- assertNotNull(parentFragment)
- assertEquals(DiscussionDetailsWebViewFragment::class.java, parentFragment!!.javaClass)
- assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
- }
-
- @Test
- fun testGetFragment_discussion_offline() {
- val url = "https://mobile.canvas.net/api/v1/courses/222/discussion_topics/123456789"
- val moduleItem = ModuleItem(
- id = 4567,
- type = "Discussion",
- url = url
- )
-
- val course = Course()
- val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
- assertNotNull(fragment)
- assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
- }
-
- private fun callGetFragment(moduleItem: ModuleItem, course: Course, moduleObject: ModuleObject?, isOnline: Boolean = true, tabs: Set = emptySet(), files: List = emptyList()): Fragment? {
- return ModuleUtility.getFragment(moduleItem, course, moduleObject, false, isOnline, tabs, files, context)
- }
-}
+///*
+// * Copyright (C) 2017 - present Instructure, Inc.
+// *
+// * Licensed under the Apache License, Version 2.0 (the "License");
+// * you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// *
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// *
+// */
+//
+//package com.instructure.student.test.util
+//
+//import android.content.Context
+//import android.os.Bundle
+//import androidx.fragment.app.Fragment
+//import androidx.test.ext.junit.runners.AndroidJUnit4
+//import com.instructure.canvasapi2.models.Course
+//import com.instructure.canvasapi2.models.ModuleItem
+//import com.instructure.canvasapi2.models.ModuleObject
+//import com.instructure.canvasapi2.models.Tab
+//import com.instructure.pandautils.features.assignments.details.AssignmentDetailsFragment
+//import com.instructure.pandautils.features.discussion.details.DiscussionDetailsWebViewFragment
+//import com.instructure.student.features.discussion.details.DiscussionDetailsFragment
+//import com.instructure.student.features.files.details.FileDetailsFragment
+//import com.instructure.student.features.modules.progression.ModuleQuizDecider
+//import com.instructure.student.features.modules.progression.NotAvailableOfflineFragment
+//import com.instructure.student.features.modules.util.ModuleUtility
+//import com.instructure.student.features.pages.details.PageDetailsFragment
+//import com.instructure.student.fragment.InternalWebviewFragment
+//import com.instructure.student.util.Const
+//import io.mockk.mockk
+//import junit.framework.TestCase
+//import org.junit.Test
+//import org.junit.runner.RunWith
+//
+//@RunWith(AndroidJUnit4::class)
+//class ModuleUtilityTest : TestCase() {
+//
+// private val context = mockk(relaxed = true)
+//
+// @Test
+// fun testGetFragment_file() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "File",
+// url = url
+// )
+//
+// var moduleObject: ModuleObject? = ModuleObject(
+// id = 1234
+// )
+//
+// val course = Course()
+// val expectedUrl = "courses/222/assignments/123456789"
+//
+// var expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putString(Const.FILE_URL, expectedUrl)
+// expectedBundle.putInt(Const.FILE_ID, 0)
+// expectedBundle.putLong(Const.ITEM_ID, moduleItem.id)
+// expectedBundle.putParcelable(com.instructure.pandautils.utils.Const.MODULE_OBJECT, moduleObject)
+//
+//
+// var parentFragment = callGetFragment(moduleItem, course, moduleObject)
+// assertNotNull(parentFragment)
+// assertEquals(FileDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+//
+// // Test module object is null
+// moduleObject = null
+// expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putString(Const.FILE_URL, expectedUrl)
+// expectedBundle.putInt(Const.FILE_ID, 0)
+// parentFragment = callGetFragment(moduleItem, course, moduleObject)
+// assertNotNull(parentFragment)
+// assertEquals(FileDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+//
+// }
+//
+// @Test
+// fun testGetFragment_fileOfflineNotAvailable() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "File",
+// url = url
+// )
+//
+// val moduleObject: ModuleObject = ModuleObject(
+// id = 1234
+// )
+//
+// val course = Course()
+//
+// val filDetailsFragment = callGetFragment(moduleItem, course, moduleObject, isOnline = false)
+// assertNotNull(filDetailsFragment)
+// assertEquals(NotAvailableOfflineFragment::class.java, filDetailsFragment!!.javaClass)
+// }
+//
+// @Test
+// fun testGetFragment_page() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/pages/hello-world"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Page",
+// url = url,
+// title = "hello-world"
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putString(PageDetailsFragment.PAGE_NAME, "hello-world")
+// expectedBundle.putBoolean(PageDetailsFragment.NAVIGATED_FROM_MODULES, false)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(PageDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_assignment() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Assignment",
+// url = url
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
+// expectedBundle.putLong(Const.ASSIGNMENT_ID, 123456789)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_assignment_offlineSynced() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Assignment",
+// url = url
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
+// expectedBundle.putLong(Const.ASSIGNMENT_ID, 123456789)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null, isOnline = false, tabs = setOf(Tab.ASSIGNMENTS_ID))
+// assertNotNull(parentFragment)
+// assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_assignment_offlineNotSynced() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Assignment",
+// url = url
+// )
+//
+// val course = Course()
+//
+// val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
+// assertNotNull(fragment)
+// assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
+// }
+//
+// @Test
+// fun testGetFragment_assignmentShardId() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/12345~6789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Assignment",
+// url = url
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
+// expectedBundle.putLong(Const.ASSIGNMENT_ID, 123450000000006789)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_assignmentSubmissionShardId() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/assignments/12345~6789/submissions"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Assignment",
+// url = url
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putLong(com.instructure.pandautils.utils.Const.COURSE_ID, course.id)
+// expectedBundle.putLong(Const.ASSIGNMENT_ID, 123450000000006789)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(AssignmentDetailsFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_externalurl_externaltool() {
+// val url = "https://instructure.com"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "ExternalUrl",
+// title = "Hello",
+// htmlUrl = url
+//
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putString(Const.INTERNAL_URL, "https://instructure.com?display=borderless")
+// expectedBundle.putString(Const.ACTION_BAR_TITLE, "Hello")
+// expectedBundle.putBoolean(Const.AUTHENTICATE, true)
+// expectedBundle.putBoolean(com.instructure.pandautils.utils.Const.IS_EXTERNAL_TOOL, true)
+// expectedBundle.putBoolean(com.instructure.pandautils.utils.Const.IS_UNSUPPORTED_FEATURE, true)
+//
+// var parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(InternalWebviewFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// // test external tool type
+//
+// val moduleItem2 = moduleItem.copy(type = "ExternalTool")
+// parentFragment = callGetFragment(moduleItem2, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(InternalWebviewFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_externalTool_offline() {
+// val url = "https://instructure.com"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "ExternalUrl",
+// title = "Hello",
+// htmlUrl = url
+//
+// )
+//
+// val course = Course()
+//
+// val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
+// assertNotNull(fragment)
+// assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
+// }
+//
+// @Test
+// fun testGetFragment_subheader() {
+// val moduleItem = ModuleItem(
+// type = "SubHeader"
+// )
+//
+// val course = Course()
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// TestCase.assertNull(parentFragment)
+// }
+//
+// @Test
+// fun testGetFragment_quiz() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/quizzes/123456789"
+// val htmlUrl = "https://mobile.canvas.net/courses/222/quizzes/123456789"
+// val apiUrl = "courses/222/quizzes/123456789"
+//
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Quiz",
+// url = url,
+// htmlUrl = htmlUrl,
+// contentId = 55
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putString(Const.URL, htmlUrl)
+// expectedBundle.putString(Const.API_URL, apiUrl)
+// expectedBundle.putLong(Const.ID, 55)
+//
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(ModuleQuizDecider::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_discussion() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/discussion_topics/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Discussion",
+// url = url
+// )
+//
+// val course = Course()
+// val expectedBundle = Bundle()
+// expectedBundle.putParcelable(Const.CANVAS_CONTEXT, course)
+// expectedBundle.putLong(DiscussionDetailsFragment.DISCUSSION_TOPIC_HEADER_ID, 123456789)
+// val parentFragment = callGetFragment(moduleItem, course, null)
+// assertNotNull(parentFragment)
+// assertEquals(DiscussionDetailsWebViewFragment::class.java, parentFragment!!.javaClass)
+// assertEquals(expectedBundle.toString(), parentFragment.arguments!!.toString())
+// }
+//
+// @Test
+// fun testGetFragment_discussion_offline() {
+// val url = "https://mobile.canvas.net/api/v1/courses/222/discussion_topics/123456789"
+// val moduleItem = ModuleItem(
+// id = 4567,
+// type = "Discussion",
+// url = url
+// )
+//
+// val course = Course()
+// val fragment = callGetFragment(moduleItem, course, null, isOnline = false)
+// assertNotNull(fragment)
+// assertEquals(NotAvailableOfflineFragment::class.java, fragment!!.javaClass)
+// }
+//
+// private fun callGetFragment(moduleItem: ModuleItem, course: Course, moduleObject: ModuleObject?, isOnline: Boolean = true, tabs: Set = emptySet(), files: List = emptyList()): Fragment? {
+// return ModuleUtility.getFragment(moduleItem, course, moduleObject, false, isOnline, tabs, files, context)
+// }
+//}
diff --git a/apps/teacher/src/main/java/com/instructure/teacher/adapters/SubmissionContentAdapter.kt b/apps/teacher/src/main/java/com/instructure/teacher/adapters/SubmissionContentAdapter.kt
index f3c816f0a..577775231 100644
--- a/apps/teacher/src/main/java/com/instructure/teacher/adapters/SubmissionContentAdapter.kt
+++ b/apps/teacher/src/main/java/com/instructure/teacher/adapters/SubmissionContentAdapter.kt
@@ -66,6 +66,5 @@ class SubmissionContentAdapter(
}
fun updateAnnotations(position: Int) {
- mContentMap[position]?.updateAnnotations()
}
}
diff --git a/libs/annotations/src/main/java/com/instructure/annotations/PDFUtils.kt b/libs/annotations/src/main/java/com/instructure/annotations/PDFUtils.kt
new file mode 100644
index 000000000..70838cbf7
--- /dev/null
+++ b/libs/annotations/src/main/java/com/instructure/annotations/PDFUtils.kt
@@ -0,0 +1,50 @@
+package com.instructure.annotations
+
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.widget.Toast
+import androidx.core.content.FileProvider
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.File
+import java.io.FileOutputStream
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+object PDFUtils {
+
+ suspend fun downloadPdf(url: String, docName: String, context: Context): File {
+ return withContext(Dispatchers.IO) {
+ val client = OkHttpClient()
+ val request = Request.Builder().url(url).build()
+ val response = client.newCall(request).execute()
+
+ if (!response.isSuccessful) {
+ throw Exception("Failed to download PDF: HTTP ${response.code}")
+ }
+ val pdfFile = File(context.getExternalFilesDir(null), docName)
+
+ response.body?.byteStream().use { input ->
+ FileOutputStream(pdfFile).use { output ->
+ input?.copyTo(output)
+ }
+ }
+ pdfFile
+ }
+ }
+
+
+ fun openPdf(context: Context, file: File) {
+ val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ setDataAndType(uri, "application/pdf")
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ }
+ try {
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(context, "No PDF viewer found", Toast.LENGTH_SHORT).show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt
index 0e125f02f..7205cefe3 100644
--- a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt
+++ b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt
@@ -16,365 +16,92 @@
package com.instructure.annotations
import android.annotation.SuppressLint
+import android.content.ActivityNotFoundException
import android.content.Context
-import android.graphics.Color
+import android.content.Intent
import android.net.Uri
-import android.os.Handler
-import android.os.Looper
-import android.util.TypedValue
-import android.view.Gravity
-import android.view.View
+import android.os.Bundle
+import android.text.Html
import android.widget.FrameLayout
-import android.widget.ImageView
+import android.widget.Toast
import androidx.annotation.ColorRes
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import com.instructure.annotations.AnnotationDialogs.AnnotationCommentDialog
-import com.instructure.annotations.AnnotationDialogs.AnnotationErrorDialog
-import com.instructure.annotations.AnnotationDialogs.FreeTextDialog
-import com.instructure.annotations.FileCaching.DocumentListenerSimpleDelegate
+import androidx.core.content.FileProvider
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.APIHelper
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.extractCanvaDocsDomain
import com.instructure.canvasapi2.utils.extractSessionId
-import com.instructure.canvasapi2.utils.isValid
-import com.instructure.canvasapi2.utils.weave.StatusCallbackError
+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
-import com.instructure.pandautils.utils.onClick
-import com.instructure.pandautils.utils.setGone
-import com.instructure.pandautils.utils.setVisible
import com.instructure.pandautils.utils.toast
-import com.instructure.pandautils.views.ProgressiveCanvasLoadingView
-import com.pspdfkit.annotations.Annotation
-import com.pspdfkit.annotations.AnnotationFlags
-import com.pspdfkit.annotations.AnnotationProvider
-import com.pspdfkit.annotations.AnnotationType
-import com.pspdfkit.annotations.appearance.AssetAppearanceStreamGenerator
-import com.pspdfkit.annotations.configuration.*
-import com.pspdfkit.annotations.stamps.CustomStampAppearanceStreamGenerator
-import com.pspdfkit.annotations.stamps.StampPickerItem
-import com.pspdfkit.configuration.PdfConfiguration
-import com.pspdfkit.configuration.page.PageLayoutMode
-import com.pspdfkit.configuration.page.PageScrollDirection
-import com.pspdfkit.document.PdfDocument
-import com.pspdfkit.listeners.DocumentListener
-import com.pspdfkit.ui.PdfFragment
-import com.pspdfkit.ui.inspector.PropertyInspectorCoordinatorLayout
-import com.pspdfkit.ui.inspector.annotation.AnnotationCreationInspectorController
-import com.pspdfkit.ui.inspector.annotation.AnnotationEditingInspectorController
-import com.pspdfkit.ui.inspector.annotation.DefaultAnnotationCreationInspectorController
-import com.pspdfkit.ui.inspector.annotation.DefaultAnnotationEditingInspectorController
-import com.pspdfkit.ui.inspector.views.BorderStylePreset
-import com.pspdfkit.ui.note.AnnotationNoteHinter
-import com.pspdfkit.ui.special_mode.controller.AnnotationCreationController
-import com.pspdfkit.ui.special_mode.controller.AnnotationEditingController
-import com.pspdfkit.ui.special_mode.controller.AnnotationSelectionController
-import com.pspdfkit.ui.special_mode.controller.AnnotationTool
-import com.pspdfkit.ui.special_mode.manager.AnnotationManager
-import com.pspdfkit.ui.toolbar.*
-import com.pspdfkit.ui.toolbar.grouping.MenuItemGroupingRule
+import com.pspdfkit.utils.PdfUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import okhttp3.Response
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import okhttp3.Request
import java.io.File
-import java.util.*
+import java.io.FileOutputStream
+import java.net.URLDecoder
+import java.nio.charset.StandardCharsets
@SuppressLint("ViewConstructor")
-abstract class PdfSubmissionView(context: Context, private val studentAnnotationView: Boolean = false, private val courseId: Long) : FrameLayout(context), AnnotationManager.OnAnnotationCreationModeChangeListener, AnnotationManager.OnAnnotationEditingModeChangeListener {
+abstract class PdfSubmissionView(
+ context: Context,
+ private val studentAnnotationView: Boolean = false,
+ private val courseId: Long
+) : FrameLayout(context) {
+ abstract var pdfContentJob: WeaveCoroutine
+
+ lateinit var pdfdownloadJob: Job
protected lateinit var docSession: DocSession
protected lateinit var apiValues: ApiValues
- protected val commentRepliesHashMap: HashMap> = HashMap()
- protected var pdfFragment: PdfFragment? = null
- protected var noteHinter: AnnotationNoteHinter? = null
- protected val supportFragmentManager: FragmentManager = (context as AppCompatActivity).supportFragmentManager
-
- private val annotationCreationList = mutableListOf(AnnotationTool.INK, AnnotationTool.HIGHLIGHT, AnnotationTool.STRIKEOUT, AnnotationTool.SQUARE, AnnotationTool.STAMP, AnnotationTool.FREETEXT, AnnotationTool.ERASER, AnnotationTool.NOTE)
- private val annotationEditList = mutableListOf(
- AnnotationType.INK,
- AnnotationType.HIGHLIGHT,
- AnnotationType.STRIKEOUT,
- AnnotationType.SQUARE,
- AnnotationType.STAMP,
- AnnotationType.FREETEXT,
- AnnotationType.NONE // Wee need this to enable the eraser
- )
-
- private val pdfConfiguration: PdfConfiguration = PdfConfiguration.Builder()
- .scrollDirection(PageScrollDirection.VERTICAL)
- .enabledAnnotationTools(annotationCreationList)
- .editableAnnotationTypes(annotationEditList)
- .setAnnotationInspectorEnabled(true)
- .layoutMode(PageLayoutMode.SINGLE)
- .textSelectionEnabled(false)
- .disableCopyPaste()
- .build()
-
- private val annotationCreationToolbar = AnnotationCreationToolbar(context)
- private val annotationEditingToolbar = AnnotationEditingToolbar(context)
- private var annotationEditingInspectorController: AnnotationEditingInspectorController? = null
- private var annotationCreationInspectorController: AnnotationCreationInspectorController? = null
- private var fileJob: Job? = null
- private var createAnnotationJob: Job? = null
- private var updateAnnotationJob: Job? = null
- private var deleteAnnotationJob: Job? = null
- private var pdfContentJob: Job? = null
- private var annotationsJob: Job? = null
- private var sendCommentJob: Job? = null
- private var currentAnnotationModeTool: AnnotationTool? = null
- private var currentAnnotationModeType: AnnotationType? = null
- private var isUpdatingWithNoNetwork = false
- private var stampRaceFlag = true
@get:ColorRes
abstract val progressColor: Int
- abstract val annotationToolbarLayout: ToolbarCoordinatorLayout
- abstract val inspectorCoordinatorLayout: PropertyInspectorCoordinatorLayout
- abstract val commentsButton: ImageView
- abstract val loadingContainer: FrameLayout
- abstract val progressBar: ProgressiveCanvasLoadingView
- abstract fun setFragment(fragment: Fragment)
- abstract fun removeContentFragment()
- abstract fun showNoInternetDialog()
- abstract fun disableViewPager()
- abstract fun enableViewPager()
- abstract fun setIsCurrentlyAnnotating(boolean: Boolean)
- abstract fun showAnnotationComments(commentList: ArrayList, headAnnotationId: String, docSession: DocSession, apiValues: ApiValues)
- abstract fun showFileError()
+ lateinit var file: File
- open fun logOnAnnotationSelectedAnalytics() {}
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- initializeSubmissionView()
- }
+ protected open fun onFileInitialized() {}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
- unregisterPdfFragmentListeners()
- removeContentFragment()
- pdfFragment = null
- createAnnotationJob?.cancel()
- updateAnnotationJob?.cancel()
- deleteAnnotationJob?.cancel()
- annotationsJob?.cancel()
- pdfContentJob?.cancel()
- fileJob?.cancel()
- }
-
- protected fun unregisterPdfFragmentListeners() {
- pdfFragment?.removeOnAnnotationCreationModeChangeListener(this)
- pdfFragment?.removeOnAnnotationEditingModeChangeListener(this)
- pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener)
- pdfFragment?.removeOnAnnotationSelectedListener(annotationSelectedListener)
- pdfFragment?.removeOnAnnotationDeselectedListener(annotationDeselectedListener)
- }
-
- /**
- * THIS HAS TO BE CALLED
- */
- @Throws(UninitializedPropertyAccessException::class)
- protected fun initializeSubmissionView() {
- annotationEditingInspectorController = DefaultAnnotationEditingInspectorController(context, inspectorCoordinatorLayout)
- annotationCreationInspectorController = DefaultAnnotationCreationInspectorController(context, inspectorCoordinatorLayout)
-
- annotationToolbarLayout.setOnContextualToolbarLifecycleListener(object : ToolbarCoordinatorLayout.OnContextualToolbarLifecycleListener {
- override fun onDisplayContextualToolbar(p0: ContextualToolbar<*>) {}
- override fun onRemoveContextualToolbar(p0: ContextualToolbar<*>) {}
-
- override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) {
- toolbar.layoutParams = ToolbarCoordinatorLayout.LayoutParams(
- ToolbarCoordinatorLayout.LayoutParams.Position.TOP, EnumSet.of(ToolbarCoordinatorLayout.LayoutParams.Position.TOP)
- )
-
- if (toolbar is AnnotationCreationToolbar) {
- setUpGrabAnnotationTool(toolbar)
- }
- }
- })
-
- annotationCreationToolbar.closeButton.setGone()
- annotationCreationToolbar.setMenuItemGroupingRule(AnnotationCreationGroupingRule(context))
-
- annotationEditingToolbar.setMenuItemGroupingRule(object : MenuItemGroupingRule {
- override fun groupMenuItems(items: MutableList, i: Int) = configureEditMenuItemGrouping(items)
- override fun areGeneratedGroupItemsSelectable() = true
- })
-
- annotationEditingToolbar.setOnMenuItemClickListener { _, contextualToolbarMenuItem ->
- if (contextualToolbarMenuItem.title == context.getString(com.pspdfkit.R.string.pspdf__edit) &&
- currentAnnotationModeType == AnnotationType.FREETEXT) {
-
- val dialog = FreeTextDialog.getInstance(supportFragmentManager, true, pdfFragment?.selectedAnnotations?.get(0)?.contents
- ?: "", freeTextDialogCallback)
- dialog.show(supportFragmentManager, FreeTextDialog::class.java.simpleName)
-
- return@setOnMenuItemClickListener true
- }
-
- if (contextualToolbarMenuItem.title == context.getString(com.pspdfkit.R.string.pspdf__delete)) {
- val annotation = pdfFragment?.selectedAnnotations?.get(0)
- // Remove the annotation
- if (annotation != null) {
- pdfFragment?.document?.annotationProvider?.removeAnnotationFromPage(annotation)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- pdfFragment?.clearSelectedAnnotations()
- pdfFragment?.exitCurrentlyActiveMode()
- pdfFragment?.enterAnnotationCreationMode()
- return@setOnMenuItemClickListener true
- }
- }
- return@setOnMenuItemClickListener false
- }
-
- configureCommentView(commentsButton)
- }
-
- private fun setUpGrabAnnotationTool(toolbar: AnnotationCreationToolbar) {
- val menuItems = toolbar.menuItems
-
- val grabItem = ContextualToolbarMenuItem.createSingleItem(
- context,
- R.id.grab_annotation,
- ContextCompat.getDrawable(context, R.drawable.ic_grab)!!,
- context.getString(R.string.grabAnnotationTool),
- context.getColor(R.color.white),
- context.getColor(R.color.white),
- ContextualToolbarMenuItem.Position.START,
- true
- )
-
- if (currentAnnotationModeTool == null || currentAnnotationModeTool == AnnotationTool.NONE) {
- Handler(Looper.getMainLooper()).post { grabItem.isSelected = true }
- }
-
- menuItems.add(grabItem)
- toolbar.menuItems = menuItems
-
- toolbar.setOnMenuItemClickListener(
- ContextualToolbar.OnMenuItemClickListener { contextualToolbar, menuItem ->
- return@OnMenuItemClickListener (
- if (menuItem.id == R.id.grab_annotation) {
- if (!menuItem.isSelected) {
- contextualToolbar.menuItems.forEach { it.isSelected = false }
- pdfFragment?.exitCurrentlyActiveMode()
- pdfFragment?.enterAnnotationCreationMode(AnnotationTool.NONE)
- menuItem.isSelected = true
- }
- true
- } else {
- if (menuItem.isSelected) {
- val grab = contextualToolbar.menuItems.find { it.id == R.id.grab_annotation }
- Handler(Looper.getMainLooper()).postDelayed ({ grab?.isSelected = true }, 50)
- }
- false
- })
- }
- )
- }
-
- open fun configureCommentView(commentsButton: ImageView) {
- //we want to offset the comment button by the height of the action bar
- val typedValue = TypedValue()
- context.theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)
- val typedArray = context.obtainStyledAttributes(typedValue.resourceId, intArrayOf(android.R.attr.actionBarSize))
- val actionBarDp = typedArray.getDimensionPixelSize(0, -1)
- typedArray.recycle()
-
- val marginDp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12f, context.resources.displayMetrics)
- val layoutParams = commentsButton.layoutParams as LayoutParams
- layoutParams.gravity = Gravity.END or Gravity.TOP
- layoutParams.topMargin = marginDp.toInt() + actionBarDp
- layoutParams.rightMargin = marginDp.toInt()
-
- commentsButton.onClick {
- openComments()
- }
- }
-
- protected fun openComments() {
- // Get current annotation in both forms
- if (pdfFragment?.selectedAnnotations.isNullOrEmpty()) {
- toast(R.string.noAnnotationSelected)
- return
- }
- val currentPdfAnnotation = pdfFragment?.selectedAnnotations?.get(0)
- val currentAnnotation = currentPdfAnnotation?.convertPDFAnnotationToCanvaDoc(docSession.documentId)
- // Assuming neither is null, continue
- if (currentPdfAnnotation != null && currentAnnotation != null) {
- // If the contents of the current annotation are empty we want to prompt them to add a comment
- if (commentRepliesHashMap[currentAnnotation.annotationId] == null || commentRepliesHashMap[currentAnnotation.annotationId]?.isEmpty() == true) {
- // No comments for this annotation, show a dialog for the user to add some if they want
- AnnotationCommentDialog.getInstance(supportFragmentManager, "", context.getString(R.string.addAnnotationComment)) { _, text ->
- setIsCurrentlyAnnotating(true) //don't want the sliding panel getting in the way
- // Create new comment reply for this annotation.
- if (text.isValid()) {
- createCommentAnnotation(currentAnnotation.annotationId, currentAnnotation.page, text)
- // Add contents to the current annotation so we can add an indicator
- if(currentAnnotation.annotationType != CanvaDocAnnotation.AnnotationType.TEXT && currentAnnotation.annotationType != CanvaDocAnnotation.AnnotationType.FREE_TEXT) {
- currentPdfAnnotation.contents = "comment"
- noteHinter?.notifyDrawablesChanged()
- pdfFragment?.notifyAnnotationHasChanged(currentPdfAnnotation)
- }
- }
- }.show(supportFragmentManager, AnnotationCommentDialog::class.java.simpleName)
- } else {
- // Otherwise, show the comment list fragment
- commentRepliesHashMap[currentAnnotation.annotationId]?.let {
- if (it.isNotEmpty()) {
- showAnnotationComments(it, currentAnnotation.annotationId, docSession, apiValues)
- }
- }
- }
- }
- }
-
- open fun setupPSPDFKit(uri: Uri) {
- val newPdfFragment = PdfFragment.newInstance(uri, pdfConfiguration)
- setFragment(newPdfFragment)
- pdfFragment = newPdfFragment
- pdfFragment?.addOnAnnotationCreationModeChangeListener(this)
- pdfFragment?.addOnAnnotationEditingModeChangeListener(this)
-
- noteHinter = AnnotationNoteHinter(context)
- pdfFragment?.addDrawableProvider(noteHinter!!)
-
- if (docSession.annotationMetadata?.canWrite() == true) {
- // push the pdf viewing screen under the toolbar
- pdfFragment?.setInsets(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, context.resources.displayMetrics).toInt(), 0, 0)
- }
-
- attachDocListener()
+ pdfContentJob.cancel()
+ pdfdownloadJob.cancel()
}
protected fun handlePdfContent(url: String) {
pdfContentJob = tryWeave {
if (url.contains("canvadoc")) {
- val redirectUrl = getCanvaDocsRedirect(url, domain = ApiPrefs.overrideDomains[courseId])
+ val redirectUrl =
+ getCanvaDocsRedirect(url, domain = ApiPrefs.overrideDomains[courseId])
//extract the domain for API use
if (redirectUrl.isNotEmpty()) {
docSession = awaitApi { CanvaDocsManager.getCanvaDoc(redirectUrl, it) }
docSession.let {
val canvaDocsDomain = extractCanvaDocsDomain(redirectUrl)
val pdfUrl = canvaDocsDomain + it.annotationUrls.pdfDownload
- apiValues = ApiValues(it.documentId, pdfUrl, extractSessionId(pdfUrl), canvaDocsDomain)
+ apiValues = ApiValues(
+ it.documentId,
+ pdfUrl,
+ extractSessionId(pdfUrl),
+ canvaDocsDomain
+ )
}
- load(apiValues.pdfUrl) { setupPSPDFKit(it) }
+ load(apiValues.pdfUrl, docSession.pdfjs.documentName)
} else {
toast(R.string.errorOccurred)
}
} else {
//keep things working if they don't have canvadocs
- load(url) { setupPSPDFKit(it) }
+ load(url, docSession.pdfjs.documentName)
}
} catch {
// Show error
@@ -383,683 +110,20 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation
}
}
- open fun attachDocListener() {
- pdfFragment?.addDocumentListener(documentListener)
- }
- fun updateAnnotations() {
- if (pdfFragment?.document != null) {
- loadAnnotations()
- }
- }
-
- private val documentListener = object : DocumentListener by DocumentListenerSimpleDelegate() {
- override fun onDocumentLoaded(pdfDocument: PdfDocument) {
- setupPdfAnnotationDefaults()
- loadCustomAppearanceGenerators()
-
- docSession.rotations?.let { rotations ->
- pdfFragment?.document?.let {
- handlePageRotation(it, rotations)
- }
+ protected fun load(url: String, docName: String) {
+ pdfdownloadJob = CoroutineScope(Dispatchers.IO).launch {
+ val fileName = URLDecoder.decode(docName, StandardCharsets.UTF_8.toString())
+ file = PDFUtils.downloadPdf(url, fileName, context)
+ withContext(Dispatchers.Main){
+ onFileInitialized()
}
-
- pdfFragment?.enterAnnotationCreationMode()
- if (docSession.annotationMetadata?.canRead() != true) return
- loadAnnotations()
}
}
- private fun loadAnnotations() {
- annotationsJob = tryWeave {
- // Snag them annotations with the session id
- val annotations = awaitApi { CanvaDocsManager.getAnnotations(apiValues.sessionId, apiValues.canvaDocsDomain, it) }
- // We don't want to trigger the annotation events here, so unregister and re-register after
- pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener)
-
- // Grab all the annotations and sort them by type (descending).
- // This will result in all of the comments being iterated over first as the COMMENT_REPLY type is last in the AnnotationType enum.
- val sortedAnnotationList = annotations.data.sortedByDescending { it.annotationType }
- for (item in sortedAnnotationList) {
- if (item.annotationType == CanvaDocAnnotation.AnnotationType.COMMENT_REPLY) {
- // Grab the annotation comments and store them to be displayed later when user selects annotation
- if (commentRepliesHashMap.containsKey(item.inReplyTo)) {
- commentRepliesHashMap[item.inReplyTo]?.add(item)
- } else {
- commentRepliesHashMap[item.inReplyTo!!] = arrayListOf(item)
- }
- } else {
- // We don't want to add deleted annotations to the view
- if (!item.deleted) {
- val annotation = item.convertCanvaDocAnnotationToPDF(this@PdfSubmissionView.context)
- if (annotation != null) {
- // If the user doesn't have at least write permissions we need to lock down all annotations
- if (docSession.annotationMetadata?.canWrite() == false || item.userId != docSession.annotationMetadata?.userId) {
- annotation.flags = EnumSet.of(AnnotationFlags.LOCKED, AnnotationFlags.LOCKEDCONTENTS)
- if (annotation.type != AnnotationType.FREETEXT) {
- annotation.flags.add(AnnotationFlags.NOZOOM)
- }
- }
-
- if(commentRepliesHashMap.containsKey(annotation.name)
- && (item.annotationType != CanvaDocAnnotation.AnnotationType.TEXT && item.annotationType != CanvaDocAnnotation.AnnotationType.FREE_TEXT)) {
- annotation.contents = "comment"
- }
-
- pdfFragment?.document?.annotationProvider?.addAnnotationToPage(annotation)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- }
- }
- }
-
- }
-
- noteHinter?.notifyDrawablesChanged()
- pdfFragment?.document?.annotationProvider?.addOnAnnotationUpdatedListener(annotationUpdateListener)
- pdfFragment?.addOnAnnotationSelectedListener(annotationSelectedListener)
- pdfFragment?.addOnAnnotationDeselectedListener(annotationDeselectedListener)
- } catch {
- // Show error
- toast(R.string.annotationErrorOccurred)
- it.printStackTrace()
- }
- }
-
- private fun handlePageRotation(pdfDocument: PdfDocument, rotationMap: HashMap) {
- // Removing the listener prevents an infinite loop with onDocumentLoaded, which is triggered
- // by the calls to setRotationOffset()
- pdfFragment?.removeDocumentListener(documentListener)
-
- rotationMap.forEach { pageRotation ->
- pageRotation.key.toIntOrNull()?.let { pageIndex ->
- pdfDocument.setRotationOffset(calculateRotationOffset(pdfDocument.getPageRotation(pageIndex), pageRotation.value), pageIndex)
- }
- }
- }
-
- protected fun load(url: String?, onFinished: (Uri) -> Unit) {
- fileJob?.cancel()
- fileJob = tryWeave {
- progressBar.isIndeterminate = true
- progressBar.setColor(ContextCompat.getColor(this@PdfSubmissionView.context, R.color.textDark))
- val teacherYellow = ContextCompat.getColor(this@PdfSubmissionView.context, progressColor)
-
- val jitterThreshold = 300L
- val showLoadingRunner = Runnable {
- loadingContainer.setVisible()
- progressBar.announceForAccessibility(getContext().getString(R.string.loading))
- }
- val startTime = System.currentTimeMillis()
- val handler = Handler(Looper.getMainLooper())
- handler.postDelayed(showLoadingRunner, jitterThreshold)
-
- // If we don't have a url we'll display an error
- val tempFile: File? = com.instructure.annotations.FileCaching.FileCache.awaitFileDownload(url!!) {
- onUI {
- progressBar.setColor(teacherYellow)
- progressBar.setProgress(it)
- }
- }
-
- if (tempFile != null) {
- progressBar.isIndeterminate = true
- onFinished(Uri.fromFile(tempFile))
- } else {
- showFileError()
- }
-
- val passedTime = System.currentTimeMillis() - startTime
- val hideLoadingRunner = Runnable { loadingContainer.setGone() }
- when {
- passedTime < jitterThreshold -> {
- handler.removeCallbacks(showLoadingRunner); hideLoadingRunner.run()
- }
- passedTime < jitterThreshold * 2 -> handler.postDelayed(hideLoadingRunner, (jitterThreshold * 2) - passedTime)
- else -> hideLoadingRunner.run()
- }
- } catch {
- // Show error
- toast(R.string.annotationErrorOccurred)
- it.printStackTrace()
- }
- }
-
- private val annotationUpdateListener = object : AnnotationProvider.OnAnnotationUpdatedListener {
- override fun onAnnotationCreated(annotation: Annotation) {
- if (!annotation.isAttached || annotationNetworkCheck(annotation)) return
-
- // If it's a freetext and it's empty that means that they haven't had a chance to fill it out
- if ((annotation.type == AnnotationType.FREETEXT) && annotation.contents.isNullOrEmpty()) {
- return
- }
-
- // If its a stamp we need to modify its rect to match the webs stamp size
- if (annotation.type == AnnotationType.STAMP) {
- annotation.transformStamp()
- // Transforming the stamp causes an update call and a race condition, often allowing for two annotations
- // to be created. This race flag will prevent the update from happening.
- stampRaceFlag = false
- }
-
- createNewAnnotation(annotation)
- }
-
- override fun onAnnotationUpdated(annotation: Annotation) {
- if (!annotation.isAttached || annotationNetworkCheck(annotation)) return
-
- if (!annotation.flags.contains(AnnotationFlags.LOCKED) && annotation.isModified && annotation.name.isValid()) {
- //we only want to update the annotation if it isn't Locked and IS modified
- updateAnnotation(annotation)
- }
- }
-
- override fun onAnnotationRemoved(annotation: Annotation) {
- if (annotationNetworkCheck(annotation)) return
-
- //removed annotation
- if (annotation.name.isValid()) {
- deleteAnnotation(annotation)
- }
- }
-
- override fun onAnnotationZOrderChanged(p0: Int, p1: MutableList, p2: MutableList) {}
- }
-
- private fun annotationNetworkCheck(annotation: Annotation): Boolean {
- if (!APIHelper.hasNetworkConnection()) {
- if (isUpdatingWithNoNetwork) {
- isUpdatingWithNoNetwork = false
- return true
- } else {
- isUpdatingWithNoNetwork = true
- if (annotation.isAttached) {
- pdfFragment?.clearSelectedAnnotations()
- pdfFragment?.document?.annotationProvider?.removeAnnotationFromPage(annotation)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- }
- showNoInternetDialog()
- }
- }
- return false
- }
-
- private val annotationSelectedListener = object : AnnotationManager.OnAnnotationSelectedListener {
- override fun onAnnotationSelected(annotation: Annotation, isCreated: Boolean) {
- logOnAnnotationSelectedAnalytics()
- }
-
- override fun onPrepareAnnotationSelection(p0: AnnotationSelectionController, annotation: Annotation, isCreated: Boolean): Boolean {
- if (APIHelper.hasNetworkConnection()) {
- if (annotation.type == AnnotationType.FREETEXT && annotation.contents.isNullOrEmpty()) {
- //this is a new free text annotation, and needs to be selected to be created
- if (supportFragmentManager.findFragmentByTag(FreeTextDialog::class.java.simpleName) == null) {
- val dialog = FreeTextDialog.getInstance(supportFragmentManager, false, "", freeTextDialogCallback)
- dialog.show(supportFragmentManager, FreeTextDialog::class.java.simpleName)
- }
- } else if (annotation.type == AnnotationType.FREETEXT) {
- setIsCurrentlyAnnotating(true)
- }
-
- if (annotation.type != AnnotationType.FREETEXT && annotation.name.isValid() && (!studentAnnotationView || hasComments(annotation))) {
- // if the annotation is an existing annotation (has an ID) and is NOT freetext
- // we want to display the button to view/make comments
- commentsButton.setVisible()
- }
- }
- return true
- }
- }
-
- private fun hasComments(annotation: Annotation): Boolean {
- val currentAnnotation = annotation.convertPDFAnnotationToCanvaDoc(docSession.documentId)
- return currentAnnotation != null
- && commentRepliesHashMap[currentAnnotation.annotationId] != null
- && commentRepliesHashMap[currentAnnotation.annotationId]?.isNotEmpty() == true
- }
-
- private val annotationDeselectedListener = AnnotationManager.OnAnnotationDeselectedListener { _, _ ->
- commentsButton.setGone()
- }
-
- //region Annotation Manipulation
- fun createNewAnnotation(annotation: Annotation) {
- if (annotation.type == AnnotationType.FREETEXT) {
- annotation.flags.remove(AnnotationFlags.NOZOOM)
- }
-
- if (docSession.annotationMetadata?.canWrite() != true) return
-
- // This is a new annotation; Post it
- post {
- commentsButton.isEnabled = false
- }
-
- createAnnotationJob = tryWeave {
- val canvaDocAnnotation = annotation.convertPDFAnnotationToCanvaDoc(apiValues.documentId)
- if (canvaDocAnnotation != null) {
- val newAnnotation = awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, generateAnnotationId(), canvaDocAnnotation, apiValues.canvaDocsDomain, it) }
-
- // Edit the annotation with the appropriate id
- annotation.name = newAnnotation.annotationId
- pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- pdfFragment?.document?.annotationProvider?.addOnAnnotationUpdatedListener(annotationUpdateListener)
- commentsButton.isEnabled = true
- if (annotation.type == AnnotationType.STAMP) {
- commentsButton.setVisible()
- openComments()
- // Now that the stamp has been created, re-enable the flag for updates
- stampRaceFlag = true
- }
- }
- } catch {
- // Show general error, make more specific in the future?
- toast(R.string.errorOccurred)
- it.printStackTrace()
- commentsButton.isEnabled = true
-
- // Just in case something went wrong, re-enable stamp updates
- if (annotation.type == AnnotationType.STAMP) stampRaceFlag = true
- }
- }
-
- private fun updateAnnotation(annotation: Annotation) {
- if (annotation.type == AnnotationType.FREETEXT) {
- annotation.flags.remove(AnnotationFlags.NOZOOM)
- }
- if (docSession.annotationMetadata?.canWrite() != true) return
-
- // Don't want to update if we just created a stamp.
- if(annotation.type == AnnotationType.STAMP && !stampRaceFlag) return
- // Annotation modified; Update it
- updateAnnotationJob = tryWeave {
- val canvaDocAnnotation = annotation.convertPDFAnnotationToCanvaDoc(apiValues.documentId)
- if (canvaDocAnnotation != null && !annotation.name.isNullOrEmpty()) {
- awaitApi { CanvaDocsManager.putAnnotation(apiValues.sessionId, annotation.name!!, canvaDocAnnotation, apiValues.canvaDocsDomain, it) }
- }
- } catch {
- if (it is StatusCallbackError) {
- val rawResponse: Response? = it.response?.raw()
- if (rawResponse?.code == 404) {
- // Not found; Annotation has been deleted and no longer exists.
- val dialog = AnnotationErrorDialog.getInstance(supportFragmentManager) {
- // Delete annotation after user clicks OK on dialog
- pdfFragment?.clearSelectedAnnotations()
- pdfFragment?.document?.annotationProvider?.removeAnnotationFromPage(annotation)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- }
- dialog.show(supportFragmentManager, AnnotationErrorDialog::class.java.simpleName)
- }
- }
-
- // Show general error, make more specific in the future?
- toast(R.string.errorOccurred)
-
- it.printStackTrace()
- }
- }
-
- private fun deleteAnnotation(annotation: Annotation) {
- // Annotation deleted; DELETE
- deleteAnnotationJob = tryWeave {
- // If it is not found, don't hit the server (it will fail)
- if (!annotation.name.isNullOrEmpty())
- awaitApi { CanvaDocsManager.deleteAnnotation(apiValues.sessionId, annotation.name!!, apiValues.canvaDocsDomain, it) }
- noteHinter?.notifyDrawablesChanged()
- } catch {
- // Show general error, make more specific in the future?
- toast(R.string.errorOccurred)
- it.printStackTrace()
- }
- }
-
- private fun createCommentAnnotation(inReplyToId: String, page: Int, comment: String?) {
- // Annotation modified; Update it
- commentsButton.isEnabled = false
-
- sendCommentJob = tryWeave {
- val newCommentReply = awaitApi {
- CanvaDocsManager.putAnnotation(apiValues.sessionId, generateAnnotationId(), createCommentReplyAnnotation(comment
- ?: "", inReplyToId, apiValues.documentId, ApiPrefs.user?.id.toString(), page), apiValues.canvaDocsDomain, it)
- }
-
- // The put request doesn't return this property, so we need to set it to true
- newCommentReply.isEditable = true
- commentRepliesHashMap[inReplyToId] = arrayListOf(newCommentReply)
- commentsButton.isEnabled = true
- } catch {
- // Show general error, make more specific in the future?
- toast(R.string.errorOccurred)
- it.printStackTrace()
- commentsButton.isEnabled = true
- }
- }
-
- //region annotation listeners
- override fun onEnterAnnotationCreationMode(controller: AnnotationCreationController) {
- // We never want to show annotation toolbars if the user doesn't have permission to write
- if (docSession.annotationMetadata?.canWrite() != true) return
- //we only want to disable the viewpager if they are actively annotating
- if (controller.activeAnnotationTool != AnnotationTool.NONE) {
- disableViewPager()
- } else {
- val grabItem = annotationCreationToolbar.menuItems.find { it.id == R.id.grab_annotation }
- grabItem?.isSelected = true
- }
-
- currentAnnotationModeTool = controller.activeAnnotationTool
-
- annotationCreationInspectorController?.bindAnnotationCreationController(controller)
- annotationCreationToolbar.bindController(controller)
- annotationToolbarLayout.displayContextualToolbar(annotationCreationToolbar, true)
- }
-
- override fun onExitAnnotationCreationMode(p0: AnnotationCreationController) {
-
- enableViewPager()
- annotationToolbarLayout.removeContextualToolbar(true)
- annotationCreationToolbar.unbindController()
- annotationCreationInspectorController?.unbindAnnotationCreationController()
-
- currentAnnotationModeTool = AnnotationTool.NONE
- }
-
- override fun onEnterAnnotationEditingMode(controller: AnnotationEditingController) {
-
- // We never want to show annotation toolbars if the user doesn't have permission to write
- if (docSession.annotationMetadata?.canWrite() == false) return
- currentAnnotationModeType = controller.currentSingleSelectedAnnotation?.type
- //we only want to disable the viewpager if they are actively annotating
- if (controller.currentSingleSelectedAnnotation != null) disableViewPager()
- annotationEditingToolbar.bindController(controller)
- annotationEditingInspectorController?.bindAnnotationEditingController(controller)
- annotationToolbarLayout.displayContextualToolbar(annotationEditingToolbar, true)
- }
-
- override fun onExitAnnotationEditingMode(controller: AnnotationEditingController) {
-
- enableViewPager()
- annotationToolbarLayout.removeContextualToolbar(true)
- annotationEditingToolbar.unbindController()
- annotationEditingInspectorController?.unbindAnnotationEditingController()
-
- currentAnnotationModeType = AnnotationType.NONE
-
- //send them back to creating annotations
- pdfFragment?.enterAnnotationCreationMode()
- }
-
- override fun onChangeAnnotationEditingMode(controller: AnnotationEditingController) {
- currentAnnotationModeType = controller.currentSingleSelectedAnnotation?.type
-
- //we only want to disable the viewpager if they are actively annotating
- if (controller.currentSingleSelectedAnnotation != null) disableViewPager()
- else enableViewPager()
- }
-
- override fun onChangeAnnotationCreationMode(controller: AnnotationCreationController) {
- //we only want to disable the viewpager if they are actively annotating
- if (controller.activeAnnotationTool != AnnotationTool.NONE) disableViewPager()
- else enableViewPager()
-
- //we want to make sure that the keyboard doesn't mess up the view if they are using these annotations
- setIsCurrentlyAnnotating(controller.activeAnnotationTool != AnnotationTool.NONE)
-
- currentAnnotationModeTool = controller.activeAnnotationTool!!
- }
-
- private fun setupPdfAnnotationDefaults() {
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.INK,
- InkAnnotationConfiguration.builder(context)
- .setAvailableColors(context.resources.getIntArray(R.array.standardAnnotationColors).toMutableList())
- .setCustomColorPickerEnabled(false)
- .setSupportedProperties(EnumSet.of(AnnotationProperty.COLOR))
- .setDefaultColor(ContextCompat.getColor(context, R.color.blueAnnotation))
- .setDefaultThickness(2f)
- .setZIndexEditingEnabled(false)
- .build()
-
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.SQUARE,
- ShapeAnnotationConfiguration.builder(context, AnnotationTool.SQUARE)
- .setAvailableColors(context.resources.getIntArray(R.array.standardAnnotationColors).toMutableList())
- .setCustomColorPickerEnabled(false)
- .setSupportedProperties(EnumSet.of(AnnotationProperty.COLOR))
- .setDefaultColor(ContextCompat.getColor(context, R.color.blueAnnotation))
- .setDefaultThickness(2f)
- .setDefaultAlpha(1f)
- .setDefaultFillColor(ContextCompat.getColor(context, R.color.transparent))
- .setDefaultBorderStylePreset(BorderStylePreset.SOLID)
- .setZIndexEditingEnabled(false)
- .build()
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.HIGHLIGHT,
- MarkupAnnotationConfiguration.builder(context, AnnotationTool.HIGHLIGHT)
- .setAvailableColors(context.resources.getIntArray(R.array.highlightAnnotationColors).toMutableList())
- .disableProperty(AnnotationProperty.ANNOTATION_ALPHA)
- .setDefaultColor(ContextCompat.getColor(context, R.color.yellowHighlightAnnotation))
- .setZIndexEditingEnabled(false)
- .build()
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.STRIKEOUT,
- MarkupAnnotationConfiguration.builder(context, AnnotationTool.STRIKEOUT)
- .setAvailableColors(context.resources.getIntArray(R.array.standardAnnotationColors).toMutableList())
- .setSupportedProperties(EnumSet.of(AnnotationProperty.COLOR))
- .setDefaultColor(ContextCompat.getColor(context, R.color.redAnnotation))
- .setZIndexEditingEnabled(false)
- .build()
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.FREETEXT,
- FreeTextAnnotationConfiguration.builder(context)
- .setSupportedProperties(EnumSet.of(AnnotationProperty.COLOR))
- .setAvailableColors(context.resources.getIntArray(R.array.standardAnnotationColors).toMutableList())
- .setDefaultColor(ContextCompat.getColor(context, R.color.darkGrayAnnotation))
- .setDefaultTextSize(smallFont)
- .setDefaultFillColor(Color.TRANSPARENT)
- .setCustomColorPickerEnabled(false)
- .setHorizontalResizingEnabled(false)
- .setVerticalResizingEnabled(false)
- .setZIndexEditingEnabled(false)
- .build()
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationType.STAMP,
- StampAnnotationConfiguration.builder(context)
- .setAvailableStampPickerItems(getAppearanceStreams())
- .setSupportedProperties(EnumSet.noneOf(AnnotationProperty::class.java))
- .setZIndexEditingEnabled(false)
- .build()
- )
- pdfFragment?.annotationConfiguration?.put(
- AnnotationTool.ERASER,
- EraserToolConfiguration.builder()
- .setDefaultThickness(5f)
- .setForceDefaults(true)
- .build()
- )
- }
-
-
- // region Stamp Appearance Streams
- private fun getAppearanceStreams(): MutableList {
- val stamps = ArrayList()
-
- // Create appearance stream generators with a PDF containing vector logo.
- val black = AssetAppearanceStreamGenerator(blackStampFile)
- val blue = AssetAppearanceStreamGenerator(blueStampFile)
- val brown = AssetAppearanceStreamGenerator(brownStampFile)
- val green = AssetAppearanceStreamGenerator(greenStampFile)
- val navy = AssetAppearanceStreamGenerator(navyStampFile)
- val orange = AssetAppearanceStreamGenerator(orangeStampFile)
- val pink = AssetAppearanceStreamGenerator(pinkStampFile)
- val purple = AssetAppearanceStreamGenerator(purpleStampFile)
- val red = AssetAppearanceStreamGenerator(redStampFile)
- val yellow = AssetAppearanceStreamGenerator(yellowStampFile)
-
- // Create picker items with custom subject and custom appearance stream generator set.
- stamps.add(StampPickerItem.fromTitle(context, blackStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(black)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, blueStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(blue)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, brownStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(brown)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, greenStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(green)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, navyStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(navy)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, orangeStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(orange)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, pinkStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(pink)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, purpleStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(purple)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, redStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(red)
- .build())
-
- stamps.add(StampPickerItem.fromTitle(context, yellowStampSubject)
- .withSize(18.66f, 26.66f)
- .withAppearanceStreamGenerator(yellow)
- .build())
-
- return stamps
- }
-
- private fun loadCustomAppearanceGenerators() {
- // Register custom stamp appearance stream generator as a global appearance stream generator.
- val customStampAppearanceStreamGenerator = CustomStampAppearanceStreamGenerator()
- pdfFragment?.document?.annotationProvider?.addAppearanceStreamGenerator(customStampAppearanceStreamGenerator)
-
- // Create appearance stream generators with a PDF containing vector logo.
- val black = AssetAppearanceStreamGenerator(blackStampFile)
- val blue = AssetAppearanceStreamGenerator(blueStampFile)
- val brown = AssetAppearanceStreamGenerator(brownStampFile)
- val green = AssetAppearanceStreamGenerator(greenStampFile)
- val navy = AssetAppearanceStreamGenerator(navyStampFile)
- val orange = AssetAppearanceStreamGenerator(orangeStampFile)
- val pink = AssetAppearanceStreamGenerator(pinkStampFile)
- val purple = AssetAppearanceStreamGenerator(purpleStampFile)
- val red = AssetAppearanceStreamGenerator(redStampFile)
- val yellow = AssetAppearanceStreamGenerator(yellowStampFile)
-
- // Register created appearance stream generator for the custom subject.
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(blackStampSubject, black)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(blueStampSubject, blue)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(brownStampSubject, brown)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(greenStampSubject, green)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(navyStampSubject, navy)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(orangeStampSubject, orange)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(pinkStampSubject, pink)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(purpleStampSubject, purple)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(redStampSubject, red)
- customStampAppearanceStreamGenerator.addAppearanceStreamGenerator(yellowStampSubject, yellow)
- }
- //endregion
-
- open fun configureEditMenuItemGrouping(toolbarMenuItems: MutableList): MutableList {
- //if current tool == freeText add edit button
- //There are 7 items total, and always need to leave room for the color, it has to show.
- //First we need to get all of the items and store them in variables for readability.... rip
- var delete: ContextualToolbarMenuItem? = null
- var color: ContextualToolbarMenuItem? = null
-
- val edit: ContextualToolbarMenuItem? = if (currentAnnotationModeType == AnnotationType.FREETEXT) {
- ContextualToolbarMenuItem.createSingleItem(context, View.generateViewId(),
- ContextCompat.getDrawable(context, com.pspdfkit.R.drawable.pspdf__ic_edit)!!,
- context.getString(com.pspdfkit.R.string.pspdf__edit), -1, -1,
- ContextualToolbarMenuItem.Position.END, false)
- } else null
-
- for (item in toolbarMenuItems) {
- when (item.title) {
- context.getString(com.pspdfkit.R.string.pspdf__edit_menu_color) -> {
- color = item
- }
- context.getString(com.pspdfkit.R.string.pspdf__delete) -> {
- delete = item
- }
- }
- }
-
- var list = mutableListOf()
- //check to make sure we have all of our items
-
- // If the user has read/write/manage we want to let them delete (and only delete) non-authored annotations
- val annotation = pdfFragment?.selectedAnnotations?.get(0)
- if (docSession.annotationMetadata?.canManage() == true && annotation?.flags?.contains(AnnotationFlags.LOCKED) == true) {
- // We need to only return a list with the delete menu item
- delete = ContextualToolbarMenuItem.createSingleItem(context, View.generateViewId(),
- ContextCompat.getDrawable(context, R.drawable.ic_trash)!!,
- context.getString(com.pspdfkit.R.string.pspdf__delete), -1, -1,
- ContextualToolbarMenuItem.Position.END, false)
- list.add(delete)
- } else {
- if (color != null && delete != null) {
- if (edit != null)
- list.add(edit)
- list.add(color)
- list.add(delete)
- } else {
- // If we don't have all items, just return the default that we have
- list = toolbarMenuItems
- }
- }
-
-
- return list
- }
-
- val freeTextDialogCallback = object : (Boolean, Boolean, String) -> Unit {
- override fun invoke(cancelled: Boolean, isEditing: Boolean, text: String) {
- if (isEditing && cancelled) return
-
- val annotation = if ((pdfFragment?.selectedAnnotations?.size ?: 0) > 0) pdfFragment?.selectedAnnotations?.get(0)
- ?: return else return
- if ((cancelled && annotation.contents.isNullOrEmpty()) || text.isEmpty()) {
- // Remove the annotation
- pdfFragment?.document?.annotationProvider?.removeAnnotationFromPage(annotation)
- pdfFragment?.notifyAnnotationHasChanged(annotation)
- pdfFragment?.clearSelectedAnnotations()
- pdfFragment?.enterAnnotationCreationMode()
- return
- }
-
- // Updating the annotation's contents triggers an update call which creates the new annotation.
- annotation.contents = text
-
- // We need to update the UI so pspdfkit knows how to handle this.
- pdfFragment?.exitCurrentlyActiveMode()
- pdfFragment?.enterAnnotationCreationMode()
+ protected fun openPdf() {
+ if(::file.isInitialized) {
+ PDFUtils.openPdf(context, file)
}
}
}
diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/DocSession.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/DocSession.kt
index 0ff4e28b7..cb409b0aa 100644
--- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/DocSession.kt
+++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/DocSession.kt
@@ -22,25 +22,28 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class DocSession(
- @SerializedName("document_id")
- var documentId: String,
- @SerializedName("urls")
- var annotationUrls: AnnotationUrls,
- @SerializedName("annotations")
- var annotationMetadata: AnnotationMetadata? = null,
- @SerializedName("panda_push")
- var pandaPush: PandaPush? = null,
- var rotations: HashMap? = null
+ @SerializedName("document_id")
+ var documentId: String,
+ @SerializedName("urls")
+ var annotationUrls: AnnotationUrls,
+ @SerializedName("pdfjs")
+ var pdfjs: Pdfjs,
+ @SerializedName("annotations")
+ var annotationMetadata: AnnotationMetadata? = null,
+ @SerializedName("panda_push")
+ var pandaPush: PandaPush? = null,
+ var rotations: HashMap? = null
) : Parcelable
@Parcelize
data class AnnotationMetadata(
- var enabled: Boolean,
- @SerializedName("user_name")
- var userName: String,
- @SerializedName("user_id")
- var userId: String,
- var permissions: String? = null) : Parcelable {
+ var enabled: Boolean,
+ @SerializedName("user_name")
+ var userName: String,
+ @SerializedName("user_id")
+ var userId: String,
+ var permissions: String? = null
+) : Parcelable {
/*
The permission field is given to us as a string with the form:
@@ -50,31 +53,39 @@ data class AnnotationMetadata(
-undefined/null (no permission present)
*/
- fun canRead() : Boolean = permissions?.contains("read") ?: false
- fun canWrite() : Boolean = permissions?.contains("write") ?: false
- fun canManage() : Boolean = permissions?.contains("manage") ?: false
+ fun canRead(): Boolean = permissions?.contains("read") ?: false
+ fun canWrite(): Boolean = permissions?.contains("write") ?: false
+ fun canManage(): Boolean = permissions?.contains("manage") ?: false
}
@Parcelize
data class AnnotationUrls(
- @SerializedName("pdf_download")
- var pdfDownload: String,
- @SerializedName("annotated_pdf_download")
- var annotatedPdfDownload: String
+ @SerializedName("pdf_download")
+ var pdfDownload: String,
+ @SerializedName("annotated_pdf_download")
+ var annotatedPdfDownload: String
+) : Parcelable
+
+@Parcelize
+data class Pdfjs(
+ @SerializedName("documentName")
+ val documentName: String,
+ @SerializedName("url")
+ val url: String
) : Parcelable
@Parcelize
data class PandaPush(
- @SerializedName("document_channel")
- var documentChannel: String? = null
+ @SerializedName("document_channel")
+ var documentChannel: String? = null
) : Parcelable
@Parcelize
data class ApiValues(
- val documentId: String,
- val pdfUrl: String,
- val sessionId: String,
- val canvaDocsDomain: String
+ val documentId: String,
+ val pdfUrl: String,
+ val sessionId: String,
+ val canvaDocsDomain: String
) : Parcelable
diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml
index 5c9d97ad5..9ec585057 100644
--- a/libs/pandares/src/main/res/values/strings.xml
+++ b/libs/pandares/src/main/res/values/strings.xml
@@ -1032,6 +1032,7 @@
Not supported on this device
Downloading
+ Downloading...
Download failed
Download successful
diff --git a/libs/pandautils/src/main/res/values/strings.xml b/libs/pandautils/src/main/res/values/strings.xml
index f04c8b0e2..4d8b51b4f 100644
--- a/libs/pandautils/src/main/res/values/strings.xml
+++ b/libs/pandautils/src/main/res/values/strings.xml
@@ -407,6 +407,7 @@
We are unable to find an external app to view this LTI tool.
This file could not be displayed. Use the button below to open the file with another app on your device.
+ Use the button below to open the file with another app on your device.
Open with…
This media format is not supported
fullscreen