diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index 1e7dcf3f3b..f71a5fba5d 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -34,7 +34,7 @@ (def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn))) (def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn))) (def touchable-without-feedback (reagent/adapt-react-class (.-TouchableWithoutFeedback ^js rn))) -(def text-input (reagent/adapt-react-class (.-TextInput ^js rn))) +(def text-input (reagent/adapt-react-class (.-TextInput ^js rn))) (def keyboard-avoiding-view-class (reagent/adapt-react-class (.-KeyboardAvoidingView ^js rn))) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 5a1bf718b4..846a7d3f5d 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -49,9 +49,10 @@ cursor (+ at-sign-idx (count name) 2)] (fx/merge cofx - {:db (-> db - (assoc-in [:chats/cursor chat-id] cursor) - (assoc-in [:chats/mention-suggestions chat-id] nil))} + {:db (-> db + (assoc-in [:chats/cursor chat-id] cursor) + (assoc-in [:chats/mention-suggestions chat-id] nil)) + :set-text-input-value [chat-id new-text text-input-ref]} (set-chat-input-text new-text chat-id) ;; NOTE(rasom): Some keyboards do not react on selection property passed to ;; text input (specifically Samsung keyboard with predictive text set on). @@ -126,7 +127,6 @@ text (get-in message [:content :text])] {:dispatch [:chat.ui.input/set-chat-input-text text current-chat-id] - :set-input-text [current-chat-id text] :db (-> db (assoc-in [:chat/inputs current-chat-id :metadata :editing-message] message) @@ -196,7 +196,7 @@ [{:keys [db] :as cofx}] (let [current-chat-id (:current-chat-id db)] (fx/merge cofx - {:set-input-text [current-chat-id ""]} + {:set-text-input-value [current-chat-id ""]} (clean-input current-chat-id) (mentions/clear-mentions) (mentions/clear-cursor)))) diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index 83ba8c770f..e009cfe163 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -208,7 +208,7 @@ e)) text-with-mentions) info (mentions/->info hydrated-mentions) new-text (string/join (map second hydrated-mentions))] - {:set-input-text [chat-id new-text] + {:set-text-input-value [chat-id new-text] :db (-> db (assoc-in [:chats/cursor chat-id] (:mention-end info)) diff --git a/src/status_im/ui2/screens/chat/components/edit/view.cljs b/src/status_im/ui2/screens/chat/components/edit/view.cljs index 4465d438af..3069b98b70 100644 --- a/src/status_im/ui2/screens/chat/components/edit/view.cljs +++ b/src/status_im/ui2/screens/chat/components/edit/view.cljs @@ -7,7 +7,7 @@ [quo2.core :as quo] [quo.gesture-handler :as gesture-handler])) -(defn edit-message [] +(defn edit-message [reset-composer-callback] [rn/view {:style style/container :accessibility-label :edit-message} [rn/view {:style style/content-container} @@ -20,7 +20,8 @@ (i18n/label :t/editing-message)]]] [gesture-handler/touchable-without-feedback {:accessibility-label :reply-cancel-button - :on-press #(rf/dispatch [:chat.ui/cancel-message-edit])} + :on-press #(do (reset-composer-callback true) + (rf/dispatch [:chat.ui/cancel-message-edit]))} [quo/button {:width 24 :size 24 :type :outline} diff --git a/src/status_im/ui2/screens/chat/composer/edit/view.cljs b/src/status_im/ui2/screens/chat/composer/edit/view.cljs index 8f918b7303..3c7d3ce8c5 100644 --- a/src/status_im/ui2/screens/chat/composer/edit/view.cljs +++ b/src/status_im/ui2/screens/chat/composer/edit/view.cljs @@ -14,8 +14,8 @@ (defn edit-message-auto-focus-wrapper [text-input-ref _] (let [had-edit (atom nil)] - (fn [_ edit] + (fn [_ edit cleanup-composer-callback] (focus-input-on-edit edit had-edit text-input-ref) (when edit [rn/view {:style style/container} - [edit/edit-message]])))) + [edit/edit-message cleanup-composer-callback]])))) diff --git a/src/status_im/ui2/screens/chat/composer/input.cljs b/src/status_im/ui2/screens/chat/composer/input.cljs index 221b8870db..86ac4ff855 100644 --- a/src/status_im/ui2/screens/chat/composer/input.cljs +++ b/src/status_im/ui2/screens/chat/composer/input.cljs @@ -19,7 +19,7 @@ [oops.core :as oops])) (defonce input-texts (atom {})) -(defonce mentions-enabled (reagent/atom {})) +(defonce mentions-enabled? (reagent/atom {})) (defonce chat-input-key (reagent/atom 1)) (defonce text-input-ref (reagent/atom nil)) @@ -29,7 +29,7 @@ :chat.ui/clear-inputs (fn [] (reset! input-texts {}) - (reset! mentions-enabled {}) + (reset! mentions-enabled? {}) (reset! chat-input-key 1))) (defn input-focus [text-input-ref] @@ -51,14 +51,14 @@ (defn reset-input [refs chat-id] (some-> ^js (quo.react/current-ref (:text-input-ref refs)) .clear) - (swap! mentions-enabled update :render not) + (swap! mentions-enabled? update :render not) (swap! input-texts dissoc chat-id)) (defn clear-input [chat-id refs] (hide-send refs) - (if (get @mentions-enabled chat-id) + (if (get @mentions-enabled? chat-id) (do - (swap! mentions-enabled dissoc chat-id) + (swap! mentions-enabled? dissoc chat-id) ;;we need this timeout, because if we clear text input and first index was a mention object with blue color, ;;after clearing text will be typed with this blue color, so we render white text first and then clear it (js/setTimeout #(reset-input refs chat-id) 50)) @@ -105,8 +105,8 @@ (when (and (empty? prev-text) (seq text)) (show-send refs)) - (when (and (not (get @mentions-enabled chat-id)) (string/index-of text "@")) - (swap! mentions-enabled assoc chat-id true)) + (when (and (not (get @mentions-enabled? chat-id)) (string/index-of text "@")) + (swap! mentions-enabled? assoc chat-id true)) ;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it ;; happens during typing because it is not needed for mention @@ -129,8 +129,8 @@ range (.-range ^js native-event) start (.-start ^js range) end (.-end ^js range)] - (when (and (not (get @mentions-enabled chat-id)) (string/index-of text "@")) - (swap! mentions-enabled assoc chat-id true)) + (when (and (not (get @mentions-enabled? chat-id)) (string/index-of text "@")) + (swap! mentions-enabled? assoc chat-id true)) (>evt [::mentions/on-text-input @@ -144,38 +144,40 @@ (when platform/android? (>evt [::mentions/calculate-suggestions mentionable-users])))) -(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change]}] - (let [cooldown-enabled? (js {:text text}))) (re-frame/reg-fx - :set-input-text - (fn [[chat-id text]] + :set-text-input-value + (fn [[chat-id text local-text-input-ref]] + (when local-text-input-ref + (reset! text-input-ref local-text-input-ref)) (if platform/ios? (.setNativeProps ^js (quo.react/current-ref @text-input-ref) (clj->js {:text text})) (do @@ -297,7 +301,7 @@ (let [text-input-handle (rn/find-node-handle @text-input-ref)] (oops/ocall manager :startActionMode text-input-handle)))) - :render + :reagent-render (fn [_] (let [ref #(do (reset! text-input-ref %) (when ref @@ -333,9 +337,9 @@ :chat-id chat-id :selection-event selection-event}))) props (merge props {:ref ref - :style nil + :style (dissoc style :margin-horizontal) :on-selection-change on-selection-change :on-selection on-selection})] [rn-selectable-text-input {:menuItems @menu-items :style style} [rn/text-input props - [children]]]))}))) + children]]))}))) diff --git a/src/status_im/ui2/screens/chat/composer/mentions.cljs b/src/status_im/ui2/screens/chat/composer/mentions.cljs index eb0a7ef595..aa5a6fa226 100644 --- a/src/status_im/ui2/screens/chat/composer/mentions.cljs +++ b/src/status_im/ui2/screens/chat/composer/mentions.cljs @@ -40,17 +40,18 @@ ens-name? (assoc :subtitle alias))]])) -(defn autocomplete-mentions [suggestions] +(defn autocomplete-mentions [suggestions text-input-ref] [:f> (fn [] (let [animation (reanimated/use-shared-value 0)] (quo.react/effect! #(reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200)))) [reanimated/view {:style (reanimated/apply-animations-to-style {:transform [{:translateY animation}]} - {:bottom 0 :position :absolute :z-index 5 :max-height 180})} + {:bottom 0 :position :absolute :z-index 5 :elevation 5 :max-height 180})} [list/flat-list {:keyboardShouldPersistTaps :always :data suggestions :key-fn first :render-fn mention-item + :render-data text-input-ref :content-container-style {:padding-bottom 12}}]]))]) diff --git a/src/status_im/ui2/screens/chat/composer/view.cljs b/src/status_im/ui2/screens/chat/composer/view.cljs index f892e373fa..c67c4da533 100644 --- a/src/status_im/ui2/screens/chat/composer/view.cljs +++ b/src/status_im/ui2/screens/chat/composer/view.cljs @@ -19,33 +19,42 @@ [i18n.i18n :as i18n] [status-im.ui2.screens.chat.composer.edit.view :as edit])) -(defn calculate-y [context keyboard-shown min-y max-y added-value] - (if keyboard-shown +(defn calculate-y [context min-y max-y added-value chat-id] + (let [input-text (:input-text (get ( (gesture/gesture-pan) @@ -73,7 +82,6 @@ (reanimated/set-shared-value shared-height (reanimated/with-timing max-height)) (set-bg-opacity 1)) (do - (swap! context assoc :state :min) (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) (set-bg-opacity 0) @@ -90,7 +98,7 @@ (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) (set-bg-opacity 0)) (when (not= (:state @context) :max) - (let [new-y (+ min-y (- (max (oget evt "nativeEvent" "contentSize" "height") 22) 22))] + (let [new-y (+ min-y (- (max (oget evt "nativeEvent" "contentSize" "height") 40) 40))] (if (< new-y max-y) (do (if (> (- max-y new-y) 120) @@ -120,58 +128,56 @@ (defn composer [chat-id] [safe-area/consumer (fn [insets] - (let [min-y 112 - context (atom {:y min-y ;current y value - :min-y min-y ;minimum y value - :dy 0 ;used for gesture - :pdy 0 ;used for gesture - :state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max - :clear false}) - keyboard-was-shown (atom false) - text-input-ref (quo.react/create-ref) - send-ref (quo.react/create-ref) - refs {:send-ref send-ref - :text-input-ref text-input-ref}] + (let [min-y 112 + context (atom {:y min-y ;current y value + :min-y min-y ;minimum y value + :dy 0 ;used for gesture + :pdy 0 ;used for gesture + :state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max + :clear false}) + keyboard-was-shown? (atom false) + text-input-ref (quo.react/create-ref) + send-ref (quo.react/create-ref) + refs {:send-ref send-ref + :text-input-ref text-input-ref}] (fn [] [:f> (fn [] - (let [reply ( keyboard-height 0) keyboard-height 360) (:top insets) (:status-bar-height @navigation-const)) ; 360 - default height - max-height (Math/abs (- max-y 56 (:bottom insets))) ; 56 - top-bar height - added-value (if (and (not (seq suggestions)) (or edit reply)) 38 0) ; increased height of input box needed when reply - min-y (+ min-y (when (or edit reply) 38)) - y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply) - translate-y (reanimated/use-shared-value 0) - shared-height (reanimated/use-shared-value min-y) - bg-opacity (reanimated/use-shared-value 0) - bg-bottom (reanimated/use-shared-value (- window-height)) - - set-bg-opacity (fn [value] - (reanimated/set-shared-value bg-bottom (if (= value 1) 0 (- window-height))) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing value))) - input-content-change (get-input-content-change context translate-y shared-height max-height - set-bg-opacity keyboard-shown min-y max-y) - bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown - min-y max-y shared-height max-height set-bg-opacity)] + max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets) (:status-bar-height @navigation-const)) ; 360 - default height + max-height (Math/abs (- max-y 56 (:bottom insets))) ; 56 - top-bar height + added-value (if (and (not (seq suggestions)) (or edit reply)) 38 0) ; increased height of input box needed when reply + min-y (+ min-y (when (or edit reply) 38)) + y (get-y-value context min-y max-y added-value max-height chat-id suggestions reply) + translate-y (reanimated/use-shared-value 0) + shared-height (reanimated/use-shared-value min-y) + bg-opacity (reanimated/use-shared-value 0) + clean-and-minimize-composer-fn #(clean-and-minimize-composer context chat-id refs min-y %) + bg-bottom (reanimated/use-shared-value (- window-height)) + set-bg-opacity (fn [value] + (reanimated/set-shared-value bg-bottom (if (= value 1) 0 (- window-height))) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing value))) + input-content-change (get-input-content-change context translate-y shared-height max-height + set-bg-opacity keyboard-shown min-y max-y) + blank-composer? (string/blank? (get @input/input-texts chat-id)) + bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y text-input-ref keyboard-shown + min-y max-y shared-height max-height set-bg-opacity) + initial-value (or (get @input/input-texts chat-id) nil)] (quo.react/effect! #(do - (when (and @keyboard-was-shown (not keyboard-shown)) + (when (and @keyboard-was-shown? (not keyboard-shown)) (swap! context assoc :state :min)) - (reset! keyboard-was-shown keyboard-shown) + (when blank-composer? + (clean-and-minimize-composer-fn false)) + (reset! keyboard-was-shown? keyboard-shown) (if (#{:max :custom-chat-unavailable} (:state @context)) (set-bg-opacity 1) (set-bg-opacity 0)) (reanimated/set-shared-value translate-y (reanimated/with-timing (- y))) (reanimated/set-shared-value shared-height (reanimated/with-timing (min y max-height))))) - (quo.react/effect! #(when (and (not edit) (= (:state @context) :max)) - (swap! context assoc :state :min) - (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) - (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) - (set-bg-opacity 0) - (re-frame/dispatch [:dismiss-keyboard])) edit) [reanimated/view {:style (reanimated/apply-animations-to-style {:height shared-height} {:z-index 2})} @@ -182,12 +188,13 @@ (styles/input-bottom-sheet window-height))} ;handle [rn/view {:style (styles/bottom-sheet-handle)}] - [edit/edit-message-auto-focus-wrapper (:text-input-ref refs) edit] - [reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply] + [edit/edit-message-auto-focus-wrapper text-input-ref edit clean-and-minimize-composer-fn] + [reply/reply-message-auto-focus-wrapper text-input-ref reply] [rn/view {:style {:height (- max-y 80 added-value)}} [input/text-input {:chat-id chat-id :on-content-size-change input-content-change :sending-image false + :initial-value initial-value :refs refs :set-active-panel #()}]]]] ;CONTROLS @@ -202,20 +209,27 @@ (utils/set-timeout #(utils/show-popup (i18n/label :t/error) (i18n/label :t/external-storage-denied)) 50))})) - :icon true :type :outline :size 32} :i/image] + :icon true + :type :outline + :size 32} :i/image] [rn/view {:width 12}] - [quo2.button/button {:icon true :type :outline :size 32} :i/reaction] + [quo2.button/button {:icon true + :type :outline + :size 32} :i/reaction] [rn/view {:flex 1}] ;;SEND button - [rn/view {:ref send-ref :style (when-not (seq (get @input/input-texts chat-id)) {:width 0 :right -100})} - [quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button - :on-press #(do (swap! context assoc :clear true) - (input/clear-input chat-id refs) - (re-frame/dispatch [:chat.ui/send-current-message]))} + [rn/view {:ref send-ref + :style (when-not (seq (get @input/input-texts chat-id)) {:width 0 + :right -100})} + [quo2.button/button {:icon true + :size 32 + :accessibility-label :send-message-button + :on-press #(do (clean-and-minimize-composer-fn false) + (re-frame/dispatch [:chat.ui/send-current-message]))} :i/arrow-up]]]) ;black background [reanimated/view {:style (reanimated/apply-animations-to-style - {:opacity bg-opacity + {:opacity bg-opacity :transform [{:translateY bg-bottom}]} (styles/bottom-sheet-background window-height))}] - [mentions/autocomplete-mentions suggestions]]))])))]) + [mentions/autocomplete-mentions suggestions text-input-ref]]))])))])