diff --git a/CHANGELOG.md b/CHANGELOG.md index 12402348..6b0330b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Unreleased + +- Adds `OfficeConversionSettings` for configuring Office document conversion parameters in Nutrient Flutter Web, including spreadsheet dimension controls. (J#HYB-861) +- Adds annotation contextual menu customization support with `AnnotationMenuConfiguration`. (J#HYB-683) +- Fixes iOS issue where signature dialog would immediately dismiss when entering annotation creation mode programmatically. (J#HYB-859) + ## 5.0.1 - 24 Jul 2025 - Update README.md files to include the latest rebranding changes for Nutrient Flutter SDK. (J#HYB-842) diff --git a/analysis_options.yaml b/analysis_options.yaml index baf99231..e0aba684 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,7 +8,8 @@ include: package:flutter_lints/flutter.yaml analyzer: -# exclude: + exclude: + - lib/src/api/nutrient_api.g.dart # - ../../core/Vendor errors: invalid_assignment: error diff --git a/android/build.gradle b/android/build.gradle index 6dad59c2..778de067 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -60,16 +60,16 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.appcompat:appcompat:1.7.1' implementation "io.nutrient:$pspdfkitMavenModuleName:$pspdfkitVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation "androidx.compose.material:material:1.8.1" + implementation "androidx.compose.material:material:1.8.3" implementation "androidx.constraintlayout:constraintlayout:2.2.1" implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1" - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0") // Use the latest stable version - implementation "androidx.compose.foundation:foundation:1.8.1" - implementation "androidx.compose.ui:ui:1.8.1" + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2") // Use the latest stable version + implementation "androidx.compose.foundation:foundation:1.8.3" + implementation "androidx.compose.ui:ui:1.8.3" implementation("io.noties.markwon:core:4.6.2") implementation("io.noties.markwon:html:4.6.2") implementation("io.noties.markwon:linkify:4.6.2") diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java index de80c727..333b6dc1 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java @@ -216,12 +216,15 @@ class ConfigurationAdapter { @NonNull private final PdfActivityConfiguration.Builder configuration; @Nullable + private final HashMap configurationMap; + @Nullable private String password = null; private boolean enableInstantComments = false; ConfigurationAdapter(@NonNull Context context, @Nullable HashMap configurationMap) { this.configuration = new PdfActivityConfiguration.Builder(context); + this.configurationMap = configurationMap; if (configurationMap != null && !configurationMap.isEmpty()) { String key = null; @@ -935,6 +938,68 @@ private static String javaToDartTypeConverted(Class clazz) { throw new IllegalArgumentException("Undefined dart type conversion for " + clazz.getName()); } + /** + * Extracts annotation menu configuration from the configuration map. + * + * @return The annotation menu configuration data, or null if not configured + */ + @Nullable + public com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData getAnnotationMenuConfiguration() { + if (configurationMap == null) { + return null; + } + + @SuppressWarnings("unchecked") + Map annotationMenuConfig = (Map) configurationMap.get("annotationMenuConfiguration"); + + if (annotationMenuConfig == null) { + return null; + } + + try { + // Extract items to remove (now using enum indices) + @SuppressWarnings("unchecked") + List itemsToRemoveIndices = (List) annotationMenuConfig.get("itemsToRemove"); + List itemsToRemove = new ArrayList<>(); + if (itemsToRemoveIndices != null) { + for (Number index : itemsToRemoveIndices) { + com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuAction action = + com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuAction.values()[index.intValue()]; + itemsToRemove.add(action); + } + } + + // Extract items to disable (now using enum indices) + @SuppressWarnings("unchecked") + List itemsToDisableIndices = (List) annotationMenuConfig.get("itemsToDisable"); + List itemsToDisable = new ArrayList<>(); + if (itemsToDisableIndices != null) { + for (Number index : itemsToDisableIndices) { + com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuAction action = + com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuAction.values()[index.intValue()]; + itemsToDisable.add(action); + } + } + + // Extract other configuration options + Boolean showStylePicker = (Boolean) annotationMenuConfig.get("showStylePicker"); + Boolean groupMarkupItems = (Boolean) annotationMenuConfig.get("groupMarkupItems"); + Number maxVisibleItems = (Number) annotationMenuConfig.get("maxVisibleItems"); + + return new com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData( + itemsToRemove, + itemsToDisable, + showStylePicker != null ? showStylePicker : true, + groupMarkupItems != null ? groupMarkupItems : false, + maxVisibleItems != null ? maxVisibleItems.longValue() : null + ); + + } catch (Exception e) { + Log.e(LOG_TAG, "Error parsing annotation menu configuration", e); + return null; + } + } + @Nullable String getPassword() { return password; diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragment.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragment.kt index 3db24e24..67c16b6e 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragment.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragment.kt @@ -28,17 +28,30 @@ import androidx.core.view.MenuHost import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import com.pspdfkit.document.PdfDocument +import com.pspdfkit.flutter.pspdfkit.annotations.AnnotationMenuHandler import com.pspdfkit.flutter.pspdfkit.api.CustomToolbarCallbacks +import com.pspdfkit.flutter.pspdfkit.GlobalAnnotationMenuConfiguration import com.pspdfkit.ui.PdfUiFragment -import com.pspdfkit.R +import com.pspdfkit.ui.toolbar.AnnotationCreationToolbar +import com.pspdfkit.ui.toolbar.AnnotationEditingToolbar +import com.pspdfkit.ui.toolbar.ContextualToolbar +import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout +import com.pspdfkit.ui.toolbar.grouping.MenuItemGroupingRule -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() private var customToolbarCallbacks: CustomToolbarCallbacks? = null private var customToolbarItems: List>? = null + // Annotation menu handler for custom contextual menus + private var annotationMenuHandler: AnnotationMenuHandler? = null + + // Toolbar grouping rule for annotation creation toolbar + private var toolbarGroupingRule: MenuItemGroupingRule? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,7 +80,7 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { */ override fun onDocumentLoaded(document: PdfDocument) { super.onDocumentLoaded(document) - // Notify the Flutter PSPDFKit plugin that the document has been loaded. + // Notify the Nutrient Flutter plugin that the document has been loaded. EventDispatcher.getInstance().notifyDocumentLoaded(document) } @@ -99,6 +112,47 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { this.customToolbarItems = items } + /** + * Sets the annotation menu handler for customizing contextual annotation menus. + * Note: The actual menu customization is now handled through annotation selection events + * in FlutterEventsHelper, not through contextual toolbar lifecycle events. + * + * @param handler The annotation menu handler to use for customization + */ + fun setAnnotationMenuHandler(handler: AnnotationMenuHandler) { + this.annotationMenuHandler = handler + Log.d("FlutterPdfUiFragment", "Annotation menu handler configured") + } + + + + /** + * Gets the effective annotation menu handler to use, falling back to global configuration + * if no widget-specific handler is set. + * + * @return The annotation menu handler to use, or null if none available + */ + private fun getEffectiveAnnotationMenuHandler(): AnnotationMenuHandler? { + return annotationMenuHandler ?: let { + // Fall back to global configuration if no widget-specific handler is set + if (GlobalAnnotationMenuConfiguration.hasConfiguration()) { + AnnotationMenuHandler.fromGlobalConfiguration(requireContext()) + } else { + null + } + } + } + + /** + * Sets the toolbar grouping rule for annotation creation toolbar. + * + * @param rule The menu item grouping rule. + */ + fun setToolbarGroupingRule(rule: MenuItemGroupingRule) { + this.toolbarGroupingRule = rule + Log.d("FlutterPdfUiFragment", "Toolbar grouping rule configured") + } + // Store titles for custom toolbar items private val customToolbarItemTitles = HashMap() @@ -157,7 +211,7 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { // Load drawable if available if (iconName != null) { val fragmentContext = activity.applicationContext ?: continue - val drawable = extractDrawableFromName(fragmentContext,iconName, iconColorHex) + val drawable = extractDrawableFromName(fragmentContext, iconName, iconColorHex) customToolbarItemDrawables[identifier] = drawable } } @@ -192,7 +246,11 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { } } - private fun extractDrawableFromName(fragmentContext: Context,iconName: String?, iconColorHex:String? ): Drawable? { + private fun extractDrawableFromName( + fragmentContext: Context, + iconName: String?, + iconColorHex: String? + ): Drawable? { var drawable: Drawable? = null @@ -261,6 +319,7 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { } override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + // Check custom toolbar items val matchingIdentifier = customToolbarItemIds.entries.find { it.value == menuItem.itemId }?.key if (matchingIdentifier != null) { @@ -284,6 +343,7 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { private fun isAndroidBackButton(identifier: String): Boolean { return androidBackButtons[identifier] == true } + /** * Sets the Android back button in the toolbar with the specified identifier and icon. * @@ -300,4 +360,53 @@ class FlutterPdfUiFragment : PdfUiFragment(), MenuProvider { } } } -} \ No newline at end of file + + + // Contextual toolbar lifecycle methods for annotation menu customization + + override fun onPrepareContextualToolbar(contextualToolbar: ContextualToolbar<*>) { + // Handle annotation creation toolbar grouping (existing functionality) + if (contextualToolbar is AnnotationCreationToolbar && toolbarGroupingRule != null) { + contextualToolbar.setMenuItemGroupingRule(toolbarGroupingRule) + Log.d( + "FlutterPdfUiFragment", + "Applied toolbar grouping rule to annotation creation toolbar" + ) + } + + // For annotation editing toolbar, use only static configuration from GlobalAnnotationMenuConfiguration + if (contextualToolbar is AnnotationEditingToolbar) { + val selectedAnnotations = pdfFragment?.selectedAnnotations + if (!selectedAnnotations.isNullOrEmpty()) { + val selectedAnnotation = selectedAnnotations.first() + Log.d( + "FlutterPdfUiFragment", + "Preparing toolbar for annotation: ${selectedAnnotation.type.name}, UUID: ${selectedAnnotation.uuid}" + ) + + // Use only static configuration from GlobalAnnotationMenuConfiguration + getEffectiveAnnotationMenuHandler()?.let { handler -> + handler.onAnnotationSelected( + selectedAnnotation, + selectedAnnotations.size > 1 + ) + handler.onPrepareContextualToolbar(contextualToolbar) + Log.d("FlutterPdfUiFragment", "Applied static annotation menu configuration to toolbar") + } + return + } + } + + // Handle annotation editing toolbar customization (fallback) + getEffectiveAnnotationMenuHandler()?.onPrepareContextualToolbar(contextualToolbar) + } + + override fun onDisplayContextualToolbar(contextualToolbar: ContextualToolbar<*>) { + // No special handling needed for display + } + + override fun onRemoveContextualToolbar(contextualToolbar: ContextualToolbar<*>) { + // Clear selected annotation when toolbar is removed + getEffectiveAnnotationMenuHandler()?.clearSelectedAnnotation() + } +} diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/GlobalAnnotationMenuConfiguration.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/GlobalAnnotationMenuConfiguration.kt new file mode 100644 index 00000000..8c135260 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/GlobalAnnotationMenuConfiguration.kt @@ -0,0 +1,62 @@ +/* + * Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. + *

+ * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW + * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. + * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. + * This notice may not be removed from this file. + */ + +package com.pspdfkit.flutter.pspdfkit + +import com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData + +/** + * Global storage for annotation menu configuration that can be shared between + * the deprecated PspdfkitPluginMethodCallHandler and the new PspdfkitApiImpl. + * + * This ensures consistent annotation menu behavior across both old and new APIs. + */ +object GlobalAnnotationMenuConfiguration { + + @Volatile + private var configuration: AnnotationMenuConfigurationData? = null + + /** + * Sets the global annotation menu configuration. + * + * @param config The annotation menu configuration to store + */ + fun setConfiguration(config: AnnotationMenuConfigurationData?) { + synchronized(this) { + configuration = config + } + } + + /** + * Gets the current global annotation menu configuration. + * + * @return The current configuration, or null if none is set + */ + fun getConfiguration(): AnnotationMenuConfigurationData? { + return configuration + } + + /** + * Clears the global annotation menu configuration. + */ + fun clearConfiguration() { + synchronized(this) { + configuration = null + } + } + + /** + * Checks if a configuration is currently set. + * + * @return true if a configuration is set, false otherwise + */ + fun hasConfiguration(): Boolean { + return configuration != null + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt index 01a15bc5..48b4c253 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt @@ -20,13 +20,13 @@ import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.FragmentManager import androidx.fragment.app.commit import androidx.fragment.app.commitNow +import com.pspdfkit.flutter.pspdfkit.annotations.AnnotationMenuHandler import com.pspdfkit.flutter.pspdfkit.api.CustomToolbarCallbacks import com.pspdfkit.flutter.pspdfkit.api.NutrientEventsCallbacks import com.pspdfkit.flutter.pspdfkit.api.NutrientViewCallbacks import com.pspdfkit.flutter.pspdfkit.api.NutrientViewControllerApi import com.pspdfkit.flutter.pspdfkit.events.FlutterEventsHelper import com.pspdfkit.flutter.pspdfkit.toolbar.FlutterMenuGroupingRule -import com.pspdfkit.flutter.pspdfkit.toolbar.FlutterViewModeController import com.pspdfkit.flutter.pspdfkit.util.addFileSchemeIfMissing import com.pspdfkit.flutter.pspdfkit.util.isImageDocument import com.pspdfkit.signatures.storage.DatabaseSignatureStorage @@ -50,16 +50,20 @@ internal class PSPDFKitView( documentPath: String? = null, configurationMap: HashMap? = null, customToolbarItems: List>? = null, - ) : PlatformView { +) : PlatformView { private var fragmentContainerView: FragmentContainerView? = FragmentContainerView(context) private val methodChannel: MethodChannel private lateinit var pdfUiFragment: PdfUiFragment private var fragmentCallbacks: FlutterPdfUiFragmentCallbacks? = null private val pspdfkitViewImpl: PspdfkitViewImpl = PspdfkitViewImpl() - private val nutrientEventsCallbacks: NutrientEventsCallbacks = NutrientEventsCallbacks(messenger, "events.callbacks.$id") - private val widgetCallbacks: NutrientViewCallbacks = NutrientViewCallbacks(messenger, "widget.callbacks.$id") - private val customToolbarCallbacks: CustomToolbarCallbacks = CustomToolbarCallbacks(messenger, "customToolbar.callbacks.$id") + private val nutrientEventsCallbacks: NutrientEventsCallbacks = + NutrientEventsCallbacks(messenger, "events.callbacks.$id") + private val widgetCallbacks: NutrientViewCallbacks = + NutrientViewCallbacks(messenger, "widget.callbacks.$id") + private val customToolbarCallbacks: CustomToolbarCallbacks = + CustomToolbarCallbacks(messenger, "customToolbar.callbacks.$id") + private var annotationMenuHandler: AnnotationMenuHandler? = null private var isFragmentAttached = false private var methodCallHandler: PSPDFKitWidgetMethodCallHandler? = null private var aiAssistant: AiAssistant? = null @@ -71,10 +75,24 @@ internal class PSPDFKitView( val configurationAdapter = ConfigurationAdapter(context, configurationMap) val password = configurationAdapter.password val pdfConfiguration = configurationAdapter.build() - val toolbarGroupingItems: List? = configurationMap?.get("toolbarItemGrouping") as List? + val toolbarGroupingItems: List? = + configurationMap?.get("toolbarItemGrouping") as List? val measurementValueConfigurations = configurationMap?.get("measurementValueConfigurations") as List>? val aiAssistantConfigurationMap = configurationMap?.get("aiAssistant") as Map? + val annotationMenuConfiguration = configurationAdapter.getAnnotationMenuConfiguration() + + // Initialize annotation menu handler if configuration is provided + if (annotationMenuConfiguration != null) { + annotationMenuHandler = AnnotationMenuHandler( + context, + annotationMenuConfiguration + ) + Log.d( + LOG_TAG, + "Initialized annotation menu handler" + ) + } try { //noinspection pspdfkit-experimental @@ -112,11 +130,16 @@ internal class PSPDFKitView( } setupAiAssistant(context, aiAssistantConfigurationMap) - fragmentCallbacks = FlutterPdfUiFragmentCallbacks(methodChannel, measurementValueConfigurations, - messenger,FlutterWidgetCallback(widgetCallbacks),aiAssistant) + fragmentCallbacks = FlutterPdfUiFragmentCallbacks( + methodChannel, measurementValueConfigurations, + messenger, FlutterWidgetCallback(widgetCallbacks), aiAssistant + ) fragmentCallbacks?.let { callbacks -> - getFragmentActivity(context).supportFragmentManager.registerFragmentLifecycleCallbacks(callbacks, true) + getFragmentActivity(context).supportFragmentManager.registerFragmentLifecycleCallbacks( + callbacks, + true + ) } } catch (e: Exception) { Log.e(LOG_TAG, "Error initializing PdfUiFragment", e) @@ -126,42 +149,78 @@ internal class PSPDFKitView( ).configuration(pdfConfiguration).build() } - getFragmentActivity(context).supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentAttached( - fm: FragmentManager, - f: Fragment, - context: Context - ) { - if (f.tag?.contains("Nutrient.Fragment") == true) { - if (toolbarGroupingItems != null) { - val groupingRule = FlutterMenuGroupingRule(context, toolbarGroupingItems) - val flutterViewModeController = FlutterViewModeController(groupingRule) - pdfUiFragment.setOnContextualToolbarLifecycleListener(flutterViewModeController) + getFragmentActivity(context).supportFragmentManager.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentAttached( + fm: FragmentManager, + f: Fragment, + context: Context + ) { + if (f.tag?.contains("Nutrient.Fragment") == true && pdfUiFragment is FlutterPdfUiFragment) { + // Set up toolbar grouping rule if available + if (toolbarGroupingItems != null) { + val groupingRule = + FlutterMenuGroupingRule(context, toolbarGroupingItems) + val flutterFragment = pdfUiFragment as? FlutterPdfUiFragment + flutterFragment?.setToolbarGroupingRule(groupingRule) + } + + // Always set FlutterPdfUiFragment as the contextual toolbar listener + // It can handle both toolbar grouping and annotation menu customization + val flutterFragment = pdfUiFragment as? FlutterPdfUiFragment + flutterFragment?.let { fragment -> + fragment.setOnContextualToolbarLifecycleListener(fragment) + } } // Process custom toolbar items if (customToolbarItems?.isNotEmpty() == true && f is PdfFragment) { - (pdfUiFragment as FlutterPdfUiFragment).setCustomToolbarItems( + val flutterFragment = pdfUiFragment as? FlutterPdfUiFragment + flutterFragment?.setCustomToolbarItems( customToolbarItems, customToolbarCallbacks ) } - // Create method call handler to handle Flutter method calls - methodCallHandler = - pdfUiFragment.pdfFragment?.let { PSPDFKitWidgetMethodCallHandler(it) } + // Set up annotation menu handler if configured + if (annotationMenuHandler != null && f is PdfFragment) { + val flutterFragment = pdfUiFragment as? FlutterPdfUiFragment + flutterFragment?.setAnnotationMenuHandler(annotationMenuHandler!!) + } + + // Dynamic annotation menu callbacks have been removed + // The annotation menu is now configured statically via annotationMenuHandler - // Set up method channel for communication with Flutter - methodCallHandler?.let { handler -> - methodChannel.setMethodCallHandler(handler) + // Create method call handler to handle Flutter method calls + // Wait for pdfFragment to be available before setting up the handler + try { + val pdfFragment = pdfUiFragment.pdfFragment + if (pdfFragment != null) { + methodCallHandler = PSPDFKitWidgetMethodCallHandler(pdfFragment) + + // Set up method channel for communication with Flutter + methodCallHandler?.let { handler -> + methodChannel.setMethodCallHandler(handler) + } + } else { + Log.w(LOG_TAG, "PdfFragment not yet available, method call handler will be set up later") + } + } catch (e: Exception) { + Log.e(LOG_TAG, "Error setting up method call handler", e) } if (configurationMap?.contains("signatureSavingStrategy") == true) { - pdfUiFragment.pdfFragment?.let { configureSignatureStorage(it) } + try { + pdfUiFragment.pdfFragment?.let { configureSignatureStorage(it) } + } catch (e: Exception) { + Log.e(LOG_TAG, "Error configuring signature storage", e) + } } + } - } - }, true) + }, + true + ) fragmentContainerView?.let { it.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { @@ -214,7 +273,8 @@ internal class PSPDFKitView( try { val fragmentActivity = getFragmentActivity(context) if (!fragmentActivity.isFinishing && !fragmentActivity.isDestroyed - && fragmentActivity.supportFragmentManager.isDestroyed.not()) { + && fragmentActivity.supportFragmentManager.isDestroyed.not() + ) { fragmentActivity.supportFragmentManager.commit { pdfUiFragment.let { if (it.isAdded) remove(it) } setReorderingAllowed(true) @@ -233,7 +293,9 @@ internal class PSPDFKitView( // Unregister callbacks and listeners fragmentCallbacks?.let { try { - getFragmentActivity(context).supportFragmentManager.unregisterFragmentLifecycleCallbacks(it) + getFragmentActivity(context).supportFragmentManager.unregisterFragmentLifecycleCallbacks( + it + ) } catch (e: Exception) { Log.e(LOG_TAG, "Error unregistering fragment lifecycle callbacks", e) } @@ -255,7 +317,8 @@ internal class PSPDFKitView( override fun onFlutterViewAttached(flutterView: View) { super.onFlutterViewAttached(flutterView) // Set up the method channel for communication with Flutter. - val flutterEventsHelper = FlutterEventsHelper(nutrientEventsCallbacks) + val flutterEventsHelper = + FlutterEventsHelper(nutrientEventsCallbacks, annotationMenuHandler) pspdfkitViewImpl.setEventDispatcher(flutterEventsHelper) NutrientViewControllerApi.setUp(messenger, pspdfkitViewImpl, id.toString()) } @@ -266,7 +329,10 @@ internal class PSPDFKitView( is FragmentActivity -> { // Verify the activity is in a valid state for fragment operations if (context.isDestroyed || context.isFinishing) { - Log.w(LOG_TAG, "Activity is finishing or destroyed, may cause issues with fragment operations") + Log.w( + LOG_TAG, + "Activity is finishing or destroyed, may cause issues with fragment operations" + ) } context } @@ -293,18 +359,18 @@ internal class PSPDFKitView( } } - private fun configureSignatureStorage(pdfFragment: PdfFragment){ + private fun configureSignatureStorage(pdfFragment: PdfFragment) { // See guides: https://www.nutrient.io/guides/android/signatures/signature-storage/ // Set the signature storage for the PdfFragment. // Set up signature storage if a signature saving strategy is configured - try { - val storage: SignatureStorage = DatabaseSignatureStorage - .withName(context,"nutrient_flutter_signature_storage") - pdfFragment.signatureStorage = storage - } catch (e: Exception) { - // Log any errors but don't crash the app - Log.e("FlutterPdfActivity", "Error setting up signature storage: " + e.message) - } + try { + val storage: SignatureStorage = DatabaseSignatureStorage + .withName(context, "nutrient_flutter_signature_storage") + pdfFragment.signatureStorage = storage + } catch (e: Exception) { + // Log any errors but don't crash the app + Log.e("FlutterPdfActivity", "Error setting up signature storage: " + e.message) + } } private fun setupAiAssistant( @@ -320,8 +386,8 @@ internal class PSPDFKitView( if (serverUrl != null && jwt != null) { val aiAssistantConfiguration = AiAssistantConfiguration( serverUrl = serverUrl, - jwt = jwt , - sessionId = sessionId?: "", + jwt = jwt, + sessionId = sessionId ?: "", userId = userId ?: "" ) aiAssistant = standaloneAiAssistant(context, aiAssistantConfiguration) diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt index 8285f480..374f049a 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt @@ -28,6 +28,7 @@ import com.pspdfkit.document.processor.PdfProcessorTask import com.pspdfkit.exceptions.NutrientException import com.pspdfkit.flutter.pspdfkit.AnnotationConfigurationAdaptor.Companion.convertAnnotationConfigurations import com.pspdfkit.flutter.pspdfkit.annotations.FlutterAnnotationPresetConfiguration +import com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData import com.pspdfkit.flutter.pspdfkit.api.AndroidPermissionStatus import com.pspdfkit.flutter.pspdfkit.api.AnnotationProcessingMode import com.pspdfkit.flutter.pspdfkit.api.NutrientApi @@ -876,6 +877,20 @@ class PspdfkitApiImpl(private var activityPluginBinding: ActivityPluginBinding?) activity.startActivity(intent) } + override fun setAnnotationMenuConfiguration( + configuration: AnnotationMenuConfigurationData, + callback: (Result) -> Unit + ) { + try { + // Store the configuration globally for use with PspdfkitPluginMethodCallHandler (deprecated) + GlobalAnnotationMenuConfiguration.setConfiguration(configuration) + + callback(Result.success(true)) + } catch (e: Exception) { + callback(Result.failure(NutrientApiError("Error setting annotation menu configuration", e.message))) + } + } + private val instantActivity: FlutterInstantPdfActivity get() { val instantPdfActivity = FlutterInstantPdfActivity.currentActivity diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt index 44e2fd44..99585fca 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt @@ -729,4 +729,45 @@ class PspdfkitViewImpl : NutrientViewControllerApi { ) } } + + override fun setAnnotationMenuConfiguration( + configuration: com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData, + callback: (Result) -> Unit + ) { + try { + val pdfFragment = pdfUiFragment as? FlutterPdfUiFragment + if (pdfFragment == null) { + callback( + Result.failure( + NutrientApiError( + "Error setting annotation menu configuration", + "PDF fragment is null or not a FlutterPdfUiFragment" + ) + ) + ) + return + } + + // Create a new annotation menu handler with the updated configuration + val handler = com.pspdfkit.flutter.pspdfkit.annotations.AnnotationMenuHandler( + pdfFragment.requireContext(), + configuration + ) + + // Update the fragment's annotation menu handler + // This will be used by the fragment's onPrepareContextualToolbar method + pdfFragment.setAnnotationMenuHandler(handler) + + callback(Result.success(true)) + } catch (e: Exception) { + callback( + Result.failure( + NutrientApiError( + "Error setting annotation menu configuration", + e.message ?: "Unknown error" + ) + ) + ) + } + } } \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationMenuHandler.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationMenuHandler.kt new file mode 100644 index 00000000..f871cd7d --- /dev/null +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationMenuHandler.kt @@ -0,0 +1,250 @@ +/* + * Copyright © 2018-2025 PSPDFKit GmbH. All rights reserved. + *

+ * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW + * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. + * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. + * This notice may not be removed from this file. + */ + +package com.pspdfkit.flutter.pspdfkit.annotations + +import android.content.Context +import android.view.View +import com.pspdfkit.annotations.Annotation +import com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuConfigurationData +import com.pspdfkit.flutter.pspdfkit.api.AnnotationMenuAction +import com.pspdfkit.flutter.pspdfkit.GlobalAnnotationMenuConfiguration +import com.pspdfkit.flutter.pspdfkit.R +import com.pspdfkit.ui.toolbar.AnnotationEditingToolbar +import com.pspdfkit.ui.toolbar.ContextualToolbar +import com.pspdfkit.ui.toolbar.ContextualToolbarMenuItem +import java.util.Locale + +/** + * Handles customization of annotation contextual menus in the Nutrient Flutter SDK on Android. + * + * This class manages the customization of annotation menus by: + * - Adding custom menu items with icons and callbacks + * - Removing or hiding default menu items + * - Hiding the style picker if configured + * - Handling menu item interactions and communicating back to Flutter + */ +class AnnotationMenuHandler( + private val context: Context, + private val configuration: AnnotationMenuConfigurationData? +) { + + companion object { + /** + * Maps AnnotationMenuAction enum values to actual Android resource IDs + */ + private fun getResourceIdForMenuAction(action: AnnotationMenuAction): Int? { + return when (action) { + AnnotationMenuAction.DELETE -> R.id.pspdf__annotation_editing_toolbar_item_delete + AnnotationMenuAction.COPY -> R.id.pspdf__annotation_editing_toolbar_item_copy + AnnotationMenuAction.CUT -> R.id.pspdf__annotation_editing_toolbar_item_cut + AnnotationMenuAction.COLOR -> R.id.pspdf__annotation_editing_toolbar_item_picker + AnnotationMenuAction.NOTE -> R.id.pspdf__annotation_editing_toolbar_item_annotation_note + AnnotationMenuAction.UNDO -> R.id.pspdf__annotation_editing_toolbar_item_undo + AnnotationMenuAction.REDO -> R.id.pspdf__annotation_editing_toolbar_item_redo + } + } + + /** + * Creates a new AnnotationMenuHandler instance with configuration from GlobalAnnotationMenuConfiguration. + * This is useful for creating handlers that automatically pick up the global configuration. + * + * @param context The context for the handler + * @return A new AnnotationMenuHandler instance with the global configuration + */ + fun fromGlobalConfiguration(context: Context): AnnotationMenuHandler { + return AnnotationMenuHandler(context, GlobalAnnotationMenuConfiguration.getConfiguration()) + } + } + + + // Currently selected annotation for callback purposes + private var selectedAnnotation: Annotation? = null + + + /** + * Called when an annotation is selected. This method stores the selected annotation + * for later use when the contextual toolbar is prepared. + * + * @param annotation The selected annotation + * @param isMultipleSelection Whether multiple annotations are selected + * @return true to allow the selection, false to prevent it + */ + fun onAnnotationSelected( + annotation: Annotation, + isMultipleSelection: Boolean + ): Boolean { + selectedAnnotation = annotation + return true // Allow the selection + } + + /** + * Called when a contextual toolbar is being prepared. This is where we customize + * the annotation editing toolbar. + * + * @param toolbar The contextual toolbar being prepared + */ + fun onPrepareContextualToolbar(toolbar: ContextualToolbar<*>) { + // Try to customize any contextual toolbar with configuration + if (configuration != null) { + try { + // Apply menu customizations directly to the toolbar + removeMenuItems(toolbar, configuration.itemsToRemove) + disableMenuItems(toolbar, configuration.itemsToDisable) + + // Apply style picker visibility configuration + if (!configuration.showStylePicker) { + removeStylePickerItems(toolbar) + } + } catch (e: Exception) { + // Silently handle errors in customization + } + } + } + + + + /** + * Removes menu items from the toolbar based on their action types. + * Uses PSPDFKit's setMenuItemVisibility API to properly hide items. + * + * @param toolbar The contextual toolbar to modify + * @param itemsToRemove List of menu actions to remove + */ + private fun removeMenuItems( + toolbar: ContextualToolbar<*>, + itemsToRemove: List + ) { + if (itemsToRemove.isEmpty()) { + return + } + + itemsToRemove.forEach { action -> + val resourceId = getResourceIdForMenuAction(action) + + if (resourceId != null) { + try { + toolbar.setMenuItemVisibility(resourceId, View.GONE) + } catch (e: Exception) { + // Silently skip items that cannot be hidden + } + } + } + } + + /** + * Disables menu items based on their action types. + * Properly disables items by setting isEnabled to false. + * + * @param toolbar The contextual toolbar to modify + * @param itemsToDisable List of menu actions to disable + */ + private fun disableMenuItems( + toolbar: ContextualToolbar<*>, + itemsToDisable: List + ) { + if (itemsToDisable.isEmpty()) { + return + } + + val menuItems = toolbar.menuItems.toMutableList() + var modified = false + + itemsToDisable.forEach { action -> + val resourceId = getResourceIdForMenuAction(action) + if (resourceId != null) { + // Find the menu item by its resource ID + val menuItem = menuItems.find { it.id == resourceId } + if (menuItem != null) { + // Disable the menu item by setting isEnabled to false + menuItem.isEnabled = false + modified = true + } + } + } + + // Apply the modified menu items back to the toolbar if any changes were made + if (modified) { + toolbar.setMenuItems(menuItems) + } + } + + + /** + * Removes style picker related items from the menu if configured to do so. + * + * @param toolbar The contextual toolbar to modify + */ + private fun removeStylePickerItems(toolbar: ContextualToolbar<*>) { + // Hide the picker item (color/style picker) + try { + toolbar.setMenuItemVisibility(R.id.pspdf__annotation_editing_toolbar_item_picker, View.GONE) + } catch (e: Exception) { + // Silently skip if item cannot be hidden + } + + // Also try to hide other potential style-related items if they exist + val potentialStyleItems = listOf( + "style", "color", "thickness", "picker" + ) + + toolbar.menuItems.forEach { item -> + val itemTitle = item.title?.toString()?.lowercase(Locale.getDefault()) + val resourceName = getResourceName(item.id)?.lowercase(Locale.getDefault()) + + val isStylePickerItem = potentialStyleItems.any { keyword -> + itemTitle?.contains(keyword) == true || resourceName?.contains(keyword) == true + } + + if (isStylePickerItem) { + try { + toolbar.setMenuItemVisibility(item.id, View.GONE) + } catch (e: Exception) { + // Silently skip if item cannot be hidden + } + } + } + } + + /** + * Gets the resource name from a resource ID. + * + * @param resourceId The resource ID + * @return The resource name, or null if not found + */ + private fun getResourceName(resourceId: Int): String? { + return try { + if (resourceId != 0) { + context.resources.getResourceEntryName(resourceId) + } else { + null + } + } catch (e: Exception) { + null + } + } + + /** + * Updates the annotation menu configuration dynamically. + * + * @param newConfiguration The new annotation menu configuration + */ + fun updateConfiguration(newConfiguration: AnnotationMenuConfigurationData?) { + // Note: We can't directly update the configuration since it's a constructor parameter. + // This method is provided for compatibility, but in practice, a new handler instance + // should be created with the new configuration. + } + + /** + * Clears the selected annotation when the selection changes. + */ + fun clearSelectedAnnotation() { + selectedAnnotation = null + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationUtils.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationUtils.kt index 889c9dc8..d94366f3 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationUtils.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/annotations/AnnotationUtils.kt @@ -109,13 +109,21 @@ object AnnotationUtils { FlutterAnnotationTool.MEASUREMENT_PERIMETER -> AnnotationToolWithVariant(AndroidAnnotationTool.MEASUREMENT_PERIMETER) FlutterAnnotationTool.MEASUREMENT_DISTANCE -> AnnotationToolWithVariant(AndroidAnnotationTool.MEASUREMENT_DISTANCE) - // Some Flutter tools don't have direct Android equivalents + // Image stamp tool - can be mapped to existing stamp functionality + FlutterAnnotationTool.STAMP_IMAGE -> AnnotationToolWithVariant(AndroidAnnotationTool.STAMP) + + // The following Flutter tools don't have corresponding Android AnnotationTool equivalents: + // - CARET: Exists as AnnotationType but no dedicated creation tool + // - RICH_MEDIA: Exists as AnnotationType but no dedicated creation tool + // - SCREEN: Exists as AnnotationType but no dedicated creation tool + // - FILE: Exists as AnnotationType but no dedicated creation tool + // - WIDGET: Exists as AnnotationType but no dedicated creation tool + // - LINK: Link annotations are typically created differently in Android FlutterAnnotationTool.CARET, FlutterAnnotationTool.RICH_MEDIA, FlutterAnnotationTool.SCREEN, FlutterAnnotationTool.FILE, FlutterAnnotationTool.WIDGET, - FlutterAnnotationTool.STAMP_IMAGE, FlutterAnnotationTool.LINK, null -> null } diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/NutrientApi.g.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/NutrientApi.g.kt index 2d384301..82989f56 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/NutrientApi.g.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/NutrientApi.g.kt @@ -269,6 +269,64 @@ enum class NutrientEvent(val raw: Int) { } } +/** + * Enumeration of default annotation menu actions that can be removed or disabled. + * + * **Platform Support:** + * - All actions can be removed or disabled on both iOS and Android + * - Some system actions (copy/paste) may be harder to remove on iOS due to system restrictions + */ +enum class AnnotationMenuAction(val raw: Int) { + /** + * Delete action - removes the annotation + * - iOS: Part of UIMenu system actions + * - Android: R.id.pspdf__annotation_editing_toolbar_item_delete + */ + DELETE(0), + /** + * Copy action - copies the annotation + * - iOS: System copy action (may be harder to remove) + * - Android: R.id.pspdf__annotation_editing_toolbar_item_copy + */ + COPY(1), + /** + * Cut action - cuts the annotation to clipboard + * - iOS: System cut action + * - Android: R.id.pspdf__annotation_editing_toolbar_item_cut + */ + CUT(2), + /** + * Color action - opens annotation color picker/inspector + * - iOS: Style picker in UIMenu + * - Android: R.id.pspdf__annotation_editing_toolbar_item_picker + */ + COLOR(3), + /** + * Note action - opens annotation note editor + * - iOS: Note action in UIMenu + * - Android: R.id.pspdf__annotation_editing_toolbar_item_annotation_note + */ + NOTE(4), + /** + * Undo action - undoes the last action + * - iOS: Undo in UIMenu + * - Android: R.id.pspdf__annotation_editing_toolbar_item_undo + */ + UNDO(5), + /** + * Redo action - redoes the previously undone action + * - iOS: Redo in UIMenu + * - Android: R.id.pspdf__annotation_editing_toolbar_item_redo + */ + REDO(6); + + companion object { + fun ofRaw(raw: Int): AnnotationMenuAction? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** Generated class from Pigeon that represents data sent in messages. */ data class PdfRect ( val x: Double, @@ -478,6 +536,108 @@ data class PointF ( ) } } + +/** + * Configuration data for annotation contextual menu + * + * This class defines how annotation menus should be configured + * when displayed to users. It supports removing actions, disabling actions, + * and controlling visual presentation options. + * + * **Usage Patterns**: + * - **Static Configuration**: Set once via [NutrientViewController.setAnnotationMenuConfiguration] + * + * **Platform Compatibility**: + * - [itemsToRemove]: Supported on Android, iOS, and Web + * - [itemsToDisable]: Supported on Android, iOS, and Web + * - [showStylePicker]: Supported on Android and iOS + * - [groupMarkupItems]: iOS only (ignored on other platforms) + * - [maxVisibleItems]: Platform-dependent behavior + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class AnnotationMenuConfigurationData ( + /** + * List of default annotation menu actions to remove completely from the menu. + * + * These actions will not appear in the contextual menu at all. + * Use this when you want to completely hide certain functionality. + * + * **Example**: Remove delete action for read-only annotations + * ```dart + * itemsToRemove: [AnnotationMenuAction.delete] + * ``` + */ + val itemsToRemove: List, + /** + * List of default annotation menu actions to disable (show as grayed out). + * + * These actions will appear in the menu but will be non-interactive. + * Use this when you want to show functionality exists but is temporarily unavailable. + * + * **Example**: Disable copy action for certain annotation types + * ```dart + * itemsToDisable: [AnnotationMenuAction.copy] + * ``` + */ + val itemsToDisable: List, + /** + * Whether to show the platform's default style picker in the annotation menu. + * + * When true, users can access color, thickness, and other style options + * directly from the annotation menu. + * + * **Platform Behavior**: + * - **iOS**: Shows style picker as part of UIMenu + * - **Android**: Shows annotation inspector/style picker + * - **Web**: Shows color picker and basic style options + */ + val showStylePicker: Boolean, + /** + * Whether to group markup annotation actions together in the menu. + * + * When true, related markup actions (highlight, underline, etc.) are + * visually grouped in the menu for better organization. + * + * **Platform Support**: iOS only (ignored on Android and Web) + */ + val groupMarkupItems: Boolean, + /** + * Maximum number of actions to show directly in the menu before creating overflow. + * + * When the number of available actions exceeds this limit, the platform + * may create a submenu or overflow menu to accommodate additional actions. + * + * **Platform Behavior**: + * - **iOS**: Respects platform UI guidelines for menu length + * - **Android**: Limited by toolbar space and screen size + * - **Web**: Creates scrollable or paginated menu as needed + * + * **Note**: If null, the platform default behavior is used. + */ + val maxVisibleItems: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): AnnotationMenuConfigurationData { + val itemsToRemove = pigeonVar_list[0] as List + val itemsToDisable = pigeonVar_list[1] as List + val showStylePicker = pigeonVar_list[2] as Boolean + val groupMarkupItems = pigeonVar_list[3] as Boolean + val maxVisibleItems = pigeonVar_list[4] as Long? + return AnnotationMenuConfigurationData(itemsToRemove, itemsToDisable, showStylePicker, groupMarkupItems, maxVisibleItems) + } + } + fun toList(): List { + return listOf( + itemsToRemove, + itemsToDisable, + showStylePicker, + groupMarkupItems, + maxVisibleItems, + ) + } +} private open class NutrientApiPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -527,35 +687,45 @@ private open class NutrientApiPigeonCodec : StandardMessageCodec() { } } 138.toByte() -> { + return (readValue(buffer) as Long?)?.let { + AnnotationMenuAction.ofRaw(it.toInt()) + } + } + 139.toByte() -> { return (readValue(buffer) as? List)?.let { PdfRect.fromList(it) } } - 139.toByte() -> { + 140.toByte() -> { return (readValue(buffer) as? List)?.let { PageInfo.fromList(it) } } - 140.toByte() -> { + 141.toByte() -> { return (readValue(buffer) as? List)?.let { DocumentSaveOptions.fromList(it) } } - 141.toByte() -> { + 142.toByte() -> { return (readValue(buffer) as? List)?.let { PdfFormOption.fromList(it) } } - 142.toByte() -> { + 143.toByte() -> { return (readValue(buffer) as? List)?.let { FormFieldData.fromList(it) } } - 143.toByte() -> { + 144.toByte() -> { return (readValue(buffer) as? List)?.let { PointF.fromList(it) } } + 145.toByte() -> { + return (readValue(buffer) as? List)?.let { + AnnotationMenuConfigurationData.fromList(it) + } + } else -> super.readValueOfType(type, buffer) } } @@ -597,28 +767,36 @@ private open class NutrientApiPigeonCodec : StandardMessageCodec() { stream.write(137) writeValue(stream, value.raw) } - is PdfRect -> { + is AnnotationMenuAction -> { stream.write(138) + writeValue(stream, value.raw) + } + is PdfRect -> { + stream.write(139) writeValue(stream, value.toList()) } is PageInfo -> { - stream.write(139) + stream.write(140) writeValue(stream, value.toList()) } is DocumentSaveOptions -> { - stream.write(140) + stream.write(141) writeValue(stream, value.toList()) } is PdfFormOption -> { - stream.write(141) + stream.write(142) writeValue(stream, value.toList()) } is FormFieldData -> { - stream.write(142) + stream.write(143) writeValue(stream, value.toList()) } is PointF -> { - stream.write(143) + stream.write(144) + writeValue(stream, value.toList()) + } + is AnnotationMenuConfigurationData -> { + stream.write(145) writeValue(stream, value.toList()) } else -> super.writeValue(stream, value) @@ -679,6 +857,14 @@ interface NutrientApi { fun generatePdfFromHtmlUri(htmlUri: String, outPutFile: String, options: Map?, callback: (Result) -> Unit) /** Configure Nutrient Analytics events. */ fun enableAnalyticsEvents(enable: Boolean) + /** + * Sets the annotation menu configuration for the global presenter. + * This configuration applies to all annotation menus in presented documents. + * + * @param configuration The annotation menu configuration to apply. + * @return True if the configuration was set successfully, false otherwise. + */ + fun setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, callback: (Result) -> Unit) companion object { /** The codec used by NutrientApi. */ @@ -1317,6 +1503,26 @@ interface NutrientApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAnnotationMenuConfiguration$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val configurationArg = args[0] as AnnotationMenuConfigurationData + api.setAnnotationMenuConfiguration(configurationArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } @@ -1635,6 +1841,14 @@ interface NutrientViewControllerApi { * exiting annotation creation mode was successful. */ fun exitAnnotationCreationMode(callback: (Result) -> Unit) + /** + * Sets the annotation menu configuration for the current view controller. + * This configuration applies only to annotation menus in the current document view. + * + * @param configuration The annotation menu configuration to apply. + * @return True if the configuration was set successfully, false otherwise. + */ + fun setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, callback: (Result) -> Unit) companion object { /** The codec used by NutrientViewControllerApi. */ @@ -2040,6 +2254,26 @@ interface NutrientViewControllerApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setAnnotationMenuConfiguration$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val configurationArg = args[0] as AnnotationMenuConfigurationData + api.setAnnotationMenuConfiguration(configurationArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt index 1afbbc12..8f65708e 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt @@ -3,6 +3,7 @@ package com.pspdfkit.flutter.pspdfkit.events import com.pspdfkit.annotations.Annotation import com.pspdfkit.annotations.AnnotationProvider import com.pspdfkit.datastructures.TextSelection +import com.pspdfkit.flutter.pspdfkit.annotations.AnnotationMenuHandler import com.pspdfkit.flutter.pspdfkit.api.NutrientEvent import com.pspdfkit.flutter.pspdfkit.api.NutrientEventsCallbacks import com.pspdfkit.ui.PdfUiFragment @@ -15,7 +16,10 @@ import com.pspdfkit.ui.special_mode.manager.TextSelectionManager * Helper class to manage event listeners for Nutrient Flutter integration. * Handles registration and removal of event listeners for various PDF events. */ -class FlutterEventsHelper(private val eventCallbacks: NutrientEventsCallbacks? = null) { +class FlutterEventsHelper( + private val eventCallbacks: NutrientEventsCallbacks? = null, + private val annotationMenuHandler: AnnotationMenuHandler? = null +) { // Map to store event listeners by event type private val eventsMap: MutableMap = mutableMapOf() @@ -73,6 +77,9 @@ class FlutterEventsHelper(private val eventCallbacks: NutrientEventsCallbacks? = annotation: Annotation, isMultipleSelection: Boolean ) { + // Notify annotation menu handler of selection + annotationMenuHandler?.onAnnotationSelected(annotation, isMultipleSelection) + sendEvent(event, mapOf( "annotation" to annotation.toInstantJson(), "isMultipleSelection" to isMultipleSelection @@ -109,6 +116,9 @@ class FlutterEventsHelper(private val eventCallbacks: NutrientEventsCallbacks? = reselected: Boolean ) { super.onAnnotationDeselected(annotation, reselected) + // Clear selected annotation from handler when deselected + annotationMenuHandler?.clearSelectedAnnotation() + if (annotation != null) { sendEvent(event, mapOf( "deselected" to mapOf( diff --git a/documentation/annotation-creation-mode.md b/documentation/annotation-creation-mode.md new file mode 100644 index 00000000..57226f50 --- /dev/null +++ b/documentation/annotation-creation-mode.md @@ -0,0 +1,193 @@ +# Annotation Creation Mode + +This guide explains how to use the annotation creation mode feature in the Nutrient Flutter SDK to programmatically enable annotation tools for users to create PDF annotations. + +## Overview + +The annotation creation mode API allows you to programmatically enter and exit specific annotation creation modes, enabling users to create various types of PDF annotations such as ink drawings, text highlights, shapes, signatures, and more. + +## Basic Usage + +To use annotation creation mode, you first need to get a reference to the `NutrientViewController` through the `onViewCreated` callback: + +```dart +NutrientViewController? _controller; + +NutrientView( + documentPath: 'path/to/document.pdf', + configuration: PdfConfiguration( + enableAnnotationEditing: true, // Required for annotation creation + ), + onViewCreated: (controller) { + setState(() { + _controller = controller; + }); + }, +) +``` + +### Entering Annotation Creation Mode + +```dart +// Enter annotation creation mode with a specific tool +await controller.enterAnnotationCreationMode(AnnotationTool.inkPen); + +// Enter annotation creation mode with default tool +await controller.enterAnnotationCreationMode(); +``` + +### Exiting Annotation Creation Mode + +```dart +// Exit the current annotation creation mode +await controller.exitAnnotationCreationMode(); +``` + +## Available Annotation Tools + +The SDK provides annotation tools through the `AnnotationTool` enum. See the [AnnotationTool API documentation](https://pub.dev/documentation/nutrient_flutter/latest/nutrient_flutter/AnnotationTool.html) for the complete list of available tools including drawing, text markup, shapes, measurements, and more. + +## Platform Compatibility + +### iOS + +- **Full support** for all annotation tools +- Special UI dialogs for signature, stamp, and image tools + +### Android + +- **Most tools supported** except: `caret`, `richMedia`, `screen`, `file`, `widget`, `link` +- These unsupported tools will return an error when used + +### Web + +- **All tools have mappings** but some with limitations +- Some tools fall back to similar functionality (e.g., `inkMagic` → regular ink) +- Advanced tools like `richMedia`, `screen` may have limited functionality + +## Complete Example + +Here's a complete example showing how to implement an annotation toolbar: + +```dart +import 'package:flutter/material.dart'; +import 'package:nutrient_flutter/nutrient_flutter.dart'; + +class AnnotationToolbarExample extends StatefulWidget { + final String documentPath; + + const AnnotationToolbarExample({Key? key, required this.documentPath}) + : super(key: key); + + @override + State createState() => _AnnotationToolbarExampleState(); +} + +class _AnnotationToolbarExampleState extends State { + NutrientViewController? _controller; + AnnotationTool? _currentTool; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('PDF Annotation'), + actions: [ + if (_currentTool != null) + IconButton( + icon: const Icon(Icons.close), + onPressed: _exitAnnotationMode, + tooltip: 'Exit annotation mode', + ), + ], + ), + body: NutrientView( + documentPath: widget.documentPath, + configuration: PdfConfiguration( + enableAnnotationEditing: true, + ), + onViewCreated: (controller) { + setState(() { + _controller = controller; + }); + }, + ), + bottomNavigationBar: _buildAnnotationToolbar(), + ); + } + + Widget _buildAnnotationToolbar() { + return Container( + color: Colors.grey[200], + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _buildToolButton(AnnotationTool.inkPen, 'Pen', Icons.brush), + _buildToolButton(AnnotationTool.highlight, 'Highlight', Icons.format_color_fill), + _buildToolButton(AnnotationTool.freeText, 'Text', Icons.text_fields), + _buildToolButton(AnnotationTool.square, 'Square', Icons.crop_square), + _buildToolButton(AnnotationTool.arrow, 'Arrow', Icons.arrow_forward), + _buildToolButton(AnnotationTool.signature, 'Sign', Icons.draw), + _buildToolButton(AnnotationTool.eraser, 'Erase', Icons.clear), + ], + ), + ), + ); + } + + Widget _buildToolButton(AnnotationTool tool, String label, IconData icon) { + final isSelected = _currentTool == tool; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () => _enterAnnotationMode(tool), + icon: Icon(icon), + color: isSelected ? Colors.blue : Colors.black87, + ), + Text( + label, + style: TextStyle( + fontSize: 12, + color: isSelected ? Colors.blue : Colors.black87, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ), + ); + } + + Future _enterAnnotationMode(AnnotationTool tool) async { + try { + final success = await _controller.enterAnnotationCreationMode(tool); + if (success == true) { + setState(() { + _currentTool = tool; + }); + } + } catch (e) { + // Handle error + } + } + + Future _exitAnnotationMode() async { + try { + await _controller.exitAnnotationCreationMode(); + setState(() { + _currentTool = null; + }); + } catch (e) { + // Handle error + } + } +} +``` + +## Summary + +The annotation creation mode API provides a simple way to programmatically activate annotation tools for PDF editing. With support for 35+ annotation types across iOS, Android, and Web platforms, developers can easily add drawing, text markup, shapes, and other annotation capabilities to their Flutter apps. For a complete working example, see `example/lib/nutrient_annotation_creation_mode_example.dart`. diff --git a/documentation/contextual-toolbar-customization.md b/documentation/contextual-toolbar-customization.md new file mode 100644 index 00000000..1e16e90f --- /dev/null +++ b/documentation/contextual-toolbar-customization.md @@ -0,0 +1,143 @@ +# Annotation Menu Customization + +This document describes the annotation contextual menu customization feature in the Nutrient Flutter SDK. + +## Overview + +The annotation menu customization feature allows developers to modify the contextual menu that appears when users select annotations in a PDF document. You can remove or disable default menu actions and control the visibility of the style picker. + +## Core Features + +### 1. Default Menu Actions + +The following default menu actions can be removed or disabled: + +- `AnnotationMenuAction.delete` - Remove annotation +- `AnnotationMenuAction.copy` - Copy annotation +- `AnnotationMenuAction.cut` - Cut annotation +- `AnnotationMenuAction.color` - Color/style picker +- `AnnotationMenuAction.note` - Add/edit note +- `AnnotationMenuAction.undo` - Undo last action +- `AnnotationMenuAction.redo` - Redo last action + +### 2. Menu Configuration Options + +- `itemsToRemove` - List of default actions to completely remove from the menu +- `itemsToDisable` - List of default actions to disable (shown but grayed out) +- `showStylePicker` - Show/hide the style picker (default: true) +- `groupMarkupItems` - Group markup annotation tools together (iOS only, default: true) +- `maxVisibleItems` - Maximum items before creating overflow menu (iOS only) + +## Platform-Specific Implementation + +### iOS Implementation + +**Technology**: Uses native `UIMenu` API with `UIAction` and `UICommand` elements + +**Capabilities**: + +- ✅ **Remove default items** - Can remove most default actions +- ✅ **Disable default items** - Native support for disabled state +- ✅ **Style picker control** - Can show/hide style options +- ✅ **Group markup items** - Can group related annotation tools +- ✅ **Max visible items** - Can suggest maximum items before overflow + +**Limitations**: + +- Some system actions (like copy/paste) may be protected by iOS +- Maximum visible items is a suggestion and may be overridden by iOS UI guidelines + +### Android Implementation + +**Technology**: Uses `ContextualToolbar` and `AnnotationEditingToolbar` APIs + +**Capabilities**: + +- ✅ **Remove default items** - Can remove toolbar items by ID +- ✅ **Disable default items** - Can disable toolbar items +- ✅ **Style picker control** - Can remove style-related items +- ❌ **Group markup items** - Not supported on Android +- ❌ **Max visible items** - Not supported on Android + +**Implementation Details**: + +- Uses `setMenuItemVisibility(View.GONE)` to remove items +- Uses `menuItem.isEnabled = false` to disable items +- Handles both `AnnotationEditingToolbar` and generic `ContextualToolbar` + +## Feature Comparison Table + +| Feature | iOS | Android | Notes | +|---------|-----|---------|-------| +| Remove Default Items | ✅ | ✅ | Both platforms fully supported | +| Disable Default Items | ✅ | ✅ | Both platforms fully supported | +| Style Picker Control | ✅ | ✅ | Both platforms fully supported | +| Group Markup Items | ✅ | ❌ | iOS only | +| Max Visible Items | ✅ | ❌ | iOS only | + +## Example Usage + +```dart +PdfConfiguration( + enableAnnotationEditing: true, + annotationMenuConfiguration: AnnotationMenuConfiguration( + // Remove actions from the menu (both platforms) + itemsToRemove: [ + AnnotationMenuAction.delete, + AnnotationMenuAction.color, + ], + // Disable actions - shown but grayed out (both platforms) + itemsToDisable: [AnnotationMenuAction.copy], + // Control style picker visibility (both platforms) + showStylePicker: true, + // iOS-only options + groupMarkupItems: true, + maxVisibleItems: 5, + ), +) +``` + +### Working Example + +See `example/lib/annotation_menu_example.dart` for a complete implementation. + +## API Reference + +### AnnotationMenuConfiguration + +```dart +class AnnotationMenuConfiguration { + /// List of default menu actions to remove + final List itemsToRemove; + + /// List of default menu actions to disable (grayed out) + final List itemsToDisable; + + /// Whether to show the style picker (default: true) + final bool showStylePicker; + + /// Group markup items together (iOS only, default: true) + final bool groupMarkupItems; + + /// Maximum visible items before overflow (iOS only) + final int? maxVisibleItems; +} +``` + +### AnnotationMenuAction Enum + +```dart +enum AnnotationMenuAction { + delete, // Remove annotation + copy, // Copy annotation + cut, // Cut annotation + color, // Color/style picker + note, // Add/edit note + undo, // Undo last action + redo, // Redo last action +} +``` + +## Summary + +The annotation menu customization feature provides essential control over the contextual menu that appears when users select annotations. Both iOS and Android platforms support the core functionality of removing and disabling default menu items, as well as controlling the style picker visibility. diff --git a/example/PDFs/test.docx b/example/PDFs/test.docx new file mode 100644 index 00000000..0f787479 Binary files /dev/null and b/example/PDFs/test.docx differ diff --git a/example/android/gradle.properties b/example/android/gradle.properties index a5c08dfb..bd1930d5 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4096M org.gradle.configureondemand=false android.useAndroidX=true android.enableJetifier=true diff --git a/example/ios/Podfile b/example/ios/Podfile index 1d520a65..a5100289 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -32,8 +32,8 @@ host_cpu = RbConfig::CONFIG["host_cpu"] target "Runner" do flutter_install_all_ios_pods __dir__ - # PSPDFKit iOS SDK version specified by PSPDFKit Flutter Plugin. Do not remove this line. - # Instant iOS SDK version specified by PSPDFKit Flutter Plugin. Do not remove this line. + pod "PSPDFKit", podspec: "https://customers.pspdfkit.com/pspdfkit-ios/nightly.podspec" + pod "Instant", podspec: "https://customers.pspdfkit.com/instant/nightly.podspec" use_modular_headers! end diff --git a/example/lib/annotation_menu_example.dart b/example/lib/annotation_menu_example.dart new file mode 100644 index 00000000..25162af4 --- /dev/null +++ b/example/lib/annotation_menu_example.dart @@ -0,0 +1,59 @@ +/// +/// Copyright © 2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +import 'package:flutter/material.dart'; + +import 'package:nutrient_flutter/nutrient_flutter.dart'; +import 'utils/platform_utils.dart'; + +/// Example demonstrating annotation contextual menu customization +/// Shows how to remove and disable menu items +class AnnotationMenuExample extends StatelessWidget { + final String documentPath; + + const AnnotationMenuExample({super.key, required this.documentPath}); + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: PlatformUtils.isAndroid(), + resizeToAvoidBottomInset: PlatformUtils.isIOS(), + appBar: AppBar( + title: const Text('Remove & Disable Menu Items'), + ), + body: SafeArea( + top: false, + bottom: false, + child: Container( + padding: PlatformUtils.isAndroid() + ? const EdgeInsets.only(top: kToolbarHeight) + : null, + child: NutrientView( + documentPath: documentPath, + configuration: PdfConfiguration( + // Enable annotation editing + enableAnnotationEditing: true, + + // Configure annotation contextual menu + annotationMenuConfiguration: const AnnotationMenuConfiguration( + // Remove the delete and color actions from default menu + itemsToRemove: [ + AnnotationMenuAction.delete, + AnnotationMenuAction.color + ], + // Disable the copy action (keeps it visible but grayed out) + itemsToDisable: [AnnotationMenuAction.copy], + ), + ), + ), + ), + ), + ); + } +} diff --git a/example/lib/examples.dart b/example/lib/examples.dart index 98bc79eb..c0915b3f 100644 --- a/example/lib/examples.dart +++ b/example/lib/examples.dart @@ -17,6 +17,7 @@ import 'package:nutrient_example/models/nutrient_example_item.dart'; import 'package:nutrient_example/nutrient_ai_assistant_example.dart'; import 'package:nutrient_example/nutrient_web_event_listeners.dart'; import 'package:nutrient_example/toolbar_customization.dart'; +import 'annotation_menu_example.dart'; import 'package:nutrient_flutter/nutrient_flutter.dart'; import 'custom_toolbar_example.dart'; @@ -33,6 +34,7 @@ import 'package:nutrient_example/measurement_tools.dart'; import 'package:nutrient_example/pdf_generation_example.dart'; import 'package:nutrient_example/save_as_example.dart'; import 'package:nutrient_example/nutrient_annotation_flags.dart'; +import 'package:nutrient_example/office_to_pdf_example.dart'; import 'basic_example.dart'; import 'form_example.dart'; @@ -142,6 +144,15 @@ List examples(BuildContext context) => [ 'Programmatically adds and removes annotations using a custom Widget.', onTap: () => annotationProcessingExample(context), ), + NutrientExampleItem( + title: 'Annotation Menu - Remove & Disable', + description: + 'Shows how to remove and disable items in the annotation contextual menu.', + onTap: () async { + await extractAsset(context, _documentPath).then((value) => + goTo(AnnotationMenuExample(documentPath: value.path), context)); + }, + ), NutrientExampleItem( title: 'Import Instant Document JSON', description: @@ -231,7 +242,14 @@ List examples(BuildContext context) => [ onTap: () async { await extractAsset(context, _documentPath).then((value) => goTo( NutrientAiAssistantExample(documentPath: value.path), context)); - }) + }), + if (kIsWeb) + NutrientExampleItem( + title: 'Office Document Conversion', + description: + 'Convert Excel, Word, and PowerPoint documents to PDF format.', + onTap: () => goTo(const OfficeToPdfExample(), context), + ) ]; List globalExamples(BuildContext context) => [ diff --git a/example/lib/main.dart b/example/lib/main.dart index 6637f0f5..2017f0ce 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -35,7 +35,7 @@ void main() { print('Analytics event: $eventName with attributes: $attributes'); } }; - runApp(const MyApp()); + runApp(const SafeArea(child: MyApp())); } class MyApp extends StatelessWidget { diff --git a/example/lib/nutrient_annotation_creation_mode_example.dart b/example/lib/nutrient_annotation_creation_mode_example.dart index 0184b83b..46bda6e7 100644 --- a/example/lib/nutrient_annotation_creation_mode_example.dart +++ b/example/lib/nutrient_annotation_creation_mode_example.dart @@ -61,6 +61,56 @@ class _AnnotationCreationModeExampleWidgetState } Widget _buildToolbar() { + // Top 20 essential annotation tools that work well across all platforms + final toolButtons = [ + // Drawing tools (3) + _buildToolButton(AnnotationTool.inkPen, 'Pen', Icons.brush), + _buildToolButton( + AnnotationTool.inkHighlighter, 'Highlighter', Icons.highlight), + _buildToolButton( + AnnotationTool.eraser, 'Eraser', Icons.auto_fix_normal), + + // Text markup tools (4) + _buildToolButton( + AnnotationTool.highlight, 'Highlight', Icons.format_color_fill), + _buildToolButton( + AnnotationTool.underline, 'Underline', Icons.format_underlined), + _buildToolButton( + AnnotationTool.strikeOut, 'Strike Out', Icons.format_strikethrough), + _buildToolButton( + AnnotationTool.squiggly, 'Squiggly', Icons.waves), + + // Text annotation tools (3) + _buildToolButton( + AnnotationTool.freeText, 'Text', Icons.text_fields), + _buildToolButton( + AnnotationTool.note, 'Note', Icons.note_add), + _buildToolButton( + AnnotationTool.freeTextCallOut, 'Callout', Icons.chat_bubble_outline), + + // Shape tools (4) + _buildToolButton( + AnnotationTool.square, 'Square', Icons.crop_square), + _buildToolButton( + AnnotationTool.circle, 'Circle', Icons.circle_outlined), + _buildToolButton(AnnotationTool.line, 'Line', Icons.show_chart), + _buildToolButton( + AnnotationTool.arrow, 'Arrow', Icons.arrow_forward), + + // Media & interactive tools (3) + _buildToolButton(AnnotationTool.stamp, 'Stamp', Icons.approval), + _buildToolButton(AnnotationTool.image, 'Image', Icons.image), + _buildToolButton(AnnotationTool.signature, 'Signature', Icons.draw), + + // Advanced tools (3) + _buildToolButton( + AnnotationTool.polygon, 'Polygon', Icons.pentagon_outlined), + _buildToolButton( + AnnotationTool.polyline, 'Polyline', Icons.polyline), + _buildToolButton( + AnnotationTool.measurementDistance, 'Measure', Icons.straighten), + ]; + return Container( color: Colors.grey[200], padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -68,25 +118,7 @@ class _AnnotationCreationModeExampleWidgetState scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildToolButton(AnnotationTool.inkPen, 'Ink Pen', Icons.brush), - _buildToolButton( - AnnotationTool.inkHighlighter, 'Highlighter', Icons.highlight), - _buildToolButton( - AnnotationTool.inkMagic, 'Magic Ink', Icons.auto_fix_high), - _buildToolButton( - AnnotationTool.freeText, 'Free Text', Icons.text_fields), - _buildToolButton( - AnnotationTool.square, 'Square', Icons.crop_square), - _buildToolButton( - AnnotationTool.circle, 'Circle', Icons.circle_outlined), - _buildToolButton(AnnotationTool.line, 'Line', Icons.show_chart), - _buildToolButton( - AnnotationTool.arrow, 'Arrow', Icons.arrow_forward), - _buildToolButton(AnnotationTool.signature, 'Signature', Icons.draw), - _buildToolButton( - AnnotationTool.eraser, 'Eraser', Icons.auto_fix_normal), - ], + children: toolButtons, ), ), ); diff --git a/example/lib/office_conversion_settings_example.dart b/example/lib/office_conversion_settings_example.dart new file mode 100644 index 00000000..bf3344df --- /dev/null +++ b/example/lib/office_conversion_settings_example.dart @@ -0,0 +1,99 @@ +/// +/// Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:nutrient_flutter/nutrient_flutter.dart'; + +/// Example demonstrating how to configure Office document conversion settings +/// for Excel spreadsheets when opening them in Nutrient Web. +class OfficeConversionSettingsExample extends StatefulWidget { + final String documentPath; + + const OfficeConversionSettingsExample({ + super.key, + required this.documentPath, + }); + + @override + State createState() => + _OfficeConversionSettingsExampleState(); +} + +class _OfficeConversionSettingsExampleState + extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Office Conversion Settings'), + ), + body: Column( + children: [ + if (kIsWeb) + Container( + padding: const EdgeInsets.all(16), + color: Colors.blue.shade50, + child: const Text( + 'This example demonstrates Office document conversion settings for spreadsheets. ' + 'The settings control the maximum dimensions of content rendered per sheet.', + style: TextStyle(fontSize: 14), + ), + ), + Expanded( + child: kIsWeb + ? NutrientView( + documentPath: widget.documentPath, + configuration: PdfConfiguration( + webConfiguration: PdfWebConfiguration( + // Configure Office conversion settings for spreadsheets + officeConversionSettings: const OfficeConversionSettings( + // Maximum height in millimeters per sheet (A4 height is 297mm) + spreadsheetMaximumContentHeightPerSheet: 500, + // Maximum width in millimeters per sheet (A4 width is 210mm) + spreadsheetMaximumContentWidthPerSheet: 300, + ), + // Other web configuration options + allowPrinting: true, + showAnnotations: true, + interactionMode: NutrientWebInteractionMode.pan, + ), + ), + ) + : const Center( + child: Text( + 'Office conversion settings are only available on the web platform.', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ); + } +} + +/// Example usage in a simple app +class OfficeConversionApp extends StatelessWidget { + const OfficeConversionApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Office Conversion Settings', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const OfficeConversionSettingsExample( + // This would be an Excel file (.xlsx) or other Office document + documentPath: 'assets/sample_spreadsheet.xlsx', + ), + ); + } +} diff --git a/example/lib/office_to_pdf_example.dart b/example/lib/office_to_pdf_example.dart new file mode 100644 index 00000000..190395fd --- /dev/null +++ b/example/lib/office_to_pdf_example.dart @@ -0,0 +1,135 @@ +/// +/// Copyright © 2023-2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +import 'package:flutter/material.dart'; +import 'package:nutrient_flutter/nutrient_flutter.dart'; + +class OfficeToPdfExample extends StatefulWidget { + const OfficeToPdfExample({super.key}); + + @override + State createState() => _OfficeToPdfExampleState(); +} + +class _OfficeToPdfExampleState extends State { + static const String _excelDocumentPath = 'PDFs/test.docx'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Office to PDF Conversion'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Card( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Office Document Support', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Text( + 'Nutrient Web SDK can load and display Office documents (Excel, Word, PowerPoint) directly. ' + 'The documents are converted to PDF format in the browser for viewing and annotation.', + ), + SizedBox(height: 16), + Text( + 'This example loads an Excel spreadsheet (test.xlsx) which will be ' + 'automatically displayed as a PDF in the viewer.', + style: TextStyle(fontStyle: FontStyle.italic), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: () => _openOfficeDocument(context), + icon: const Icon(Icons.table_chart), + label: const Text('Open Excel Document'), + ), + const SizedBox(height: 16), + const Card( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Features:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + '• Supports Excel (.xlsx), Word (.docx), and PowerPoint (.pptx)\n' + '• Converts documents to PDF in the browser\n' + '• No server-side processing required\n' + '• Can export the converted document as PDF\n' + '• Supports annotations on converted documents', + ), + SizedBox(height: 16), + Text( + 'Note: Office document support is only available on Web platform for now', + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.orange, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Future _openOfficeDocument(BuildContext context) async { + try { + // Configure the viewer + final configuration = PdfConfiguration( + documentLabelEnabled: false, + scrollDirection: ScrollDirection.vertical, + ); + + // Open the Office document + // On Web, Nutrient will load the Office file and display it as PDF + await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => NutrientView( + documentPath: _excelDocumentPath, + configuration: configuration, + ), + ), + ); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to open Office document: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } +} diff --git a/example/lib/toolbar_customization.dart b/example/lib/toolbar_customization.dart index 1e037799..6b2f6d1e 100644 --- a/example/lib/toolbar_customization.dart +++ b/example/lib/toolbar_customization.dart @@ -59,6 +59,18 @@ class ToolbarCustomization extends StatelessWidget { AnnotationToolbarItem.underline ]), ], + // Configure annotation contextual menu on iOS and Android + annotationMenuConfiguration: + const AnnotationMenuConfiguration( + // Remove the delete action from default menu + itemsToRemove: [AnnotationMenuAction.delete], + // Show style picker for color selection + showStylePicker: true, + // Group markup items together + groupMarkupItems: true, + // Limit to 5 visible items before creating overflow menu + maxVisibleItems: 5, + ), webConfiguration: PdfWebConfiguration( toolbarItems: [ ...defaultWebToolbarItems?.reversed ?? [], diff --git a/ios/Classes/AnnotationMenuHelper.swift b/ios/Classes/AnnotationMenuHelper.swift new file mode 100644 index 00000000..6df67057 --- /dev/null +++ b/ios/Classes/AnnotationMenuHelper.swift @@ -0,0 +1,274 @@ +// +// Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. +// +// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +// This notice may not be removed from this file. +// + +import Foundation +import PSPDFKit +import PSPDFKitUI + +/// Helper class for managing custom annotation contextual menus in PSPDFKit +@objc class AnnotationMenuHelper: NSObject { + + /// Configuration data for annotation menus + private static var menuConfiguration: AnnotationMenuConfigurationData? + + + /// Currently selected annotations for menu context + private static var selectedAnnotations: [Annotation] = [] + + /// Maps AnnotationMenuAction enum values to PSPDFKit action identifier patterns (static version for external access) + static func getActionIdentifiersStatic(for action: AnnotationMenuAction) -> [String] { + return getActionIdentifiers(for: action) + } + + /// Maps AnnotationMenuAction enum values to PSPDFKit action identifier patterns and title patterns + private static func getActionIdentifiers(for action: AnnotationMenuAction) -> [String] { + switch action { + case .delete: + // PSPDFKit and iOS system delete action identifiers and titles + return ["delete", "remove", "trash", "Delete", "Remove"] + case .copy: + return ["copy", "duplicate", "Copy", "Duplicate"] + case .cut: + return ["cut", "Cut"] + case .color: + return ["color", "style", "picker", "inspector", "Color", "Style", "Picker", "Inspector", "Appearance"] + case .note: + return ["note", "comment", "Note", "Comment"] + case .undo: + return ["undo", "Undo"] + case .redo: + return ["redo", "Redo"] + default: + return [] + } + } + + /// Sets up annotation menu configuration (used during initialization) + /// - Parameters: + /// - configuration: The annotation menu configuration from Flutter + static func setupAnnotationMenu(configuration: AnnotationMenuConfigurationData?) { + self.menuConfiguration = configuration + + // Configuration setup complete + } + + /// Updates the annotation menu configuration dynamically + /// - Parameter configuration: The new annotation menu configuration + static func updateConfiguration(configuration: AnnotationMenuConfigurationData?) { + self.menuConfiguration = configuration + + // Configuration updated + } + + /// Updates the currently selected annotations for menu context + /// - Parameter annotations: The currently selected annotations + static func updateSelectedAnnotations(_ annotations: [Annotation]) { + self.selectedAnnotations = annotations + } + + /// Creates a custom contextual menu for the selected annotation + /// - Parameters: + /// - annotation: The annotation to create a menu for + /// - defaultActions: The default PSPDFKit menu actions + /// - Returns: A configured UIMenu with custom and default items + @available(iOS 13.0, *) + static func createContextualMenu(for annotation: Annotation, defaultActions: [UIMenuElement]) -> UIMenu? { + // Check if we have a configuration + guard let config = menuConfiguration else { + return UIMenu(title: "", children: defaultActions) + } + + let filteredActions = filterMenuElements(defaultActions, config: config) + + return UIMenu(title: "", children: filteredActions) + } + + /// Recursively filters menu elements based on the configuration + /// - Parameters: + /// - elements: The menu elements to filter + /// - config: The annotation menu configuration + /// - Returns: Filtered array of menu elements + @available(iOS 13.0, *) + private static func filterMenuElements(_ elements: [UIMenuElement], config: AnnotationMenuConfigurationData) -> [UIMenuElement] { + return elements.compactMap { element -> UIMenuElement? in + if let action = element as? UIAction { + // Check if this action should be removed + if shouldRemoveAction(action, config: config) { + return nil + } + + // Check if this action should be disabled + if let disabledAction = getDisabledActionIfNeeded(action, config: config) { + return disabledAction + } + + return action + + } else if let menu = element as? UIMenu { + // Recursively filter submenu items + let filteredChildren = filterMenuElements(menu.children, config: config) + if !filteredChildren.isEmpty { + return UIMenu(title: menu.title, image: menu.image, identifier: menu.identifier, + options: menu.options, children: filteredChildren) + } else { + return nil + } + } + + return element + } + } + + /// Determines if an action should be removed based on configuration + /// - Parameters: + /// - action: The UIAction to check + /// - config: The annotation menu configuration + /// - Returns: True if the action should be removed + @available(iOS 13.0, *) + private static func shouldRemoveAction(_ action: UIAction, config: AnnotationMenuConfigurationData) -> Bool { + let title = action.title + let identifier = action.identifier.rawValue + + // Check against items to remove + for itemToRemove in config.itemsToRemove { + if matchesAction(title: title, identifier: identifier, action: itemToRemove) { + return true + } + } + + // Check style picker visibility + if !config.showStylePicker && isStylePickerAction(title: title, identifier: identifier) { + return true + } + + return false + } + + /// Creates a disabled version of an action if it should be disabled + /// - Parameters: + /// - action: The UIAction to check + /// - config: The annotation menu configuration + /// - Returns: A disabled UIAction if the action should be disabled, nil otherwise + @available(iOS 13.0, *) + private static func getDisabledActionIfNeeded(_ action: UIAction, config: AnnotationMenuConfigurationData) -> UIAction? { + let title = action.title + let identifier = action.identifier.rawValue + + // Check against items to disable + for itemToDisable in config.itemsToDisable { + if matchesAction(title: title, identifier: identifier, action: itemToDisable) { + let disabledAction = UIAction(title: action.title, image: action.image) { _ in + // No action for disabled items + } + disabledAction.attributes = .disabled + return disabledAction + } + } + + return nil + } + + /// Checks if a title/identifier matches a specific annotation menu action + /// - Parameters: + /// - title: The action title + /// - identifier: The action identifier + /// - action: The AnnotationMenuAction to match against + /// - Returns: True if the title or identifier matches the action + private static func matchesAction(title: String, identifier: String, action: AnnotationMenuAction) -> Bool { + let patterns = getActionIdentifiers(for: action) + let titleLower = title.lowercased() + let identifierLower = identifier.lowercased() + + for pattern in patterns { + let patternLower = pattern.lowercased() + + // Exact match + if titleLower == patternLower || identifierLower == patternLower { + return true + } + + // Word boundary match for titles (avoid partial matches) + if isWordBoundaryMatch(text: titleLower, pattern: patternLower) { + return true + } + + // Identifier suffix match (e.g., "com.example.delete" matches "delete") + if identifierLower.hasSuffix("." + patternLower) { + return true + } + } + + return false + } + + /// Checks if a pattern matches as a complete word within the text + /// - Parameters: + /// - text: The text to search in + /// - pattern: The pattern to search for + /// - Returns: True if the pattern matches as a complete word + private static func isWordBoundaryMatch(text: String, pattern: String) -> Bool { + // Use NSRegularExpression for proper word boundary matching + do { + let regex = try NSRegularExpression(pattern: "\\b" + NSRegularExpression.escapedPattern(for: pattern) + "\\b", options: .caseInsensitive) + let range = NSRange(location: 0, length: text.utf16.count) + return regex.firstMatch(in: text, options: [], range: range) != nil + } catch { + // Fallback to simple containment if regex fails + return text.contains(pattern) + } + } + + /// Checks if an action is related to the style picker + /// - Parameters: + /// - title: The action title + /// - identifier: The action identifier + /// - Returns: True if the action is style picker related + private static func isStylePickerAction(title: String, identifier: String) -> Bool { + return matchesAction(title: title, identifier: identifier, action: .color) + } + + + + /// Determines if an annotation should show custom contextual menu + /// - Parameter annotation: The annotation to check + /// - Returns: True if custom menu should be shown + static func shouldShowCustomMenu(for annotation: Annotation) -> Bool { + return menuConfiguration != nil + } + + /// Gets the current annotation menu configuration + /// - Returns: The current configuration, if any + static func getCurrentConfiguration() -> AnnotationMenuConfigurationData? { + return menuConfiguration + } + + /// Cleans up annotation menu resources + static func cleanup() { + menuConfiguration = nil + selectedAnnotations.removeAll() + } +} + +// MARK: - Menu Identifiers + +/// Common PSPDFKit menu item identifiers for reference +private enum PSPDFMenuIdentifier { + static let copy = "Copy" + static let delete = "Delete" + static let duplicate = "Duplicate" + static let style = "Style" + static let note = "Note" + static let highlight = "Highlight" + static let underline = "Underline" + static let strikeout = "Strikeout" + static let squiggly = "Squiggly" + static let edit = "Edit" + static let remove = "Remove" + static let inspector = "Inspector" +} \ No newline at end of file diff --git a/ios/Classes/PspdfPlatformView.m b/ios/Classes/PspdfPlatformView.m index 4903b871..0e1e9204 100644 --- a/ios/Classes/PspdfPlatformView.m +++ b/ios/Classes/PspdfPlatformView.m @@ -122,13 +122,23 @@ - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId argum } if (_pdfViewController.configuration.userInterfaceViewMode == PSPDFUserInterfaceViewModeNever) { - // In this mode PDFViewController doesn’t hide the navigation bar on its own to avoid getting stuck. + // In this mode PDFViewController doesn't hide the navigation bar on its own to avoid getting stuck. _navigationController.navigationBarHidden = YES; } [_navigationController setViewControllers:@[_pdfViewController] animated:NO]; __weak id weakSelf = self; + // Handle annotation menu configuration if present (shared for both branches) + NSDictionary *configurationDictionary = [PspdfkitFlutterConverter processConfigurationOptionsDictionaryForPrefix:args[@"configuration"]]; + if (configurationDictionary && (id)configurationDictionary != NSNull.null && configurationDictionary[@"annotationMenuConfiguration"]) { + // Pass the raw dictionary to the Swift implementation to handle parsing + NSDictionary *annotationMenuDict = configurationDictionary[@"annotationMenuConfiguration"]; + if ([annotationMenuDict isKindOfClass:[NSDictionary class]]) { + [_platformViewImpl setAnnotationMenuConfigurationFromDictionary:annotationMenuDict]; + } + } + [_platformViewImpl setViewControllerWithController:_pdfViewController]; [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { @@ -185,6 +195,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [PspdfkitFlutterHelper processMethodCall:call result:result forViewController:self.pdfViewController]; } + # pragma mark - PSPDFViewControllerDelegate - (void)pdfViewControllerDidDismiss:(PSPDFViewController *)pdfController { diff --git a/ios/Classes/PspdfkitApiImpl.swift b/ios/Classes/PspdfkitApiImpl.swift index 82345eca..164ae27d 100644 --- a/ios/Classes/PspdfkitApiImpl.swift +++ b/ios/Classes/PspdfkitApiImpl.swift @@ -593,6 +593,17 @@ public class PspdfkitApiImpl: NSObject, NutrientApi, PDFViewControllerDelegate, } } + func setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, completion: @escaping (Result) -> Void) { + NSLog("PspdfkitApiImpl: setAnnotationMenuConfiguration called") + + // Store the configuration globally for presentation-based PDFs (using PspdfkitApiImpl) + // Use updateConfiguration to ensure immediate effect + AnnotationMenuHelper.updateConfiguration(configuration: configuration) + + NSLog("PspdfkitApiImpl: Annotation menu configuration updated successfully") + completion(.success(true)) + } + private func setupAnalyticsClient(){ if let messenger { flutterAnalyticsClient = FlutterAnalyticsClient(analyticsEventsCallback: AnalyticsEventsCallback(binaryMessenger: messenger, messageChannelSuffix: PspdfkitApiImpl.messageChannelSuffix)) diff --git a/ios/Classes/PspdfkitPlatformViewImpl.swift b/ios/Classes/PspdfkitPlatformViewImpl.swift index 05c170ae..d2247ba8 100644 --- a/ios/Classes/PspdfkitPlatformViewImpl.swift +++ b/ios/Classes/PspdfkitPlatformViewImpl.swift @@ -8,6 +8,7 @@ // import Foundation +import UIKit import PSPDFKit @objc(PspdfkitPlatformViewImpl) @@ -19,8 +20,10 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV private var viewId: String? = nil; private var eventsHelper: FlutterEventsHelper? = nil; private var customToolbarItems: [[String: Any]] = []; + private var annotationMenuConfiguration: AnnotationMenuConfigurationData? = nil; private var lastReportedPageIndex: Int? = nil; + @objc public func setViewController(controller: PDFViewController){ self.pdfViewController = controller self.pdfViewController?.delegate = self @@ -28,6 +31,9 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV // Set the host view for the annotation toolbar controller controller.annotationToolbarController?.updateHostView(nil, container: nil, viewController: controller) CustomToolbarHelper.setupCustomToolbarItems(for: pdfViewController!, customToolbarItems:customToolbarItems, callbacks: customToolbarCallbacks) + + // Setup annotation menu helper with configuration + AnnotationMenuHelper.setupAnnotationMenu(configuration: annotationMenuConfiguration) } public func pdfViewController(_ pdfController: PDFViewController, didChange document: Document?) { @@ -42,11 +48,17 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV } public func pdfViewController(_ pdfController: PDFViewController, didSelect annotations: [Annotation], on pageView: PDFPageView) { + // Update annotation menu helper with selected annotations + AnnotationMenuHelper.updateSelectedAnnotations(annotations) + // Call the event helper to notify the listeners. eventsHelper?.annotationSelected(annotations: annotations) } public func pdfViewController(_ pdfController: PDFViewController, didDeselect annotations: [Annotation], on pageView: PDFPageView) { + // Clear selected annotations in menu helper + AnnotationMenuHelper.updateSelectedAnnotations([]) + // Call the event helper to notify the listeners. eventsHelper?.annotationDeselected(annotations: annotations) } @@ -360,10 +372,6 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV pdfViewController.annotationStateManager.toggleState(toolWithVariant.annotationTool, variant: toolWithVariant.variant) completion(.success(true)) } - - pdfViewController.annotationStateManager.toggleState(toolWithVariant.annotationTool, variant: toolWithVariant.variant) - // Ensure the annotation toolbar is visible - completion(.success(true)) } else { // Default to ink pen if the tool is not supported let defaultTool = AnnotationToolWithVariant(annotationTool: .ink, variant: nil) @@ -407,6 +415,34 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV } } + // MARK: - Annotation Menu Delegate Methods + + + /// Provides custom contextual menu for annotations (iOS 13+) + @available(iOS 13.0, *) + public func pdfViewController(_ pdfController: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu { + + // If no annotations are selected, return the suggested menu + guard !annotations.isEmpty, let firstAnnotation = annotations.first else { + return suggestedMenu + } + + // Apply static configuration if available + + if let configuration = self.annotationMenuConfiguration { + AnnotationMenuHelper.updateConfiguration(configuration: configuration) + AnnotationMenuHelper.updateSelectedAnnotations(annotations) + + // Create custom menu with the static configuration + if let customMenu = AnnotationMenuHelper.createContextualMenu(for: firstAnnotation, defaultActions: suggestedMenu.children) { + return customMenu + } + } + + // Return the default suggested menu if no custom menu is configured + return suggestedMenu + } + public func pdfViewController(_ pdfController: PDFViewController, shouldShow controller: UIViewController, options: [String: Any]? = nil, animated: Bool) -> Bool { let stampController = PSPDFChildViewControllerForClass(controller, StampViewController.self) as? StampViewController // Check if custom default stamps are configured and disable date stamps only if they are @@ -429,6 +465,78 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV } } + /// Updates the annotation menu configuration (Pigeon API method) + /// - Parameters: + /// - configuration: The new annotation menu configuration + /// - completion: Completion callback with success/failure result + func setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, completion: @escaping (Result) -> Void) { + do { + NSLog("PspdfkitPlatformViewImpl: setAnnotationMenuConfiguration called") + + // Update the stored configuration - this will be applied when the menu is actually shown + self.annotationMenuConfiguration = configuration + + // Immediately update the annotation menu helper with the new configuration + // This ensures that any currently visible menus or immediate menu requests use the new config + AnnotationMenuHelper.updateConfiguration(configuration: configuration) + + NSLog("PspdfkitPlatformViewImpl: Annotation menu configuration updated successfully") + + // Return success + completion(.success(true)) + } catch { + NSLog("PspdfkitPlatformViewImpl: Error updating annotation menu configuration: \(error)") + completion(.failure(error)) + } + } + + /// Updates the annotation menu configuration (internal method) + /// - Parameter configuration: The new annotation menu configuration + func setAnnotationMenuConfiguration(_ configuration: AnnotationMenuConfigurationData?) { + self.annotationMenuConfiguration = configuration + + // Update the annotation menu helper + AnnotationMenuHelper.setupAnnotationMenu(configuration: configuration) + } + + /// Updates the annotation menu configuration from a dictionary (called from Objective-C) + /// - Parameter dictionary: The dictionary containing annotation menu configuration + @objc public func setAnnotationMenuConfigurationFromDictionary(_ dictionary: [String: Any]) { + do { + // Convert dictionary to AnnotationMenuConfigurationData + let configuration = try parseAnnotationMenuConfiguration(from: dictionary) + setAnnotationMenuConfiguration(configuration) + } catch { + print("Warning: Failed to parse annotation menu configuration: \(error)") + } + } + + /// Parses annotation menu configuration from a dictionary + private func parseAnnotationMenuConfiguration(from dictionary: [String: Any]) throws -> AnnotationMenuConfigurationData { + let itemsToRemoveArray = dictionary["itemsToRemove"] as? [Int] ?? [] + let itemsToDisableArray = dictionary["itemsToDisable"] as? [Int] ?? [] + let showStylePicker = dictionary["showStylePicker"] as? Bool ?? true + let groupMarkupItems = dictionary["groupMarkupItems"] as? Bool ?? false + let maxVisibleItems = dictionary["maxVisibleItems"] as? Int64 + + // Convert enum indices to AnnotationMenuAction + let itemsToRemove: [AnnotationMenuAction] = itemsToRemoveArray.compactMap { index in + return AnnotationMenuAction(rawValue: index) + } + + let itemsToDisable: [AnnotationMenuAction] = itemsToDisableArray.compactMap { index in + return AnnotationMenuAction(rawValue: index) + } + + return AnnotationMenuConfigurationData( + itemsToRemove: itemsToRemove, + itemsToDisable: itemsToDisable, + showStylePicker: showStylePicker, + groupMarkupItems: groupMarkupItems, + maxVisibleItems: maxVisibleItems + ) + } + @objc public func register( binaryMessenger: FlutterBinaryMessenger, viewId: String, customToolbarItems: [[String: Any]]){ self.viewId = viewId pspdfkitWidgetCallbacks = NutrientViewCallbacks(binaryMessenger: binaryMessenger, messageChannelSuffix: "widget.callbacks.\(viewId)") @@ -442,13 +550,19 @@ public class PspdfkitPlatformViewImpl: NSObject, NutrientViewControllerApi, PDFV customToolbarCallbacks = CustomToolbarCallbacks(binaryMessenger: binaryMessenger, messageChannelSuffix: "customToolbar.callbacks.\(viewId)") self.customToolbarItems = customToolbarItems + } @objc public func unRegister(binaryMessenger: FlutterBinaryMessenger){ NotificationCenter.default.removeObserver(self) pspdfkitWidgetCallbacks = nil customToolbarCallbacks = nil + annotationMenuConfiguration = nil lastReportedPageIndex = nil + + // Clean up annotation menu helper + AnnotationMenuHelper.cleanup() + NutrientViewControllerApiSetup.setUp(binaryMessenger: binaryMessenger, api: nil, messageChannelSuffix: viewId ?? "") if eventsHelper != nil { eventsHelper = nil diff --git a/ios/Classes/api/NutrientApi.g.swift b/ios/Classes/api/NutrientApi.g.swift index 2231990a..78949f02 100644 --- a/ios/Classes/api/NutrientApi.g.swift +++ b/ios/Classes/api/NutrientApi.g.swift @@ -234,6 +234,42 @@ enum NutrientEvent: Int { case textSelectionChanged = 8 } +/// Enumeration of default annotation menu actions that can be removed or disabled. +/// +/// **Platform Support:** +/// - All actions can be removed or disabled on both iOS and Android +/// - Some system actions (copy/paste) may be harder to remove on iOS due to system restrictions +enum AnnotationMenuAction: Int { + /// Delete action - removes the annotation + /// - iOS: Part of UIMenu system actions + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_delete + case delete = 0 + /// Copy action - copies the annotation + /// - iOS: System copy action (may be harder to remove) + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_copy + case copy = 1 + /// Cut action - cuts the annotation to clipboard + /// - iOS: System cut action + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_cut + case cut = 2 + /// Color action - opens annotation color picker/inspector + /// - iOS: Style picker in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_picker + case color = 3 + /// Note action - opens annotation note editor + /// - iOS: Note action in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_annotation_note + case note = 4 + /// Undo action - undoes the last action + /// - iOS: Undo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_undo + case undo = 5 + /// Redo action - redoes the previously undone action + /// - iOS: Redo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_redo + case redo = 6 +} + /// Generated class from Pigeon that represents data sent in messages. struct PdfRect { var x: Double @@ -484,6 +520,102 @@ struct PointF { } } +/// Configuration data for annotation contextual menu +/// +/// This class defines how annotation menus should be configured +/// when displayed to users. It supports removing actions, disabling actions, +/// and controlling visual presentation options. +/// +/// **Usage Patterns**: +/// - **Static Configuration**: Set once via [NutrientViewController.setAnnotationMenuConfiguration] +/// +/// **Platform Compatibility**: +/// - [itemsToRemove]: Supported on Android, iOS, and Web +/// - [itemsToDisable]: Supported on Android, iOS, and Web +/// - [showStylePicker]: Supported on Android and iOS +/// - [groupMarkupItems]: iOS only (ignored on other platforms) +/// - [maxVisibleItems]: Platform-dependent behavior +/// +/// Generated class from Pigeon that represents data sent in messages. +struct AnnotationMenuConfigurationData { + /// List of default annotation menu actions to remove completely from the menu. + /// + /// These actions will not appear in the contextual menu at all. + /// Use this when you want to completely hide certain functionality. + /// + /// **Example**: Remove delete action for read-only annotations + /// ```dart + /// itemsToRemove: [AnnotationMenuAction.delete] + /// ``` + var itemsToRemove: [AnnotationMenuAction] + /// List of default annotation menu actions to disable (show as grayed out). + /// + /// These actions will appear in the menu but will be non-interactive. + /// Use this when you want to show functionality exists but is temporarily unavailable. + /// + /// **Example**: Disable copy action for certain annotation types + /// ```dart + /// itemsToDisable: [AnnotationMenuAction.copy] + /// ``` + var itemsToDisable: [AnnotationMenuAction] + /// Whether to show the platform's default style picker in the annotation menu. + /// + /// When true, users can access color, thickness, and other style options + /// directly from the annotation menu. + /// + /// **Platform Behavior**: + /// - **iOS**: Shows style picker as part of UIMenu + /// - **Android**: Shows annotation inspector/style picker + /// - **Web**: Shows color picker and basic style options + var showStylePicker: Bool + /// Whether to group markup annotation actions together in the menu. + /// + /// When true, related markup actions (highlight, underline, etc.) are + /// visually grouped in the menu for better organization. + /// + /// **Platform Support**: iOS only (ignored on Android and Web) + var groupMarkupItems: Bool + /// Maximum number of actions to show directly in the menu before creating overflow. + /// + /// When the number of available actions exceeds this limit, the platform + /// may create a submenu or overflow menu to accommodate additional actions. + /// + /// **Platform Behavior**: + /// - **iOS**: Respects platform UI guidelines for menu length + /// - **Android**: Limited by toolbar space and screen size + /// - **Web**: Creates scrollable or paginated menu as needed + /// + /// **Note**: If null, the platform default behavior is used. + var maxVisibleItems: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> AnnotationMenuConfigurationData? { + let itemsToRemove = pigeonVar_list[0] as! [AnnotationMenuAction] + let itemsToDisable = pigeonVar_list[1] as! [AnnotationMenuAction] + let showStylePicker = pigeonVar_list[2] as! Bool + let groupMarkupItems = pigeonVar_list[3] as! Bool + let maxVisibleItems: Int64? = nilOrValue(pigeonVar_list[4]) + + return AnnotationMenuConfigurationData( + itemsToRemove: itemsToRemove, + itemsToDisable: itemsToDisable, + showStylePicker: showStylePicker, + groupMarkupItems: groupMarkupItems, + maxVisibleItems: maxVisibleItems + ) + } + func toList() -> [Any?] { + return [ + itemsToRemove, + itemsToDisable, + showStylePicker, + groupMarkupItems, + maxVisibleItems, + ] + } +} + private class NutrientApiPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -542,17 +674,25 @@ private class NutrientApiPigeonCodecReader: FlutterStandardReader { } return nil case 138: - return PdfRect.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return AnnotationMenuAction(rawValue: enumResultAsInt) + } + return nil case 139: - return PageInfo.fromList(self.readValue() as! [Any?]) + return PdfRect.fromList(self.readValue() as! [Any?]) case 140: - return DocumentSaveOptions.fromList(self.readValue() as! [Any?]) + return PageInfo.fromList(self.readValue() as! [Any?]) case 141: - return PdfFormOption.fromList(self.readValue() as! [Any?]) + return DocumentSaveOptions.fromList(self.readValue() as! [Any?]) case 142: - return FormFieldData.fromList(self.readValue() as! [Any?]) + return PdfFormOption.fromList(self.readValue() as! [Any?]) case 143: + return FormFieldData.fromList(self.readValue() as! [Any?]) + case 144: return PointF.fromList(self.readValue() as! [Any?]) + case 145: + return AnnotationMenuConfigurationData.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -588,23 +728,29 @@ private class NutrientApiPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? NutrientEvent { super.writeByte(137) super.writeValue(value.rawValue) - } else if let value = value as? PdfRect { + } else if let value = value as? AnnotationMenuAction { super.writeByte(138) + super.writeValue(value.rawValue) + } else if let value = value as? PdfRect { + super.writeByte(139) super.writeValue(value.toList()) } else if let value = value as? PageInfo { - super.writeByte(139) + super.writeByte(140) super.writeValue(value.toList()) } else if let value = value as? DocumentSaveOptions { - super.writeByte(140) + super.writeByte(141) super.writeValue(value.toList()) } else if let value = value as? PdfFormOption { - super.writeByte(141) + super.writeByte(142) super.writeValue(value.toList()) } else if let value = value as? FormFieldData { - super.writeByte(142) + super.writeByte(143) super.writeValue(value.toList()) } else if let value = value as? PointF { - super.writeByte(143) + super.writeByte(144) + super.writeValue(value.toList()) + } else if let value = value as? AnnotationMenuConfigurationData { + super.writeByte(145) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -673,6 +819,12 @@ protocol NutrientApi { func generatePdfFromHtmlUri(htmlUri: String, outPutFile: String, options: [String: Any]?, completion: @escaping (Result) -> Void) /// Configure Nutrient Analytics events. func enableAnalyticsEvents(enable: Bool) throws + /// Sets the annotation menu configuration for the global presenter. + /// This configuration applies to all annotation menus in presented documents. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + func setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -1228,6 +1380,28 @@ class NutrientApiSetup { } else { enableAnalyticsEventsChannel.setMessageHandler(nil) } + /// Sets the annotation menu configuration for the global presenter. + /// This configuration applies to all annotation menus in presented documents. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + let setAnnotationMenuConfigurationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAnnotationMenuConfiguration\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setAnnotationMenuConfigurationChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let configurationArg = args[0] as! AnnotationMenuConfigurationData + api.setAnnotationMenuConfiguration(configuration: configurationArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setAnnotationMenuConfigurationChannel.setMessageHandler(nil) + } } } /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. @@ -1560,6 +1734,12 @@ protocol NutrientViewControllerApi { /// Returns a [Future] that completes with a boolean indicating whether /// exiting annotation creation mode was successful. func exitAnnotationCreationMode(completion: @escaping (Result) -> Void) + /// Sets the annotation menu configuration for the current view controller. + /// This configuration applies only to annotation menus in the current document view. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + func setAnnotationMenuConfiguration(configuration: AnnotationMenuConfigurationData, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -1945,6 +2125,28 @@ class NutrientViewControllerApiSetup { } else { exitAnnotationCreationModeChannel.setMessageHandler(nil) } + /// Sets the annotation menu configuration for the current view controller. + /// This configuration applies only to annotation menus in the current document view. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + let setAnnotationMenuConfigurationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setAnnotationMenuConfiguration\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setAnnotationMenuConfigurationChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let configurationArg = args[0] as! AnnotationMenuConfigurationData + api.setAnnotationMenuConfiguration(configuration: configurationArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setAnnotationMenuConfigurationChannel.setMessageHandler(nil) + } } } /// Generated protocol from Pigeon that represents a handler of messages from Flutter. diff --git a/ios/Classes/helpers/AnnotationHelper.swift b/ios/Classes/helpers/AnnotationHelper.swift index a22cc612..1ae785e7 100644 --- a/ios/Classes/helpers/AnnotationHelper.swift +++ b/ios/Classes/helpers/AnnotationHelper.swift @@ -106,9 +106,21 @@ class AnnotationHelper { return AnnotationToolWithVariant(annotationTool: .polyLine, variant: PSPDFKit.Annotation.Variant.perimeterMeasurement) case AnnotationTool.measurementDistance: return AnnotationToolWithVariant(annotationTool: .line, variant: PSPDFKit.Annotation.Variant.distanceMeasurement) - // Some Flutter tools don't have direct iOS equivalents - case AnnotationTool.caret, AnnotationTool.richMedia, AnnotationTool.screen,AnnotationTool.file, AnnotationTool.widget, AnnotationTool.stampImage,AnnotationTool.link: - return nil + // Additional supported tools + case AnnotationTool.caret: + return AnnotationToolWithVariant(annotationTool: .caret) + case AnnotationTool.richMedia: + return AnnotationToolWithVariant(annotationTool: .richMedia) + case AnnotationTool.screen: + return AnnotationToolWithVariant(annotationTool: .screen) + case AnnotationTool.file: + return AnnotationToolWithVariant(annotationTool: .file) + case AnnotationTool.widget: + return AnnotationToolWithVariant(annotationTool: .widget) + case AnnotationTool.stampImage: + return AnnotationToolWithVariant(annotationTool: .stamp) + case AnnotationTool.link: + return AnnotationToolWithVariant(annotationTool: .link) } } diff --git a/ios/nutrient_flutter.podspec b/ios/nutrient_flutter.podspec index 0785f64f..4989cc4d 100644 --- a/ios/nutrient_flutter.podspec +++ b/ios/nutrient_flutter.podspec @@ -18,8 +18,8 @@ Pod::Spec.new do |s| s.source_files = "Classes/**/*.{h,m,swift}" s.public_header_files = "Classes/**/*.h" s.dependency("Flutter") - s.dependency("PSPDFKit", "14.10.0") - s.dependency("Instant", "14.10.0") + s.dependency("PSPDFKit") + s.dependency("Instant") s.swift_version = "5.0" s.platform = :ios, "16.0" s.version = "5.0.1" diff --git a/lib/nutrient_flutter.dart b/lib/nutrient_flutter.dart index 8d4f3223..e30ad37a 100644 --- a/lib/nutrient_flutter.dart +++ b/lib/nutrient_flutter.dart @@ -18,6 +18,7 @@ export 'src/widgets/nutrient_view.dart' // All other exports. export 'src/pdf_configuration.dart'; export 'src/web/nutrient_web_configuration.dart'; +export 'src/web/office_conversion_settings.dart'; export 'src/types.dart'; export 'src/web/models/models.dart'; export 'src/configuration_options.dart'; @@ -32,6 +33,7 @@ export 'src/api/nutrient_api.g.dart'; export 'src/annotation_preset_configurations.dart'; export 'src/annotations/annotations.dart'; +export 'src/annotations/annotation_menu_configuration.dart'; export 'src/web/models/nutrient_web_events.dart'; export 'src/nutrient.dart'; diff --git a/lib/src/annotations/annotation_extensions.dart b/lib/src/annotations/annotation_extensions.dart index 36421481..d3d85d59 100644 --- a/lib/src/annotations/annotation_extensions.dart +++ b/lib/src/annotations/annotation_extensions.dart @@ -75,6 +75,26 @@ extension AnnotationToolWebExtension on AnnotationTool { case AnnotationTool.link: return 'link'; + // Widget annotation tools + case AnnotationTool.widget: + return 'widget'; + case AnnotationTool.file: + return 'file'; + + // Other annotation tools + case AnnotationTool.caret: + return 'caret'; + case AnnotationTool.redaction: + return 'redaction'; + case AnnotationTool.sound: + return 'sound'; + case AnnotationTool.richMedia: + return 'richMedia'; + case AnnotationTool.screen: + return 'screen'; + case AnnotationTool.cloudy: + return 'cloudy'; + // Measurement tools case AnnotationTool.measurementDistance: return 'measurementDistance'; @@ -150,6 +170,26 @@ extension AnnotationToolWebExtension on AnnotationTool { case AnnotationTool.link: return 'LINK'; + // Widget annotation tools + case AnnotationTool.widget: + return 'FORM_CREATOR'; + case AnnotationTool.file: + return 'DOCUMENT_EDITOR'; + + // Other annotation tools + case AnnotationTool.caret: + return 'COMMENT_MARKER'; + case AnnotationTool.redaction: + return 'REDACT_TEXT_HIGHLIGHTER'; + case AnnotationTool.sound: + return 'NOTE'; // Sound annotations use note interaction mode + case AnnotationTool.richMedia: + return 'NOTE'; // Rich media annotations use note interaction mode + case AnnotationTool.screen: + return 'NOTE'; // Screen annotations use note interaction mode + case AnnotationTool.cloudy: + return 'SHAPE_RECTANGLE'; // Cloudy is a border style, not a separate interaction mode + // Measurement tools case AnnotationTool.measurementDistance: return 'DISTANCE'; diff --git a/lib/src/annotations/annotation_menu_configuration.dart b/lib/src/annotations/annotation_menu_configuration.dart new file mode 100644 index 00000000..94c8c22a --- /dev/null +++ b/lib/src/annotations/annotation_menu_configuration.dart @@ -0,0 +1,65 @@ +/// +/// Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +import 'package:nutrient_flutter/src/api/nutrient_api.g.dart'; + +/// Configuration for annotation contextual menu customization. +class AnnotationMenuConfiguration { + + /// Default menu actions to remove from the contextual menu. + final List itemsToRemove; + + /// Default menu actions to disable (show as grayed out). + final List itemsToDisable; + + /// Whether to show the default style picker. Defaults to true. + final bool showStylePicker; + + /// Whether to group markup annotations (highlight, underline, etc.) together. + /// + /// **Platform Support:** iOS only + final bool groupMarkupItems; + + /// Maximum number of items to show before creating an overflow menu. + /// + /// **Platform Support:** + /// - iOS: ✅ Respects platform UI guidelines + /// - Android: ⚠️ Limited by toolbar constraints + final int? maxVisibleItems; + + const AnnotationMenuConfiguration({ + this.itemsToRemove = const [], + this.itemsToDisable = const [], + this.showStylePicker = true, + this.groupMarkupItems = true, + this.maxVisibleItems, + }); + + + Map toMap() { + return { + 'itemsToRemove': itemsToRemove.map((action) => action.index).toList(), + 'itemsToDisable': itemsToDisable.map((action) => action.index).toList(), + 'showStylePicker': showStylePicker, + 'groupMarkupItems': groupMarkupItems, + 'maxVisibleItems': maxVisibleItems, + }; + } + + /// Converts to Pigeon data object. + AnnotationMenuConfigurationData toPigeonData() { + return AnnotationMenuConfigurationData( + itemsToRemove: itemsToRemove, + itemsToDisable: itemsToDisable, + showStylePicker: showStylePicker, + groupMarkupItems: groupMarkupItems, + maxVisibleItems: maxVisibleItems, + ); + } +} diff --git a/lib/src/api/nutrient_api.g.dart b/lib/src/api/nutrient_api.g.dart index be7ebc4a..8be0b590 100644 --- a/lib/src/api/nutrient_api.g.dart +++ b/lib/src/api/nutrient_api.g.dart @@ -21,8 +21,7 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -132,25 +131,18 @@ enum AnnotationProcessingMode { enum DocumentPermissions { /// Allow printing of document. printing, - /// Modify the contents of the document. modification, - /// Copy text and images from the document. extract, - /// Add or modify text annotations, fill in interactive form fields. annotationsAndForms, - /// Fill in existing interactive form fields (including signature fields). fillForms, - /// Extract text and images from the document. extractAccessibility, - /// Assemble the document (insert, rotate, or delete pages and create document outline items or thumbnail images). assemble, - /// Print high quality. printHighQuality, } @@ -181,32 +173,60 @@ enum PdfFormFieldTypes { enum NutrientEvent { /// Event triggered when annotations are created. annotationsCreated, - /// Event triggered when annotations are pressed. annotationsDeselected, - /// Event triggered when annotations are updated. annotationsUpdated, - /// Event triggered when annotations are deleted. annotationsDeleted, - /// Event triggered when annotations are focused. annotationsSelected, - /// Event triggered when form field values are updated. formFieldValuesUpdated, - /// Event triggered when form fields are loaded. formFieldSelected, - /// Event triggered when form fields are about to be saved. formFieldDeselected, - /// Event triggered when text selection changes. textSelectionChanged, } +/// Enumeration of default annotation menu actions that can be removed or disabled. +/// +/// **Platform Support:** +/// - All actions can be removed or disabled on both iOS and Android +/// - Some system actions (copy/paste) may be harder to remove on iOS due to system restrictions +enum AnnotationMenuAction { + /// Delete action - removes the annotation + /// - iOS: Part of UIMenu system actions + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_delete + delete, + /// Copy action - copies the annotation + /// - iOS: System copy action (may be harder to remove) + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_copy + copy, + /// Cut action - cuts the annotation to clipboard + /// - iOS: System cut action + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_cut + cut, + /// Color action - opens annotation color picker/inspector + /// - iOS: Style picker in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_picker + color, + /// Note action - opens annotation note editor + /// - iOS: Note action in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_annotation_note + note, + /// Undo action - undoes the last action + /// - iOS: Undo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_undo + undo, + /// Redo action - redoes the previously undone action + /// - iOS: Redo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_redo + redo, +} + class PdfRect { PdfRect({ required this.x, @@ -491,6 +511,107 @@ class PointF { } } +/// Configuration data for annotation contextual menu +/// +/// This class defines how annotation menus should be configured +/// when displayed to users. It supports removing actions, disabling actions, +/// and controlling visual presentation options. +/// +/// **Usage Patterns**: +/// - **Static Configuration**: Set once via [NutrientViewController.setAnnotationMenuConfiguration] +/// +/// **Platform Compatibility**: +/// - [itemsToRemove]: Supported on Android, iOS, and Web +/// - [itemsToDisable]: Supported on Android, iOS, and Web +/// - [showStylePicker]: Supported on Android and iOS +/// - [groupMarkupItems]: iOS only (ignored on other platforms) +/// - [maxVisibleItems]: Platform-dependent behavior +class AnnotationMenuConfigurationData { + AnnotationMenuConfigurationData({ + required this.itemsToRemove, + required this.itemsToDisable, + required this.showStylePicker, + required this.groupMarkupItems, + this.maxVisibleItems, + }); + + /// List of default annotation menu actions to remove completely from the menu. + /// + /// These actions will not appear in the contextual menu at all. + /// Use this when you want to completely hide certain functionality. + /// + /// **Example**: Remove delete action for read-only annotations + /// ```dart + /// itemsToRemove: [AnnotationMenuAction.delete] + /// ``` + List itemsToRemove; + + /// List of default annotation menu actions to disable (show as grayed out). + /// + /// These actions will appear in the menu but will be non-interactive. + /// Use this when you want to show functionality exists but is temporarily unavailable. + /// + /// **Example**: Disable copy action for certain annotation types + /// ```dart + /// itemsToDisable: [AnnotationMenuAction.copy] + /// ``` + List itemsToDisable; + + /// Whether to show the platform's default style picker in the annotation menu. + /// + /// When true, users can access color, thickness, and other style options + /// directly from the annotation menu. + /// + /// **Platform Behavior**: + /// - **iOS**: Shows style picker as part of UIMenu + /// - **Android**: Shows annotation inspector/style picker + /// - **Web**: Shows color picker and basic style options + bool showStylePicker; + + /// Whether to group markup annotation actions together in the menu. + /// + /// When true, related markup actions (highlight, underline, etc.) are + /// visually grouped in the menu for better organization. + /// + /// **Platform Support**: iOS only (ignored on Android and Web) + bool groupMarkupItems; + + /// Maximum number of actions to show directly in the menu before creating overflow. + /// + /// When the number of available actions exceeds this limit, the platform + /// may create a submenu or overflow menu to accommodate additional actions. + /// + /// **Platform Behavior**: + /// - **iOS**: Respects platform UI guidelines for menu length + /// - **Android**: Limited by toolbar space and screen size + /// - **Web**: Creates scrollable or paginated menu as needed + /// + /// **Note**: If null, the platform default behavior is used. + int? maxVisibleItems; + + Object encode() { + return [ + itemsToRemove, + itemsToDisable, + showStylePicker, + groupMarkupItems, + maxVisibleItems, + ]; + } + + static AnnotationMenuConfigurationData decode(Object result) { + result as List; + return AnnotationMenuConfigurationData( + itemsToRemove: (result[0] as List?)!.cast(), + itemsToDisable: (result[1] as List?)!.cast(), + showStylePicker: result[2]! as bool, + groupMarkupItems: result[3]! as bool, + maxVisibleItems: result[4] as int?, + ); + } +} + + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -498,51 +619,57 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is AndroidPermissionStatus) { + } else if (value is AndroidPermissionStatus) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is AnnotationType) { + } else if (value is AnnotationType) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is AnnotationTool) { + } else if (value is AnnotationTool) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is AnnotationToolVariant) { + } else if (value is AnnotationToolVariant) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is AnnotationProcessingMode) { + } else if (value is AnnotationProcessingMode) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is DocumentPermissions) { + } else if (value is DocumentPermissions) { buffer.putUint8(134); writeValue(buffer, value.index); - } else if (value is PdfVersion) { + } else if (value is PdfVersion) { buffer.putUint8(135); writeValue(buffer, value.index); - } else if (value is PdfFormFieldTypes) { + } else if (value is PdfFormFieldTypes) { buffer.putUint8(136); writeValue(buffer, value.index); - } else if (value is NutrientEvent) { + } else if (value is NutrientEvent) { buffer.putUint8(137); writeValue(buffer, value.index); - } else if (value is PdfRect) { + } else if (value is AnnotationMenuAction) { buffer.putUint8(138); - writeValue(buffer, value.encode()); - } else if (value is PageInfo) { + writeValue(buffer, value.index); + } else if (value is PdfRect) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is DocumentSaveOptions) { + } else if (value is PageInfo) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is PdfFormOption) { + } else if (value is DocumentSaveOptions) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is FormFieldData) { + } else if (value is PdfFormOption) { buffer.putUint8(142); writeValue(buffer, value.encode()); - } else if (value is PointF) { + } else if (value is FormFieldData) { buffer.putUint8(143); writeValue(buffer, value.encode()); + } else if (value is PointF) { + buffer.putUint8(144); + writeValue(buffer, value.encode()); + } else if (value is AnnotationMenuConfigurationData) { + buffer.putUint8(145); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -551,45 +678,50 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : AndroidPermissionStatus.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; return value == null ? null : AnnotationType.values[value]; - case 131: + case 131: final int? value = readValue(buffer) as int?; return value == null ? null : AnnotationTool.values[value]; - case 132: + case 132: final int? value = readValue(buffer) as int?; return value == null ? null : AnnotationToolVariant.values[value]; - case 133: + case 133: final int? value = readValue(buffer) as int?; return value == null ? null : AnnotationProcessingMode.values[value]; - case 134: + case 134: final int? value = readValue(buffer) as int?; return value == null ? null : DocumentPermissions.values[value]; - case 135: + case 135: final int? value = readValue(buffer) as int?; return value == null ? null : PdfVersion.values[value]; - case 136: + case 136: final int? value = readValue(buffer) as int?; return value == null ? null : PdfFormFieldTypes.values[value]; - case 137: + case 137: final int? value = readValue(buffer) as int?; return value == null ? null : NutrientEvent.values[value]; - case 138: + case 138: + final int? value = readValue(buffer) as int?; + return value == null ? null : AnnotationMenuAction.values[value]; + case 139: return PdfRect.decode(readValue(buffer)!); - case 139: + case 140: return PageInfo.decode(readValue(buffer)!); - case 140: + case 141: return DocumentSaveOptions.decode(readValue(buffer)!); - case 141: + case 142: return PdfFormOption.decode(readValue(buffer)!); - case 142: + case 143: return FormFieldData.decode(readValue(buffer)!); - case 143: + case 144: return PointF.decode(readValue(buffer)!); + case 145: + return AnnotationMenuConfigurationData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -601,11 +733,9 @@ class NutrientApi { /// Constructor for [NutrientApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - NutrientApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + NutrientApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -613,10 +743,8 @@ class NutrientApi { final String pigeonVar_messageChannelSuffix; Future getFrameworkVersion() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getFrameworkVersion$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getFrameworkVersion$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -638,16 +766,13 @@ class NutrientApi { } Future setLicenseKey(String? licenseKey) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setLicenseKey$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setLicenseKey$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([licenseKey]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([licenseKey]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -663,18 +788,14 @@ class NutrientApi { } } - Future setLicenseKeys(String? androidLicenseKey, String? iOSLicenseKey, - String? webLicenseKey) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setLicenseKeys$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setLicenseKeys(String? androidLicenseKey, String? iOSLicenseKey, String? webLicenseKey) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setLicenseKeys$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel - .send([androidLicenseKey, iOSLicenseKey, webLicenseKey]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([androidLicenseKey, iOSLicenseKey, webLicenseKey]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -690,18 +811,14 @@ class NutrientApi { } } - Future present(String document, - {Map? configuration}) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.present$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future present(String document, {Map? configuration}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.present$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([document, configuration]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([document, configuration]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -717,21 +834,14 @@ class NutrientApi { } } - Future presentInstant( - String serverUrl, - String jwt, { - Map? configuration, - }) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.presentInstant$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future presentInstant(String serverUrl, String jwt, {Map? configuration, }) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.presentInstant$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([serverUrl, jwt, configuration]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([serverUrl, jwt, configuration]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -747,18 +857,14 @@ class NutrientApi { } } - Future setFormFieldValue( - String value, String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setFormFieldValue(String value, String fullyQualifiedName) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([value, fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([value, fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -775,16 +881,13 @@ class NutrientApi { } Future getFormFieldValue(String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -801,16 +904,13 @@ class NutrientApi { } Future applyInstantJson(String annotationsJson) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.applyInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.applyInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotationsJson]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotationsJson]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -827,10 +927,8 @@ class NutrientApi { } Future exportInstantJson() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.exportInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.exportInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -852,16 +950,13 @@ class NutrientApi { } Future addAnnotation(String annotation, String? attachment) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.addAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.addAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotation, attachment]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotation, attachment]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -878,16 +973,13 @@ class NutrientApi { } Future removeAnnotation(String annotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.removeAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.removeAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -904,16 +996,13 @@ class NutrientApi { } Future getAnnotations(int pageIndex, String type) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex, type]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex, type]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -930,10 +1019,8 @@ class NutrientApi { } Future getAllUnsavedAnnotations() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -955,16 +1042,13 @@ class NutrientApi { } Future updateAnnotation(String annotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.updateAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.updateAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -980,18 +1064,14 @@ class NutrientApi { } } - Future processAnnotations(AnnotationType type, - AnnotationProcessingMode processingMode, String destinationPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.processAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future processAnnotations(AnnotationType type, AnnotationProcessingMode processingMode, String destinationPath) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.processAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel - .send([type, processingMode, destinationPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([type, processingMode, destinationPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1008,16 +1088,13 @@ class NutrientApi { } Future importXfdf(String xfdfString) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.importXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.importXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfString]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfString]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1034,16 +1111,13 @@ class NutrientApi { } Future exportXfdf(String xfdfPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.exportXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.exportXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1060,10 +1134,8 @@ class NutrientApi { } Future save() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.save$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.save$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1085,16 +1157,13 @@ class NutrientApi { } Future setDelayForSyncingLocalChanges(double delay) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setDelayForSyncingLocalChanges$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setDelayForSyncingLocalChanges$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([delay]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([delay]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1111,16 +1180,13 @@ class NutrientApi { } Future setListenToServerChanges(bool listen) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setListenToServerChanges$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setListenToServerChanges$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([listen]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([listen]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1137,10 +1203,8 @@ class NutrientApi { } Future syncAnnotations() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.syncAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.syncAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1162,10 +1226,8 @@ class NutrientApi { } Future checkAndroidWriteExternalStoragePermission() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.checkAndroidWriteExternalStoragePermission$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.checkAndroidWriteExternalStoragePermission$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1186,12 +1248,9 @@ class NutrientApi { } } - Future - requestAndroidWriteExternalStoragePermission() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.requestAndroidWriteExternalStoragePermission$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future requestAndroidWriteExternalStoragePermission() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.requestAndroidWriteExternalStoragePermission$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1218,10 +1277,8 @@ class NutrientApi { } Future openAndroidSettings() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.openAndroidSettings$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.openAndroidSettings$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1242,18 +1299,14 @@ class NutrientApi { } } - Future setAnnotationPresetConfigurations( - Map configurations) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAnnotationPresetConfigurations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setAnnotationPresetConfigurations(Map configurations) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAnnotationPresetConfigurations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([configurations]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([configurations]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1270,10 +1323,8 @@ class NutrientApi { } Future getTemporaryDirectory() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getTemporaryDirectory$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getTemporaryDirectory$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1300,16 +1351,13 @@ class NutrientApi { } Future setAuthorName(String name) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAuthorName$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAuthorName$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([name]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([name]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1326,10 +1374,8 @@ class NutrientApi { } Future getAuthorName() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAuthorName$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.getAuthorName$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1359,18 +1405,14 @@ class NutrientApi { /// [pages]: [NewPage]s to be added to the PDF. /// [outputPath]: The path to the output file. /// Returns the path to the generated PDF path or null if the input is invalid or if the PDF generation fails. - Future generatePdf( - List> pages, String outputPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future generatePdf(List> pages, String outputPath) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pages, outputPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pages, outputPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1391,18 +1433,14 @@ class NutrientApi { /// [html]: The HTML string to be converted to PDF. /// [outPutFile]: The path to the output file. /// Returns the path to the generated PDF file or null if the input is invalid or if the PDF generation fails. - Future generatePdfFromHtmlString( - String html, String outPutFile, Map? options) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdfFromHtmlString$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future generatePdfFromHtmlString(String html, String outPutFile, Map? options) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdfFromHtmlString$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([html, outPutFile, options]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([html, outPutFile, options]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1418,18 +1456,14 @@ class NutrientApi { } } - Future generatePdfFromHtmlUri( - String htmlUri, String outPutFile, Map? options) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdfFromHtmlUri$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future generatePdfFromHtmlUri(String htmlUri, String outPutFile, Map? options) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.generatePdfFromHtmlUri$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([htmlUri, outPutFile, options]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([htmlUri, outPutFile, options]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1447,16 +1481,13 @@ class NutrientApi { /// Configure Nutrient Analytics events. Future enableAnalyticsEvents(bool enable) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.enableAnalyticsEvents$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.enableAnalyticsEvents$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([enable]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([enable]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1471,6 +1502,34 @@ class NutrientApi { return; } } + + /// Sets the annotation menu configuration for the global presenter. + /// This configuration applies to all annotation menus in presented documents. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + Future setAnnotationMenuConfiguration(AnnotationMenuConfigurationData configuration) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientApi.setAnnotationMenuConfiguration$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([configuration]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as bool?); + } + } } abstract class NutrientApiCallbacks { @@ -1512,19 +1571,11 @@ abstract class NutrientApiCallbacks { /// Called when instant document download fails. void onInstantDownloadFailed(String documentId, String error); - static void setUp( - NutrientApiCallbacks? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(NutrientApiCallbacks? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfActivityOnPause$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfActivityOnPause$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); @@ -1535,19 +1586,15 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfFragmentAdded$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfFragmentAdded$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); @@ -1558,26 +1605,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onDocumentLoaded$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onDocumentLoaded$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onDocumentLoaded was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onDocumentLoaded was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1587,19 +1630,15 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfViewControllerWillDismiss$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfViewControllerWillDismiss$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); @@ -1610,19 +1649,15 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfViewControllerDidDismiss$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onPdfViewControllerDidDismiss$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); @@ -1633,26 +1668,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncStarted$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncStarted$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncStarted was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncStarted was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1662,26 +1693,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFinished$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFinished$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFinished was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFinished was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1691,26 +1718,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFailed$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFailed$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFailed was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantSyncFailed was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1723,26 +1746,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFinished$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFinished$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFinished was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFinished was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1755,26 +1774,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFailed$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFailed$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFailed was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantAuthenticationFailed was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1787,26 +1802,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFinished$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFinished$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFinished was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFinished was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1816,26 +1827,22 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFailed$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFailed$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFailed was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientApiCallbacks.onInstantDownloadFailed was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -1848,9 +1855,8 @@ abstract class NutrientApiCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } @@ -1862,11 +1868,9 @@ class NutrientViewControllerApi { /// Constructor for [NutrientViewControllerApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - NutrientViewControllerApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + NutrientViewControllerApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -1875,18 +1879,14 @@ class NutrientViewControllerApi { /// Sets the value of a form field by specifying its fully qualified field name. /// This method is deprecated. Use [PdfDocument.setFormFieldValue] instead. - Future setFormFieldValue( - String value, String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setFormFieldValue(String value, String fullyQualifiedName) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([value, fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([value, fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1904,16 +1904,13 @@ class NutrientViewControllerApi { /// Gets the form field value by specifying its fully qualified name. Future getFormFieldValue(String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1931,16 +1928,13 @@ class NutrientViewControllerApi { /// Applies Instant document JSON to the presented document. Future applyInstantJson(String annotationsJson) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.applyInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.applyInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotationsJson]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotationsJson]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -1958,10 +1952,8 @@ class NutrientViewControllerApi { /// Exports Instant document JSON from the presented document. Future exportInstantJson() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exportInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exportInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -1985,16 +1977,13 @@ class NutrientViewControllerApi { /// Adds the given annotation to the presented document. /// `jsonAnnotation` can either be a JSON string or a valid JSON Dictionary (iOS) / HashMap (Android). Future addAnnotation(String annotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.addAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.addAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2013,16 +2002,13 @@ class NutrientViewControllerApi { /// Removes the given annotation from the presented document. /// `jsonAnnotation` can either be a JSON string or a valid JSON Dictionary (iOS) / HashMap (Android). Future removeAnnotation(String annotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.removeAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.removeAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2040,16 +2026,13 @@ class NutrientViewControllerApi { /// Returns a list of JSON dictionaries for all the annotations of the given `type` on the given `pageIndex`. Future getAnnotations(int pageIndex, String type) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex, type]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex, type]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2072,10 +2055,8 @@ class NutrientViewControllerApi { /// Returns a list of JSON dictionaries for all the unsaved annotations in the presented document. Future getAllUnsavedAnnotations() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2103,18 +2084,14 @@ class NutrientViewControllerApi { /// Processes annotations of the given type with the provided processing /// mode and stores the PDF at the given destination path. - Future processAnnotations(AnnotationType type, - AnnotationProcessingMode processingMode, String destinationPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.processAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future processAnnotations(AnnotationType type, AnnotationProcessingMode processingMode, String destinationPath) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.processAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel - .send([type, processingMode, destinationPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([type, processingMode, destinationPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2137,16 +2114,13 @@ class NutrientViewControllerApi { /// Imports annotations from the XFDF file at the given path. Future importXfdf(String xfdfString) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.importXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.importXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfString]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfString]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2169,16 +2143,13 @@ class NutrientViewControllerApi { /// Exports annotations to the XFDF file at the given path. Future exportXfdf(String xfdfPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exportXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exportXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2202,10 +2173,8 @@ class NutrientViewControllerApi { /// Saves the document back to its original location if it has been changed. /// If there were no changes to the document, the document file will not be modified. Future save() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.save$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.save$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2235,18 +2204,14 @@ class NutrientViewControllerApi { /// @param configurations A map of annotation tools and their corresponding configurations. /// @param modifyAssociatedAnnotations Whether to modify the annotations associated with the old configuration. Only used for Android. /// @return True if the configurations were set successfully, false otherwise. - Future setAnnotationConfigurations( - Map> configurations) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setAnnotationConfigurations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setAnnotationConfigurations(Map> configurations) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setAnnotationConfigurations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([configurations]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([configurations]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2266,16 +2231,13 @@ class NutrientViewControllerApi { /// pageIndex The index of the page. This is a zero-based index. /// Returns a [Future] that completes with the visible rect of the given page. Future getVisibleRect(int pageIndex) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getVisibleRect$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getVisibleRect$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2300,18 +2262,14 @@ class NutrientViewControllerApi { /// pageIndex The index of the page. This is a zero-based index. /// rect The rect to zoom to. /// Returns a [Future] that completes when the zoom operation is done. - Future zoomToRect( - int pageIndex, PdfRect rect, bool? animated, double? duration) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.zoomToRect$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future zoomToRect(int pageIndex, PdfRect rect, bool? animated, double? duration) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.zoomToRect$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex, rect, animated, duration]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex, rect, animated, duration]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2336,16 +2294,13 @@ class NutrientViewControllerApi { /// pageIndex The index of the page. This is a zero-based index. /// Returns a [Future] that completes with the zoom scale of the given page. Future getZoomScale(int pageIndex) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getZoomScale$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.getZoomScale$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2367,16 +2322,13 @@ class NutrientViewControllerApi { } Future addEventListener(NutrientEvent event) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.addEventListener$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.addEventListener$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([event]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([event]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2393,16 +2345,13 @@ class NutrientViewControllerApi { } Future removeEventListener(NutrientEvent event) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.removeEventListener$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.removeEventListener$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([event]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([event]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2425,18 +2374,14 @@ class NutrientViewControllerApi { /// /// Returns a [Future] that completes with a boolean indicating whether /// entering annotation creation mode was successful. - Future enterAnnotationCreationMode( - AnnotationTool? annotationTool) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.enterAnnotationCreationMode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future enterAnnotationCreationMode(AnnotationTool? annotationTool) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.enterAnnotationCreationMode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotationTool]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotationTool]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2457,10 +2402,8 @@ class NutrientViewControllerApi { /// Returns a [Future] that completes with a boolean indicating whether /// exiting annotation creation mode was successful. Future exitAnnotationCreationMode() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exitAnnotationCreationMode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.exitAnnotationCreationMode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2480,17 +2423,43 @@ class NutrientViewControllerApi { return (pigeonVar_replyList[0] as bool?); } } + + /// Sets the annotation menu configuration for the current view controller. + /// This configuration applies only to annotation menus in the current document view. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + Future setAnnotationMenuConfiguration(AnnotationMenuConfigurationData configuration) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.NutrientViewControllerApi.setAnnotationMenuConfiguration$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([configuration]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as bool?); + } + } } class PdfDocumentApi { /// Constructor for [PdfDocumentApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - PdfDocumentApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + PdfDocumentApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -2500,16 +2469,13 @@ class PdfDocumentApi { /// Returns the page info for the given page index. /// pageIndex The index of the page. This is a zero-based index. Future getPageInfo(int pageIndex) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getPageInfo$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getPageInfo$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2534,16 +2500,13 @@ class PdfDocumentApi { /// options:[DocumentSaveOptions] The options to use when exporting the document. /// Returns a [Uint8List] containing the exported PDF data. Future exportPdf(DocumentSaveOptions? options) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportPdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportPdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([options]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([options]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2565,16 +2528,13 @@ class PdfDocumentApi { } Future> getFormField(String fieldName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormField$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormField$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([fieldName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([fieldName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2591,17 +2551,14 @@ class PdfDocumentApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as Map?)! - .cast(); + return (pigeonVar_replyList[0] as Map?)!.cast(); } } /// Returns a list of all form fields in the document. Future>> getFormFields() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormFields$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormFields$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2623,24 +2580,19 @@ class PdfDocumentApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast>(); + return (pigeonVar_replyList[0] as List?)!.cast>(); } } /// Sets the value of a form field by specifying its fully qualified field name. - Future setFormFieldValue( - String value, String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + Future setFormFieldValue(String value, String fullyQualifiedName) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.setFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([value, fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([value, fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2658,16 +2610,13 @@ class PdfDocumentApi { /// Gets the form field value by specifying its fully qualified name. Future getFormFieldValue(String fullyQualifiedName) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getFormFieldValue$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([fullyQualifiedName]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([fullyQualifiedName]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2685,16 +2634,13 @@ class PdfDocumentApi { /// Applies Instant document JSON to the presented document. Future applyInstantJson(String annotationsJson) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.applyInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.applyInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([annotationsJson]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([annotationsJson]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2712,10 +2658,8 @@ class PdfDocumentApi { /// Exports Instant document JSON from the presented document. Future exportInstantJson() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportInstantJson$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportInstantJson$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2739,16 +2683,13 @@ class PdfDocumentApi { /// Adds the given annotation to the presented document. /// `jsonAnnotation` can either be a JSON string or a valid JSON Dictionary (iOS) / HashMap (Android). Future addAnnotation(String jsonAnnotation, Object? attachment) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.addAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.addAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([jsonAnnotation, attachment]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([jsonAnnotation, attachment]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2767,16 +2708,13 @@ class PdfDocumentApi { /// Updates the given annotation in the presented document. /// `jsonAnnotation` can either be a JSON string or a valid JSON Dictionary (iOS) / HashMap (Android). Future updateAnnotation(String jsonAnnotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.updateAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.updateAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([jsonAnnotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([jsonAnnotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2795,16 +2733,13 @@ class PdfDocumentApi { /// Removes the given annotation from the presented document. /// `jsonAnnotation` can either be a JSON string or a valid JSON Dictionary (iOS) / HashMap (Android). Future removeAnnotation(String jsonAnnotation) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.removeAnnotation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.removeAnnotation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([jsonAnnotation]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([jsonAnnotation]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2822,16 +2757,13 @@ class PdfDocumentApi { /// Returns a list of JSON dictionaries for all the annotations of the given `type` on the given `pageIndex`. Future getAnnotations(int pageIndex, String type) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([pageIndex, type]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([pageIndex, type]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2854,10 +2786,8 @@ class PdfDocumentApi { /// Returns a list of JSON dictionaries for all the unsaved annotations in the presented document. Future getAllUnsavedAnnotations() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getAllUnsavedAnnotations$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -2885,16 +2815,13 @@ class PdfDocumentApi { /// Imports annotations from the XFDF file at the given path. Future importXfdf(String xfdfString) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.importXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.importXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfString]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfString]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2917,16 +2844,13 @@ class PdfDocumentApi { /// Exports annotations to the XFDF file at the given path. Future exportXfdf(String xfdfPath) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportXfdf$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.exportXfdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([xfdfPath]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([xfdfPath]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2950,16 +2874,13 @@ class PdfDocumentApi { /// Saves the document back to its original location if it has been changed. /// If there were no changes to the document, the document file will not be modified. Future save(String? outputPath, DocumentSaveOptions? options) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.save$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.save$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([outputPath, options]); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([outputPath, options]); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -2982,10 +2903,8 @@ class PdfDocumentApi { /// Get the total number of pages in the document. Future getPageCount() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getPageCount$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.nutrient_flutter.PdfDocumentApi.getPageCount$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -3021,31 +2940,22 @@ abstract class NutrientViewCallbacks { void onPageChanged(String documentId, int pageIndex); - void onPageClick( - String documentId, int pageIndex, PointF? point, Object? annotation); + void onPageClick(String documentId, int pageIndex, PointF? point, Object? annotation); void onDocumentSaved(String documentId, String? path); - static void setUp( - NutrientViewCallbacks? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(NutrientViewCallbacks? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentLoaded$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentLoaded$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentLoaded was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentLoaded was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -3055,26 +2965,22 @@ abstract class NutrientViewCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentError$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentError$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentError was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentError was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -3087,26 +2993,22 @@ abstract class NutrientViewCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageChanged$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageChanged$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageChanged was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageChanged was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -3119,26 +3021,22 @@ abstract class NutrientViewCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageClick$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageClick$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageClick was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageClick was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -3147,33 +3045,28 @@ abstract class NutrientViewCallbacks { assert(arg_pageIndex != null, 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onPageClick was null, expected non-null int.'); final PointF? arg_point = (args[2] as PointF?); - final Object? arg_annotation = args[3]; + final Object? arg_annotation = (args[3] as Object?); try { - api.onPageClick( - arg_documentId!, arg_pageIndex!, arg_point, arg_annotation); + api.onPageClick(arg_documentId!, arg_pageIndex!, arg_point, arg_annotation); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentSaved$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentSaved$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentSaved was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientViewCallbacks.onDocumentSaved was null.'); final List args = (message as List?)!; final String? arg_documentId = (args[0] as String?); assert(arg_documentId != null, @@ -3184,9 +3077,8 @@ abstract class NutrientViewCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } @@ -3199,39 +3091,30 @@ abstract class NutrientEventsCallbacks { void onEvent(NutrientEvent event, Object? data); - static void setUp( - NutrientEventsCallbacks? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(NutrientEventsCallbacks? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.NutrientEventsCallbacks.onEvent$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.NutrientEventsCallbacks.onEvent$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientEventsCallbacks.onEvent was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientEventsCallbacks.onEvent was null.'); final List args = (message as List?)!; final NutrientEvent? arg_event = (args[0] as NutrientEvent?); assert(arg_event != null, 'Argument for dev.flutter.pigeon.nutrient_flutter.NutrientEventsCallbacks.onEvent was null, expected non-null NutrientEvent.'); - final Object? arg_data = args[1]; + final Object? arg_data = (args[1] as Object?); try { api.onEvent(arg_event!, arg_data); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } @@ -3244,40 +3127,30 @@ abstract class AnalyticsEventsCallback { void onEvent(String event, Map? attributes); - static void setUp( - AnalyticsEventsCallback? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(AnalyticsEventsCallback? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.AnalyticsEventsCallback.onEvent$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.AnalyticsEventsCallback.onEvent$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.AnalyticsEventsCallback.onEvent was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.AnalyticsEventsCallback.onEvent was null.'); final List args = (message as List?)!; final String? arg_event = (args[0] as String?); assert(arg_event != null, 'Argument for dev.flutter.pigeon.nutrient_flutter.AnalyticsEventsCallback.onEvent was null, expected non-null String.'); - final Map? arg_attributes = - (args[1] as Map?)?.cast(); + final Map? arg_attributes = (args[1] as Map?)?.cast(); try { api.onEvent(arg_event!, arg_attributes); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } @@ -3292,26 +3165,18 @@ abstract class CustomToolbarCallbacks { /// Called when a custom toolbar item is tapped void onCustomToolbarItemTapped(String identifier); - static void setUp( - CustomToolbarCallbacks? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(CustomToolbarCallbacks? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.nutrient_flutter.CustomToolbarCallbacks.onCustomToolbarItemTapped$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.nutrient_flutter.CustomToolbarCallbacks.onCustomToolbarItemTapped$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.nutrient_flutter.CustomToolbarCallbacks.onCustomToolbarItemTapped was null.'); + 'Argument for dev.flutter.pigeon.nutrient_flutter.CustomToolbarCallbacks.onCustomToolbarItemTapped was null.'); final List args = (message as List?)!; final String? arg_identifier = (args[0] as String?); assert(arg_identifier != null, @@ -3321,9 +3186,8 @@ abstract class CustomToolbarCallbacks { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } diff --git a/lib/src/pdf_configuration.dart b/lib/src/pdf_configuration.dart index 12daf978..f1890f50 100644 --- a/lib/src/pdf_configuration.dart +++ b/lib/src/pdf_configuration.dart @@ -186,6 +186,9 @@ class PdfConfiguration { final bool? androidEnableAiAssistant; + /// Configuration for annotation contextual menu customization. + final AnnotationMenuConfiguration? annotationMenuConfiguration; + PdfConfiguration({ this.scrollDirection, this.pageTransition, @@ -240,6 +243,7 @@ class PdfConfiguration { this.signatureCreationConfiguration, this.aiAssistantConfiguration, this.androidEnableAiAssistant, + this.annotationMenuConfiguration, }); /// Returns a [Map] representation of the [PdfConfiguration] object. @@ -297,6 +301,7 @@ class PdfConfiguration { 'signatureCreationConfiguration': signatureCreationConfiguration?.toMap(), 'aiAssistant': aiAssistantConfiguration?.toMap(), 'enableAiAssistant': androidEnableAiAssistant, + 'annotationMenuConfiguration': annotationMenuConfiguration?.toMap(), }..removeWhere((key, value) => value == null); } diff --git a/lib/src/types.dart b/lib/src/types.dart index b6bb88c0..19eded8d 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -387,6 +387,9 @@ typedef OnCustomToolbarItemTappedCallback = void Function(String identifier); typedef NutrientViewCreatedCallback = void Function( NutrientViewController controller); +typedef OnBuildAnnotationMenuCallback = AnnotationMenuConfigurationData? Function( + String documentId, Object annotation); + extension WebShowSignatureValidationStatusMode on ShowSignatureValidationStatusMode { String? get webName { diff --git a/lib/src/web/nutrient_web_configuration.dart b/lib/src/web/nutrient_web_configuration.dart index 6fcc8df5..795835ae 100644 --- a/lib/src/web/nutrient_web_configuration.dart +++ b/lib/src/web/nutrient_web_configuration.dart @@ -205,6 +205,24 @@ class PdfWebConfiguration { /// Main toolbar items. If not set, the default toolbar items will be used. final List? toolbarItems; + /// Office conversion settings for documents like .docx, .xlsx, .pptx. + /// + /// These settings control how Office documents are converted to PDF for viewing. + /// Currently supports configuration for spreadsheet dimensions: + /// - `spreadsheetMaximumContentHeightPerSheet`: Maximum height in millimeters per sheet + /// - `spreadsheetMaximumContentWidthPerSheet`: Maximum width in millimeters per sheet + /// + /// Example: + /// ```dart + /// PdfWebConfiguration( + /// officeConversionSettings: OfficeConversionSettings( + /// spreadsheetMaximumContentHeightPerSheet: 500, + /// spreadsheetMaximumContentWidthPerSheet: 300, + /// ), + /// ) + /// ``` + final OfficeConversionSettings? officeConversionSettings; + /// Annotation toolbar items callback. If not set, the default annotation toolbar items will be used. final NutrientWebAnnotationToolbarItemsCallback? annotationToolbarItems; @@ -274,7 +292,8 @@ class PdfWebConfiguration { this.disableForms, this.disableTextSelection, this.toolbarItems, - this.annotationToolbarItems}); + this.annotationToolbarItems, + this.officeConversionSettings}); Map toMap() { return { @@ -336,7 +355,8 @@ class PdfWebConfiguration { 'disableTextSelection': disableTextSelection, 'toolbarItems': toolbarItems, 'annotationToolbarItems': annotationToolbarItems, - 'authPayload': authPayload + 'authPayload': authPayload, + ...?officeConversionSettings?.toMap() }..removeWhere((key, value) => value == null); } } diff --git a/lib/src/web/office_conversion_settings.dart b/lib/src/web/office_conversion_settings.dart new file mode 100644 index 00000000..9d6902fd --- /dev/null +++ b/lib/src/web/office_conversion_settings.dart @@ -0,0 +1,46 @@ +/// +/// Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +/// Configuration options for Office document conversion in Nutrient Web. +/// +/// These settings control how Office documents (.docx, .xlsx, .pptx) are +/// converted to PDF for viewing in the web viewer. +class OfficeConversionSettings { + /// Maximum height (in millimeters) for spreadsheet content per sheet. + /// + /// This setting controls the maximum height of content that will be rendered + /// for each sheet in Excel spreadsheets. Content exceeding this height will + /// be truncated. The default value is platform-specific. + final num? spreadsheetMaximumContentHeightPerSheet; + + /// Maximum width (in millimeters) for spreadsheet content per sheet. + /// + /// This setting controls the maximum width of content that will be rendered + /// for each sheet in Excel spreadsheets. Content exceeding this width will + /// be truncated. The default value is platform-specific. + final num? spreadsheetMaximumContentWidthPerSheet; + + /// Creates an instance of [OfficeConversionSettings]. + /// + /// All parameters are optional and will use platform defaults if not specified. + const OfficeConversionSettings({ + this.spreadsheetMaximumContentHeightPerSheet, + this.spreadsheetMaximumContentWidthPerSheet, + }); + + /// Converts this configuration to a Map for JavaScript interop. + Map toMap() { + return { + 'spreadsheetMaximumContentHeightPerSheet': + spreadsheetMaximumContentHeightPerSheet, + 'spreadsheetMaximumContentWidthPerSheet': + spreadsheetMaximumContentWidthPerSheet, + }..removeWhere((key, value) => value == null); + } +} \ No newline at end of file diff --git a/lib/src/widgets/nutrient_view.dart b/lib/src/widgets/nutrient_view.dart index 111e3a78..91d1671d 100644 --- a/lib/src/widgets/nutrient_view.dart +++ b/lib/src/widgets/nutrient_view.dart @@ -50,6 +50,7 @@ class NutrientView extends StatefulWidget { /// Called when a custom toolbar item is tapped. final OnCustomToolbarItemTappedCallback? onCustomToolbarItemTapped; + /// Creates a new [NutrientView] widget. const NutrientView({ Key? key, diff --git a/lib/src/widgets/nutrient_view_controller.dart b/lib/src/widgets/nutrient_view_controller.dart index c8c83c3c..96839610 100644 --- a/lib/src/widgets/nutrient_view_controller.dart +++ b/lib/src/widgets/nutrient_view_controller.dart @@ -38,6 +38,14 @@ abstract class NutrientViewController { Map configurations, ); + /// Sets the annotation menu configuration dynamically. + /// This allows you to update the annotation contextual menu configuration at runtime. + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + Future setAnnotationMenuConfiguration( + AnnotationMenuConfiguration configuration, + ); + /// Adds an event listener for the given event. /// @param event. The event to listen for. Future addEventListener( @@ -75,18 +83,10 @@ abstract class NutrientViewController { /// Returns a [Future] that completes with the zoom scale of the given page. Future getZoomScale(int pageIndex); - /// Enters annotation creation mode. - /// - /// If [annotationTool] is provided, that specific tool will be activated. - /// If no tool is provided, the default annotation tool will be used. - /// - /// Returns a [Future] that completes with a boolean indicating whether - /// entering annotation creation mode was successful. + /// Enters annotation creation mode for the specified annotation tool. + /// If no tool is specified, defaults to [AnnotationTool.inkPen]. Future enterAnnotationCreationMode([AnnotationTool? annotationTool]); - /// Exits annotation creation mode. - /// - /// Returns a [Future] that completes with a boolean indicating whether - /// exiting annotation creation mode was successful. + /// Exits annotation creation mode and returns to normal viewer interaction. Future exitAnnotationCreationMode(); } diff --git a/lib/src/widgets/nutrient_view_controller_native.dart b/lib/src/widgets/nutrient_view_controller_native.dart index 160a0d7c..b9abc1e2 100644 --- a/lib/src/widgets/nutrient_view_controller_native.dart +++ b/lib/src/widgets/nutrient_view_controller_native.dart @@ -85,6 +85,23 @@ class NutrientViewControllerNative return _pspdfkitWidgetControllerApi.save(); } + @override + Future setAnnotationMenuConfiguration( + AnnotationMenuConfiguration configuration, + ) { + // Convert AnnotationMenuConfiguration to AnnotationMenuConfigurationData for Pigeon API + final configData = AnnotationMenuConfigurationData( + itemsToRemove: configuration.itemsToRemove.toList(), + itemsToDisable: configuration.itemsToDisable.toList(), + showStylePicker: configuration.showStylePicker, + groupMarkupItems: configuration.groupMarkupItems, + maxVisibleItems: configuration.maxVisibleItems, + ); + + return _pspdfkitWidgetControllerApi + .setAnnotationMenuConfiguration(configData); + } + @override Future setAnnotationConfigurations( Map configurations) { @@ -267,6 +284,7 @@ class NutrientViewControllerNative onCustomToolbarItemTappedListener?.call(identifier); } + @override void addWebEventListener( NutrientWebEvent event, Function(dynamic p1) callback) { diff --git a/lib/src/widgets/nutrient_view_controller_web.dart b/lib/src/widgets/nutrient_view_controller_web.dart index c54568ed..b19feb0b 100644 --- a/lib/src/widgets/nutrient_view_controller_web.dart +++ b/lib/src/widgets/nutrient_view_controller_web.dart @@ -50,6 +50,19 @@ class NutrientViewControllerWeb extends NutrientViewController return Future.value(true); } + @override + Future setAnnotationMenuConfiguration( + AnnotationMenuConfiguration configuration, + ) { + // Web implementation: annotation menu configuration is handled differently on web + // This would need to be implemented using the web-specific API + // For now, we'll return true to indicate the API is available + if (kDebugMode) { + print('setAnnotationMenuConfiguration called on web - not yet implemented'); + } + return Future.value(true); + } + @override Future setAnnotationConfigurations( Map configurations) async { diff --git a/lib/src/widgets/pspdfkit_flutter_widget_controller_impl.dart b/lib/src/widgets/pspdfkit_flutter_widget_controller_impl.dart index 98fd42b1..f4de0cc0 100644 --- a/lib/src/widgets/pspdfkit_flutter_widget_controller_impl.dart +++ b/lib/src/widgets/pspdfkit_flutter_widget_controller_impl.dart @@ -324,4 +324,5 @@ class PspdfkitFlutterWidgetControllerImpl throw UnimplementedError( 'removeWebEventListener is only supported on web.'); } + } diff --git a/pigeons/nutrient.dart b/pigeons/nutrient.dart index 1e835428..4705cdea 100644 --- a/pigeons/nutrient.dart +++ b/pigeons/nutrient.dart @@ -417,6 +417,15 @@ abstract class NutrientApi { /// Configure Nutrient Analytics events. void enableAnalyticsEvents(bool enable); + + /// Sets the annotation menu configuration for the global presenter. + /// This configuration applies to all annotation menus in presented documents. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + @async + bool? setAnnotationMenuConfiguration( + AnnotationMenuConfigurationData configuration); } @FlutterApi() @@ -566,6 +575,15 @@ abstract class NutrientViewControllerApi { /// exiting annotation creation mode was successful. @async bool? exitAnnotationCreationMode(); + + /// Sets the annotation menu configuration for the current view controller. + /// This configuration applies only to annotation menus in the current document view. + /// + /// @param configuration The annotation menu configuration to apply. + /// @return True if the configuration was set successfully, false otherwise. + @async + bool? setAnnotationMenuConfiguration( + AnnotationMenuConfigurationData configuration); } @HostApi() @@ -661,6 +679,7 @@ abstract class NutrientViewCallbacks { String documentId, int pageIndex, PointF? point, Object? annotation); void onDocumentSaved(String documentId, String? path); + } @FlutterApi() @@ -715,3 +734,125 @@ abstract class CustomToolbarCallbacks { /// Called when a custom toolbar item is tapped void onCustomToolbarItemTapped(String identifier); } + +/// Enumeration of default annotation menu actions that can be removed or disabled. +/// +/// **Platform Support:** +/// - All actions can be removed or disabled on both iOS and Android +/// - Some system actions (copy/paste) may be harder to remove on iOS due to system restrictions +enum AnnotationMenuAction { + /// Delete action - removes the annotation + /// - iOS: Part of UIMenu system actions + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_delete + delete, + + /// Copy action - copies the annotation + /// - iOS: System copy action (may be harder to remove) + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_copy + copy, + + /// Cut action - cuts the annotation to clipboard + /// - iOS: System cut action + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_cut + cut, + + /// Color action - opens annotation color picker/inspector + /// - iOS: Style picker in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_picker + color, + + /// Note action - opens annotation note editor + /// - iOS: Note action in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_annotation_note + note, + + /// Undo action - undoes the last action + /// - iOS: Undo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_undo + undo, + + /// Redo action - redoes the previously undone action + /// - iOS: Redo in UIMenu + /// - Android: R.id.pspdf__annotation_editing_toolbar_item_redo + redo, +} + +/// Configuration data for annotation contextual menu +/// +/// This class defines how annotation menus should be configured +/// when displayed to users. It supports removing actions, disabling actions, +/// and controlling visual presentation options. +/// +/// **Usage Patterns**: +/// - **Static Configuration**: Set once via [NutrientViewController.setAnnotationMenuConfiguration] +/// +/// **Platform Compatibility**: +/// - [itemsToRemove]: Supported on Android, iOS, and Web +/// - [itemsToDisable]: Supported on Android, iOS, and Web +/// - [showStylePicker]: Supported on Android and iOS +/// - [groupMarkupItems]: iOS only (ignored on other platforms) +/// - [maxVisibleItems]: Platform-dependent behavior +class AnnotationMenuConfigurationData { + /// List of default annotation menu actions to remove completely from the menu. + /// + /// These actions will not appear in the contextual menu at all. + /// Use this when you want to completely hide certain functionality. + /// + /// **Example**: Remove delete action for read-only annotations + /// ```dart + /// itemsToRemove: [AnnotationMenuAction.delete] + /// ``` + final List itemsToRemove; + + /// List of default annotation menu actions to disable (show as grayed out). + /// + /// These actions will appear in the menu but will be non-interactive. + /// Use this when you want to show functionality exists but is temporarily unavailable. + /// + /// **Example**: Disable copy action for certain annotation types + /// ```dart + /// itemsToDisable: [AnnotationMenuAction.copy] + /// ``` + final List itemsToDisable; + + /// Whether to show the platform's default style picker in the annotation menu. + /// + /// When true, users can access color, thickness, and other style options + /// directly from the annotation menu. + /// + /// **Platform Behavior**: + /// - **iOS**: Shows style picker as part of UIMenu + /// - **Android**: Shows annotation inspector/style picker + /// - **Web**: Shows color picker and basic style options + final bool showStylePicker; + + /// Whether to group markup annotation actions together in the menu. + /// + /// When true, related markup actions (highlight, underline, etc.) are + /// visually grouped in the menu for better organization. + /// + /// **Platform Support**: iOS only (ignored on Android and Web) + final bool groupMarkupItems; + + /// Maximum number of actions to show directly in the menu before creating overflow. + /// + /// When the number of available actions exceeds this limit, the platform + /// may create a submenu or overflow menu to accommodate additional actions. + /// + /// **Platform Behavior**: + /// - **iOS**: Respects platform UI guidelines for menu length + /// - **Android**: Limited by toolbar space and screen size + /// - **Web**: Creates scrollable or paginated menu as needed + /// + /// **Note**: If null, the platform default behavior is used. + final int? maxVisibleItems; + + AnnotationMenuConfigurationData({ + required this.itemsToRemove, + required this.itemsToDisable, + required this.showStylePicker, + required this.groupMarkupItems, + this.maxVisibleItems, + }); +} + diff --git a/pubspec.yaml b/pubspec.yaml index 80922381..c1111da0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,6 @@ dev_dependencies: sdk: flutter pigeon: ^24.2.1 build_runner: ^2.2.1 - pigeon_build_runner: ^1.1.1 mockito: ^5.3.1 # Ignore false positives for security scanning diff --git a/test/html_pdf_converter_test.mocks.dart b/test/html_pdf_converter_test.mocks.dart index 20ab5c4a..4b52a6f8 100644 --- a/test/html_pdf_converter_test.mocks.dart +++ b/test/html_pdf_converter_test.mocks.dart @@ -1,5 +1,5 @@ -// Mocks generated by Mockito 5.4.5 from annotations -// in pspdfkit_flutter/test/html_pdf_converter_test.dart. +// Mocks generated by Mockito 5.4.6 from annotations +// in nutrient_flutter/test/html_pdf_converter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes diff --git a/test/office_conversion_settings_test.dart b/test/office_conversion_settings_test.dart new file mode 100644 index 00000000..5886a4f4 --- /dev/null +++ b/test/office_conversion_settings_test.dart @@ -0,0 +1,81 @@ +/// +/// Copyright © 2024-2025 PSPDFKit GmbH. All rights reserved. +/// +/// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +/// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +/// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +/// This notice may not be removed from this file. +/// + +import 'package:flutter_test/flutter_test.dart'; +import 'package:nutrient_flutter/nutrient_flutter.dart'; + +void main() { + group('OfficeConversionSettings', () { + test('should serialize to Map correctly', () { + const settings = OfficeConversionSettings( + spreadsheetMaximumContentHeightPerSheet: 500, + spreadsheetMaximumContentWidthPerSheet: 300, + ); + + final map = settings.toMap(); + + expect(map['spreadsheetMaximumContentHeightPerSheet'], equals(500)); + expect(map['spreadsheetMaximumContentWidthPerSheet'], equals(300)); + }); + + test('should omit null values from Map', () { + const settings = OfficeConversionSettings( + spreadsheetMaximumContentHeightPerSheet: 500, + // spreadsheetMaximumContentWidthPerSheet is null + ); + + final map = settings.toMap(); + + expect(map['spreadsheetMaximumContentHeightPerSheet'], equals(500)); + expect(map.containsKey('spreadsheetMaximumContentWidthPerSheet'), isFalse); + }); + + test('should handle all null values', () { + const settings = OfficeConversionSettings(); + + final map = settings.toMap(); + + expect(map.isEmpty, isTrue); + }); + }); + + group('PdfWebConfiguration with OfficeConversionSettings', () { + test('should include office conversion settings in configuration map', () { + final webConfig = PdfWebConfiguration( + officeConversionSettings: const OfficeConversionSettings( + spreadsheetMaximumContentHeightPerSheet: 800, + spreadsheetMaximumContentWidthPerSheet: 600, + ), + allowPrinting: true, + showAnnotations: true, + ); + + final map = webConfig.toMap(); + + expect(map['spreadsheetMaximumContentHeightPerSheet'], equals(800)); + expect(map['spreadsheetMaximumContentWidthPerSheet'], equals(600)); + expect(map['allowPrinting'], equals(true)); + expect(map['showAnnotations'], equals(true)); + }); + + test('should flatten office conversion settings in map', () { + final webConfig = PdfWebConfiguration( + officeConversionSettings: const OfficeConversionSettings( + spreadsheetMaximumContentHeightPerSheet: 1000, + ), + ); + + final map = webConfig.toMap(); + + // Settings should be flattened, not nested + expect(map['spreadsheetMaximumContentHeightPerSheet'], equals(1000)); + expect(map.containsKey('officeConversionSettings'), isFalse); + }); + }); +} \ No newline at end of file