diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index 03819714b9..5efcfd518a 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -19,6 +19,11 @@ import android.view.WindowManager; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.WebStorage; +import android.content.Context; +import android.view.View; +import android.widget.TextView; +import android.widget.EditText; +import android.view.inputmethod.InputMethodManager; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; @@ -30,6 +35,9 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.UIBlock; +import com.facebook.react.uimanager.NativeViewHierarchyManager; import statusgo.SignalHandler; import statusgo.Statusgo; @@ -1386,5 +1394,24 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL }); } } + + @ReactMethod + public void resetKeyboardInputCursor(final int reactTagToReset, final int selection) { + UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class); + uiManager.addUIBlock(new UIBlock() { + @Override + public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { + InputMethodManager imm = (InputMethodManager) getReactApplicationContext().getBaseContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + View viewToReset = nativeViewHierarchyManager.resolveView(reactTagToReset); + imm.restartInput(viewToReset); + try { + EditText textView = (EditText) viewToReset; + textView.setSelection(selection); + } catch (Exception e) {} + } + } + }); + } } diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index a8949c4d32..585c3880a7 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -9,6 +9,8 @@ (def platform (.-Platform ^js rn)) +(def find-node-handle (.-findNodeHandle ^js rn)) + (def view (reagent/adapt-react-class (.-View ^js rn))) (def image (reagent/adapt-react-class (.-Image rn))) (def text (reagent/adapt-react-class (.-Text ^js rn))) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 5a6fc77de9..07486e57b4 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -30,16 +30,24 @@ {:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}) (fx/defn select-mention - [{:keys [db] :as cofx} {:keys [alias name searched-text match] :as user}] + [{:keys [db] :as cofx} text-input-ref {:keys [alias name searched-text match] :as user}] (let [chat-id (:current-chat-id db) new-text (mentions/new-input-text-with-mention cofx user) - at-sign-idx (get-in db [:chats chat-id :mentions :at-sign-idx])] + at-sign-idx (get-in db [:chats chat-id :mentions :at-sign-idx]) + cursor (+ at-sign-idx (count name) 2)] (fx/merge cofx {:db (-> db - (assoc-in [:chats/cursor chat-id] (+ at-sign-idx (count name) 2)) + (assoc-in [:chats/cursor chat-id] cursor) (assoc-in [:chats/mention-suggestions chat-id] nil))} (set-chat-input-text new-text) + ;; NOTE(rasom): Some keyboards do not react on selection property passed to + ;; text input (specifically Samsung keyboard with predictive text set on). + ;; In this case, if the user continues typing after the programmatic change, + ;; the new text is added to the last known cursor position before + ;; programmatic change. By calling `reset-text-input-cursor` we force the + ;; keyboard's cursor position to be changed before the next input. + (mentions/reset-text-input-cursor text-input-ref cursor) ;; NOTE(roman): on-text-input event is not dispatched when we change input ;; programmatically, so we have to call `on-text-input` manually (mentions/on-text-input diff --git a/src/status_im/chat/models/mentions.cljs b/src/status_im/chat/models/mentions.cljs index 89f8af7c2e..d59df27dd3 100644 --- a/src/status_im/chat/models/mentions.cljs +++ b/src/status_im/chat/models/mentions.cljs @@ -1,10 +1,14 @@ (ns status-im.chat.models.mentions (:require [clojure.string :as string] + [re-frame.core :as re-frame] [status-im.utils.fx :as fx] [status-im.contact.db :as contact.db] [status-im.utils.platform :as platform] [taoensso.timbre :as log] - [status-im.utils.utils :as utils])) + [status-im.utils.utils :as utils] + [status-im.native-module.core :as status] + [quo.react-native :as rn] + [quo.react :as react])) (def at-sign "@") @@ -450,3 +454,14 @@ (calculate-suggestions mentionable-users)) (clear-suggestions cofx))))) +(re-frame/reg-fx + ::reset-text-input-cursor + (fn [[ref cursor]] + (when ref + (status/reset-keyboard-input + (rn/find-node-handle (react/current-ref ref)) + cursor)))) + +(fx/defn reset-text-input-cursor + [_ ref cursor] + {::reset-text-input-cursor [ref cursor]}) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index efbc7db4b5..281f243584 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -506,8 +506,8 @@ (handlers/register-handler-fx :chat.ui/select-mention - (fn [cofx [_ mention]] - (chat.input/select-mention cofx mention))) + (fn [cofx [_ text-input-ref mention]] + (chat.input/select-mention cofx text-input-ref mention))) (handlers/register-handler-fx :chat.ui/set-chat-input-text diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 501d3d9999..be74e9b043 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -380,3 +380,8 @@ (defn deactivate-keep-awake [] (log/debug "[native-module] deactivateKeepAwake") (.deactivateKeepAwake ^js (status))) + +(defn reset-keyboard-input [input selection] + (log/debug "[native-module] resetKeyboardInput") + (when platform/android? + (.resetKeyboardInputCursor ^js (status) input selection))) diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index 21d781d350..856f97d115 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -197,7 +197,7 @@ input-with-mentions)]])) (defn mention-item - [[_ {:keys [identicon alias name nickname] :as user}]] + [text-input-ref [_ {:keys [identicon alias name nickname] :as user}]] (let [ens-name? (not= alias name)] [list-item/list-item (cond-> {:icon @@ -233,14 +233,14 @@ :title-text-weight :medium :on-press (fn [] - (re-frame/dispatch [:chat.ui/select-mention user]))} + (re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))} ens-name? (assoc :subtitle alias))])) (def chat-input-height (reagent/atom nil)) -(defn autocomplete-mentions [] +(defn autocomplete-mentions [text-input-ref] (let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])] (when (and (seq suggestions) @chat-input-height) (let [height (+ 16 (* 52 (min 4.5 (count suggestions))))] @@ -254,7 +254,7 @@ :header [rn/view {:style {:height 8}}] :data suggestions :key-fn first - :render-fn #(mention-item %)}]]])))) + :render-fn #(mention-item text-input-ref %)}]]])))) (defn chat-input [{:keys [set-active-panel active-panel on-send-press reply diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index 9eee5a2f87..695e32d0bc 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -301,7 +301,7 @@ ;; :always doesn't work and keyboard is hidden on pressing suggestion. ;; Scrolling of suggestions doesn't work neither in this case. (when platform/android? - [components/autocomplete-mentions]) + [components/autocomplete-mentions text-input-ref]) (when show-input? [accessory/view {:y position-y :pan-state pan-state