Message input issues (#14450)

Message input issues (#14450)
This commit is contained in:
Ibrahem Khalil 2022-12-15 11:49:54 +02:00 committed by GitHub
parent 08fb0de7b0
commit 4e6a37fdd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 126 deletions

View File

@ -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)))

View File

@ -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))))

View File

@ -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))

View File

@ -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}

View File

@ -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]]))))

View File

@ -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? (<sub [:chats/current-chat-cooldown-enabled?])
mentionable-users (<sub [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)
mentions-enabled (get @mentions-enabled chat-id)
props {:style (style/text-input)
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
:text-align-vertical :center
:multiline true
:editable (not cooldown-enabled?)
:blur-on-submit false
:auto-focus false
:on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @quo.colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
:underline-color-android :transparent
:auto-capitalize :sentences
:auto-correct false
:spell-check false
:on-content-size-change on-content-size-change
:on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users)
:on-change (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image)
:on-text-input (partial on-text-input mentionable-users chat-id)}
(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change initial-value]}]
(let [_ (reset! text-input-ref (:text-input-ref refs))
cooldown-enabled? (<sub [:chats/current-chat-cooldown-enabled?])
mentionable-users (<sub [:chats/mentionable-users])
timeout-id (reagent/atom nil)
last-text-change (reagent/atom nil)
mentions-enabled? (get @mentions-enabled? chat-id)
props {:style (style/text-input)
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
:text-align-vertical :center
:multiline true
:editable (not cooldown-enabled?)
:blur-on-submit false
:auto-focus false
:default-value initial-value
:on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @quo.colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
:underline-color-android :transparent
:auto-capitalize :sentences
:auto-correct false
:spell-check false
:on-content-size-change on-content-size-change
:on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users)
:on-change (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image)
:on-text-input (partial on-text-input mentionable-users chat-id)}
input-with-mentions (<sub [:chat/input-with-mentions])
children (fn []
(if mentions-enabled
(if mentions-enabled?
(map-indexed
(fn [index [item text]]
[index [item]]
@ -203,8 +205,10 @@
(.setNativeProps ^js text-input (clj->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]]))})))

View File

@ -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}}]]))])

View File

@ -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 (<sub [:chat/inputs]) chat-id))
num-lines (count (string/split input-text "\n"))]
(if (= (:state @context) :max)
max-y
(do (swap! context assoc :state :max) max-y)
(if (< (:y @context) max-y)
(+ (:y @context) added-value)
(do
(swap! context assoc :state :max)
max-y)))
(do
(swap! context assoc :state :min)
min-y)))
(if (<= 5 num-lines)
(do (swap! context assoc :state :max) max-y)
(do (swap! context assoc :state :min) min-y))))))
(defn calculate-y-with-mentions [y max-y max-height chat-id suggestions reply]
(let [input-text (:input-text (get (<sub [:chat/inputs]) chat-id))
num-lines (count (string/split input-text "\n"))
text-height (* num-lines 22)
mentions-height (min 132 (+ 16 (* 46 (- (count suggestions) 1))))
should-translate (if (< (- max-height text-height) mentions-height) true false)
should-translate? (if (< (- max-height text-height) mentions-height) true false)
min-value (if-not reply mentions-height (+ mentions-height 44))
; translate value when mentions list appear while at bottom of expanded input sheet
mentions-translate-value (if should-translate (min min-value (- mentions-height (- max-height text-height))) mentions-height)]
(when (or (< y max-y) should-translate) mentions-translate-value)))
mentions-translate-value (if should-translate? (min min-value (- mentions-height (- max-height text-height))) mentions-height)]
(when (or (< y max-y) should-translate?) mentions-translate-value)))
(defn get-y-value [context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply]
(let [y (calculate-y context keyboard-shown min-y max-y added-value)]
y (+ y (when (seq suggestions) (calculate-y-with-mentions y max-y max-height chat-id suggestions reply)))))
(defn get-y-value [context min-y max-y added-value max-height chat-id suggestions reply]
(let [y (calculate-y context min-y max-y added-value chat-id)
y-with-mentions (calculate-y-with-mentions y max-y max-height chat-id suggestions reply)]
(+ y (when (seq suggestions) y-with-mentions))))
(defn- clean-and-minimize-composer
([context chat-id refs min-y]
(clean-and-minimize-composer context chat-id refs min-y false))
([context chat-id refs min-y edit?]
(input/clear-input chat-id refs)
(swap! context assoc :y (if edit?
(- min-y 38)
min-y))
(swap! context assoc :clear true)))
(defn get-bottom-sheet-gesture [context translate-y text-input-ref keyboard-shown min-y max-y shared-height max-height set-bg-opacity]
(-> (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 (<sub [:chats/reply-message])
edit (<sub [:chats/edit-message])
suggestions (<sub [:chat/mention-suggestions])
{window-height :height} (rn/use-window-dimensions)
(let [reply (<sub [:chats/reply-message])
edit (<sub [:chats/edit-message])
suggestions (<sub [:chat/mention-suggestions])
{window-height :height} (rn/use-window-dimensions)
{:keys [keyboard-shown keyboard-height]} (rn/use-keyboard)
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 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]]))])))])