diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecFormattingAttributesChange.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecFormattingAttributesChange.java new file mode 100644 index 0000000..74280a5 --- /dev/null +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecFormattingAttributesChange.java @@ -0,0 +1,72 @@ +package org.wordpress.mobile.ReactNativeAztec; + +import android.text.TextUtils; + +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 emitted by Aztec native view when attributes detected. + */ + +public class ReactAztecFormattingAttributesChange extends Event { + + private static final String EVENT_NAME = "topFormatAttributeChanges"; + + private static final String KEY_EVENT_DATA_ATTRIBUTES = "attributes"; + + private static final String KEY_ATTRIBUTES_DATA_LINK = "link"; + + private static final String KEY_LINK_DATA_IS_ACTIVE = "isActive"; + private static final String KEY_LINK_DATA_IS_URL = "url"; + + private String mUrl; + + public ReactAztecFormattingAttributesChange(int viewId) { + super(viewId); + } + + public void setLinkData(String url) { + mUrl = url; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public boolean canCoalesce() { + return false; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + + WritableMap attributesData = Arguments.createMap(); + attributesData.putMap(KEY_ATTRIBUTES_DATA_LINK, getLinkData()); + + WritableMap eventData = Arguments.createMap(); + eventData.putMap(KEY_EVENT_DATA_ATTRIBUTES, attributesData); + + return eventData; + } + + private WritableMap getLinkData() { + WritableMap linkData = Arguments.createMap(); + if (!TextUtils.isEmpty(mUrl)) { + linkData.putBoolean(KEY_LINK_DATA_IS_ACTIVE, true); + linkData.putString(KEY_LINK_DATA_IS_URL, mUrl); + } + else { + linkData.putBoolean(KEY_LINK_DATA_IS_ACTIVE, false); + } + return linkData; + } +} diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index aa33d5b..91a0bee 100644 --- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -47,6 +47,9 @@ public class ReactAztecManager extends SimpleViewManager { private static final int FOCUS_TEXT_INPUT = 1; private static final int BLUR_TEXT_INPUT = 2; private static final int COMMAND_NOTIFY_APPLY_FORMAT = 100; + private static final int COMMAND_NOTIFY_SET_LINK = 101; + private static final int COMMAND_NOTIFY_REMOVE_LINK = 102; + // we define the same codes in ReactAztecText as they have for ReactNative's TextInput, so // it's easier to handle focus between Aztec and TextInput instances on the same screen. @@ -104,6 +107,11 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onActiveFormatsChange"))) + .put( + "topFormatAttributeChanges", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onActiveFormatAttributesChange"))) .put( "topEndEditing", MapBuilder.of( @@ -259,6 +267,11 @@ public void setOnActiveFormatsChange(final ReactAztecText view, boolean onActive view.shouldHandleActiveFormatsChange = onActiveFormatsChange; } + @ReactProp(name = "onActiveFormatAttributesChange", defaultBoolean = false) + public void setOnActiveFormatAttributesChange(final ReactAztecText view, boolean onActiveFormatAttributesChange) { + view.shouldHandleActiveFormatAttributesChange = onActiveFormatAttributesChange; + } + @ReactProp(name = "onSelectionChange", defaultBoolean = false) public void setOnSelectionChange(final ReactAztecText view, boolean onSelectionChange) { view.shouldHandleOnSelectionChange = onSelectionChange; @@ -289,6 +302,8 @@ public Map getCommandsMap() { .put("applyFormat", COMMAND_NOTIFY_APPLY_FORMAT) .put("focusTextInput", mFocusTextInputCommandCode) .put("blurTextInput", mBlurTextInputCommandCode) + .put("setLink", COMMAND_NOTIFY_SET_LINK) + .put("removeLink", COMMAND_NOTIFY_REMOVE_LINK) .build(); } @@ -307,6 +322,16 @@ public void receiveCommand(final ReactAztecText parent, int commandType, @Nullab parent.clearFocusFromJS(); return; } + else if (commandType == COMMAND_NOTIFY_SET_LINK) { + final String url = args.getString(0); + final String title = args.getString(1); + parent.link(url, title); + return; + } + else if (commandType == COMMAND_NOTIFY_REMOVE_LINK) { + parent.removeLink(); + return; + } super.receiveCommand(parent, commandType, args); } diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java index 7563f51..8952f0e 100644 --- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java @@ -4,6 +4,7 @@ import android.graphics.Rect; import android.support.annotation.Nullable; import android.text.Editable; +import android.text.TextUtils; import android.text.InputType; import android.text.TextWatcher; import android.view.inputmethod.InputMethodManager; @@ -51,6 +52,7 @@ public class ReactAztecText extends AztecText { boolean shouldHandleOnBackspace = false; boolean shouldHandleOnSelectionChange = false; boolean shouldHandleActiveFormatsChange = false; + boolean shouldHandleActiveFormatAttributesChange = false; public ReactAztecText(ThemedReactContext reactContext) { super(reactContext); @@ -77,6 +79,7 @@ public boolean onBackspaceKey() { public void onSelectionChanged(int selStart, int selEnd) { ReactAztecText.this.updateToolbarButtons(selStart, selEnd); ReactAztecText.this.propagateSelectionChanges(selStart, selEnd); + ReactAztecText.this.propagateAttributesChange(); } }); this.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); @@ -154,6 +157,8 @@ protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { public void setContentSizeWatcher(ContentSizeWatcher contentSizeWatcher) { mContentSizeWatcher = contentSizeWatcher; + + } private void onContentSizeChange() { @@ -194,6 +199,9 @@ private void updateToolbarButtons(ArrayList appliedStyles) { if (currentStyle == AztecTextFormat.FORMAT_STRIKETHROUGH) { formattingOptions.add("strikethrough"); } + if (currentStyle == AztecTextFormat.FORMAT_LINK) { + formattingOptions.add("link"); + } } // Check if the same formatting event was already sent @@ -205,6 +213,7 @@ private void updateToolbarButtons(ArrayList appliedStyles) { // no need to send any event now return; } + lastSentFormattingOptionsEventString = newOptionsAsString; if (shouldHandleActiveFormatsChange) { @@ -231,6 +240,17 @@ private void propagateSelectionChanges(int selStart, int selEnd) { ); } + private void propagateAttributesChange() { + if (shouldHandleActiveFormatAttributesChange) { + ReactContext reactContext = (ReactContext) getContext(); + EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + ReactAztecFormattingAttributesChange reactAztecFormattingAttributesChange = + new ReactAztecFormattingAttributesChange(getId()); + reactAztecFormattingAttributesChange.setLinkData(linkFormatter.getSelectedUrlWithAnchor().getFirst()); + eventDispatcher.dispatchEvent(reactAztecFormattingAttributesChange); + } + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { onContentSizeChange(); @@ -331,6 +351,9 @@ public void applyFormat(String format) { case ("strikethrough"): newFormats.add(AztecTextFormat.FORMAT_STRIKETHROUGH); break; + case ("link") : + newFormats.add(AztecTextFormat.FORMAT_LINK); + break; } if (newFormats.size() == 0) { @@ -378,6 +401,14 @@ private ArrayList getNewStylesList(ArrayList newFormat return textFormats; } + public void link (String url, String title) { + if (TextUtils.isEmpty(title)) { + title = getSelectedText(); + } + link(url, title, false); + applyFormat("link"); + } + /** * This class will redirect *TextChanged calls to the listeners only in the case where the text * is changed by the user, and not explicitly set by JS.