diff --git a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java index 8c759756..51c1b2d7 100644 --- a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java +++ b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java @@ -11,6 +11,9 @@ import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; +import com.pspdfkit.react.events.PdfViewAnnotationChangedEvent; +import com.pspdfkit.react.events.PdfViewAnnotationTappedEvent; +import com.pspdfkit.react.events.PdfViewDocumentSavedEvent; import com.pspdfkit.react.events.PdfViewStateChangedEvent; import com.pspdfkit.views.PdfView; @@ -25,6 +28,7 @@ public class ReactPdfViewManager extends ViewGroupManager { public static final int COMMAND_ENTER_ANNOTATION_CREATION_MODE = 1; public static final int COMMAND_EXIT_CURRENTLY_ACTIVE_MODE = 2; + public static final int COMMAND_SAVE_CURRENT_DOCUMENT = 3; @Override public String getName() { @@ -58,7 +62,9 @@ public Map getCommandsMap() { "enterAnnotationCreationMode", COMMAND_ENTER_ANNOTATION_CREATION_MODE, "exitCurrentlyActiveMode", - COMMAND_EXIT_CURRENTLY_ACTIVE_MODE); + COMMAND_EXIT_CURRENTLY_ACTIVE_MODE, + "saveCurrentDocument", + COMMAND_SAVE_CURRENT_DOCUMENT); } @ReactProp(name = "fragmentTag") @@ -82,10 +88,18 @@ public void setPageIndex(PdfView view, int pageIndex) { view.setPageIndex(pageIndex); } + @ReactProp(name = "disableDefaultActionForTappedAnnotations") + public void setDisableDefaultActionForTappedAnnotations(PdfView view, boolean disableDefaultActionForTappedAnnotations) { + view.setDisableDefaultActionForTappedAnnotations(disableDefaultActionForTappedAnnotations); + } + @Nullable @Override public Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.of(PdfViewStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onStateChanged")); + return MapBuilder.of(PdfViewStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onStateChanged"), + PdfViewDocumentSavedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDocumentSaved"), + PdfViewAnnotationTappedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onAnnotationTapped"), + PdfViewAnnotationChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onAnnotationsChanged")); } @Override @@ -97,6 +111,9 @@ public void receiveCommand(PdfView root, int commandId, @Nullable ReadableArray case COMMAND_EXIT_CURRENTLY_ACTIVE_MODE: root.exitCurrentlyActiveMode(); break; + case COMMAND_SAVE_CURRENT_DOCUMENT: + root.saveCurrentDocument(); + break; } } diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationChangedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationChangedEvent.java new file mode 100644 index 00000000..21c98140 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationChangedEvent.java @@ -0,0 +1,75 @@ +package com.pspdfkit.react.events; + +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.pspdfkit.annotations.Annotation; +import com.pspdfkit.react.helper.JsonUtilities; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Event sent by the {@link com.pspdfkit.views.PdfView} when an annotation was selected. + */ +public class PdfViewAnnotationChangedEvent extends Event { + + public static final String EVENT_NAME = "pdfViewAnnotationChanged"; + public static final String EVENT_TYPE_CHANGED = "changed"; + public static final String EVENT_TYPE_ADDED = "added"; + public static final String EVENT_TYPE_REMOVED = "removed"; + + @NonNull + private final String eventType; + + @NonNull + private final Annotation annotation; + + public PdfViewAnnotationChangedEvent(@IdRes int viewId, @NonNull String eventType, @NonNull Annotation annotation) { + super(viewId); + this.eventType = eventType; + this.annotation = annotation; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + try { + Map map = new HashMap<>(); + map.put("change", eventType); + + Map annotationMap; + if (EVENT_TYPE_REMOVED.equalsIgnoreCase(eventType)) { + // For removed annotation we can't get the instant json so manually create something. + annotationMap = new HashMap<>(); + annotationMap.put("name", annotation.getName()); + annotationMap.put("creatorName", annotation.getCreator()); + } else { + JSONObject instantJson = new JSONObject(annotation.toInstantJson()); + annotationMap = JsonUtilities.jsonObjectToMap(instantJson); + } + + List> annotations = new ArrayList<>(); + annotations.add(annotationMap); + map.put("annotations", annotations); + + WritableMap eventData = Arguments.makeNativeMap(map); + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); + } catch (JSONException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java new file mode 100644 index 00000000..a7ee10b9 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java @@ -0,0 +1,49 @@ +package com.pspdfkit.react.events; + +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.pspdfkit.annotations.Annotation; +import com.pspdfkit.react.helper.JsonUtilities; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Map; + +/** + * Event sent by the {@link com.pspdfkit.views.PdfView} when an annotation was selected. + */ +public class PdfViewAnnotationTappedEvent extends Event { + + public static final String EVENT_NAME = "pdfViewAnnotationTapped"; + + @NonNull + private final Annotation annotation; + + public PdfViewAnnotationTappedEvent(@IdRes int viewId, @NonNull Annotation annotation) { + super(viewId); + this.annotation = annotation; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + try { + JSONObject instantJson = new JSONObject(annotation.toInstantJson()); + Map map = JsonUtilities.jsonObjectToMap(instantJson); + WritableMap eventData = Arguments.makeNativeMap(map); + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); + } catch (JSONException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewDocumentSavedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewDocumentSavedEvent.java new file mode 100644 index 00000000..1c5d2555 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewDocumentSavedEvent.java @@ -0,0 +1,31 @@ +package com.pspdfkit.react.events; + +import android.support.annotation.IdRes; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Event sent by the {@link com.pspdfkit.views.PdfView} when the document was saved. + */ +public class PdfViewDocumentSavedEvent extends Event { + + public static final String EVENT_NAME = "pdfViewDocumentSaved"; + + public PdfViewDocumentSavedEvent(@IdRes int viewId) { + super(viewId); + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + WritableMap eventData = Arguments.createMap(); + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); + } +} diff --git a/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java b/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java new file mode 100644 index 00000000..eb483b8b --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java @@ -0,0 +1,49 @@ +package com.pspdfkit.react.helper; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class JsonUtilities { + + /** + * Converts the given {@link JSONObject} to a {@link Map}. + */ + public static Map jsonObjectToMap(JSONObject object) throws JSONException { + Map map = new HashMap<>(object.length()); + + Iterator keysItr = object.keys(); + while (keysItr.hasNext()) { + String key = keysItr.next(); + Object value = object.get(key); + + if (value instanceof JSONArray) { + value = toList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject) value); + } + map.put(key, value); + } + return map; + } + + private static List toList(JSONArray array) throws JSONException { + List list = new ArrayList<>(array.length()); + for (int i = 0; i < array.length(); i++) { + Object value = array.get(i); + if (value instanceof JSONArray) { + value = toList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject) value); + } + list.add(value); + } + return list; + } +} diff --git a/android/src/main/java/com/pspdfkit/views/PdfView.java b/android/src/main/java/com/pspdfkit/views/PdfView.java index 3269b5bb..11f33a32 100644 --- a/android/src/main/java/com/pspdfkit/views/PdfView.java +++ b/android/src/main/java/com/pspdfkit/views/PdfView.java @@ -8,9 +8,7 @@ import android.util.AttributeSet; import android.view.Choreographer; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import com.facebook.react.uimanager.events.EventDispatcher; @@ -51,6 +49,7 @@ public class PdfView extends FrameLayout { private FrameLayout container; private PdfViewModeController pdfViewModeController; + private PdfViewDocumentListener pdfViewDocumentListener; private PdfThumbnailBar pdfThumbnailBar; @@ -114,6 +113,8 @@ public void doFrame(long frameTimeNanos) { public void inject(FragmentManager fragmentManager, EventDispatcher eventDispatcher) { this.fragmentManager = fragmentManager; this.eventDispatcher = eventDispatcher; + pdfViewDocumentListener = new PdfViewDocumentListener(this, + eventDispatcher); } public void setFragmentTag(String fragmentTag) { @@ -152,6 +153,10 @@ public void setPageIndex(int pageIndex) { setupFragment(); } + public void setDisableDefaultActionForTappedAnnotations(boolean disableDefaultActionForTappedAnnotations) { + pdfViewDocumentListener.setDisableDefaultActionForTappedAnnotations(disableDefaultActionForTappedAnnotations); + } + private void setupFragment() { if (fragmentTag != null && configuration != null && document != null) { PdfFragment pdfFragment = (PdfFragment) fragmentManager.findFragmentByTag(fragmentTag); @@ -159,7 +164,7 @@ private void setupFragment() { pdfFragment = PdfFragment.newInstance(document, this.configuration.getConfiguration()); prepareFragment(pdfFragment); } else { - ViewGroup parent = (ViewGroup) pdfFragment.getView().getParent(); + View fragmentView = pdfFragment.getView(); if (pdfFragment.getDocument() != null && !pdfFragment.getDocument().getUid().equals(document.getUid())) { fragmentManager.beginTransaction() .remove(pdfFragment) @@ -168,7 +173,7 @@ private void setupFragment() { // The document changed create a new PdfFragment. pdfFragment = PdfFragment.newInstance(document, this.configuration.getConfiguration()); prepareFragment(pdfFragment); - } else if (parent != this) { + } else if (fragmentView != null && fragmentView.getParent() != this) { // We only need to detach the fragment if the parent view changed. pdfViewModeController.resetToolbars(); fragmentManager.beginTransaction() @@ -206,13 +211,16 @@ public void onPageChanged(@NonNull PdfDocument document, int pageIndex) { pdfFragment.addOnAnnotationEditingModeChangeListener(pdfViewModeController); pdfFragment.addOnFormElementEditingModeChangeListener(pdfViewModeController); pdfFragment.addOnTextSelectionModeChangeListener(pdfViewModeController); + pdfFragment.addDocumentListener(pdfViewDocumentListener); + pdfFragment.addOnAnnotationSelectedListener(pdfViewDocumentListener); + pdfFragment.addOnAnnotationUpdatedListener(pdfViewDocumentListener); setupThumbnailBar(pdfFragment); fragmentManager.beginTransaction() .add(pdfFragment, fragmentTag) .commitNow(); - View fragmentView = pdfFragment.onCreateView(LayoutInflater.from(getContext()), this, null); + View fragmentView = pdfFragment.getView(); addView(fragmentView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @@ -298,4 +306,10 @@ public void exitCurrentlyActiveMode() { fragment.exitCurrentlyActiveMode(); } } + + public void saveCurrentDocument() { + if (fragment != null) { + fragment.save(); + } + } } diff --git a/android/src/main/java/com/pspdfkit/views/PdfViewDocumentListener.java b/android/src/main/java/com/pspdfkit/views/PdfViewDocumentListener.java new file mode 100644 index 00000000..60bba371 --- /dev/null +++ b/android/src/main/java/com/pspdfkit/views/PdfViewDocumentListener.java @@ -0,0 +1,119 @@ +package com.pspdfkit.views; + +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.MotionEvent; + +import com.facebook.react.uimanager.events.EventDispatcher; +import com.pspdfkit.annotations.Annotation; +import com.pspdfkit.annotations.AnnotationProvider; +import com.pspdfkit.document.DocumentSaveOptions; +import com.pspdfkit.document.PdfDocument; +import com.pspdfkit.listeners.DocumentListener; +import com.pspdfkit.react.events.PdfViewAnnotationChangedEvent; +import com.pspdfkit.react.events.PdfViewAnnotationTappedEvent; +import com.pspdfkit.react.events.PdfViewDocumentSavedEvent; +import com.pspdfkit.ui.special_mode.controller.AnnotationSelectionController; +import com.pspdfkit.ui.special_mode.manager.AnnotationManager; + +class PdfViewDocumentListener implements DocumentListener, AnnotationManager.OnAnnotationSelectedListener, AnnotationProvider.OnAnnotationUpdatedListener { + + @NonNull + private final PdfView parent; + + @NonNull + private final EventDispatcher eventDispatcher; + + private boolean disableDefaultActionForTappedAnnotations = false; + + PdfViewDocumentListener(@NonNull PdfView parent, @NonNull EventDispatcher eventDispatcher) { + this.parent = parent; + this.eventDispatcher = eventDispatcher; + } + + + public void setDisableDefaultActionForTappedAnnotations(boolean disableDefaultActionForTappedAnnotations) { + this.disableDefaultActionForTappedAnnotations = disableDefaultActionForTappedAnnotations; + } + + @Override + public void onDocumentLoaded(@NonNull PdfDocument pdfDocument) { + + } + + @Override + public void onDocumentLoadFailed(@NonNull Throwable throwable) { + + } + + @Override + public boolean onDocumentSave(@NonNull PdfDocument pdfDocument, @NonNull DocumentSaveOptions documentSaveOptions) { + return true; + } + + @Override + public void onDocumentSaved(@NonNull PdfDocument pdfDocument) { + eventDispatcher.dispatchEvent(new PdfViewDocumentSavedEvent(parent.getId())); + } + + @Override + public void onDocumentSaveFailed(@NonNull PdfDocument pdfDocument, @NonNull Throwable throwable) { + + } + + @Override + public void onDocumentSaveCancelled(PdfDocument pdfDocument) { + + } + + @Override + public boolean onPageClick(@NonNull PdfDocument pdfDocument, int i, @Nullable MotionEvent motionEvent, @Nullable PointF pointF, @Nullable Annotation annotation) { + return false; + } + + @Override + public boolean onDocumentClick() { + return false; + } + + @Override + public void onPageChanged(@NonNull PdfDocument pdfDocument, int i) { + + } + + @Override + public void onDocumentZoomed(@NonNull PdfDocument pdfDocument, int i, float v) { + + } + + @Override + public void onPageUpdated(@NonNull PdfDocument pdfDocument, int i) { + + } + + @Override + public boolean onPrepareAnnotationSelection(@NonNull AnnotationSelectionController annotationSelectionController, @NonNull Annotation annotation, boolean annotationCreated) { + eventDispatcher.dispatchEvent(new PdfViewAnnotationTappedEvent(parent.getId(), annotation)); + return !disableDefaultActionForTappedAnnotations; + } + + @Override + public void onAnnotationSelected(@NonNull Annotation annotation, boolean annotationCreated) { + } + + @Override + public void onAnnotationCreated(@NonNull Annotation annotation) { + eventDispatcher.dispatchEvent(new PdfViewAnnotationChangedEvent(parent.getId(), PdfViewAnnotationChangedEvent.EVENT_TYPE_ADDED, annotation)); + } + + @Override + public void onAnnotationUpdated(@NonNull Annotation annotation) { + eventDispatcher.dispatchEvent(new PdfViewAnnotationChangedEvent(parent.getId(), PdfViewAnnotationChangedEvent.EVENT_TYPE_CHANGED, annotation)); + } + + @Override + public void onAnnotationRemoved(@NonNull Annotation annotation) { + eventDispatcher.dispatchEvent(new PdfViewAnnotationChangedEvent(parent.getId(), PdfViewAnnotationChangedEvent.EVENT_TYPE_REMOVED, annotation)); + } +} \ No newline at end of file diff --git a/index.js b/index.js index 2589e42e..b4ed70a5 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -// Copyright © 2018 PSPDFKit GmbH. All rights reserved. +// Copyright © 2018 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. @@ -44,13 +44,13 @@ class PSPDFKitView extends React.Component { this.props.onStateChanged(event.nativeEvent); } }; - + _onDocumentSaved = (event) => { if (this.props.onDocumentSaved) { this.props.onDocumentSaved(event.nativeEvent); } }; - + _onAnnotationTapped = (event) => { if (this.props.onAnnotationTapped) { this.props.onAnnotationTapped(event.nativeEvent); @@ -88,6 +88,19 @@ class PSPDFKitView extends React.Component { [] ); }; + + /** + * Saves the currently opened document. + * + * @platform android + */ + saveCurrentDocument = function () { + UIManager.dispatchViewManagerCommand( + findNodeHandle(this.refs.pdfView), + UIManager.RCTPSPDFKitView.Commands.saveCurrentDocument, + [] + ) + } } PSPDFKitView.propTypes = { @@ -123,10 +136,8 @@ PSPDFKitView.propTypes = { showCloseButton: PropTypes.bool, /** * Controls wheter or not the default action for tapped annotations is processed. Defaults to processing the action (false). - * - * @platform ios */ - disableDefaultActionForTappedAnnotations: PropTypes.bool, + disableDefaultActionForTappedAnnotations: PropTypes.bool, /** * Callback that is called when the user tapped the close button. * If you provide this function, you need to handle dismissal yourself. @@ -137,14 +148,15 @@ PSPDFKitView.propTypes = { onCloseButtonPressed: PropTypes.func, /** * Callback that is called when the document is saved. - * - * @platform ios */ onDocumentSaved: PropTypes.func, /** - * Callback that is called when the user taps on an annotation. - * - * @platform ios + * Callback that is called when an annotation is added, changed, or removed. + * Returns an object with the following structure: + * { + * change: "changed"|"added"|"removed", + * annotations: [instantJson] + * } */ onAnnotationTapped: PropTypes.func, /** diff --git a/samples/Catalog/Catalog.android.js b/samples/Catalog/Catalog.android.js index 8d5ff2b9..120abbe2 100644 --- a/samples/Catalog/Catalog.android.js +++ b/samples/Catalog/Catalog.android.js @@ -67,8 +67,8 @@ var examples = [ name: "Open local document", description: "Open document from external storage directory.", action: () => { - requestExternalStoragePermission(function() { - extractFromAssetsIfMissing("Annual Report.pdf", function() { + requestExternalStoragePermission(function () { + extractFromAssetsIfMissing("Annual Report.pdf", function () { PSPDFKit.present(DOCUMENT, {}); }); }); @@ -78,8 +78,8 @@ var examples = [ name: "Open local image document", description: "Open image image document from external storage directory.", action: () => { - requestExternalStoragePermission(function() { - extractFromAssetsIfMissing("android.png", function() { + requestExternalStoragePermission(function () { + extractFromAssetsIfMissing("android.png", function () { PSPDFKit.presentImage(IMAGE_DOCUMENT, CONFIGURATION_IMAGE_DOCUMENT); }); }); @@ -90,7 +90,7 @@ var examples = [ description: "You can configure the builder with dictionary representation of the PSPDFConfiguration object.", action: () => { - requestExternalStoragePermission(function() { + requestExternalStoragePermission(function () { PSPDFKit.present(DOCUMENT, CONFIGURATION); }); } @@ -131,28 +131,28 @@ function extractFromAssetsIfMissing(assetFile, callback) { console.log(assetFile + " does not exist, extracting it from assets folder to the external storage directory."); RNFS.existsAssets(assetFile).then((exist) => { // Check if the file is present in the assets folder. - if(exist) { + if (exist) { // File exists so it can be extracted to the external storage directory. RNFS.copyFileAssets(assetFile, "/sdcard/" + assetFile).then(() => { // File copied successfully from assets folder to external storage directory. callback(); }) - .catch((error) => { - console.log(error); - }); + .catch((error) => { + console.log(error); + }); } else { // File does not exist, it should never happen. throw new Error(assetFile + " couldn't be extracted as it was not found in the project assets folder."); } }) - .catch((error) => { - console.log(error); - }); + .catch((error) => { + console.log(error); + }); } }) - .catch((error) => { - console.log(error); - }); + .catch((error) => { + console.log(error); + }); } async function requestExternalStoragePermission(callback) { @@ -286,7 +286,7 @@ class PdfViewScreen extends Component<{}> { backgroundColor: processColor("lightgrey"), showThumbnailBar: "scrollable" }} - pageIndex={4} + pageIndex={this.state.currentPageIndex} fragmentTag="PDF1" onStateChanged={event => { this.setState({ @@ -298,12 +298,28 @@ class PdfViewScreen extends Component<{}> { }} style={{ flex: 1, color: pspdfkitColor }} /> - - {"Page " + - (this.state.currentPageIndex + 1) + - " of " + - this.state.pageCount} - + + + {"Page " + + (this.state.currentPageIndex + 1) + + " of " + + this.state.pageCount} + + +