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) (s/explain spec prop)
false))) 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 height 44) ; 22 line-height + 11*2 vertical padding
(def multiline-height 88) ; 3 * 22 three line-height + 11* vertical padding (def multiline-height 88) ; 3 * 22 three line-height + 11* vertical padding

View File

@ -16,6 +16,10 @@
(oset! ref "current" val) (oset! ref "current" val)
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] (deftype StateHook [value set-value]
cljs.core/IHash cljs.core/IHash
(-hash [o] (goog/getUid o)) (-hash [o] (goog/getUid o))

View File

@ -238,12 +238,13 @@
(fx/defn close-chat (fx/defn close-chat
{:events [:close-chat]} {:events [:close-chat]}
[{:keys [db] :as cofx} chat-id] [{:keys [db] :as cofx}]
(chat.state/reset-visible-item) (let [chat-id (:current-chat-id db)]
(fx/merge cofx (chat.state/reset-visible-item)
{:db (dissoc db :current-chat-id)} (fx/merge cofx
(offload-messages chat-id) {:db (dissoc db :current-chat-id)}
(navigation/navigate-back))) (offload-messages chat-id)
(navigation/navigate-back))))
(fx/defn remove-chat (fx/defn remove-chat
"Removes chat completely from app, producing all necessary effects for that" "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}] [{:keys [db] :as cofx} text-input-ref {:keys [alias name searched-text match] :as user}]
(let [chat-id (:current-chat-id db) (let [chat-id (:current-chat-id db)
new-text (mentions/new-input-text-with-mention cofx user) 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)] cursor (+ at-sign-idx (count name) 2)]
(fx/merge (fx/merge
cofx cofx

View File

@ -122,6 +122,11 @@
#(re-frame/dispatch [::messages-loaded chat-id session-id %]) #(re-frame/dispatch [::messages-loaded chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages 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 (fx/defn load-messages
[{:keys [db now] :as cofx} chat-id] [{:keys [db now] :as cofx} chat-id]
(when-not (get-in db [:pagination-info chat-id :messages-initialized?]) (when-not (get-in db [:pagination-info chat-id :messages-initialized?])

View File

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

View File

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

View File

@ -66,8 +66,6 @@
(reg-root-key-sub :animations :animations) (reg-root-key-sub :animations :animations)
(reg-root-key-sub :ui/search :ui/search) (reg-root-key-sub :ui/search :ui/search)
(reg-root-key-sub :web3-node-version :web3-node-version) (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 :sync-data :sync-data)
(reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?) (reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?)
(reg-root-key-sub :qr-modal :qr-modal) (reg-root-key-sub :qr-modal :qr-modal)
@ -789,12 +787,6 @@
(fn [input] (fn [input]
(:input-text 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 (re-frame/reg-sub
:chats/current-chat-membership :chats/current-chat-membership
:<- [:chats/current-chat-id] :<- [:chats/current-chat-id]
@ -827,6 +819,12 @@
(not group-chat) (not group-chat)
(assoc :show-input? true))))) (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 (re-frame/reg-sub
:current-chat/metadata :current-chat/metadata
:<- [:chats/current-raw-chat] :<- [:chats/current-raw-chat]
@ -914,6 +912,12 @@
(fn [chats [_ chat-id]] (fn [chats [_ chat-id]]
(get-in chats [chat-id :public?]))) (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 (re-frame/reg-sub
:chats/message-list :chats/message-list
:<- [::message-lists] :<- [::message-lists]
@ -1019,6 +1023,31 @@
(fn [{:keys [metadata]}] (fn [{:keys [metadata]}]
(:sending-image 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 (re-frame/reg-sub
:public-chat.new/topic-error-message :public-chat.new/topic-error-message
:<- [:public-group-topic] :<- [:public-group-topic]

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@
:padding-vertical 8 :padding-vertical 8
:border-top-width 1 :border-top-width 1
:border-top-color (:ui-01 @colors/theme) :border-top-color (:ui-01 @colors/theme)
:background-color (:ui-background @colors/theme)
:align-items :flex-end :align-items :flex-end
:flex-direction :row}) :flex-direction :row})
@ -21,8 +20,9 @@
:border-bottom-left-radius 16 :border-bottom-left-radius 16
:margin-horizontal 8}) :margin-horizontal 8})
(defn input-row [] (def input-row
{:flex-direction :row {:flex-direction :row
:overflow :hidden
:align-items :flex-end}) :align-items :flex-end})
(defn text-input-wrapper [] (defn text-input-wrapper []
@ -41,7 +41,6 @@
:min-height 34 :min-height 34
:max-height 144 :max-height 144
:margin 0 :margin 0
:border-width 0
:flex-shrink 1 :flex-shrink 1
:color (:text-01 @colors/theme) :color (:text-01 @colors/theme)
:padding-horizontal 12} :padding-horizontal 12}
@ -50,11 +49,13 @@
{:padding-top 2 {:padding-top 2
:padding-bottom 6}))) :padding-bottom 6})))
(defn actions-wrapper [invisible] (defn actions-wrapper [show-send]
{:flex-direction :row (merge
:padding-left 4 (when show-send
:min-height 34 {:width 0 :left -88})
:left (if invisible -88 0)}) {:flex-direction :row
:padding-left 4
:min-height 34}))
(defn touchable-icon [] (defn touchable-icon []
{:padding-horizontal 10 {:padding-horizontal 10
@ -97,17 +98,12 @@
:margin-horizontal 5}) :margin-horizontal 5})
(defn send-message-container [] (defn send-message-container []
{:background-color (:interactive-01 @colors/theme) {:background-color (:interactive-01 @colors/theme)
:width 26 :width 26
:height 26 :height 26
:border-radius 13 :border-radius 13
:justify-content :center :justify-content :center
:align-items :center}) :align-items :center})
(defn in-input-buttons []
{:flex-direction :row
:height 34
:overflow :hidden})
(defn send-icon-color [] (defn send-icon-color []
colors/white) colors/white)
@ -118,6 +114,5 @@
:right 0 :right 0
:bottom bottom :bottom bottom
:background-color (colors/get-color :ui-background) :background-color (colors/get-color :ui-background)
:flex-direction :column
:border-top-width 1 :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?]] connected? [:mailserver/connected?]]
(let [ids (:ids gaps)] (let [ids (:ids gaps)]
(when-not (and first-gap? might-have-join-time-messages?) (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 [react/touchable-highlight
{:on-press (when (and connected? (not in-progress?)) {:on-press (when (and connected? (not in-progress?))
(on-press ids first-gap? idx list-ref chat-id)) (on-press ids first-gap? idx list-ref chat-id))

View File

@ -163,6 +163,9 @@
:else [one-to-one-chat-accents current-chat])) :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] (defn options [chat-id message-id]
(fn [] (fn []
[react/view [react/view

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,6 @@
[status-im.ui.screens.bottom-sheets.views :as bottom-sheets] [status-im.ui.screens.bottom-sheets.views :as bottom-sheets]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.reloader :as reloader] [status-im.reloader :as reloader]
[status-im.utils.platform :as platform]
[status-im.i18n.i18n :as i18n] [status-im.i18n.i18n :as i18n]
["react-native" :as rn] ["react-native" :as rn]
["react-native-languages" :default react-native-languages] ["react-native-languages" :default react-native-languages]
@ -60,25 +59,9 @@
(reagent/create-class (reagent/create-class
{:component-did-mount {:component-did-mount
(fn [_] (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 ^js react/app-state "change" app-state-change-handler)
(.addEventListener react-native-languages "change" on-languages-change) (.addEventListener react-native-languages "change" on-languages-change)
(.addEventListener react-native-shake (.addEventListener react-native-shake "ShakeEvent" on-shake)
"ShakeEvent"
on-shake)
(.hide ^js splash-screen) (.hide ^js splash-screen)
(utils.universal-links/initialize)) (utils.universal-links/initialize))
:component-will-unmount :component-will-unmount

View File

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

View File

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