[#10385] [perfromance][chat] Optimize input field in chat

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2020-04-23 18:06:26 +02:00
parent f280d5a90e
commit f73754af34
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
12 changed files with 132 additions and 222 deletions

View File

@ -32,12 +32,12 @@
"keyboardDidShow"
(fn [e]
(let [h (.. e -endCoordinates -height)]
(dispatch [:set :keyboard-height h])
(dispatch [:set :keyboard-max-height h]))))
(dispatch-sync [:set :keyboard-height h])
(dispatch-sync [:set :keyboard-max-height h]))))
(.addListener react/keyboard
"keyboardDidHide"
(fn [_]
(dispatch [:set :keyboard-height 0])))
(dispatch-sync [:set :keyboard-height 0])))
(.hide react/splash-screen)
(.addEventListener react/app-state "change" app-state-change-handler)
(.addEventListener rn-dependencies/react-native-languages "change" on-languages-change)

View File

@ -308,3 +308,8 @@
(fx/merge (assoc-in cofx [:db :contacts/identity] identity)
(contact.core/create-contact identity)
(navigation/navigate-to-cofx :profile nil)))
(fx/defn input-on-focus
{:events [:chat.ui/input-on-focus]}
[{db :db}]
{:db (set-chat-ui-props db {:input-bottom-sheet nil})})

View File

@ -27,7 +27,7 @@
"Set input text for current-chat. Takes db and input text and cofx
as arguments and returns new fx. Always clear all validation messages."
[{{:keys [current-chat-id] :as db} :db} new-input]
{:db (assoc-in db [:chats current-chat-id :input-text] (text->emoji new-input))})
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
(defn- start-cooldown [{:keys [db]} cooldowns]
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
@ -67,6 +67,12 @@
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::focus-rn-component cmp-ref}))
(fx/defn chat-input-clear
"Returns fx for focusing on active chat input reference"
[{{:keys [current-chat-id chat-ui-props]} :db} ref]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::clear-rn-component cmp-ref}))
(fx/defn reply-to-message
"Sets reference to previous chat message and focuses on input"
[{:keys [db] :as cofx} message-id]
@ -103,6 +109,7 @@
:response-to message-id
:ens-name preferred-name})
(set-chat-input-text nil)
(chat-input-clear :input-ref)
(process-cooldown)))))
(fx/defn send-sticker-fx
@ -117,15 +124,15 @@
(fx/defn send-current-message
"Sends message from current chat input"
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text]} (get-in db [:chats current-chat-id])]
(let [{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])]
(plain-text-message-fx input-text current-chat-id cofx)))
(fx/defn send-transaction-result
{:events [:chat/send-transaction-result]}
[cofx chat-id params result]
[cofx chat-id params result])
;;TODO: should be implemented on status-go side
;;see https://github.com/status-im/team-core/blob/6c3d67d8e8bd8500abe52dab06a59e976ec942d2/rfc-001.md#status-gostatus-react-interface
)
;; effects
@ -136,3 +143,11 @@
(.focus ref)
(catch :default e
(log/debug "Cannot focus the reference")))))
(re-frame/reg-fx
::clear-rn-component
(fn [ref]
(try
(.clear ref)
(catch :default e
(log/debug "Cannot clear the reference")))))

View File

@ -17,6 +17,7 @@
(s/def :chat/loaded-chats (s/nilable seq?))
(s/def :chat/bot-db (s/nilable map?))
(s/def :chat/cooldowns (s/nilable number?)) ; number of cooldowns given for spamming send button
(s/def :chat/inputs (s/nilable map?))
(s/def :chat/cooldown-enabled? (s/nilable boolean?))
(s/def :chat/last-outgoing-message-sent-at (s/nilable number?))
(s/def :chat/spam-messages-frequency (s/nilable number?)) ; number of consecutive spam messages sent

View File

