Skip to content

Commit

Permalink
Allow styling text in composer when selecting it with native actions
Browse files Browse the repository at this point in the history
  • Loading branch information
qfrank committed Nov 9, 2022
1 parent 1dc0fd4 commit ee26cbd
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package im.status.ethereum.module;

import android.view.ActionMode;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.textinput.ReactEditText;

import javax.annotation.Nonnull;

class RNSelectableTextInputModule extends ReactContextBaseJavaModule {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package im.status.ethereum.module;


import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.Menu;
import android.view.MenuItem;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
Expand All @@ -17,7 +15,6 @@
import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -26,7 +23,6 @@ public class RNSelectableTextInputViewManager extends ReactViewManager {
public static final String REACT_CLASS = "RNSelectableTextInput";
private String[] _menuItems = new String[0];


@Override
public String getName() {
return REACT_CLASS;
Expand All @@ -37,31 +33,21 @@ public ReactViewGroup createViewInstance(ThemedReactContext context) {
return new ReactViewGroup(context);
}


@ReactProp(name = "menuItems")
public void setMenuItems(ReactViewGroup reactViewGroup, ReadableArray items) {
if(items != null) {
List<String> result = new ArrayList<String>(items.size());
for (int i = 0; i < items.size(); i++) {
result.add(items.getString(i));
}

this._menuItems = result.toArray(new String[items.size()]);

// PREVIOUS CODE
// registerSelectionListener(result.toArray(new String[items.size()]), reactViewGroup);
}

}

public void registerSelectionListener(final ReactEditText view) {
view.setCustomSelectionActionModeCallback(new Callback() {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Called when action mode is first created. The menu supplied
// will be used to generate action buttons for the action mode
// Android Smart Linkify feature pushes extra options into the menu
// and would override the generated menu items
menu.clear();
for (int i = 0; i < _menuItems.length; i++) {
menu.add(0, i, 0, _menuItems[i]);
Expand All @@ -76,7 +62,6 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) {

@Override
public void onDestroyActionMode(ActionMode mode) {
// Called when an action mode is about to be exited and
}

@Override
Expand Down
181 changes: 94 additions & 87 deletions src/status_im/ui2/screens/chat/composer/input.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
(declare selectable-text-input)

(re-frame/reg-fx
:chat.ui/clear-inputs
(fn []
(reset! input-texts {})
(reset! mentions-enabled {})
(reset! chat-input-key 1)))
:chat.ui/clear-inputs
(fn []
(reset! input-texts {})
(reset! mentions-enabled {})
(reset! chat-input-key 1)))

(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
Expand Down Expand Up @@ -76,13 +76,13 @@
;; typing. Timeout might be canceled on `on-change`.
(when platform/ios?
(reset!
timeout-id
(utils.utils/set-timeout
#(>evt [::mentions/on-selection-change
{:start start
:end end}
mentionable-users])
50)))
timeout-id
(utils.utils/set-timeout
#(>evt [::mentions/on-selection-change
{:start start
:end end}
mentionable-users])
50)))
;; NOTE(rasom): on Android we dispatch event only in case if there
;; was no text changes during last 50ms. `on-selection-change` is
;; dispatched after `on-change`, that's why there is no another way
Expand Down Expand Up @@ -131,11 +131,11 @@
(swap! mentions-enabled assoc chat-id true))

(>evt
[::mentions/on-text-input
{:new-text text
:previous-text previous-text
:start start
:end end}])
[::mentions/on-text-input
{:new-text text
:previous-text previous-text
:start start
:end end}])
;; NOTE(rasom): on Android `on-text-input` is dispatched after
;; `on-change`, that's why mention suggestions are calculated
;; on `on-change`
Expand Down Expand Up @@ -176,11 +176,11 @@
:on-text-input (partial on-text-input mentionable-users chat-id)}
(fn []
(if mentions-enabled
(for [[idx [type text]] (map-indexed
(fn [idx item]
[idx item])
(<sub [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
(for [[index [type text]] (map-indexed
(fn [idx item]
[idx item])
(<sub [:chat/input-with-mentions]))]
^{:key (str index "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color colors/primary-50}})
text])
(get @input-texts chat-id)))]))
Expand All @@ -202,30 +202,32 @@
tail (subs full-text selection-end)]
(str head content tail)))

