chat input performance improvements

Signed-off-by: andrey <motor4ik@gmail.com>
This commit is contained in:
andrey 2021-03-17 08:44:36 +01:00
parent 7c4c520712
commit 3de7c37d90
No known key found for this signature in database
GPG Key ID: 89B67245FD2F0272
23 changed files with 442 additions and 415 deletions

View File

@ -65,11 +65,6 @@
(s/explain spec prop)
false)))
;; TODO(Ferossgp): Check performance for android layout animations
(when (and platform/android?
(aget rn/ui-manager "setLayoutAnimationEnabledExperimental"))
(ocall rn/ui-manager "setLayoutAnimationEnabledExperimental" true))
(def height 44) ; 22 line-height + 11*2 vertical padding
(def multiline-height 88) ; 3 * 22 three line-height + 11* vertical padding

View File

@ -16,6 +16,10 @@
(oset! ref "current" val)
val)
(defn set-native-props [^js ref ^js props]
(when-let [curr-ref ^js (current-ref ref)]
(.setNativeProps curr-ref props)))
(deftype StateHook [value set-value]
cljs.core/IHash
(-hash [o] (goog/getUid o))

View File

@ -238,12 +238,13 @@
(fx/defn close-chat
{:events [:close-chat]}
[{:keys [db] :as cofx} chat-id]
(chat.state/reset-visible-item)
(fx/merge cofx
{:db (dissoc db :current-chat-id)}
(offload-messages chat-id)
(navigation/navigate-back)))
[{:keys [db] :as cofx}]
(let [chat-id (:current-chat-id db)]
(chat.state/reset-visible-item)
(fx/merge cofx
{:db (dissoc db :current-chat-id)}
(offload-messages chat-id)
(navigation/navigate-back))))
(fx/defn remove-chat
"Removes chat completely from app, producing all necessary effects for that"

View File

@ -43,7 +43,7 @@
[{: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/mentions chat-id :mentions :at-sign-idx])
cursor (+ at-sign-idx (count name) 2)]
(fx/merge
cofx

View File

@ -122,6 +122,11 @@
#(re-frame/dispatch [::messages-loaded chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages chat-id session-id %]))))))))
(fx/defn load-more-messages-for-current-chat
{:events [:chat.ui/load-more-messages-for-current-chat]}
[{:keys [db] :as cofx}]
(load-more-messages cofx (:current-chat-id db) false))
(fx/defn load-messages
[{:keys [db now] :as cofx} chat-id]
(when-not (get-in db [:pagination-info chat-id :messages-initialized?])

View File

@ -448,7 +448,7 @@
(fx/defn on-text-input
{:events [::on-text-input]}
[{:keys [db] :as cofx} {:keys [new-text previous-text start end] :as args}]
[{:keys [db]} {:keys [previous-text start end] :as args}]
(log/debug "[mentions] on-text-input args" args)
(let [normalized-previous-text
;; NOTE(rasom): on iOS `previous-text` contains entire input's text. To
@ -457,16 +457,14 @@
previous-text
(subs previous-text start end))
chat-id (:current-chat-id db)
previous-state (get-in db [:chats chat-id :mentions])
text (get-in db [:chat/inputs chat-id :input-text])
previous-state (get-in db [:chats/mentions chat-id :mentions])
new-state (-> previous-state
(merge args)
(assoc :previous-text normalized-previous-text))
old-at-idxs (:at-idxs new-state)
new-at-idxs (calc-at-idxs new-state)
new-state (assoc new-state :at-idxs new-at-idxs)]
(log/debug "[mentions] on-text-input state" new-state)
{:db (assoc-in db [:chats chat-id :mentions] new-state)}))
{:db (assoc-in db [:chats/mentions chat-id :mentions] new-state)}))
(defn calculate-input [text [first-idx :as idxs]]
(if-not first-idx
@ -496,8 +494,7 @@
[{:keys [db]} mentionable-users]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
{:keys [new-text start end] :as state}
(get-in db [:chats chat-id :mentions])
state (get-in db [:chats/mentions chat-id :mentions])
new-at-idxs (check-idx-for-mentions
text
(:at-idxs state)
@ -506,25 +503,25 @@
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
{:db (-> db
(update-in
[:chats chat-id :mentions]
[:chats/mentions chat-id :mentions]
assoc
:at-idxs new-at-idxs)
(assoc-in [:chat/inputs-with-mentions chat-id] calculated-input))}))
(fx/defn calculate-suggestions
{:events [::calculate-suggestions]}
[{:keys [db] :as cofx} mentionable-users]
[{:keys [db]} mentionable-users]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
{:keys [new-text at-idxs start end] :as state}
(get-in db [:chats chat-id :mentions])
(get-in db [:chats/mentions chat-id :mentions])
new-text (or new-text text)]
(log/debug "[mentions] calculate suggestions"
"state" state)
(if-not (seq at-idxs)
{:db (-> db
(assoc-in [:chats/mention-suggestions chat-id] nil)
(assoc-in [:chats chat-id :mentions :at-idxs] nil)
(assoc-in [:chats/mentions chat-id :mentions :at-idxs] nil)
(assoc-in [:chat/inputs-with-mentions chat-id] [[:text text]]))}
(let [new-at-idxs (check-idx-for-mentions
text
@ -550,7 +547,7 @@
"mentions" (count mentions))
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
{:db (-> db
(update-in [:chats chat-id :mentions]
(update-in [:chats/mentions chat-id :mentions]
assoc
:at-sign-idx at-sign-idx
:at-idxs new-at-idxs
@ -563,7 +560,7 @@
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
{:keys [mention-end at-sign-idx]}
(get-in db [:chats chat-id :mentions])]
(get-in db [:chats/mentions chat-id :mentions])]
(log/debug "[mentions] clear suggestions"
"state" new-input-text-with-mention)
(string/join
@ -589,7 +586,7 @@
(fx/merge
cofx
{:db (-> db
(update-in [:chats chat-id] dissoc :mentions)
(update-in [:chats/mentions chat-id] dissoc :mentions)
(update :chat/inputs-with-mentions dissoc chat-id))}
(clear-suggestions))))
@ -607,7 +604,7 @@
mentionable-users]
(let [chat-id (:current-chat-id db)
{:keys [mention-end at-idxs]}
(get-in db [:chats chat-id :mentions])]
(get-in db [:chats/mentions chat-id :mentions])]
(when (seq at-idxs)
(if (some
(fn [{:keys [from to] :as idx}]
@ -617,7 +614,7 @@
at-idxs)
(fx/merge
cofx
{:db (update-in db [:chats chat-id :mentions]
{:db (update-in db [:chats/mentions chat-id :mentions]
assoc
:start end
:end end

View File

@ -4,8 +4,7 @@
[status-im.wallet.db :as wallet.db]))
;; initial state of app-db
(def app-db {:keyboard-height 0
:contacts/contacts {}
(def app-db {:contacts/contacts {}
:pairing/installations {}
:group/selected-contacts #{}
:chats {}

View File

@ -66,8 +66,6 @@
(reg-root-key-sub :animations :animations)
(reg-root-key-sub :ui/search :ui/search)
(reg-root-key-sub :web3-node-version :web3-node-version)
(reg-root-key-sub :keyboard-height :keyboard-height)
(reg-root-key-sub :keyboard-max-height :keyboard-max-height)
(reg-root-key-sub :sync-data :sync-data)
(reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?)
(reg-root-key-sub :qr-modal :qr-modal)
@ -789,12 +787,6 @@
(fn [input]
(:input-text input)))
(re-frame/reg-sub
:chats/current-chat-input-text
:<- [:chats/current-chat-inputs]
(fn [input]
(:input-text input)))
(re-frame/reg-sub
:chats/current-chat-membership
:<- [:chats/current-chat-id]
@ -827,6 +819,12 @@
(not group-chat)
(assoc :show-input? true)))))
(re-frame/reg-sub
:chats/current-chat-chat-view
:<- [:chats/current-chat]
(fn [current-chat]
(select-keys current-chat [:chat-id :show-input? :group-chat :admins :invitation-admin :public? :chat-type :color :chat-name])))
(re-frame/reg-sub
:current-chat/metadata
:<- [:chats/current-raw-chat]
@ -914,6 +912,12 @@
(fn [chats [_ chat-id]]
(get-in chats [chat-id :public?])))
(re-frame/reg-sub
:chats/might-have-join-time-messages?
:<- [::chats]
(fn [chats [_ chat-id]]
(get-in chats [chat-id :might-have-join-time-messages?])))
(re-frame/reg-sub
:chats/message-list
:<- [::message-lists]
@ -1019,6 +1023,31 @@
(fn [{:keys [metadata]}]
(:sending-image metadata)))
(re-frame/reg-sub
:chats/chat-toolbar
:<- [:disconnected?]
:<- [:multiaccounts/login]
:<- [:chats/sending-image]
:<- [:mainnet?]
:<- [:current-chat/one-to-one-chat?]
:<- [:current-chat/metadata]
:<- [:chats/reply-message]
(fn [[disconnected? {:keys [processing]} sending-image mainnet? one-to-one-chat? {:keys [public?]} reply]]
(let [sending-image (seq sending-image)]
{:send (and (not (or processing disconnected?)))
:stickers (and mainnet?
(not sending-image)
(not reply))
:image (and (not reply)
(not public?))
:extensions (and one-to-one-chat?
(or config/commands-enabled? mainnet?)
(not reply))
:audio (and (not sending-image)
(not reply)
(not public?))
:sending-image sending-image})))
(re-frame/reg-sub
:public-chat.new/topic-error-message
:<- [:public-group-topic]

View File

@ -49,26 +49,27 @@
(animation/parallel
[(animation/timing blue-bar-left-margin (easing :out 0))
(animation/timing white-bar-left-margin (easing :out 0))])]))))}
[react/view {:style {:width parent-width
:position :absolute
:top -3
:z-index 3
:height 3
:background-color colors/white}
:accessibility-label :loading-indicator}
[react/animated-view {:style (animated-bar-style blue-bar-left-margin
parent-width
colors/blue)}]
[react/animated-view {:style (assoc (animated-bar-style white-bar-left-margin
parent-width
colors/white)
:left (* 0.15 parent-width))}]]))
[react/view
[react/view {:style {:width parent-width
:position :absolute
:top -3
:z-index 3
:height 3
:background-color colors/white}
:accessibility-label :loading-indicator}
[react/animated-view {:style (animated-bar-style blue-bar-left-margin
parent-width
colors/blue)}]
[react/animated-view {:style (assoc (animated-bar-style white-bar-left-margin
parent-width
colors/white)
:left (* 0.15 parent-width))}]]]))
(defview loading-indicator []
(letsubs [ui-status-properties [:connectivity/ui-status-properties]
(letsubs [fetching? [:mailserver/fetching?]
window-width [:dimensions/window-width]]
(when (:loading-indicator? ui-status-properties)
[loading-indicator-anim @window-width])))
(when fetching?
[loading-indicator-anim window-width])))
(defn hide-sheet-and-dispatch [event]
(re-frame/dispatch [:bottom-sheet/hide])

View File

@ -230,7 +230,7 @@
[components/separator-dark]
[react/view
(when loading?
[connectivity/loading-indicator window-width])]
[connectivity/loading-indicator-anim window-width])]
[browser-component {:dapp? dapp?
:dapp dapp
:error? error?

View File

@ -281,14 +281,14 @@
[cancel-button (:cancel-disabled? @state) #(stop-recording base-params)]]
[rec-button-view (merge base-params {:state state})]
[react/animated-view {:style {:opacity ctrl-buttons-anim-value}}
[input/send-button {:on-send-press (fn [] (cond
(= :ready-to-send (:general @state))
(do
(reset-timer timer)
(animate-buttons false false base-params)
(send-audio-msessage state))
[input/send-button (fn [] (cond
(= :ready-to-send (:general @state))
(do
(reset-timer timer)
(animate-buttons false false base-params)
(send-audio-msessage state))
(#{:recording :recording-paused} (:general @state))
(stop-recording (merge base-params
{:on-success
#(send-audio-msessage state)}))))}]]]])))
(#{:recording :recording-paused} (:general @state))
(stop-recording (merge base-params
{:on-success
#(send-audio-msessage state)}))))]]]])))

View File

@ -10,16 +10,17 @@
[status-im.chat.constants :as chat.constants]
[status-im.utils.utils :as utils.utils]
[quo.components.animated.pressable :as pressable]
[quo.animated :as animated]
[status-im.utils.config :as config]
[re-frame.core :as re-frame]
[status-im.i18n.i18n :as i18n]
[clojure.string :as string]
[status-im.chat.models.mentions :as mentions]
[status-im.ui.components.list.views :as list]
[quo.components.list.item :as list-item]
[status-im.ui.screens.chat.photos :as photos]
[reagent.core :as reagent]))
[reagent.core :as reagent]
[clojure.string :as string]))
(defn input-focus [text-input-ref]
(some-> ^js (react/current-ref text-input-ref) .focus))
(def panel->icons {:extensions :main-icons/commands
:images :main-icons/photo})
@ -70,9 +71,8 @@
[icons/icon :main-icons/keyboard (styles/icon false)]
[icons/icon :main-icons/speech (styles/icon false)])]])
(defn send-button [{:keys [on-send-press]}]
[pressable/pressable {:type :scale
:on-press on-send-press}
(defn send-button [on-send]
[rn/touchable-opacity {:on-press-in on-send}
[rn/view {:style (styles/send-message-button)}
[icons/icon :main-icons/arrow-up
{:container-style (styles/send-message-container)
@ -81,8 +81,8 @@
(defn on-selection-change [timeout-id last-text-change mentionable-users args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)]
start (.-start selection)
end (.-end selection)]
;; NOTE(rasom): on iOS we do not dispatch this event immediately
;; because it is needed only in case if selection is changed without
;; typing. Timeout might be canceled on `on-change`.
@ -107,8 +107,50 @@
:end end}
mentionable-users]))))
(defn on-change [on-text-change last-text-change timeout-id mentionable-users args]
(let [text (.-text ^js (.-nativeEvent ^js args))]
(defonce input-texts (atom {}))
(defonce mentions-enabled (reagent/atom {}))
(defn show-send [{:keys [actions-ref send-ref sticker-ref]}]
(quo.react/set-native-props actions-ref #js {:width 0 :left -88})
(quo.react/set-native-props send-ref #js {:width nil :right nil})
(quo.react/set-native-props sticker-ref #js {:width 0 :right -100}))
(defn hide-send [{:keys [actions-ref send-ref sticker-ref]}]
(quo.react/set-native-props actions-ref #js {:width nil :left nil})
(quo.react/set-native-props send-ref #js {:width 0 :right -100})
(quo.react/set-native-props sticker-ref #js {:width nil :right nil}))
(defn reset-input [refs chat-id]
(some-> ^js (react/current-ref (:text-input-ref refs)) .clear)
(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)
(do
(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))
(reset-input refs chat-id)))
(defn on-text-change [val chat-id]
(swap! input-texts assoc chat-id val)
;;we still store it in app-db for mentions, we don't have reactions in views
(re-frame/dispatch [:chat.ui/set-chat-input-text val]))
(defn on-change [last-text-change timeout-id mentionable-users refs chat-id sending-image args]
(let [text (.-text ^js (.-nativeEvent ^js args))
prev-text (get @input-texts chat-id)]
(when (and (seq prev-text) (empty? text) (not sending-image))
(hide-send refs))
(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))
;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it
;; happens during typing because it is not needed for mention
;; suggestions calculation
@ -116,19 +158,23 @@
(utils.utils/clear-timeout @timeout-id))
(when platform/android?
(reset! last-text-change (js/Date.now)))
(on-text-change text)
(on-text-change text chat-id)
;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`,
;; that's why mention suggestions are calculated on `on-change`
(when platform/ios?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn on-text-input [mentionable-users args]
(let [native-event (.-nativeEvent ^js args)
text (.-text ^js native-event)
(defn on-text-input [mentionable-users chat-id args]
(let [native-event (.-nativeEvent ^js args)
text (.-text ^js native-event)
previous-text (.-previousText ^js native-event)
range (.-range ^js native-event)
start (.-start ^js range)
end (.-end ^js range)]
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))
(re-frame/dispatch
[::mentions/on-text-input
{:new-text text
@ -141,14 +187,15 @@
(when platform/android?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn text-input
[{:keys [cooldown-enabled? input-with-mentions on-text-change set-active-panel text-input-ref]}]
(let [mentionable-users @(re-frame/subscribe [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)]
(defn text-input [{:keys [set-active-panel refs chat-id sending-image]}]
(let [cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)
mentions-enabled (get @mentions-enabled chat-id)]
[rn/text-input
{:style (styles/text-input)
:ref text-input-ref
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
:text-align-vertical :center
@ -165,45 +212,45 @@
:underline-color-android :transparent
:auto-capitalize :sentences
:on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users)
:on-change (partial on-change
on-text-change last-text-change timeout-id mentionable-users)
:on-text-input (partial on-text-input mentionable-users)}
(for [[idx [type text]] (map-indexed
(fn [idx item]
[idx item])
input-with-mentions)]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention)
{:style {:color "#0DA4C9"}})
text])]))
: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)}
(if mentions-enabled
(for [[idx [type text]] (map-indexed
(fn [idx item]
[idx item])
@(re-frame/subscribe [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color "#0DA4C9"}})
text])
(get @input-texts chat-id))]))
(defn mention-item
[[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref]
(let [ens-name? (not= alias name)]
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium
:title-text-weight :medium
:on-press
(fn []
(re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))}
@ -211,14 +258,14 @@
ens-name?
(assoc :subtitle alias))]))
(def chat-input-height (reagent/atom nil))
(def chat-toolbar-height (reagent/atom nil))
(defn autocomplete-mentions [text-input-ref]
(defn autocomplete-mentions [text-input-ref bottom]
(let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])]
(when (and (seq suggestions) @chat-input-height)
(when (seq suggestions)
(let [height (+ 16 (* 52 (min 4.5 (count suggestions))))]
[rn/view
{:style (styles/autocomplete-container @chat-input-height)
{:style (styles/autocomplete-container bottom)
:accessibility-label :suggestions-list}
[rn/view
{:style {:height height}}
@ -231,120 +278,87 @@
:render-data text-input-ref
:render-fn mention-item}]]]))))
(defn chat-input
[{:keys [set-active-panel active-panel on-send-press reply
show-send show-image show-stickers show-extensions
sending-image input-focus show-audio]
:as props}]
[rn/view {:style (styles/toolbar)
:on-layout #(reset! chat-input-height
(-> ^js % .-nativeEvent .-layout .-height))}
[rn/view {:style (styles/actions-wrapper (and (not show-extensions)
(not show-image)))}
(when show-extensions
[touchable-icon {:panel :extensions
:accessibility-label :show-extensions-icon
:active active-panel
:set-active set-active-panel}])
(when show-image
[touchable-icon {:panel :images
:accessibility-label :show-photo-icon
:active active-panel
:set-active set-active-panel}])]
[:<>
;; NOTE(rasom): on iOS `autocomplete-mentions` should be placed inside
;; `chat-input` (otherwise suggestions will be hidden by keyboard) but
;; outside animated view below because it adds horizontal margin
(when platform/ios?
[autocomplete-mentions])
[animated/view
{:style (styles/input-container)}
(when reply
[reply/reply-message reply])
(when (seq sending-image)
[reply/send-image sending-image])
[rn/view {:style (styles/input-row)}
[text-input props]
[rn/view {:style (styles/in-input-buttons)}
(when show-send
[send-button {:on-send-press on-send-press}])
(when show-stickers
[touchable-stickers-icon {:panel :stickers
:accessibility-label :show-stickers-icon
:active active-panel
:input-focus input-focus
:set-active set-active-panel}])
(when show-audio
[touchable-audio-icon {:panel :audio
:accessibility-label :show-audio-message-icon
:active active-panel
:input-focus input-focus
:set-active set-active-panel}])]]]]])
(defn on-chat-toolbar-layout [^js ev]
(reset! chat-toolbar-height (-> ev .-nativeEvent .-layout .-height)))
(defn focus-input-on-reply [reply had-reply text-input-ref]
;;when we show reply we focus input
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
(js/setTimeout #(input-focus text-input-ref) 250))))
(defn reply-message [text-input-ref]
(let [had-reply (atom nil)]
(fn []
(let [reply @(re-frame/subscribe [:chats/reply-message])]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply/reply-message reply])))))
(defn send-image []
(let [sending-image @(re-frame/subscribe [:chats/sending-image])]
(when (seq sending-image)
[reply/send-image sending-image])))
(defn actions [extensions image show-send actions-ref active-panel set-active-panel]
[rn/view {:style (styles/actions-wrapper show-send)
:ref actions-ref}
(when extensions
[touchable-icon {:panel :extensions
:accessibility-label :show-extensions-icon
:active active-panel
:set-active set-active-panel}])
(when image
[touchable-icon {:panel :images
:accessibility-label :show-photo-icon
:active active-panel
:set-active set-active-panel}])])
(defn chat-toolbar []
(let [previous-layout (atom nil)
had-reply (atom nil)]
(fn [{:keys [active-panel set-active-panel text-input-ref on-text-change]}]
(let [disconnected? @(re-frame/subscribe [:disconnected?])
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
mainnet? @(re-frame/subscribe [:mainnet?])
input-text
@(re-frame/subscribe [:chats/current-chat-input-text])
input-with-mentions
@(re-frame/subscribe [:chat/input-with-mentions])
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
one-to-one-chat? @(re-frame/subscribe [:current-chat/one-to-one-chat?])
{:keys [public?
chat-id]} @(re-frame/subscribe [:current-chat/metadata])
reply @(re-frame/subscribe [:chats/reply-message])
sending-image @(re-frame/subscribe [:chats/sending-image])
input-focus (fn []
(some-> ^js (react/current-ref text-input-ref) .focus))
clear-input (fn []
(some-> ^js (react/current-ref text-input-ref) .clear))
empty-text (string/blank? (string/trim (or input-text "")))
show-send (and (or (not empty-text)
sending-image)
(not (or processing disconnected?)))
show-stickers (and empty-text
mainnet?
(not sending-image)
(not reply))
show-image (and empty-text
(not reply)
(not public?))
show-extensions (and empty-text
one-to-one-chat?
(or config/commands-enabled? mainnet?)
(not reply))
show-audio (and empty-text
(not sending-image)
(not reply)
(not public?))]
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
(js/setTimeout input-focus 250)))
(when (and platform/ios? (not= @previous-layout [show-send show-stickers show-extensions show-audio]))
(reset! previous-layout [show-send show-stickers show-extensions show-audio])
(when (seq @previous-layout)
(rn/configure-next
(:ease-opacity-200 rn/custom-animations))))
[chat-input {:set-active-panel set-active-panel
:active-panel active-panel
:text-input-ref text-input-ref
:input-focus input-focus
:reply reply
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
(clear-input))
:text-value input-text
:input-with-mentions input-with-mentions
:on-text-change on-text-change
:cooldown-enabled? cooldown-enabled?
:show-send show-send
:show-stickers show-stickers
:show-image show-image
:show-audio show-audio
:sending-image sending-image
:show-extensions show-extensions
:chat-id chat-id}]))))
(let [actions-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
sticker-ref (quo.react/create-ref)
toolbar-options (re-frame/subscribe [:chats/chat-toolbar])]
(fn [{:keys [active-panel set-active-panel text-input-ref chat-id]}]
(let [;we want to control components on native level, so instead of RN state we set native props via reference
;we don't react on input text in this view, @input-texts below is a regular atom
refs {:actions-ref actions-ref
:send-ref send-ref
:sticker-ref sticker-ref
:text-input-ref text-input-ref}
{:keys [send stickers image extensions audio sending-image]} @toolbar-options
show-send (or sending-image (seq (get @input-texts chat-id)))]
[rn/view {:style (styles/toolbar)
:on-layout on-chat-toolbar-layout}
;;EXTENSIONS and IMAGE buttons
[actions extensions image show-send actions-ref active-panel set-active-panel]
[rn/view {:style (styles/input-container)}
[reply-message text-input-ref]
[send-image]
[rn/view {:style styles/input-row}
[text-input {:chat-id chat-id
:sending-image sending-image
:refs refs
:set-active-panel set-active-panel}]
;;SEND button
[rn/view {:ref send-ref :style (when-not show-send {:width 0 :right -100})}
(when send
[send-button #(do (clear-input chat-id refs)
(re-frame/dispatch [:chat.ui/send-current-message]))])]
;;STICKERS and AUDIO buttons
[rn/view {:style (merge {:flex-direction :row} (when show-send {:width 0 :right -100}))
:ref sticker-ref}
(when stickers
[touchable-stickers-icon {:panel :stickers
:accessibility-label :show-stickers-icon
:active active-panel
:input-focus #(input-focus text-input-ref)
:set-active set-active-panel}])
(when audio
[touchable-audio-icon {:panel :audio
:accessibility-label :show-audio-message-icon
:active active-panel
:input-focus #(input-focus text-input-ref)
:set-active set-active-panel}])]]]]))))

View File

@ -8,7 +8,6 @@
:padding-vertical 8
:border-top-width 1
:border-top-color (:ui-01 @colors/theme)
:background-color (:ui-background @colors/theme)
:align-items :flex-end
:flex-direction :row})
@ -21,8 +20,9 @@
:border-bottom-left-radius 16
:margin-horizontal 8})
(defn input-row []
(def input-row
{:flex-direction :row
:overflow :hidden
:align-items :flex-end})
(defn text-input-wrapper []
@ -41,7 +41,6 @@
:min-height 34
:max-height 144
:margin 0
:border-width 0
:flex-shrink 1
:color (:text-01 @colors/theme)
:padding-horizontal 12}
@ -50,11 +49,13 @@
{:padding-top 2
:padding-bottom 6})))
(defn actions-wrapper [invisible]
{:flex-direction :row
:padding-left 4
:min-height 34
:left (if invisible -88 0)})
(defn actions-wrapper [show-send]
(merge
(when show-send
{:width 0 :left -88})
{:flex-direction :row
:padding-left 4
:min-height 34}))
(defn touchable-icon []
{:padding-horizontal 10
@ -97,17 +98,12 @@
:margin-horizontal 5})
(defn send-message-container []
{:background-color (:interactive-01 @colors/theme)
:width 26
:height 26
:border-radius 13
:justify-content :center
:align-items :center})
(defn in-input-buttons []
{:flex-direction :row
:height 34
:overflow :hidden})
{:background-color (:interactive-01 @colors/theme)
:width 26
:height 26
:border-radius 13
:justify-content :center
:align-items :center})
(defn send-icon-color []
colors/white)
@ -118,6 +114,5 @@
:right 0
:bottom bottom
:background-color (colors/get-color :ui-background)
:flex-direction :column
:border-top-width 1
:border-top-color (colors/get-color :ui-01)})
:border-top-color (colors/get-color :ui-01)})

View File

@ -30,7 +30,7 @@
connected? [:mailserver/connected?]]
(let [ids (:ids gaps)]
(when-not (and first-gap? might-have-join-time-messages?)
[react/view {:style style/gap-container}
[react/view {:style (style/gap-container)}
[react/touchable-highlight
{:on-press (when (and connected? (not in-progress?))
(on-press ids first-gap? idx list-ref chat-id))

View File

@ -163,6 +163,9 @@
:else [one-to-one-chat-accents current-chat]))
(defn current-chat-actions []
[actions @(re-frame/subscribe [:chats/current-chat])])
(defn options [chat-id message-id]
(fn []
[react/view

View File

@ -4,5 +4,11 @@
(defonce scrolling (atom nil))
(defn start-scrolling []
(reset! scrolling true))
(defn stop-scrolling []
(reset! scrolling false))
(defn reset-visible-item []
(reset! first-not-visible-item nil))

View File

@ -1,7 +1,7 @@
(ns status-im.ui.screens.chat.styles.input.gap
(:require [status-im.ui.components.colors :as colors]))
(def gap-container
(defn gap-container []
{:align-self :stretch
:margin-top 24
:margin-bottom 24

View File

@ -31,15 +31,9 @@
(i18n/label :chat-is-a-contact)
(i18n/label :chat-is-not-a-contact))]]))
(defn toolbar-content-view [{:keys [group-chat
invitation-admin
color
chat-id
contacts
chat-type
chat-name
public?]}]
(when chat-id
(defn toolbar-content-view []
(let [{:keys [group-chat invitation-admin color chat-id contacts chat-type chat-name public?]}
@(re-frame/subscribe [:chats/current-chat])]
[react/view {:style st/toolbar-container}
[react/view {:margin-right 10}
[react/touchable-highlight {:on-press #(when-not group-chat (re-frame/dispatch [:chat.ui/show-profile chat-id]))}

View File

@ -20,7 +20,6 @@
[status-im.ui.screens.chat.image.views :as image]
[status-im.ui.screens.chat.state :as state]
[status-im.ui.screens.chat.extensions.views :as extensions]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.components.invite.chat :as invite.chat]
@ -32,18 +31,28 @@
[clojure.string :as string]
[status-im.constants :as constants]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]))
[status-im.utils.utils :as utils]
[quo.design-system.colors :as quo.colors]))
(defn topbar [current-chat]
[topbar/topbar
{:content [toolbar-content/toolbar-content-view current-chat]
:navigation {:on-press #(re-frame/dispatch [:close-chat (:chat-id current-chat)])}
:right-accessories [{:icon :main-icons/more
:accessibility-label :chat-menu-button
:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions current-chat])
:height 256}])}]}])
(defn topbar []
;;we don't use topbar component, because we want chat view as simple (fast) as possible
[react/view {:height 56 :border-bottom-width 1 :border-bottom-color (:ui-01 @quo.colors/theme)}
[react/touchable-highlight {:on-press-in #(re-frame/dispatch [:close-chat])
:accessibility-label :back-button
:style {:height 56 :width 40 :align-items :center :justify-content :center
:padding-left 16}}
[icons/icon :main-icons/arrow-left]]
[react/view {:flex 1 :left 52 :right 52 :top 0 :bottom 0 :position :absolute}
[toolbar-content/toolbar-content-view]]
[react/touchable-highlight {:on-press-in #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/current-chat-actions])
:height 256}])
:accessibility-label :chat-menu-button
:style {:right 0 :top 0 :bottom 0 :position :absolute
:height 56 :width 40 :align-items :center :justify-content :center
:padding-right 16}}
[icons/icon :main-icons/more]]])
(defn invitation-requests [chat-id admins]
(let [current-pk @(re-frame/subscribe [:multiaccount/public-key])
@ -59,16 +68,15 @@
(i18n/label :t/group-membership-request)]]])))))
(defn add-contact-bar [public-key]
(let [added? @(re-frame/subscribe [:contacts/contact-added? public-key])]
(when-not added?
[react/touchable-highlight
{:on-press
#(re-frame/dispatch [:contact.ui/add-to-contact-pressed public-key])
:accessibility-label :add-to-contacts-button}
[react/view {:style (style/add-contact)}
[icons/icon :main-icons/add
{:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]])))
(when-not @(re-frame/subscribe [:contacts/contact-added? public-key])
[react/touchable-highlight
{:on-press
#(re-frame/dispatch [:contact.ui/add-to-contact-pressed public-key])
:accessibility-label :add-to-contacts-button}
[react/view {:style (style/add-contact)}
[icons/icon :main-icons/add
{:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]]))
(defn chat-intro [{:keys [chat-id
chat-name
@ -114,7 +122,6 @@
(defn chat-intro-header-container
[{:keys [group-chat invitation-admin
chat-type
might-have-join-time-messages?
color chat-id chat-name
public?]}
no-messages]
@ -131,7 +138,7 @@
:chat-name chat-name
:public? public?
:color color
:loading-messages? might-have-join-time-messages?
:loading-messages? @(re-frame/subscribe [:chats/might-have-join-time-messages? chat-id])
:no-messages? no-messages}]
(if group-chat
[chat-intro opts]
@ -233,6 +240,7 @@
(let [loading-messages? @(re-frame/subscribe [:chats/loading-messages? chat-id])
no-messages? @(re-frame/subscribe [:chats/chat-no-messages? chat-id])
all-loaded? @(re-frame/subscribe [:chats/all-loaded? chat-id])]
(println "LIST FOOTER" (or loading-messages? (not chat-id) (not all-loaded?)))
[react/view {:style (when platform/android? {:scaleY -1})}
(if (or loading-messages? (not chat-id) (not all-loaded?))
[react/view {:height 324 :align-items :center :justify-content :center}
@ -265,16 +273,28 @@
:show-input? show-input?)
space-keeper]))])
(defn messages-view
[{:keys [chat bottom-space pan-responder space-keeper show-input?]}]
(def list-key-fn #(or (:message-id %) (:value %)))
(def list-ref #(reset! messages-list-ref %))
;;TODO this is not really working in pair with inserting new messages because we stop inserting new messages
;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because we
;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will work wrong
(defn list-on-end-reached []
(if @state/scrolling
(re-frame/dispatch [:chat.ui/load-more-messages-for-current-chat])
(utils/set-timeout #(re-frame/dispatch [:chat.ui/load-more-messages-for-current-chat])
(if platform/low-device? 700 200))))
(defn messages-view [{:keys [chat bottom-space pan-responder space-keeper show-input?]}]
(let [{:keys [group-chat chat-id public?]} chat
messages @(re-frame/subscribe [:chats/chat-messages-stream chat-id])
current-public-key @(re-frame/subscribe [:multiaccount/public-key])]
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn #(or (:message-id %) (:value %))
:ref #(reset! messages-list-ref %)
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data messages
@ -286,75 +306,63 @@
:show-input? show-input?}
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
;;TODO this is not really working in pair with inserting new messages because we stop inserting new messages
;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because we
;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will work wrong
:on-end-reached (fn []
(if @state/scrolling
(re-frame/dispatch [:chat.ui/load-more-messages chat-id])
(utils/set-timeout #(re-frame/dispatch [:chat.ui/load-more-messages chat-id])
(if platform/low-device? 500 200))))
:on-scroll-to-index-failed #() ;;don't remove this
:on-end-reached list-on-end-reached
:on-scroll-to-index-failed identity ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin #(reset! state/scrolling true)
:onMomentumScrollEnd #(reset! state/scrolling false)
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
(defn chat []
(let [bottom-space (reagent/atom 0)
panel-space (reagent/atom 52)
active-panel (reagent/atom nil)
position-y (animated/value 0)
pan-state (animated/value 0)
(let [bottom-space (reagent/atom 0)
panel-space (reagent/atom 52)
active-panel (reagent/atom nil)
position-y (animated/value 0)
pan-state (animated/value 0)
text-input-ref (quo.react/create-ref)
on-update #(when-not (zero? %) (reset! panel-space %))
pan-responder (accessory/create-pan-responder position-y pan-state)
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
on-update #(when-not (zero? %) (reset! panel-space %))
pan-responder (accessory/create-pan-responder position-y pan-state)
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
set-active-panel (get-set-active-panel active-panel)
on-text-change #(re-frame/dispatch [:chat.ui/set-chat-input-text %])]
on-close #(set-active-panel nil)]
(fn []
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat}
@(re-frame/subscribe [:chats/current-chat])]
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as chat}
;;we want to react only on these fields, do not use full chat map here
@(re-frame/subscribe [:chats/current-chat-chat-view])
max-bottom-space (max @bottom-space @panel-space)]
[:<>
[topbar]
[connectivity/loading-indicator]
[topbar current-chat]
[:<>
(when current-chat
(if group-chat
[invitation-requests chat-id admins]
[add-contact-bar chat-id]))
;;MESSAGES LIST
[messages-view {:chat current-chat
:bottom-space (max @bottom-space @panel-space)
:pan-responder pan-responder
:space-keeper space-keeper
:show-input? show-input?}]]
(when chat-id
(if group-chat
[invitation-requests chat-id admins]
[add-contact-bar chat-id]))
;;MESSAGES LIST
[messages-view {:chat chat
:bottom-space max-bottom-space
:pan-responder pan-responder
:space-keeper space-keeper
:show-input? show-input?}]
(when (and group-chat invitation-admin)
[accessory/view {:y position-y
:on-update-inset on-update}
[invitation-bar chat-id]])
;; NOTE(rasom): on android we have to place `autocomplete-mentions`
;; outside `accessory/view` because otherwise :keyboardShouldPersistTaps
;; :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 text-input-ref])
[components/autocomplete-mentions text-input-ref max-bottom-space]
(when show-input?
[accessory/view {:y position-y
:pan-state pan-state
:has-panel (boolean @active-panel)
:on-close #(set-active-panel nil)
:on-close on-close
:on-update-inset on-update}
[components/chat-toolbar
{:active-panel @active-panel
:set-active-panel set-active-panel
:text-input-ref text-input-ref
:on-text-change on-text-change}]
{:chat-id chat-id
:active-panel @active-panel
:set-active-panel set-active-panel
:text-input-ref text-input-ref}]
[bottom-sheet @active-panel]])]))))

View File

@ -152,29 +152,31 @@
[communities.views/community-home-list-item home-item]))
(defn communities-and-chats [items loading? search-filter hide-home-tooltip?]
(if loading?
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/activity-indicator {:animating true}]]
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
(not @search-active?))
[welcome-blank-page]
[list/flat-list
{:key-fn :chat-id
:keyboard-should-persist-taps :always
:data items
:render-fn render-fn
:header [:<>
(when (or (seq items) @search-active? (seq search-filter))
[search-input-wrapper search-filter items])
[referral-item/list-item]
(when (and (empty? items)
(or @search-active? (seq search-filter)))
[start-suggestion search-filter])]
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}])))
[:<>
[connectivity/loading-indicator]
(if loading?
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/activity-indicator {:animating true}]]
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
(not @search-active?))
[welcome-blank-page]
[list/flat-list
{:key-fn #(or (:chat-id %) (:id %))
:keyboard-should-persist-taps :always
:data items
:render-fn render-fn
:header [:<>
(when (or (seq items) @search-active? (seq search-filter))
[search-input-wrapper search-filter items])
[referral-item/list-item]
(when (and (empty? items)
(or @search-active? (seq search-filter)))
[start-suggestion search-filter])]
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}]))])
(views/defview chats-list []
(views/letsubs [loading? [:chats/loading?]
@ -196,6 +198,5 @@
[topbar/topbar {:title (i18n/label :t/chat)
:navigation :none
:right-component [connectivity/connectivity-button]}]
[connectivity/loading-indicator]
[chats-list]
[plus-button]])

View File

@ -15,7 +15,6 @@
[status-im.ui.screens.bottom-sheets.views :as bottom-sheets]
[status-im.utils.config :as config]
[status-im.reloader :as reloader]
[status-im.utils.platform :as platform]
[status-im.i18n.i18n :as i18n]
["react-native" :as rn]
["react-native-languages" :default react-native-languages]
@ -60,25 +59,9 @@
(reagent/create-class
{:component-did-mount
(fn [_]
(.addListener ^js react/keyboard
(if platform/ios?
"keyboardWillShow"
"keyboardDidShow")
(fn [^js e]
(let [h (.. e -endCoordinates -height)]
(re-frame/dispatch-sync [:set :keyboard-height h])
(re-frame/dispatch-sync [:set :keyboard-max-height h]))))
(.addListener ^js react/keyboard
(if platform/ios?
"keyboardWillHide"
"keyboardDidHide")
(fn [_]
(re-frame/dispatch-sync [:set :keyboard-height 0])))
(.addEventListener ^js react/app-state "change" app-state-change-handler)
(.addEventListener react-native-languages "change" on-languages-change)
(.addEventListener react-native-shake
"ShakeEvent"
on-shake)
(.addEventListener react-native-shake "ShakeEvent" on-shake)
(.hide ^js splash-screen)
(utils.universal-links/initialize))
:component-will-unmount

View File

@ -108,39 +108,33 @@
:font-size 15 :line-height 22}}
(str (i18n/format-currency (* amount price) (:code wallet-currency)) " " (:code wallet-currency))])))
(views/defview select-account-sheet [{:keys [from message]}]
(views/letsubs [window-height [:dimensions/window-height]
keyboard-height [:keyboard-height]]
(let [small-screen? (< (- window-height keyboard-height) 450)]
[react/view {:style (styles/acc-sheet)}
[header {:small-screen? small-screen?
:label :t/select-account}]
[react/view {:flex-direction :row :padding-horizontal 24 :align-items :center
:margin-vertical (if small-screen? 8 16)}]
(when-not small-screen?
[quo/list-header
(i18n/label :t/from)])
[react/view {:flex-direction :row :flex 1 :align-items :center}
(when small-screen?
[react/i18n-text {:style {:width 50 :text-align :right :color colors/gray} :key :t/from}])
[react/view {:flex 1}
[render-account from nil ::commands/set-selected-account]]]
[toolbar/toolbar
{:left
[react/view {:padding-horizontal 8}
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:set :commands/select-account nil])}
(i18n/label :t/cancel)]]
:right
[quo/button
{:accessibility-label :select-account-bottom-sheet
:disabled (nil? from)
:on-press #(re-frame/dispatch
[::commands/accept-request-address-for-transaction
(:message-id message)
(:address from)])}
(i18n/label :t/share)]}]])))
(defn select-account-sheet [{:keys [from message]}]
[react/view {:style (styles/acc-sheet)}
[header {:small-screen? false
:label :t/select-account}]
[react/view {:flex-direction :row :padding-horizontal 24 :align-items :center
:margin-vertical 16}]
[quo/list-header
(i18n/label :t/from)]
[react/view {:flex-direction :row :flex 1 :align-items :center}
[react/view {:flex 1}
[render-account from nil ::commands/set-selected-account]]]
[toolbar/toolbar
{:left
[react/view {:padding-horizontal 8}
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [:set :commands/select-account nil])}
(i18n/label :t/cancel)]]
:right
[quo/button
{:accessibility-label :select-account-bottom-sheet
:disabled (nil? from)
:on-press #(re-frame/dispatch
[::commands/accept-request-address-for-transaction
(:message-id message)
(:address from)])}
(i18n/label :t/share)]}]])
(defview select-account []
(letsubs [data [:commands/select-account]]

View File

@ -60,8 +60,6 @@
;; TODO: Add message explaining db export
(let [db-json (types/clj->json (select-keys db [:app-state
:current-chat-id
:keyboard-height
:keyboard-max-height
:network
:network-status
:peers-count