From cd7600dedf7d0973557302b4c3c4dacdc94413bd Mon Sep 17 00:00:00 2001 From: PSPDFKit Date: Fri, 3 Jan 2025 10:57:57 +0300 Subject: [PATCH] Release 4.1.0 --- ACKNOWLEDGEMENTS.md | 325 ++++++++++++++---- CHANGELOG.md | 14 +- android/build.gradle | 5 + android/config.gradle | 8 +- .../pspdfkit/ConfigurationAdapter.java | 4 +- .../pspdfkit/FlutterPdfUiFragmentCallbacks.kt | 20 ++ .../flutter/pspdfkit/FlutterWidgetCallback.kt | 35 ++ .../pspdfkit/flutter/pspdfkit/PSPDFKitView.kt | 5 + .../flutter/pspdfkit/PspdfkitApiImpl.kt | 23 +- .../flutter/pspdfkit/PspdfkitPlugin.java | 8 + .../flutter/pspdfkit/PspdfkitViewImpl.kt | 15 + .../flutter/pspdfkit/api/PspdfkitApi.g.kt | 228 +++++++++++- .../pspdfkit/events/FlutterAnalyticsClient.kt | 91 +++++ .../pspdfkit/events/FlutterEventsHelper.kt | 250 ++++++++++++++ .../flutter/pspdfkit/forms/FormHelper.kt | 6 +- .../android/app/src/main/AndroidManifest.xml | 5 +- example/android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 24 +- example/integration_test/form_test.dart | 7 +- example/ios/Podfile | 2 +- example/lib/examples.dart | 43 ++- example/lib/main.dart | 6 + example/lib/pspdfkit_annotations_example.dart | 41 +++ .../lib/pspdfkit_event_listeners_example.dart | 132 +++++-- ...spdfkit_instant_collaboration_example.dart | 2 + example/lib/pspdfkit_manual_save_example.dart | 13 +- .../lib/pspdfkit_pdf_generation_example.dart | 2 +- example/lib/pspdfkit_save_as_example.dart | 1 + example/pubspec.yaml | 4 +- ios/Classes/EventsHelper.swift | 203 +++++++++++ ios/Classes/ExportHelper.swift | 9 +- ios/Classes/FlutterAnalyticsClient.swift | 25 ++ ios/Classes/PspdfkitApiImpl.swift | 14 +- .../PspdfkitMeasurementConvertor.swift | 2 - ios/Classes/PspdfkitPlatformViewImpl.swift | 63 +++- ios/Classes/api/PspdfkitApi.g.swift | 232 ++++++++++++- ios/pspdfkit_flutter.podspec | 10 +- lib/pspdfkit.dart | 7 + lib/src/annotation_preset_configurations.dart | 6 +- lib/src/api/pspdfkit_api.g.dart | 275 ++++++++++++++- lib/src/document/pdf_document.dart | 2 +- lib/src/document/pdf_document_native.dart | 2 +- lib/src/document/pdf_document_web.dart | 2 +- lib/src/events/nutrient_events_extension.dart | 28 ++ lib/src/processor/new_page.dart | 7 +- lib/src/pspdfkit_flutter_api_impl.dart | 23 +- lib/src/pspdfkit_flutter_method_channel.dart | 5 + .../pspdfkit_flutter_platform_interface.dart | 8 + lib/src/pspdfkit_flutter_web.dart | 5 + lib/src/types.dart | 6 + ...pdfkit_flutter_widget_controller_impl.dart | 39 ++- lib/src/widgets/pspdfkit_widget.dart | 13 +- .../widgets/pspdfkit_widget_controller.dart | 11 +- .../pspdfkit_widget_controller_native.dart | 19 +- .../pspdfkit_widget_controller_web.dart | 10 +- lib/src/widgets/pspdfkit_widget_web.dart | 17 + licenses/pigeon.txt | 27 ++ pigeons/pspdfkit.dart | 58 ++++ pubspec.yaml | 4 +- test/html_pdf_converter_test.dart | 49 ++- test/html_pdf_converter_test.mocks.dart | 41 ++- 62 files changed, 2284 insertions(+), 260 deletions(-) create mode 100644 android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterAnalyticsClient.kt create mode 100644 android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt create mode 100644 ios/Classes/EventsHelper.swift create mode 100644 ios/Classes/FlutterAnalyticsClient.swift create mode 100644 lib/src/events/nutrient_events_extension.dart create mode 100644 licenses/pigeon.txt diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index d2acc4eb..1071ac5d 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -21,40 +21,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------------------------------------------------------------- -## AutoValue (https://github.com/google/auto/tree/master/value) - -Copyright 2013 Google, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - --------------------------------------------------------------------------------- -## AutoValue: Parcel Extension (https://github.com/rharter/auto-value-parcel) - -Copyright 2015 Ryan Harter. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -------------------------------------------------------------------------------- ## Barcode Scanner Libraries for Android (https://github.com/dm77/barcodescanner) @@ -2237,33 +2203,6 @@ desirable to choose a license that granted us the same protections for new code that were granted to the IJG for code derived from their software. --------------------------------------------------------------------------------- -## LoopKit (https://github.com/LoopKit/LoopKit) - -The MIT License (MIT) - -Copyright (c) 2015 Nathan Racklyeft -Copyright (c) 2016 LoopKit Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -------------------------------------------------------------------------------- ## Mantle (https://github.com/Mantle/Mantle) @@ -3864,6 +3803,35 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +-------------------------------------------------------------------------------- +## Pigeon (https://pub.dev/packages/pigeon) + +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + -------------------------------------------------------------------------------- ## PorterTokenizer (https://github.com/mozilla/releases-comm-central/blob/master/mailnews/extensions/fts3/src/Normalize.c) @@ -3924,6 +3892,241 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +-------------------------------------------------------------------------------- +## socket.io-client-swift (https://github.com/socketio/socket.io-client-swift) + +The MIT License (MIT) + +Copyright (c) 2014-2015 Erik Little + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +This library makes use of the following third party libraries: + +Starscream +---------- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -------------------------------------------------------------------------------- ## SQLite (https://www.sqlite.org/) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dfd00b8..67c88b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ## Newest Release +### 4.1.0 - 03 January 2024 + +- Adds support for `PspdfkitWidgetController.addEventListener` to iOS and Android. (J#HYB-550)` +- Adds support for the latest Android Studio version. (J#HYB-539) +- Nutrient Flutter SDK now requires Android Gradle Plugin 8.0.0 or later. (J#HYB-539) +- Nutrient Flutter SDK now requires Flutter 3.27.0 or later. (J#HYB-596) +- Updates for Nutrient Android SDK 2024.9.0. +- Updates for Nutrient iOS SDK 14.3.0. +- Fixes onPageChanged callback being triggered early on iOS. (J#HYB-596) + +## Previous Releases + ### 4.0.0 - 01 Nov 2024 - Adds Pigeon for communication between Flutter and native iOS and Android platforms. (J#HYB-455) @@ -9,8 +21,6 @@ - Updates for Nutrient iOS SDK 14.1.1. (#45458) - Updates for Nutrient Web SDK 2024.7.0. (#45458) -## Previous Releases - ### 3.12.0 - 30 Jul 2024 ### 3.12.1 - 11 Sep 2024 diff --git a/android/build.gradle b/android/build.gradle index f3d2ef92..388fd584 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -19,6 +19,7 @@ buildscript { } } + group 'com.pspdfkit.flutter.pspdfkit' version pspdfkitFlutterVersion @@ -45,6 +46,10 @@ android { targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" minSdkVersion androidMinSdkVersion diff --git a/android/config.gradle b/android/config.gradle index d26dd6ed..b807a375 100644 --- a/android/config.gradle +++ b/android/config.gradle @@ -38,7 +38,7 @@ if (pspdfkitMavenUrl == null || pspdfkitMavenUrl == '') { ext.pspdfkitVersion = localProperties.getProperty('pspdfkit.version') if (pspdfkitVersion == null || pspdfkitVersion == '') { - ext.pspdfkitVersion = '2024.6.1' + ext.pspdfkitVersion = '2024.9.0' } ext.pspdfkitMavenModuleName = 'pspdfkit' @@ -52,8 +52,8 @@ if (!pubspecFile.exists()) { def pubspecYaml = new Yaml().load(pubspecFile.newInputStream()) ext.pspdfkitFlutterVersion = pubspecYaml.version -ext.androidCompileSdkVersion = 34 +ext.androidCompileSdkVersion = 35 ext.androidMinSdkVersion = 21 -ext.androidTargetSdkVersion = 34 -ext.androidGradlePluginVersion = '7.4.2' +ext.androidTargetSdkVersion = 35 +ext.androidGradlePluginVersion = '8.7.2' ext.kotlinVersion = "1.9.20" 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 eb45a39a..311433b7 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/ConfigurationAdapter.java @@ -27,6 +27,7 @@ import com.pspdfkit.configuration.page.PageLayoutMode; import com.pspdfkit.configuration.page.PageScrollDirection; import com.pspdfkit.configuration.page.PageScrollMode; +import com.pspdfkit.configuration.search.SearchType; import com.pspdfkit.configuration.settings.SettingsMenuItemType; import com.pspdfkit.configuration.sharing.ShareFeatures; import com.pspdfkit.configuration.theming.ThemeMode; @@ -476,7 +477,7 @@ private void configureFitPageToWidth(boolean fitPageToWidth) { } private void configureInlineSearch(boolean inlineSearch) { - final int searchType = inlineSearch ? PdfActivityConfiguration.SEARCH_INLINE : PdfActivityConfiguration.SEARCH_MODULAR; + final SearchType searchType = inlineSearch ? SearchType.INLINE : SearchType.MODULAR; configuration.setSearchType(searchType); } @@ -860,7 +861,6 @@ PdfActivityConfiguration build() { this.configuration .enabledAnnotationTools(annotationTools); } - return configuration.build(); } } diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt index 946d33d6..c61745f8 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt @@ -16,8 +16,12 @@ package com.pspdfkit.flutter.pspdfkit /// This notice may not be removed from this file. /// import android.content.Context +import android.graphics.PointF +import android.view.MotionEvent import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import com.pspdfkit.annotations.Annotation +import com.pspdfkit.document.DocumentSaveOptions import com.pspdfkit.document.PdfDocument import com.pspdfkit.flutter.pspdfkit.api.PdfDocumentApi import com.pspdfkit.flutter.pspdfkit.document.FlutterPdfDocument @@ -114,4 +118,20 @@ class FlutterPdfUiFragmentCallbacks( } } } + + override fun onPageClick( + document: PdfDocument, + pageIndex: Int, + event: MotionEvent?, + pagePosition: PointF?, + clickedAnnotation: Annotation? + ): Boolean { + flutterWidgetCallback.onPageClick(document, pageIndex, event, pagePosition, clickedAnnotation) + return true + } + + override fun onDocumentSave(document: PdfDocument, saveOptions: DocumentSaveOptions): Boolean { + flutterWidgetCallback.onDocumentSave(document,saveOptions) + return true + } } \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterWidgetCallback.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterWidgetCallback.kt index b20a8641..789bc743 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterWidgetCallback.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterWidgetCallback.kt @@ -9,6 +9,10 @@ package com.pspdfkit.flutter.pspdfkit +import android.graphics.PointF +import android.view.MotionEvent +import com.pspdfkit.annotations.Annotation +import com.pspdfkit.document.DocumentSaveOptions import com.pspdfkit.document.PdfDocument import com.pspdfkit.flutter.pspdfkit.api.PspdfkitWidgetCallbacks import com.pspdfkit.listeners.DocumentListener @@ -36,4 +40,35 @@ class FlutterWidgetCallback( ) {} super.onDocumentLoadFailed(exception) } + + override fun onPageClick( + document: PdfDocument, + pageIndex: Int, + event: MotionEvent?, + pagePosition: PointF?, + clickedAnnotation: Annotation? + ): Boolean { + var flutterPointF: com.pspdfkit.flutter.pspdfkit.api.PointF? = null + pagePosition?.let { + flutterPointF = com.pspdfkit.flutter.pspdfkit.api.PointF( + pagePosition.x.toDouble(), + pagePosition.x.toDouble() + ) + } + pspdfkitWidgetCallbacks?.onPageClick( + document.uid, + pageIndex.toLong(), + flutterPointF, + clickedAnnotation?.toInstantJson() + ) {} + return true + } + + override fun onDocumentSave(document: PdfDocument, saveOptions: DocumentSaveOptions): Boolean { + pspdfkitWidgetCallbacks?.onDocumentSaved( + document.uid, + document.documentSource.fileUri?.path + ) {} + return true + } } \ 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 d630d8e6..d829721d 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt @@ -26,8 +26,10 @@ import com.pspdfkit.document.processor.PdfProcessor import com.pspdfkit.document.processor.PdfProcessor.ProcessorProgress import com.pspdfkit.document.processor.PdfProcessorTask import com.pspdfkit.flutter.pspdfkit.AnnotationConfigurationAdaptor.Companion.convertAnnotationConfigurations +import com.pspdfkit.flutter.pspdfkit.api.NutrientEventsCallbacks import com.pspdfkit.flutter.pspdfkit.api.PspdfkitWidgetCallbacks import com.pspdfkit.flutter.pspdfkit.api.PspdfkitWidgetControllerApi +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.DocumentJsonDataProvider @@ -70,6 +72,7 @@ internal class PSPDFKitView( private val pdfUiFragment: PdfUiFragment private var fragmentCallbacks: FlutterPdfUiFragmentCallbacks? = null private val pspdfkitViewImpl: PspdfkitViewImpl = PspdfkitViewImpl() + private val nutrientEventsCallbacks: NutrientEventsCallbacks = NutrientEventsCallbacks(messenger, "events.callbacks.$id") private val widgetCallbacks: PspdfkitWidgetCallbacks = PspdfkitWidgetCallbacks(messenger, "widget.callbacks.$id") @@ -171,6 +174,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) + pspdfkitViewImpl.setEventDispatcher(flutterEventsHelper) PspdfkitWidgetControllerApi.setUp(messenger, pspdfkitViewImpl, id.toString()) } 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 e61bbcf2..1ef5a780 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitApiImpl.kt @@ -16,6 +16,7 @@ import android.provider.Settings import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity +import com.pspdfkit.PSPDFKit import com.pspdfkit.PSPDFKit.VERSION import com.pspdfkit.PSPDFKit.initialize import com.pspdfkit.document.formatters.DocumentJsonFormatter @@ -31,6 +32,7 @@ import com.pspdfkit.flutter.pspdfkit.api.AndroidPermissionStatus import com.pspdfkit.flutter.pspdfkit.api.AnnotationProcessingMode import com.pspdfkit.flutter.pspdfkit.api.PspdfkitApi import com.pspdfkit.flutter.pspdfkit.api.PspdfkitApiError +import com.pspdfkit.flutter.pspdfkit.events.FlutterAnalyticsClient import com.pspdfkit.flutter.pspdfkit.pdfgeneration.PdfPageAdaptor import com.pspdfkit.flutter.pspdfkit.util.DocumentJsonDataProvider import com.pspdfkit.flutter.pspdfkit.util.Preconditions @@ -66,15 +68,21 @@ import java.nio.charset.StandardCharsets class PspdfkitApiImpl(private var activityPluginBinding: ActivityPluginBinding?) : PspdfkitApi { private var disposable: Disposable? = null + private var analyticsEventClient: FlutterAnalyticsClient? = null fun dispose() { disposable?.dispose() + analyticsEventClient = null } fun setActivityPluginBinding(activityPluginBinding: ActivityPluginBinding?) { this.activityPluginBinding = activityPluginBinding } + fun setAnalyticsEventClient(analyticsEventClient: FlutterAnalyticsClient?) { + this.analyticsEventClient = analyticsEventClient + } + override fun getFrameworkVersion(callback: (Result) -> Unit) { callback(Result.success(VERSION)) } @@ -139,9 +147,6 @@ class PspdfkitApiImpl(private var activityPluginBinding: ActivityPluginBinding?) val configurationAdapterInstant = ConfigurationAdapter( activityPluginBinding?.activity as FragmentActivity, configurationMapInstant ) - val annotationTools = - configurationAdapterInstant.build().configuration.enabledAnnotationTools - annotationTools.add(AnnotationTool.INSTANT_COMMENT_MARKER) val intentInstant = InstantPdfActivityIntentBuilder.fromInstantDocument( activityPluginBinding?.activity as FragmentActivity, serverUrl, jwt ).activityClass(FlutterInstantPdfActivity::class.java) @@ -474,13 +479,13 @@ class PspdfkitApiImpl(private var activityPluginBinding: ActivityPluginBinding?) }) } - override fun importXfdf(xfdfPath: String, callback: (Result) -> Unit) { + override fun importXfdf(xfdfString: String, callback: (Result) -> Unit) { checkNotNull(activityPluginBinding) { "ActivityPluginBinding is null" } val document = Preconditions.requireDocumentNotNull( activityPluginBinding?.activity as PdfActivity, "Pspdfkit.processAnnotations()" ) - val dataProvider = DocumentJsonDataProvider(xfdfPath) + val dataProvider = DocumentJsonDataProvider(xfdfString) // The async parse method is recommended (so you can easily offload parsing from the UI thread). disposable = XfdfFormatter.parseXfdfAsync(document, dataProvider) .subscribeOn(Schedulers.io()) // Specify the thread on which to parse XFDF. @@ -748,6 +753,14 @@ class PspdfkitApiImpl(private var activityPluginBinding: ActivityPluginBinding?) } } + override fun enableAnalyticsEvents(enable: Boolean) { + if (enable) { + analyticsEventClient?.let { PSPDFKit.addAnalyticsClient(it) } + } else { + analyticsEventClient?.let { PSPDFKit.removeAnalyticsClient(it) } + } + } + private fun setLicenseKey(activity: FragmentActivity, licenseKey: String?) { try { initialize( diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitPlugin.java b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitPlugin.java index ff77ef59..5e1fc4a5 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitPlugin.java +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitPlugin.java @@ -40,8 +40,12 @@ import com.pspdfkit.document.processor.PdfProcessorTask; import com.pspdfkit.exceptions.PSPDFKitException; import com.pspdfkit.flutter.pspdfkit.annotations.FlutterAnnotationPresetConfiguration; +import com.pspdfkit.flutter.pspdfkit.api.AnalyticsEventsCallback; +import com.pspdfkit.flutter.pspdfkit.api.NutrientEventsCallbacks; import com.pspdfkit.flutter.pspdfkit.api.PspdfkitApi; import com.pspdfkit.flutter.pspdfkit.api.PspdfkitFlutterApiCallbacks; +import com.pspdfkit.flutter.pspdfkit.events.FlutterAnalyticsClient; +import com.pspdfkit.flutter.pspdfkit.events.FlutterEventsHelper; import com.pspdfkit.flutter.pspdfkit.pdfgeneration.PdfPageAdaptor; import com.pspdfkit.flutter.pspdfkit.util.DocumentJsonDataProvider; import com.pspdfkit.flutter.pspdfkit.util.MeasurementHelper; @@ -151,6 +155,8 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { // Setup the PSPDFKit API. PspdfkitApi.Companion.setUp(binding.getBinaryMessenger(),pspdfkitApi,MESSAGE_CHANNEL_SUFFIX); PspdfkitFlutterApiCallbacks pspdfkitFlutterApiCallbacks = new PspdfkitFlutterApiCallbacks(binding.getBinaryMessenger(),MESSAGE_CHANNEL_SUFFIX); + AnalyticsEventsCallback callback = new AnalyticsEventsCallback(binding.getBinaryMessenger(),MESSAGE_CHANNEL_SUFFIX); + pspdfkitApi.setAnalyticsEventClient(new FlutterAnalyticsClient(callback)); eventDispatcher.setPspdfkitApiCallbacks(new PspdfkitApiCallbacks(pspdfkitFlutterApiCallbacks)); } @@ -268,6 +274,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { Intent intentInstant = InstantPdfActivityIntentBuilder + .Companion .fromInstantDocument(activity, documentUrl, jwt) .activityClass(FlutterInstantPdfActivity.class) .configuration(configurationAdapterInstant.build()) @@ -929,6 +936,7 @@ private void detachActivityPluginBinding() { activityPluginBinding.removeRequestPermissionsResultListener(this); activityPluginBinding = null; pspdfkitApi.setActivityPluginBinding(null); + } } 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 00c34df7..46b618bd 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitViewImpl.kt @@ -18,9 +18,11 @@ import com.pspdfkit.flutter.pspdfkit.AnnotationConfigurationAdaptor.Companion.co import com.pspdfkit.flutter.pspdfkit.annotations.FlutterAnnotationPresetConfiguration import com.pspdfkit.flutter.pspdfkit.api.AnnotationProcessingMode import com.pspdfkit.flutter.pspdfkit.api.AnnotationType +import com.pspdfkit.flutter.pspdfkit.api.NutrientEvent import com.pspdfkit.flutter.pspdfkit.api.PdfRect import com.pspdfkit.flutter.pspdfkit.api.PspdfkitApiError import com.pspdfkit.flutter.pspdfkit.api.PspdfkitWidgetControllerApi +import com.pspdfkit.flutter.pspdfkit.events.FlutterEventsHelper import com.pspdfkit.flutter.pspdfkit.util.DocumentJsonDataProvider import com.pspdfkit.flutter.pspdfkit.util.Preconditions.requireNotNullNotEmpty import com.pspdfkit.flutter.pspdfkit.util.ProcessorHelper.annotationTypeFromString @@ -44,6 +46,7 @@ import java.util.Locale class PspdfkitViewImpl : PspdfkitWidgetControllerApi { private var pdfUiFragment: PdfUiFragment? = null private var disposable: Disposable? = null + private var eventDispatcher: FlutterEventsHelper? = null /** * Sets the PdfFragment to be used by the controller. @@ -54,6 +57,10 @@ class PspdfkitViewImpl : PspdfkitWidgetControllerApi { this.pdfUiFragment = pdfFragment } + fun setEventDispatcher(eventDispatcher: FlutterEventsHelper) { + this.eventDispatcher = eventDispatcher + } + /** * Disposes the controller and releases all resources. */ @@ -617,4 +624,12 @@ class PspdfkitViewImpl : PspdfkitWidgetControllerApi { } } } + + override fun addEventListener(event: NutrientEvent) { + eventDispatcher?.setEventListener(pdfUiFragment!!, event) + } + + override fun removeEventListener(event: NutrientEvent) { + eventDispatcher?.removeEventListener(pdfUiFragment!!, event) + } } \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/PspdfkitApi.g.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/PspdfkitApi.g.kt index fd7eb406..c1904f11 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/PspdfkitApi.g.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/api/PspdfkitApi.g.kt @@ -239,6 +239,33 @@ enum class PdfFormFieldTypes(val raw: Int) { } } +enum class NutrientEvent(val raw: Int) { + /** Event triggered when annotations are created. */ + ANNOTATIONS_CREATED(0), + /** Event triggered when annotations are pressed. */ + ANNOTATIONS_DESELECTED(1), + /** Event triggered when annotations are updated. */ + ANNOTATIONS_UPDATED(2), + /** Event triggered when annotations are deleted. */ + ANNOTATIONS_DELETED(3), + /** Event triggered when annotations are focused. */ + ANNOTATIONS_SELECTED(4), + /** Event triggered when form field values are updated. */ + FORM_FIELD_VALUES_UPDATED(5), + /** Event triggered when form fields are loaded. */ + FORM_FIELD_SELECTED(6), + /** Event triggered when form fields are about to be saved. */ + FORM_FIELD_DESELECTED(7), + /** Event triggered when text selection changes. */ + TEXT_SELECTION_CHANGED(8); + + companion object { + fun ofRaw(raw: Int): NutrientEvent? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** Generated class from Pigeon that represents data sent in messages. */ data class PdfRect ( val x: Double, @@ -427,6 +454,27 @@ data class FormFieldData ( ) } } + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PointF ( + val x: Double, + val y: Double +) + { + companion object { + fun fromList(pigeonVar_list: List): PointF { + val x = pigeonVar_list[0] as Double + val y = pigeonVar_list[1] as Double + return PointF(x, y) + } + } + fun toList(): List { + return listOf( + x, + y, + ) + } +} private open class PspdfkitApiPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -471,30 +519,40 @@ private open class PspdfkitApiPigeonCodec : StandardMessageCodec() { } } 137.toByte() -> { + return (readValue(buffer) as Long?)?.let { + NutrientEvent.ofRaw(it.toInt()) + } + } + 138.toByte() -> { return (readValue(buffer) as? List)?.let { PdfRect.fromList(it) } } - 138.toByte() -> { + 139.toByte() -> { return (readValue(buffer) as? List)?.let { PageInfo.fromList(it) } } - 139.toByte() -> { + 140.toByte() -> { return (readValue(buffer) as? List)?.let { DocumentSaveOptions.fromList(it) } } - 140.toByte() -> { + 141.toByte() -> { return (readValue(buffer) as? List)?.let { PdfFormOption.fromList(it) } } - 141.toByte() -> { + 142.toByte() -> { return (readValue(buffer) as? List)?.let { FormFieldData.fromList(it) } } + 143.toByte() -> { + return (readValue(buffer) as? List)?.let { + PointF.fromList(it) + } + } else -> super.readValueOfType(type, buffer) } } @@ -532,24 +590,32 @@ private open class PspdfkitApiPigeonCodec : StandardMessageCodec() { stream.write(136) writeValue(stream, value.raw) } - is PdfRect -> { + is NutrientEvent -> { stream.write(137) + writeValue(stream, value.raw) + } + is PdfRect -> { + stream.write(138) writeValue(stream, value.toList()) } is PageInfo -> { - stream.write(138) + stream.write(139) writeValue(stream, value.toList()) } is DocumentSaveOptions -> { - stream.write(139) + stream.write(140) writeValue(stream, value.toList()) } is PdfFormOption -> { - stream.write(140) + stream.write(141) writeValue(stream, value.toList()) } is FormFieldData -> { - stream.write(141) + stream.write(142) + writeValue(stream, value.toList()) + } + is PointF -> { + stream.write(143) writeValue(stream, value.toList()) } else -> super.writeValue(stream, value) @@ -606,6 +672,8 @@ interface PspdfkitApi { */ fun generatePdfFromHtmlString(html: String, outPutFile: String, options: Map?, callback: (Result) -> Unit) fun generatePdfFromHtmlUri(htmlUri: String, outPutFile: String, options: Map?, callback: (Result) -> Unit) + /** Configure Nutrient Analytics events. */ + fun enableAnalyticsEvents(enable: Boolean) companion object { /** The codec used by PspdfkitApi. */ @@ -1187,6 +1255,24 @@ interface PspdfkitApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pspdfkit_flutter.PspdfkitApi.enableAnalyticsEvents$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val enableArg = args[0] as Boolean + val wrapped: List = try { + api.enableAnalyticsEvents(enableArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } @@ -1486,6 +1572,8 @@ interface PspdfkitWidgetControllerApi { * Returns a [Future] that completes with the zoom scale of the given page. */ fun getZoomScale(pageIndex: Long, callback: (Result) -> Unit) + fun addEventListener(event: NutrientEvent) + fun removeEventListener(event: NutrientEvent) companion object { /** The codec used by PspdfkitWidgetControllerApi. */ @@ -1817,6 +1905,42 @@ interface PspdfkitWidgetControllerApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pspdfkit_flutter.PspdfkitWidgetControllerApi.addEventListener$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val eventArg = args[0] as NutrientEvent + val wrapped: List = try { + api.addEventListener(eventArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pspdfkit_flutter.PspdfkitWidgetControllerApi.removeEventListener$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val eventArg = args[0] as NutrientEvent + val wrapped: List = try { + api.removeEventListener(eventArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } @@ -2236,4 +2360,90 @@ class PspdfkitWidgetCallbacks(private val binaryMessenger: BinaryMessenger, priv } } } + fun onPageClick(documentIdArg: String, pageIndexArg: Long, pointArg: PointF?, annotationArg: Any?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.pspdfkit_flutter.PspdfkitWidgetCallbacks.onPageClick$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(documentIdArg, pageIndexArg, pointArg, annotationArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(PspdfkitApiError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + fun onDocumentSaved(documentIdArg: String, pathArg: String?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.pspdfkit_flutter.PspdfkitWidgetCallbacks.onDocumentSaved$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(documentIdArg, pathArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(PspdfkitApiError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class NutrientEventsCallbacks(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by NutrientEventsCallbacks. */ + val codec: MessageCodec by lazy { + PspdfkitApiPigeonCodec() + } + } + fun onEvent(eventArg: NutrientEvent, dataArg: Any?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.pspdfkit_flutter.NutrientEventsCallbacks.onEvent$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(eventArg, dataArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(PspdfkitApiError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class AnalyticsEventsCallback(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by AnalyticsEventsCallback. */ + val codec: MessageCodec by lazy { + PspdfkitApiPigeonCodec() + } + } + fun onEvent(eventArg: String, attributesArg: Map?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.pspdfkit_flutter.AnalyticsEventsCallback.onEvent$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(eventArg, attributesArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(PspdfkitApiError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } } diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterAnalyticsClient.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterAnalyticsClient.kt new file mode 100644 index 00000000..99e73211 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterAnalyticsClient.kt @@ -0,0 +1,91 @@ +package com.pspdfkit.flutter.pspdfkit.events + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import com.pspdfkit.analytics.AnalyticsClient +import com.pspdfkit.flutter.pspdfkit.api.AnalyticsEventsCallback + +class FlutterAnalyticsClient(private var eventsCallback: AnalyticsEventsCallback): AnalyticsClient { + + private val mainHandler = Handler(Looper.getMainLooper()) + + override fun onEvent(event: String, bundle: Bundle?) { + mainHandler.post { + if (bundle != null) { + val map = bundleToMap(bundle) + eventsCallback.onEvent(event, map){} + } else { + eventsCallback.onEvent(event, null){} + } + } + } + + private fun bundleToMap(bundle: Bundle): Map { + val map = mutableMapOf() + for (key in bundle.keySet()) { + val value = when (val obj = bundle.get(key)) { + // Basic types supported by Flutter + is String -> obj + is Boolean -> obj + is Int -> obj.toLong() // Convert to Long for consistency + is Long -> obj + is Float -> obj.toDouble() // Convert to Double for consistency + is Double -> obj + + // Arrays - convert to List + is BooleanArray -> obj.toList() + is IntArray -> obj.map { it.toLong() } + is LongArray -> obj.toList() + is FloatArray -> obj.map { it.toDouble() } + is DoubleArray -> obj.toList() + is Array<*> -> obj.map { convertToFlutterType(it) } + + // Nested structures + is Bundle -> bundleToMap(obj) + is List<*> -> obj.map { convertToFlutterType(it) } + is Map<*, *> -> obj.entries.associate { + it.key.toString() to convertToFlutterType(it.value) + } + + // Convert unsupported types to string representation + else -> obj?.toString() + } + if (value != null) { + map[key] = value + } + } + return map + } + + private fun convertToFlutterType(value: Any?): Any? { + return when (value) { + // Basic types supported by Flutter + is String, is Boolean, is Long, is Double -> value + // Convert to supported number types + is Int -> value.toLong() + is Float -> value.toDouble() + is Short -> value.toLong() + is Byte -> value.toLong() + is Char -> value.toString() + + // Arrays and Collections + is BooleanArray -> value.toList() + is IntArray -> value.map { it.toLong() } + is LongArray -> value.toList() + is FloatArray -> value.map { it.toDouble() } + is DoubleArray -> value.toList() + is Array<*> -> value.map { convertToFlutterType(it) } + is List<*> -> value.map { convertToFlutterType(it) } + is Map<*, *> -> value.entries.associate { + it.key.toString() to convertToFlutterType(it.value) + } + + // Nested Bundle + is Bundle -> bundleToMap(value) + + // Convert unsupported types to string + else -> value?.toString() + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..4e6a7a68 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/events/FlutterEventsHelper.kt @@ -0,0 +1,250 @@ +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.api.NutrientEvent +import com.pspdfkit.flutter.pspdfkit.api.NutrientEventsCallbacks +import com.pspdfkit.listeners.DocumentListener +import com.pspdfkit.ui.PdfUiFragment +import com.pspdfkit.ui.special_mode.controller.AnnotationSelectionController +import com.pspdfkit.ui.special_mode.manager.AnnotationManager +import com.pspdfkit.ui.special_mode.manager.FormManager +import com.pspdfkit.ui.special_mode.manager.TextSelectionManager + +class FlutterEventsHelper(val eventCallbacks: NutrientEventsCallbacks? = null) { + + private val eventsMap: MutableMap = mutableMapOf() + + fun setEventListener(pdfUiFragment: PdfUiFragment, event: NutrientEvent) { + // Don't add duplicate listeners + if (eventsMap.containsKey(event)) { + return + } + + when (event) { + NutrientEvent.ANNOTATIONS_CREATED -> { + val listener = object : AnnotationProvider.OnAnnotationUpdatedListener { + override fun onAnnotationCreated(p0: Annotation) { + eventCallbacks?.onEvent( + NutrientEvent.ANNOTATIONS_CREATED, + mapOf("annotation" to p0.toInstantJson()), + ) { } + } + + override fun onAnnotationUpdated(annotation: Annotation) { + } + + override fun onAnnotationRemoved(p0: Annotation) { + } + + override fun onAnnotationZOrderChanged( + p0: Int, + p1: MutableList, + p2: MutableList + ) { + } + } + pdfUiFragment.pdfFragment?.addOnAnnotationUpdatedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.ANNOTATIONS_UPDATED -> { + val listener = object : AnnotationProvider.OnAnnotationUpdatedListener { + override fun onAnnotationCreated(p0: Annotation) {} + override fun onAnnotationUpdated(annotation: Annotation) { + eventCallbacks?.onEvent( + NutrientEvent.ANNOTATIONS_UPDATED, + mapOf("annotation" to annotation.toInstantJson()), + ) { } + } + + override fun onAnnotationRemoved(p0: Annotation) { + + } + + override fun onAnnotationZOrderChanged( + p0: Int, + p1: MutableList, + p2: MutableList + ) { + } + } + pdfUiFragment.pdfFragment?.addOnAnnotationUpdatedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.ANNOTATIONS_DELETED -> { + val listener = object : AnnotationProvider.OnAnnotationUpdatedListener { + override fun onAnnotationCreated(p0: Annotation) {} + override fun onAnnotationUpdated(annotation: Annotation) {} + override fun onAnnotationRemoved(annotation: Annotation) { + eventCallbacks?.onEvent( + NutrientEvent.ANNOTATIONS_DELETED, + mapOf("annotation" to annotation.toInstantJson()), + ) { } + } + + override fun onAnnotationZOrderChanged( + p0: Int, + p1: MutableList, + p2: MutableList + ) { + } + } + pdfUiFragment.pdfFragment?.addOnAnnotationUpdatedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.ANNOTATIONS_SELECTED -> { + val listener = object : AnnotationManager.OnAnnotationSelectedListener { + override fun onPrepareAnnotationSelection( + controller: AnnotationSelectionController, + annotation: Annotation, + isMultipleSelection: Boolean + ): Boolean { + return true + } + + override fun onAnnotationSelected( + annotation: Annotation, + isMultipleSelection: Boolean + ) { + eventCallbacks?.onEvent( + NutrientEvent.ANNOTATIONS_SELECTED, + mapOf( + "annotation" to annotation.toInstantJson(), + "isMultipleSelection" to isMultipleSelection + ) + ) { } + } + } + pdfUiFragment.pdfFragment?.addOnAnnotationSelectedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.ANNOTATIONS_DESELECTED -> { + val listener = + AnnotationManager.OnAnnotationDeselectedListener { annotation, isMultipleSelection -> + eventCallbacks?.onEvent( + NutrientEvent.ANNOTATIONS_DESELECTED, + mapOf( + "annotation" to annotation.toInstantJson(), + "isMultipleSelection" to isMultipleSelection + ) + ) { } + } + pdfUiFragment.pdfFragment?.addOnAnnotationDeselectedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.TEXT_SELECTION_CHANGED -> { + val listener = object : TextSelectionManager.OnTextSelectionChangeListener { + + override fun onBeforeTextSelectionChange( + p0: TextSelection?, + p1: TextSelection? + ): Boolean { + eventCallbacks?.onEvent( + event, mapOf("text" to p1?.text), + ) { } + return true + } + + override fun onAfterTextSelectionChange( + p0: TextSelection?, + p1: TextSelection? + ) { + eventCallbacks?.onEvent( + event, mapOf("text" to p1?.text), + ) { + + } + } + } + pdfUiFragment.pdfFragment?.addOnTextSelectionChangeListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.FORM_FIELD_VALUES_UPDATED -> { + val listener = FormManager.OnFormElementUpdatedListener { + eventCallbacks?.onEvent( + NutrientEvent.FORM_FIELD_VALUES_UPDATED, + mapOf( + "formElement" to it.fullyQualifiedName, + "annotation" to it.annotation.toInstantJson() + ) + ) { } + } + pdfUiFragment.pdfFragment?.addOnFormElementUpdatedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.FORM_FIELD_SELECTED -> { + val listener = FormManager.OnFormElementSelectedListener { formElement -> + eventCallbacks?.onEvent( + NutrientEvent.FORM_FIELD_SELECTED, + mapOf( + "formElement" to formElement.fullyQualifiedName, + "annotation" to formElement.annotation.toInstantJson() + ) + ) { } + } + pdfUiFragment.pdfFragment?.addOnFormElementSelectedListener(listener) + eventsMap[event] = listener + } + + NutrientEvent.FORM_FIELD_DESELECTED -> { + val listener = + FormManager.OnFormElementDeselectedListener { formElement, p1 -> + eventCallbacks?.onEvent( + NutrientEvent.FORM_FIELD_DESELECTED, + mapOf( + "formElement" to formElement.fullyQualifiedName, + "isDeselected" to p1, + "annotation" to formElement.annotation.toInstantJson() + ) + ) { } + } + pdfUiFragment.pdfFragment?.addOnFormElementDeselectedListener(listener) + eventsMap[event] = listener + } + } + } + + fun removeEventListener(fragment: PdfUiFragment, event: NutrientEvent) { + val listener = eventsMap[event] ?: return + when (event) { + NutrientEvent.ANNOTATIONS_CREATED, + NutrientEvent.ANNOTATIONS_DELETED, + NutrientEvent.ANNOTATIONS_UPDATED -> { + fragment.pdfFragment?.removeOnAnnotationUpdatedListener(listener as AnnotationProvider.OnAnnotationUpdatedListener) + } + + NutrientEvent.ANNOTATIONS_SELECTED -> { + fragment.pdfFragment?.removeOnAnnotationSelectedListener(listener as AnnotationManager.OnAnnotationSelectedListener) + } + + NutrientEvent.TEXT_SELECTION_CHANGED -> { + fragment.pdfFragment?.removeOnTextSelectionChangeListener(listener as TextSelectionManager.OnTextSelectionChangeListener) + } + + NutrientEvent.FORM_FIELD_VALUES_UPDATED -> { + fragment.pdfFragment?.removeOnFormElementViewUpdatedListener(listener as FormManager.OnFormElementViewUpdatedListener) + } + + NutrientEvent.FORM_FIELD_SELECTED -> { + fragment.pdfFragment?.removeOnFormElementSelectedListener(listener as FormManager.OnFormElementSelectedListener) + } + + NutrientEvent.ANNOTATIONS_DESELECTED -> { + fragment.pdfFragment?.removeOnAnnotationDeselectedListener(listener as AnnotationManager.OnAnnotationDeselectedListener) + } + + NutrientEvent.FORM_FIELD_DESELECTED -> { + fragment.pdfFragment?.removeOnFormElementDeselectedListener(listener as FormManager.OnFormElementDeselectedListener) + } + } + eventsMap.remove(event) + } +} diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/forms/FormHelper.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/forms/FormHelper.kt index be1590df..e2999220 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/forms/FormHelper.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/forms/FormHelper.kt @@ -9,11 +9,9 @@ import com.pspdfkit.forms.FormType import com.pspdfkit.forms.ListBoxFormField import com.pspdfkit.forms.RadioButtonFormField import com.pspdfkit.forms.TextFormField -import com.pspdfkit.internal.forms.getFormatString -import com.pspdfkit.internal.forms.getInputFormat -object FormHelper { +object FormHelper { private val formFieldTypeMap: Map = mapOf( FormType.TEXT to PdfFormFieldTypes.TEXT, FormType.CHECKBOX to PdfFormFieldTypes.CHECKBOX, @@ -41,8 +39,6 @@ object FormHelper { formFieldsMap["alternateFieldName"] = formField.alternateFieldName formFieldsMap["mappingName"] = formField.mappingName formFieldsMap["annotation"] = formField.formElement.annotation.toInstantJson() - formFieldsMap["inputFormatString"] = formField.formElement.getFormatString() ?: "" - formFieldsMap["inputFormat"] = formField.formElement.getInputFormat().name // Extract the specific form field properties. formFieldsMap.putAll(getSpecificFormFieldProperties(formField, formFieldsMap)) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index ee6e3a0d..48a46e91 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -7,8 +7,7 @@ ~ This notice may not be removed from this file. --> + xmlns:tools="http://schemas.android.com/tools">