Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.ContextMenu
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
Expand All @@ -30,14 +31,24 @@ import androidx.lifecycle.Lifecycle
import com.pspdfkit.document.PdfDocument
import com.pspdfkit.flutter.pspdfkit.api.CustomToolbarCallbacks
import com.pspdfkit.ui.PdfUiFragment
import com.pspdfkit.R
import com.pspdfkit.ui.toolbar.ContextualToolbar
import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout
import com.pspdfkit.annotations.Annotation
import com.pspdfkit.ui.annotations.OnAnnotationSelectedListener
import com.pspdfkit.ui.special_mode.controller.AnnotationSelectionController

class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider {

class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider, ToolbarCoordinatorLayout.OnContextualToolbarLifecycleListener {

// Maps identifier strings to menu item IDs to track custom toolbar items
private val customToolbarItemIds = HashMap<String, Int>()
private var customToolbarCallbacks: CustomToolbarCallbacks? = null
private var customToolbarItems: List<Map<String, Any>>? = null

// Store current contextual toolbar for annotation access
private var currentContextualToolbar: ContextualToolbar<*>? = null

private var annotationSelectionController: AnnotationSelectionController? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -60,6 +71,23 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider {
return super.onCreateView(inflater, container, savedInstanceState)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setOnContextualToolbarLifecycleListener(this)
pdfFragment?.addOnAnnotationSelectedListener(object : OnAnnotationSelectedListener {
override fun onPrepareAnnotationSelection(
controller: AnnotationSelectionController,
annotation: Annotation,
annotationCreated: Boolean
): Boolean {
annotationSelectionController = controller
return true
}
override fun onAnnotationSelected(annotation: Annotation, annotationCreated: Boolean) {}
override fun onAnnotationDeselected(annotation: Annotation, reselected: Boolean) {}
})
}

/**
* Called when the document is loaded. Notifies Flutter that the document has been loaded.
*
Expand Down Expand Up @@ -300,4 +328,84 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider {
}
}
}

/**
* Helper method to determine if annotation editing should be disabled based on annotation custom data.
*
* When annotations have 'hideDelete': true in their customData, this will disable the entire
* contextual toolbar and annotation resizing, but still allow moving the annotation.
*
* References:
* - Android Contextual Toolbars: https://www.nutrient.io/guides/android/customizing-the-interface/customizing-the-toolbar/
* - Annotation Custom Data: https://www.nutrient.io/guides/android/annotations/annotation-json/
* - ContextualToolbar API: https://www.nutrient.io/api/android/kdoc/pspdfkit/com.pspdfkit.ui.toolbar/-contextual-toolbar/
*
* @return True if editing should be disabled (contextual toolbar hidden, resizing disabled), false otherwise
*/
private fun shouldDisableEditing(): Boolean {
try {
if (document != null) {
Log.d("FlutterPdfUiFragment", "Checking annotations for hideDelete custom data")

val annotations: List<Annotation> = pdfFragment?.selectedAnnotations ?: emptyList()

for (annotation in annotations) {
val customData = annotation.customData
if (customData != null && customData.has("hideDelete")) {
val hideDeleteValue = customData.get("hideDelete")
val shouldHide = hideDeleteValue == "true" || hideDeleteValue == true
if (shouldHide) {
Log.d("FlutterPdfUiFragment", "Annotation ${annotation.type} has hideDelete=true, disabling editing")
return true
}
}
}
}
return false
} catch (e: Exception) {
Log.e("FlutterPdfUiFragment", "Error checking annotation custom data", e)
return false
}
}

override fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) {
currentContextualToolbar = toolbar
val shouldDisableEditing = shouldDisableEditing()
annotationSelectionController?.isResizeEnabled = true
if (shouldDisableEditing) {
// Completely hide the contextual toolbar for protected annotations
// This removes access to resize handles and all editing actions
Log.d("FlutterPdfUiFragment", "Hiding contextual toolbar for protected annotation")
toolbar.visibility = View.GONE
annotationSelectionController?.isResizeEnabled = false
// This is the most direct approach available in PSPDFKit Android
// to prevent users from accessing resize handles for protected annotations
}else{
// Ensure the toolbar is visible for non-protected annotations
toolbar.visibility = View.VISIBLE
annotationSelectionController?.isResizeEnabled = true
}
}

override fun onDisplayContextualToolbar(toolbar: ContextualToolbar<*>) {
currentContextualToolbar = toolbar
val shouldDisableEditing = shouldDisableEditing()
annotationSelectionController?.isResizeEnabled = true
if (shouldDisableEditing) {
// Completely hide the contextual toolbar for protected annotations
// This removes access to resize handles and all editing actions
Log.d("FlutterPdfUiFragment", "Hiding contextual toolbar for protected annotation")
toolbar.visibility = View.GONE
annotationSelectionController?.isResizeEnabled = false
// This effectively prevents access to resize handles and editing controls
}else{
// Ensure the toolbar is visible for non-protected annotations
toolbar.visibility = View.VISIBLE
annotationSelectionController?.isResizeEnabled = true
}
}

override fun onRemoveContextualToolbar(toolbar: ContextualToolbar<*>) {
currentContextualToolbar = null
}
}
14 changes: 14 additions & 0 deletions example/lib/examples.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import 'manual_save_example.dart';
import 'annotation_processing_example.dart';
import 'password_example.dart';
import 'nutrient_annotation_creation_mode_example.dart';
import 'hide_delete_annotation_example.dart';

const String _documentPath = 'PDFs/PSPDFKit.pdf';
const String _measurementsDocs = 'PDFs/Measurements.pdf';
Expand Down Expand Up @@ -110,6 +111,12 @@ List<NutrientExampleItem> examples(BuildContext context) => [
'Programmatically adds and removes annotations using a custom Widget.',
onTap: () => annotationsExample(context),
),
NutrientExampleItem(
title: 'Protected Annotations (Move Only)',
description:
'Demonstrates how to create protected annotations that can be moved but not edited or resized. Uses custom data to disable contextual menus while preserving movement functionality.',
onTap: () => hideDeleteAnnotationExample(context),
),
NutrientExampleItem(
title: 'Annotation Flags Example',
description: 'Shows how to click an annotation and modify its flags.',
Expand Down Expand Up @@ -614,3 +621,10 @@ void annotationFlagsExample(BuildContext context) {
context,
);
}

void hideDeleteAnnotationExample(BuildContext context) async {
final extractedDocument = await extractAsset(context, _documentPath);
await Navigator.of(context).push<dynamic>(MaterialPageRoute<dynamic>(
builder: (_) => HideDeleteAnnotationExampleWidget(
documentPath: extractedDocument.path)));
}
Loading