(def first-level-menus {:cut (fn [{:keys [content] :as params}]
(let [new-text (calculate-input-text params "")]
(react/copy-to-clipboard content)
(update-input-text params new-text)))
(def first-level-menus {:cut (fn [{:keys [content] :as params}]
(let [new-text (calculate-input-text params "")]
(react/copy-to-clipboard content)
(update-input-text params new-text)))

:copy-to-clipboard (fn [{:keys [content]}]
(react/copy-to-clipboard content))

:paste (fn [params]
(let [callback (fn [paste-content]
(let [content (string/trim paste-content)
new-text (calculate-input-text params content)]
(update-input-text params new-text)))]
(react/get-from-clipboard callback)))

:biu (fn [{:keys [first-level text-input-handle menu-items selection-start selection-end]}]
(let [show-menu #(.startActionMode (selectable-text-input-manager) text-input-handle)]
(reset! first-level false)
(reset! menu-items second-level-menu-items)
;to avoid something discusting like this https://lightrun.com/answers/facebook-react-native-textinput-controlled-selection-broken-on-both-ios-and-android
;use native invoke instead! do not use setNativeProps! e.g. (.setNativeProps ^js text-input (clj->js {:selection {:start selection-start :end selection-end}}))
(.setSelection (selectable-text-input-manager) text-input-handle selection-start selection-end)
;set a delay to wait RNSelectableTextInputViewManager#_menuItems get updated, need figure out a better way
(js/setTimeout show-menu 200)))})
:paste (fn [params]
(let [callback (fn [paste-content]
(let [content (string/trim paste-content)
new-text (calculate-input-text params content)]
(update-input-text params new-text)))]
(react/get-from-clipboard callback)))

:biu (fn [{:keys [first-level text-input-handle menu-items selection-start selection-end]}]
(let [show-menu #(-> (selectable-text-input-manager)
(.startActionMode text-input-handle))]
(reset! first-level false)
(reset! menu-items second-level-menu-items)
;to avoid something disgusting like this https://lightrun.com/answers/facebook-react-native-textinput-controlled-selection-broken-on-both-ios-and-android
;use native invoke instead! do not use setNativeProps! e.g. (.setNativeProps ^js text-input (clj->js {:selection {:start selection-start :end selection-end}}))
(-> (selectable-text-input-manager)
(.setSelection text-input-handle selection-start selection-end))
;set a delay to wait RNSelectableTextInputViewManager#_menuItems get updated, need figure out a better way
(js/setTimeout show-menu 200)))})

(def first-level-menu-items (map i18n/label (keys first-level-menus)))

Expand All @@ -239,9 +241,9 @@
(update-input-text params new-text)
(reset-to-first-level-menu first-level menu-items)))

(def second-level-menus {:bold #(append-markdown-char % "**")
(def second-level-menus {:bold #(append-markdown-char % "**")

:italic #(append-markdown-char % "*")
:italic #(append-markdown-char % "*")

:strikethrough #(append-markdown-char % "~~")})

Expand All @@ -258,46 +260,51 @@
menu-items (reagent/atom first-level-menu-items)
first-level (reagent/atom true)]
(reagent/create-class
{:component-did-mount (fn [self]
(when @text-input-ref
(let [self-handle (rn/find-node-handle self)
text-input-handle (rn/find-node-handle @text-input-ref)]
(.setupMenuItems (selectable-text-input-manager) self-handle text-input-handle))))
:render
(fn [_]
(let [old-ref (:ref props)
ref #(do (reset! text-input-ref %)
(when old-ref
(quo.react/set-ref-val! old-ref %)))
old-on-selection-change (:on-selection-change props)
on-selection-change (fn [args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)
no-selection (<= (- end start) 0)]
(when no-selection
(do (.hideLastActionMode (selectable-text-input-manager))
(reset-to-first-level-menu first-level menu-items))))
(when old-on-selection-change
(old-on-selection-change args)))
props (merge props {:ref ref
:style nil
:on-selection-change on-selection-change
:on-selection (fn [event]
(let [native-event (.-nativeEvent event)
native-event (types/js->clj native-event)
{:keys [eventType content selectionStart selectionEnd]} native-event
full-text (:input-text (<sub [:chats/current-chat-inputs]))]
(on-menu-item-touched {:first-level first-level
:event-type eventType
:content content
:selection-start selectionStart
:selection-end selectionEnd
:text-input @text-input-ref
:text-input-handle (rn/find-node-handle @text-input-ref)
:full-text full-text
:menu-items menu-items
:chat-id chat-id})))})]
[rn-selectable-text-input {:menuItems @menu-items :style style}
[rn/text-input props
[children]]]))})))
{:component-did-mount
(fn [this]
(when @text-input-ref
(let [selectable-text-input-handle (rn/find-node-handle this)
text-input-handle (rn/find-node-handle @text-input-ref)]
(-> (selectable-text-input-manager)
(.setupMenuItems selectable-text-input-handle text-input-handle)))))

:render
(fn [_]
(let [old-ref (:ref props)
ref #(do (reset! text-input-ref %)
(when old-ref
(quo.react/set-ref-val! old-ref %)))
old-on-selection-change (:on-selection-change props)
on-selection-change (fn [args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)
no-selection (<= (- end start) 0)]
(when no-selection
(do (-> (selectable-text-input-manager)
.hideLastActionMode)
(reset-to-first-level-menu first-level menu-items))))
(when old-on-selection-change
(old-on-selection-change args)))
on-selection (fn [event]
(let [native-event (.-nativeEvent event)
native-event (types/js->clj native-event)
{:keys [eventType content selectionStart selectionEnd]} native-event
full-text (:input-text (<sub [:chats/current-chat-inputs]))]
(on-menu-item-touched {:first-level first-level
:event-type eventType
:content content
:selection-start selectionStart
:selection-end selectionEnd
:text-input @text-input-ref
:text-input-handle (rn/find-node-handle @text-input-ref)
:full-text full-text
:menu-items menu-items
:chat-id chat-id})))
props (merge props {:ref ref
:style nil
:on-selection-change on-selection-change
:on-selection on-selection})]
[rn-selectable-text-input {:menuItems @menu-items :style style}
[rn/text-input props
[children]]]))})))

0 comments on commit ee26cbd

Please sign in to comment.