@ -30,11 +30,11 @@
"keyboardWillShow"
(fn [e]
(let [h (.. e -endCoordinates -height)]
(dispatch [:set :keyboard-height h])
(dispatch [:set :keyboard-max-height h]))))
(dispatch-sync [:set :keyboard-height h])
(dispatch-sync [:set :keyboard-max-height h]))))
(.addListener react/keyboard
"keyboardWillHide"
#(dispatch [:set :keyboard-height 0]))
#(dispatch-sync [:set :keyboard-height 0]))
(.hide react/splash-screen)
(.addEventListener react/app-state "change" app-state-change-handler)
(.addEventListener rn-dependencies/react-native-languages "change" on-languages-change)

View File

@ -126,6 +126,7 @@
(reg-root-key-sub :group-chat-profile/editing? :group-chat-profile/editing?)
(reg-root-key-sub :group-chat-profile/profile :group-chat-profile/profile)
(reg-root-key-sub :selected-participants :selected-participants)
(reg-root-key-sub :chat/inputs :chat/inputs)
;;browser
(reg-root-key-sub :browsers :browser/browsers)
@ -607,25 +608,14 @@
:else 272)))
(re-frame/reg-sub
:chats/input-margin
:chats/empty-chat-panel-height
:<- [:keyboard-height]
:<- [:chats/current-chat-ui-prop :input-bottom-sheet]
(fn [[kb-height input-bottom-sheet]]
(cond
;; During the transition of bottom sheet and close of keyboard happens
;; The heights are summed up and input grows too much
(not (nil? input-bottom-sheet))
0
(and platform/iphone-x? (> kb-height 0))
(- kb-height tabs.styles/minimized-tabs-height 34)
platform/ios?
(+ kb-height (- (if (> kb-height 0)
tabs.styles/minimized-tabs-height
0)))
:default 0)))
(fn [kb-height]
(if (and platform/ios? (pos? kb-height))
(- kb-height
tabs.styles/minimized-tabs-height
(if platform/iphone-x? 34 0))
0)))
(re-frame/reg-sub
:chats/active-chats
@ -706,9 +696,10 @@
(re-frame/reg-sub
:chats/current-chat-input-text
:<- [:chats/current-raw-chat]
(fn [chat]
(:input-text chat)))
:<- [:chats/current-chat-id]
:<- [:chat/inputs]
(fn [[chat-id inputs]]
(get-in inputs [chat-id :input-text])))
(re-frame/reg-sub
:chats/current-chat

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.chat.input.input
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[status-im.ui.screens.chat.styles.input.input :as style]
[status-im.ui.screens.chat.styles.message.message :as message-style]
@ -12,103 +11,26 @@
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.utils.platform :as platform]
[status-im.utils.config :as config]
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.extensions.views :as extensions]))
(defview basic-text-input [{:keys [set-container-width-fn height single-line-input?]}]
(letsubs [input-text [:chats/current-chat-input-text]
cooldown-enabled? [:chats/cooldown-enabled?]]
[react/text-input
(merge
{:ref #(when % (re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-ref %}]))
:accessibility-label :chat-message-input
:multiline (not single-line-input?)
:default-value (or input-text "")
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:on-submit-editing #(when single-line-input?
(re-frame/dispatch [:chat.ui/send-current-message]))
:on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %))))
:on-change #(re-frame/dispatch [:chat.ui/set-chat-input-text (.-text (.-nativeEvent %))])
:on-selection-change #(let [s (-> (.-nativeEvent %)
(.-selection))
end (.-end s)]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:selection end}]))
:style (style/input-view single-line-input?)
:placeholder-text-color colors/gray
:auto-capitalize :sentences}
(when cooldown-enabled?
{:placeholder (i18n/label :cooldown/text-input-disabled)}))]))
(defview basic-text-input-desktop [{:keys [set-container-width-fn height single-line-input? set-text state-text]}]
(letsubs [inp-ref (atom nil)
cooldown-enabled? [:chats/cooldown-enabled?]]
[react/text-input
(merge
{:ref #(when % (do
(reset! inp-ref %)
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-ref %}])))
:accessibility-label :chat-message-input
:multiline (not single-line-input?)
:default-value @state-text
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:submit-shortcut {:key "Enter"}
:on-submit-editing #(do
(.clear @inp-ref)
(.focus @inp-ref)
(re-frame/dispatch [:chat.ui/set-chat-input-text @state-text])
(re-frame/dispatch [:chat.ui/send-current-message])
(set-text ""))
:on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %))))
:on-change #(do
(set-text (.-text (.-nativeEvent %))))
:on-end-editing #(re-frame/dispatch [:chat.ui/set-chat-input-text @state-text])
:on-selection-change #(let [s (-> (.-nativeEvent %)
(.-selection))
end (.-end s)]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:selection end}]))
:style (style/input-view single-line-input?)
:placeholder-text-color colors/gray
:auto-capitalize :sentences}
(when cooldown-enabled?
{:placeholder (i18n/label :cooldown/text-input-disabled)}))]))
(defview invisible-input [{:keys [set-layout-width-fn value]}]
(letsubs [input-text [:chats/current-chat-input-text]]
[react/text {:style style/invisible-input-text
:on-layout #(let [w (-> (.-nativeEvent %)
(.-layout)
(.-width))]
(set-layout-width-fn w))}
(or input-text "")]))
(defn get-options [type]
(case (keyword type)
:phone {:keyboard-type "phone-pad"}
:password {:secure-text-entry true}
:number {:keyboard-type "numeric"}
nil))
(defview input-view [{:keys [single-line-input? set-text state-text]}]
(let [component (reagent/current-component)
set-layout-width-fn #(reagent/set-state component {:width %})
set-container-width-fn #(reagent/set-state component {:container-width %})
{:keys [width]} (reagent/state component)]
[react/view {:style style/input-root}
[react/animated-view {:style style/input-animated}
[invisible-input {:set-layout-width-fn set-layout-width-fn}]
(if platform/desktop?
[basic-text-input-desktop {:set-container-width-fn set-container-width-fn
:single-line-input? single-line-input?
:set-text set-text
:state-text state-text}]
[basic-text-input {:set-container-width-fn set-container-width-fn
:single-line-input? single-line-input?}])]]))
(defn basic-text-input [input-text cooldown-enabled?]
[react/text-input
{:ref #(when % (re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-ref %}]))
:accessibility-label :chat-message-input
:multiline true
:default-value (or input-text "")
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch-sync [:chat.ui/input-on-focus])
:on-change #(re-frame/dispatch [:chat.ui/set-chat-input-text (.-text (.-nativeEvent %))])
:style style/input-view
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
:placeholder-text-color colors/gray
:auto-capitalize :sentences}])
(defview reply-message [from alias message-text]
(letsubs [{:keys [ens-name]} [:contacts/contact-name-by-identity from]
@ -121,51 +43,34 @@
(defview reply-message-view []
(letsubs [{:keys [content from alias] :as message} [:chats/reply-message]]
(when message
[react/view {:style style/reply-message-container}
[react/view {:style style/reply-message}
[photos/member-photo from]
[reply-message from alias (:text content)]
[react/touchable-highlight
{:style style/cancel-reply-highlight
:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])
:accessibility-label :cancel-message-reply}
[react/view {:style style/cancel-reply-container}
[vector-icons/icon :main-icons/close {:container-style style/cancel-reply-icon
:width 19
:height 19
:color colors/white}]]]]])))
[react/view {:style style/reply-message}
[photos/member-photo from]
[reply-message from alias (:text content)]
[react/touchable-highlight
{:style style/cancel-reply-highlight
:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])
:accessibility-label :cancel-message-reply}
[react/view {:style style/cancel-reply-container}
[vector-icons/icon :main-icons/close {:container-style style/cancel-reply-icon
:width 19
:height 19
:color colors/white}]]]])))
(defview container []
(letsubs [margin [:chats/input-margin]
mainnet? [:mainnet?]
input-text [:chats/current-chat-input-text]
result-box [:chats/current-chat-ui-prop :result-box]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
one-to-one-chat? [:current-chat/one-to-one-chat?]
state-text (reagent/atom "")]
{:component-will-unmount #(when platform/desktop?
(re-frame/dispatch [:chat.ui/set-chat-input-text @state-text]))
:component-did-mount #(when-not (string/blank? input-text) (reset! state-text input-text))}
(let [single-line-input? (:singleLineInput result-box)
set-text #(reset! state-text %)
input-text-empty? (if platform/desktop?
(string/blank? state-text)
(string/blank? input-text))]
[react/view {:style (style/root margin)}
(letsubs [mainnet? [:mainnet?]
input-text [:chats/current-chat-input-text]
cooldown-enabled? [:chats/cooldown-enabled?]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
one-to-one-chat? [:current-chat/one-to-one-chat?]]
(let [input-text-empty? (string/blank? (string/trim (or input-text "")))]
[react/view {:style (style/root)}
[reply-message-view]
[react/view {:style style/input-container}
[input-view {:single-line-input? single-line-input? :set-text set-text :state-text state-text}]
[basic-text-input input-text cooldown-enabled?]
(when (and input-text-empty? mainnet?)
[stickers/button (= :stickers input-bottom-sheet)])
(when (and one-to-one-chat? input-text-empty? (or config/commands-enabled? mainnet?))
[extensions/button (= :extensions input-bottom-sheet)])
(when-not input-text-empty?
(if platform/desktop?
[send-button/send-button-view {:input-text @state-text}
#(do
(re-frame/dispatch [:chat.ui/set-chat-input-text @state-text])
(re-frame/dispatch [:chat.ui/send-current-message])
(set-text ""))]
[send-button/send-button-view {:input-text input-text}
#(re-frame/dispatch [:chat.ui/send-current-message])]))]])))
[send-button/send-button-view input-text-empty?
#(re-frame/dispatch [:chat.ui/send-current-message])])]])))

View File

@ -1,21 +1,19 @@
(ns status-im.ui.screens.chat.input.send-button
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[status-im.ui.screens.chat.styles.input.send-button :as style]
(:require [status-im.ui.screens.chat.styles.input.send-button :as style]
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.colors :as colors]))
(defn sendable? [input-text disconnected? login-processing?]
(let [trimmed (string/trim input-text)]
(not (or (string/blank? trimmed)
login-processing?
disconnected?))))
(defn sendable? [input-text-empty? disconnected? login-processing?]
(not (or input-text-empty?
login-processing?
disconnected?)))
(defview send-button-view [{:keys [input-text]} on-send-press]
(defview send-button-view [input-text-empty? on-send-press]
(letsubs [disconnected? [:disconnected?]
{:keys [processing]} [:multiaccounts/login]]
(when (sendable? input-text disconnected? processing)
(when (sendable? input-text-empty? disconnected? processing)
[react/touchable-highlight
{:on-press on-send-press}
[vector-icons/icon :main-icons/arrow-up

View File

@ -137,12 +137,11 @@
:background-color colors/blue}}]))
(defview stickers-view []
(letsubs [selected-pack [:stickers/selected-pack]
installed-packs [:stickers/installed-packs-vals]
panel-height [:chats/chat-panel-height]
bottom-anim-value (anim/create-value @panel-height)
alpha-value (anim/create-value 0)]
(letsubs [selected-pack [:stickers/selected-pack]
installed-packs [:stickers/installed-packs-vals]
panel-height [:chats/chat-panel-height]
bottom-anim-value (anim/create-value @panel-height)
alpha-value (anim/create-value 0)]
{:component-did-mount #(show-panel-anim bottom-anim-value alpha-value)}
[react/animated-view {:style {:background-color colors/white
:height panel-height

View File

@ -8,26 +8,25 @@
(def border-height 1)
(def max-input-height (* 5 min-input-height))
(defn root [margin-bottom]
(defn root []
{:background-color colors/white
:margin-bottom margin-bottom
:flex-direction :column
:border-top-width border-height
:border-top-color colors/gray-lighter})
(def reply-message
{:flex-direction :row
:align-items :flex-start
{:flex-direction :row
:align-items :flex-start
:justify-content :space-between
:padding-top 8
:padding-bottom 8
:padding-right 8
:padding-left 8})
:padding-top 8
:padding-bottom 8
:padding-right 8
:padding-left 8})
(def reply-message-content
{:flex-direction :column
:padding-left 8
:padding-right 8
:padding-right 8
:max-height 140})
(defn reply-message-author [chosen?]
@ -45,46 +44,40 @@
{:flex-direction :column-reverse})
(def reply-message-to-container
{:flex-direction :row
:height 18
:padding-top 0
:padding-bottom 0
:padding-right 8
{:flex-direction :row
:height 18
:padding-top 0
:padding-bottom 0
:padding-right 8
:justify-content :flex-start})
(def reply-icon
{:width 20
:margin-top 1
{:width 20
:margin-top 1
:margin-bottom 1
:margin-right 0})
:margin-right 0})
(def cancel-reply-highlight
{:align-self :flex-start
:width 19
:height 19})
:width 19
:height 19})
(def cancel-reply-container
{:flex-direction :row
:justify-content :flex-end
:height "100%"})
:height "100%"})
(def cancel-reply-icon
{:background-color colors/gray
:width 21
:height 21
:align-items :center
:justify-content :center
:width 21
:height 21
:align-items :center
:justify-content :center
:border-radius 12})
(def input-container
{:flex-direction :row
:align-items :flex-end
:padding-left 14})
(def input-root
{:padding-top padding-vertical
:padding-bottom padding-vertical
:flex 1})
{:flex-direction :row
:align-items :flex-end})
(def input-animated
{:align-items :flex-start
@ -93,16 +86,13 @@
:min-height min-input-height
:max-height max-input-height})
(styles/defn input-view [single-line-input?]
{:flex 1
:padding-top 9
:padding-bottom 5
:padding-right 12
:min-height min-input-height
:max-height (if single-line-input?
min-input-height
max-input-height)
:android {:padding-top 3}})
(def input-view
{:flex 1
:padding-top 12
:padding-bottom 15
:padding-horizontal 12
:min-height min-input-height
:max-height max-input-height})
(def invisible-input-text
{:position :absolute

View File

@ -136,6 +136,10 @@
:on-scroll-to-index-failed #() ;;don't remove this
:keyboard-should-persist-taps :handled}]))
(defview empty-bottom-sheet []
(letsubs [input-bottom-sheet [:chats/empty-chat-panel-height]]
[react/view {:height input-bottom-sheet}]))
(defview bottom-sheet []
(letsubs [input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]]
(case input-bottom-sheet
@ -143,7 +147,7 @@
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
nil)))
[empty-bottom-sheet])))
(defview chat []
(letsubs [{:keys [chat-id show-input? group-chat contact] :as current-chat}

View File

@ -42,6 +42,7 @@
:mailserver/topics {}
:mailserver/pending-requests 0
:chat/cooldowns 0
:chat/inputs {}
:chat/cooldown-enabled? false
:chat/last-outgoing-message-sent-at 0
:chat/spam-messages-frequency 0
@ -203,6 +204,7 @@
:browser/options
:navigation/screen-params
:chat/cooldowns
:chat/inputs
:chat/cooldown-enabled?
:chat/last-outgoing-message-sent-at
:chat/spam-messages-frequency