messages improvements

This commit is contained in:
andrey 2021-02-16 15:16:32 +01:00
parent f572f5ef5e
commit 25d5672355
No known key found for this signature in database
GPG Key ID: 89B67245FD2F0272
56 changed files with 1195 additions and 1247 deletions

View File

@ -3,7 +3,6 @@
[taoensso.timbre :as log]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.transport.filters.core :as transport.filters]
[status-im.contact.core :as contact.core]
[status-im.data-store.chats :as chats-store]
[status-im.data-store.messages :as messages-store]
[status-im.ethereum.json-rpc :as json-rpc]
@ -18,7 +17,9 @@
[status-im.utils.types :as types]
[status-im.add-new.db :as new-public-chat.db]
[status-im.mailserver.topics :as mailserver.topics]
[status-im.mailserver.constants :as mailserver.constants]))
[status-im.mailserver.constants :as mailserver.constants]
[status-im.chat.models.loading :as loading]
[status-im.ui.screens.chat.state :as chat.state]))
(defn chats []
(:chats (types/json->clj (js/require "./chats.js"))))
@ -68,6 +69,12 @@
([cofx chat-id]
(timeline-chat? (get-chat cofx chat-id))))
(defn profile-chat?
([chat]
(:profile-public-key chat))
([cofx chat-id]
(profile-chat? (get-chat cofx chat-id))))
(defn set-chat-ui-props
"Updates ui-props in active chat by merging provided kvs into them"
[{:keys [current-chat-id] :as db} kvs]
@ -165,19 +172,6 @@
[{:keys [db] :as cofx} chat-id on-success]
(chats-store/save-chat cofx (get-in db [:chats chat-id]) on-success))
(fx/defn handle-mark-all-read-successful
{:events [::mark-all-read-successful]}
[{:keys [db] :as cofx} chat-id]
{:db (assoc-in db [:chats chat-id :unviewed-messages-count] 0)})
(fx/defn handle-mark-all-read
{:events [:chat.ui/mark-all-read-pressed
:chat.ui/mark-public-all-read]}
[{:keys [db] :as cofx} chat-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "markAllRead")
:params [chat-id]
:on-success #(re-frame/dispatch [::mark-all-read-successful chat-id])}]})
(fx/defn add-public-chat
"Adds new public group chat to db"
[cofx topic profile-public-key timeline?]
@ -198,8 +192,7 @@
:contacts #{}
:public? true
:might-have-join-time-messages? (get-in cofx [:db :multiaccount :use-mailservers?])
:unviewed-messages-count 0
:loaded-unviewed-messages-ids #{}}
:unviewed-messages-count 0}
nil))
(fx/defn clear-history
@ -232,6 +225,23 @@
(assoc-in [:chats chat-id :is-active] false)
(assoc-in [:current-chat-id] nil))})
(fx/defn offload-messages
{:events [:offload-messages]}
[{:keys [db]} chat-id]
{:db (-> db
(update :messages dissoc chat-id)
(update :message-lists dissoc chat-id)
(update :pagination-info dissoc chat-id))})
(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-to-cofx :home {})))
(fx/defn remove-chat
"Removes chat completely from app, producing all necessary effects for that"
{:events [:chat.ui/remove-chat]}
@ -240,43 +250,27 @@
(mailserver/remove-gaps chat-id)
(mailserver/remove-range chat-id)
(deactivate-chat chat-id)
(offload-messages chat-id)
(clear-history chat-id true)
(transport.filters/stop-listening chat-id)
(when (not (= (:view-id db) :home))
(navigation/navigate-to-cofx :home {}))))
(fx/defn offload-all-messages
{:events [::offload-all-messages]}
[{:keys [db] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)]
{:db
(-> db
(dissoc :loaded-chat-id)
(update :messages dissoc current-chat-id)
(update :message-lists dissoc current-chat-id)
(update :pagination-info dissoc current-chat-id))}))
(fx/defn preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
{:events [:chat.ui/preload-chat-data]}
[{:keys [db] :as cofx} chat-id]
(let [old-current-chat-id (:current-chat-id db)]
(fx/merge cofx
{:dispatch [:load-messages]}
(when-not (= old-current-chat-id chat-id)
(offload-all-messages))
(fn [{:keys [db]}]
{:db (assoc db :current-chat-id chat-id)})
;; Group chat don't need this to load as all the loading of topics
;; happens on membership changes
(when-not (or (group-chat? cofx chat-id) (timeline-chat? cofx chat-id))
(transport.filters/load-chat chat-id)))))
(fx/merge cofx
(when-not (or (group-chat? cofx chat-id) (timeline-chat? cofx chat-id))
(transport.filters/load-chat chat-id))
(loading/load-messages chat-id)))
(fx/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-chat]}
[{db :db :as cofx} chat-id]
(fx/merge cofx
{:db (assoc db :inactive-chat-id chat-id)}
{:db (assoc db :current-chat-id chat-id)}
(preload-chat-data chat-id)
(navigation/navigate-to-cofx :chat-stack {:screen :chat})))
@ -287,17 +281,18 @@
;; don't allow to open chat with yourself
(when (not= (multiaccounts.model/current-public-key cofx) chat-id)
(fx/merge cofx
{:dispatch [:chat.ui/navigate-to-chat chat-id]}
(upsert-chat {:chat-id chat-id
:is-active true}
nil)
(transport.filters/load-chat chat-id)
(navigate-to-chat chat-id))))
(def timeline-chat-id "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a")
(transport.filters/load-chat chat-id))))
(defn profile-chat-topic [public-key]
(str "@" public-key))
(defn my-profile-chat-topic [db]
(profile-chat-topic (get-in db [:multiaccount :public-key])))
(fx/defn start-public-chat
"Starts a new public chat"
{:events [:chat.ui/start-public-chat]}
@ -310,7 +305,7 @@
(add-public-chat topic profile-public-key false)
(transport.filters/load-chat topic)
#(when-not dont-navigate?
(navigate-to-chat % topic))))
{:dispatch [:chat.ui/navigate-to-chat topic]})))
{:utils/show-popup {:title (i18n/label :t/cant-open-public-chat)
:content (i18n/label :t/invalid-public-chat-topic)}}))
@ -327,8 +322,8 @@
(fx/defn start-timeline-chat
"Starts a new timeline chat"
[cofx]
(when-not (active-chat? cofx timeline-chat-id)
(add-public-chat cofx timeline-chat-id nil true)))
(when-not (active-chat? cofx constants/timeline-chat-id)
(add-public-chat cofx constants/timeline-chat-id nil true)))
(fx/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)"
@ -344,17 +339,6 @@
(i18n/label :cooldown/warning-message)
#())))
(fx/defn show-profile-without-adding-contact
{:events [:chat.ui/show-profile-without-adding-contact]}
[{:keys [db] :as cofx} identity]
(let [my-public-key (get-in db [:multiaccount :public-key])]
(if (= my-public-key identity)
(navigation/navigate-to-cofx cofx :profile-stack {:screen :my-profile})
(fx/merge
cofx
{:db (assoc db :contacts/identity identity)}
(navigation/navigate-to-cofx :profile nil)))))
(fx/defn mute-chat-failed
{:events [::mute-chat-failed]}
[{:keys [db] :as cofx} chat-id muted? error]
@ -380,10 +364,16 @@
(fx/defn show-profile
{:events [:chat.ui/show-profile]}
[cofx identity]
(fx/merge (assoc-in cofx [:db :contacts/identity] identity)
(contact.core/create-contact identity)
(navigation/navigate-to-cofx :profile nil)))
[{:keys [db] :as cofx} identity]
(let [my-public-key (get-in db [:multiaccount :public-key])]
(if (= my-public-key identity)
(navigation/navigate-to-cofx cofx :profile-stack {:screen :my-profile})
(fx/merge
cofx
{:db (assoc db :contacts/identity identity)
:dispatch [:chat.ui/preload-chat-data (profile-chat-topic identity)]}
(start-profile-chat identity)
(navigation/navigate-to-cofx :profile nil)))))
(fx/defn clear-history-pressed
{:events [:chat.ui/clear-history-pressed]}
@ -398,13 +388,12 @@
(fx/defn chat-ui-fill-gaps
{:events [:chat.ui/fill-gaps]}
[{:keys [db] :as cofx} gap-ids]
(let [chat-id (:current-chat-id db)
topics (mailserver.topics/topics-for-current-chat db)
gaps (keep
(fn [id]
(get-in db [:mailserver/gaps chat-id id]))
gap-ids)]
[{:keys [db] :as cofx} gap-ids chat-id]
(let [topics (mailserver.topics/topics-for-chat db chat-id)
gaps (keep
(fn [id]
(get-in db [:mailserver/gaps chat-id id]))
gap-ids)]
(mailserver/fill-the-gap
cofx
{:gaps gaps
@ -413,16 +402,14 @@
(fx/defn chat-ui-fetch-more
{:events [:chat.ui/fetch-more]}
[{:keys [db] :as cofx}]
(let [chat-id (:current-chat-id db)
{:keys [lowest-request-from]}
[{:keys [db] :as cofx} chat-id]
(let [{:keys [lowest-request-from]}
(get-in db [:mailserver/ranges chat-id])
topics (mailserver.topics/topics-for-current-chat db)
gaps [{:id :first-gap
:to lowest-request-from
:from (- lowest-request-from mailserver.constants/one-day)}]]
topics (mailserver.topics/topics-for-chat db chat-id)
gaps [{:id :first-gap
:to lowest-request-from
:from (- lowest-request-from mailserver.constants/one-day)}]]
(mailserver/fill-the-gap
cofx
{:gaps gaps

View File

@ -9,7 +9,8 @@
[status-im.utils.image-processing :as image-processing]
[taoensso.timbre :as log]
[clojure.string :as string]
[status-im.utils.platform :as platform]))
[status-im.utils.platform :as platform]
[status-im.chat.models :as chat]))
(def maximum-image-size-px 2000)
@ -55,24 +56,24 @@
(re-frame/reg-fx
::chat-open-image-picker-camera
(fn []
(fn [current-chat-id]
(react/show-image-picker-camera
#(re-frame/dispatch [:chat.ui/image-captured (.-path %)]) {})))
#(re-frame/dispatch [:chat.ui/image-captured current-chat-id (.-path %)]) {})))
(re-frame/reg-fx
::chat-open-image-picker
(fn []
(fn [chat-id]
(react/show-image-picker
(fn [^js images]
;; NOTE(Ferossgp): Because we can't highlight the already selected images inside
;; gallery, we just clean previous state and set all newly picked images
(when (and platform/ios? (pos? (count images)))
(re-frame/dispatch [:chat.ui/clear-sending-images]))
(re-frame/dispatch [:chat.ui/clear-sending-images chat-id]))
(doseq [^js result (if platform/ios?
(take config/max-images-batch images)
[images])]
(resize-and-call (.-path result)
#(re-frame/dispatch [:chat.ui/image-selected (result->id result) %]))))
#(re-frame/dispatch [:chat.ui/image-selected chat-id (result->id result) %]))))
;; NOTE(Ferossgp): On android you cannot set max limit on images, when a user
;; selects too many images the app crashes.
{:media-type "photo"
@ -80,10 +81,10 @@
(re-frame/reg-fx
::image-selected
(fn [uri]
(fn [[uri chat-id]]
(resize-and-call
uri
#(re-frame/dispatch [:chat.ui/image-selected uri %]))))
#(re-frame/dispatch [:chat.ui/image-selected chat-id uri %]))))
(re-frame/reg-fx
::camera-roll-get-photos
@ -97,12 +98,12 @@
(fx/defn image-captured
{:events [:chat.ui/image-captured]}
[{:keys [db]} uri]
(let [current-chat-id (:current-chat-id db)
[{:keys [db]} chat-id uri]
(let [current-chat-id (or chat-id (:current-chat-id db))
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(when (and (< (count images) config/max-images-batch)
(not (get images uri)))
{::image-selected uri})))
{::image-selected [uri current-chat-id]})))
(fx/defn camera-roll-get-photos
{:events [:chat.ui/camera-roll-get-photos]}
@ -116,20 +117,24 @@
(fx/defn clear-sending-images
{:events [:chat.ui/clear-sending-images]}
[{:keys [db]}]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})}))
[{:keys [db]} current-chat-id]
{:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})})
(fx/defn cancel-sending-image
{:events [:chat.ui/cancel-sending-image]}
[cofx]
(clear-sending-images cofx))
[{:keys [db] :as cofx} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))]
(clear-sending-images cofx current-chat-id)))
(fx/defn cancel-sending-image-timeline
{:events [:chat.ui/cancel-sending-image-timeline]}
[{:keys [db] :as cofx}]
(cancel-sending-image cofx (chat/my-profile-chat-topic db)))
(fx/defn image-selected
{:events [:chat.ui/image-selected]}
[{:keys [db]} original uri]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})}))
[{:keys [db]} current-chat-id original uri]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})})
(fx/defn image-unselected
{:events [:chat.ui/image-unselected]}
@ -139,31 +144,46 @@
(fx/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]}
[{:keys [db]}]
(let [current-chat-id (:current-chat-id db)
[{:keys [db]} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker nil})))
{::chat-open-image-picker current-chat-id})))
(fx/defn chat-open-image-picker-timeline
{:events [:chat.ui/open-image-picker-timeline]}
[{:keys [db] :as cofx}]
(chat-open-image-picker cofx (chat/my-profile-chat-topic db)))
(fx/defn chat-show-image-picker-camera
{:events [:chat.ui/show-image-picker-camera]}
[{:keys [db]}]
(let [current-chat-id (:current-chat-id db)
[{:keys [db]} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker-camera nil})))
{::chat-open-image-picker-camera current-chat-id})))
(fx/defn chat-show-image-picker-camera-timeline
{:events [:chat.ui/show-image-picker-camera-timeline]}
[{:keys [db] :as cofx}]
(chat-show-image-picker-camera cofx (chat/my-profile-chat-topic db)))
(fx/defn camera-roll-pick
{:events [:chat.ui/camera-roll-pick]}
[{:keys [db]} uri]
(let [current-chat-id (:current-chat-id db)
[{:keys [db]} uri chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(if (get-in db [:chats current-chat-id :timeline?])
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-image] {})
::image-selected uri}
::image-selected [uri current-chat-id]}
(when (and (< (count images) config/max-images-batch)
(not (get images uri)))
{::image-selected uri}))))
{::image-selected [uri current-chat-id]}))))
(fx/defn camera-roll-pick-timeline
{:events [:chat.ui/camera-roll-pick-timeline]}
[{:keys [db] :as cofx} uri]
(camera-roll-pick cofx uri (chat/my-profile-chat-topic db)))
(fx/defn save-image-to-gallery
{:events [:chat.ui/save-image-to-gallery]}

View File

@ -28,8 +28,15 @@
"Set input text for current-chat. Takes db and input text and cofx
as arguments and returns new fx. Always clear all validation messages."
{:events [:chat.ui/set-chat-input-text]}
[{{:keys [current-chat-id] :as db} :db} new-input]
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
[{db :db} new-input chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))]
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}))
(fx/defn set-timeline-input-text
{:events [:chat.ui/set-timeline-input-text]}
[{db :db} new-input]
{:db (assoc-in db [:chat/inputs (chat/my-profile-chat-topic db) :input-text]
(text->emoji new-input))})
(fx/defn select-mention
{:events [:chat.ui/select-mention]}
@ -43,7 +50,7 @@
{:db (-> db
(assoc-in [:chats/cursor chat-id] cursor)
(assoc-in [:chats/mention-suggestions chat-id] nil))}
(set-chat-input-text new-text)
(set-chat-input-text new-text chat-id)
;; NOTE(rasom): Some keyboards do not react on selection property passed to
;; text input (specifically Samsung keyboard with predictive text set on).
;; In this case, if the user continues typing after the programmatic change,
@ -132,8 +139,8 @@
:ens-name preferred-name})))
(defn build-image-messages
[{{:keys [current-chat-id] :as db} :db} chat-id]
(let [images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
[{db :db} chat-id]
(let [images (get-in db [:chat/inputs chat-id :metadata :sending-image])]
(mapv (fn [[_ {:keys [uri]}]]
{:chat-id chat-id
:content-type constants/content-type-image
@ -141,13 +148,12 @@
:text (i18n/label :t/update-to-see-image)})
images)))
(fx/defn clean-input [{:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
(fx/merge cofx
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))}
(set-chat-input-text nil))))
(fx/defn clean-input [{:keys [db] :as cofx} current-chat-id]
(fx/merge cofx
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))}
(set-chat-input-text nil current-chat-id)))
(fx/defn send-messages [{:keys [db] :as cofx} input-text current-chat-id]
(let [image-messages (build-image-messages cofx current-chat-id)
@ -155,17 +161,23 @@
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(fx/merge cofx
(clean-input cofx)
(clean-input cofx (:current-chat-id db))
(process-cooldown)
(chat.message/send-messages messages)))))
(fx/defn send-my-status-message
"when not empty, proceed by sending text message with public key topic"
{:events [:profile.ui/send-my-status-message]}
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])
chat-id (chat/profile-chat-topic (get-in db [:multiaccount :public-key]))]
(send-messages cofx input-text chat-id)))
[{db :db :as cofx}]
(let [current-chat-id (chat/my-profile-chat-topic db)
{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])
image-messages (build-image-messages cofx current-chat-id)
text-message (build-text-message cofx input-text current-chat-id)
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(fx/merge cofx
(clean-input current-chat-id)
(chat.message/send-messages messages)))))
(fx/defn send-audio-message
[cofx audio-path duration current-chat-id]

View File

@ -1,27 +1,26 @@
(ns status-im.chat.models.loading
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.ui.screens.chat.state :as chat.state]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]
[status-im.mailserver.core :as mailserver]
[status-im.utils.fx :as fx]
[status-im.chat.models.reactions :as reactions]
[status-im.chat.models.message-list :as message-list]
[taoensso.timbre :as log]
[status-im.chat.models.message-seen :as message-seen]
[status-im.chat.models.mentions :as mentions]))
[status-im.ethereum.json-rpc :as json-rpc]
[clojure.string :as string]))
(defn cursor->clock-value
[^js cursor]
(js/parseInt (.substring cursor 51 64)))
(defn clock-value->cursor [clock-value]
(str "000000000000000000000000000000000000000000000000000" clock-value "0x0000000000000000000000000000000000000000000000000000000000000000"))
(str "000000000000000000000000000000000000000000000000000"
clock-value
"0x0000000000000000000000000000000000000000000000000000000000000000"))
(fx/defn update-chats-in-app-db
{:events [:chats-list/load-success]}
[{:keys [db] :as cofx} new-chats]
[{:keys [db]} new-chats]
(let [old-chats (:chats db)
chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(assoc acc chat-id chat))
@ -31,35 +30,13 @@
{:db (assoc db :chats chats
:chats/loading? false)}))
(fx/defn handle-chat-visibility-changed
{:events [:chat.ui/message-visibility-changed]}
[{:keys [db] :as cofx} ^js event]
(let [^js viewable-items (.-viewableItems event)
^js last-element (aget viewable-items (dec (.-length viewable-items)))]
(when last-element
(let [last-element-clock-value (:clock-value (.-item last-element))
chat-id (:chat-id (.-item last-element))]
(when (and last-element-clock-value
(get-in db [:pagination-info chat-id :messages-initialized?]))
(let [new-messages (reduce-kv (fn [acc message-id {:keys [clock-value] :as v}]
(if (<= last-element-clock-value clock-value)
(assoc acc message-id v)
acc))
{}
(get-in db [:messages chat-id]))]
{:db (-> db
(assoc-in [:messages chat-id] new-messages)
(assoc-in [:pagination-info chat-id] {:all-loaded? false
:messages-initialized? true
:cursor (clock-value->cursor last-element-clock-value)})
(assoc-in [:message-lists chat-id] (message-list/add-many nil (vals new-messages))))}))))))
(fx/defn initialize-chats
"Initialize persisted chats on startup"
[cofx]
(data-store.chats/fetch-chats-rpc cofx {:on-success
#(re-frame/dispatch
[:chats-list/load-success %])}))
(fx/defn handle-failed-loading-messages
{:events [::failed-loading-messages]}
[{:keys [db]} current-chat-id _ err]
@ -67,104 +44,88 @@
(when current-chat-id
{:db (assoc-in db [:pagination-info current-chat-id :loading-messages?] false)}))
(fx/defn handle-mark-all-read-successful
{:events [::mark-all-read-successful]}
[{:keys [db]} chat-id]
{:db (assoc-in db [:chats chat-id :unviewed-messages-count] 0)})
(fx/defn handle-mark-all-read
{:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]}
[_ chat-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "markAllRead")
:params [chat-id]
:on-success #(re-frame/dispatch [::mark-all-read-successful chat-id])}]})
(fx/defn messages-loaded
"Loads more messages for current chat"
{:events [::messages-loaded]}
[{{:keys [current-chat-id] :as db} :db :as cofx}
chat-id
session-id
{:keys [cursor messages]}]
(when-not (or (nil? current-chat-id)
(not= chat-id current-chat-id)
(and (get-in db [:pagination-info current-chat-id :messages-initialized?])
(not= session-id
(get-in db [:pagination-info current-chat-id :messages-initialized?]))))
(let [already-loaded-messages (get-in db [:messages current-chat-id])
loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{})
users (get-in db [:chats current-chat-id :users] {})
[{db :db} chat-id session-id {:keys [cursor messages]}]
(when-not (and (get-in db [:pagination-info chat-id :messages-initialized?])
(not= session-id
(get-in db [:pagination-info chat-id :messages-initialized?])))
(let [already-loaded-messages (get-in db [:messages chat-id])
;; We remove those messages that are already loaded, as we might get some duplicates
{:keys [all-messages
new-messages
last-clock-value
unviewed-message-ids
users]}
(reduce (fn [{:keys [last-clock-value all-messages users] :as acc}
{:keys [clock-value seen message-id alias name identicon from]
:as message}]
(let [nickname (get-in db [:contacts/contacts from :nickname])]
(cond-> acc
(and alias (not= alias ""))
(update :users assoc from
(mentions/add-searchable-phrases
{:alias alias
:name (or name alias)
:identicon identicon
:public-key from
:nickname nickname}))
(or (nil? last-clock-value)
(> last-clock-value clock-value))
(assoc :last-clock-value clock-value)
{:keys [all-messages new-messages senders]}
(reduce (fn [{:keys [all-messages] :as acc}
{:keys [message-id alias from]
:as message}]
(cond-> acc
(and (not (string/blank? alias))
(not (get-in db [:chats chat-id :users from])))
(update :senders assoc from message)
(not seen)
(update :unviewed-message-ids conj message-id)
(nil? (get all-messages message-id))
(update :new-messages conj message)
(nil? (get all-messages message-id))
(update :new-messages conj message)
:always
(update :all-messages assoc message-id message)))
{:all-messages already-loaded-messages
:senders {}
:new-messages []}
messages)
current-clock-value (get-in db [:pagination-info chat-id :cursor-clock-value])
clock-value (when cursor (cursor->clock-value cursor))]
{:dispatch [:chat/add-senders-to-chat-users (vals senders)]
:db (-> db
(update-in [:pagination-info chat-id :cursor-clock-value]
#(if (and (seq cursor) (or (not %) (< clock-value %)))
clock-value
%))
:always
(update :all-messages assoc message-id message))))
{:all-messages already-loaded-messages
:unviewed-message-ids loaded-unviewed-messages-ids
:users users
:new-messages []}
messages)]
(fx/merge cofx
{:db (-> db
(assoc-in [:pagination-info current-chat-id :cursor-clock-value] (when (seq cursor) (cursor->clock-value cursor)))
(assoc-in [:chats current-chat-id :loaded-unviewed-messages-ids] unviewed-message-ids)
(assoc-in [:chats current-chat-id :users] users)
(assoc-in [:pagination-info current-chat-id :loading-messages?] false)
(assoc-in [:messages current-chat-id] all-messages)
(update-in [:message-lists current-chat-id] message-list/add-many new-messages)
(assoc-in [:pagination-info current-chat-id :cursor] cursor)
(assoc-in [:pagination-info current-chat-id :all-loaded?]
(empty? cursor)))}
(message-seen/mark-messages-seen current-chat-id)))))
(update-in [:pagination-info chat-id :cursor]
#(if (or (empty? cursor) (not current-clock-value) (< clock-value current-clock-value))
cursor
%))
(assoc-in [:pagination-info chat-id :loading-messages?] false)
(assoc-in [:messages chat-id] all-messages)
(update-in [:message-lists chat-id] message-list/add-many new-messages)
(assoc-in [:pagination-info chat-id :all-loaded?]
(empty? cursor)))})))
(fx/defn load-more-messages
{:events [:chat.ui/load-more-messages]}
[{:keys [db] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)]
(when-let [session-id (get-in db [:pagination-info current-chat-id :messages-initialized?])]
(when-not (or (get-in db [:pagination-info current-chat-id :all-loaded?])
(get-in db [:pagination-info current-chat-id :loading-messages?]))
(let [cursor (get-in db [:pagination-info current-chat-id :cursor])
load-messages-fx (data-store.messages/messages-by-chat-id-rpc
current-chat-id
cursor
constants/default-number-of-messages
#(re-frame/dispatch [::messages-loaded current-chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages current-chat-id session-id %]))]
(fx/merge cofx
load-messages-fx
(reactions/load-more-reactions cursor)
(mailserver/load-gaps-fx current-chat-id)))))))
[{:keys [db]} chat-id first-request]
(when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])]
(when (and
(not (get-in db [:pagination-info chat-id :all-loaded?]))
(not (get-in db [:pagination-info chat-id :loading-messages?])))
(let [cursor (get-in db [:pagination-info chat-id :cursor])]
(when (or first-request cursor)
(merge
{:db (assoc-in db [:pagination-info chat-id :loading-messages?] true)}
{:utils/dispatch-later [{:ms 100 :dispatch [:load-more-reactions cursor chat-id]}
{:ms 100 :dispatch [:load-gaps chat-id]}]}
(data-store.messages/messages-by-chat-id-rpc
chat-id
cursor
constants/default-number-of-messages
#(re-frame/dispatch [::messages-loaded chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages chat-id session-id %]))))))))
(fx/defn load-messages
{:events [:load-messages]}
[{:keys [db now] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)]
(if-not (get-in db [:pagination-info current-chat-id :messages-initialized?])
(do
; reset chat first-not-visible-items state
(chat.state/reset)
(fx/merge cofx
{:db (-> db
;; We keep track of whether there's a loaded chat
;; which will be reset only if we hit home
(assoc :loaded-chat-id current-chat-id)
(assoc-in [:pagination-info current-chat-id :messages-initialized?] now))}
(message-seen/mark-messages-seen current-chat-id)
(load-more-messages)))
;; We mark messages as seen in case we received them while on a different tab
(message-seen/mark-messages-seen cofx current-chat-id))))
[{:keys [db now] :as cofx} chat-id]
(when-not (get-in db [:pagination-info chat-id :messages-initialized?])
(fx/merge cofx
{:db (assoc-in db [:pagination-info chat-id :messages-initialized?] now)
:utils/dispatch-later [{:ms 500 :dispatch [:chat.ui/mark-all-read-pressed chat-id]}]}
(load-more-messages chat-id true))))

View File

@ -1,214 +1,188 @@
(ns status-im.chat.models.message
(:require [re-frame.core :as re-frame]
[status-im.chat.models :as chat-model]
[status-im.chat.models.loading :as chat-loading]
(:require [status-im.chat.models :as chat-model]
[status-im.chat.models.message-list :as message-list]
[status-im.constants :as constants]
[status-im.data-store.messages :as data-store.messages]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.transport.message.protocol :as protocol]
[status-im.ui.screens.chat.state :as view.state]
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]
[status-im.chat.models.mentions :as mentions]
[clojure.string :as string]))
[clojure.string :as string]
[status-im.contact.db :as contact.db]
[status-im.utils.types :as types]
[status-im.ui.screens.chat.state :as view.state]
[status-im.chat.models.loading :as chat.loading]
[status-im.utils.platform :as platform]))
(defn- prepare-message
[message current-chat?]
(cond-> message
current-chat?
(assoc :seen true)))
(defn- message-loaded?
[db chat-id message-id]
(get-in db [:messages chat-id message-id]))
(defn- earlier-than-deleted-at?
[db chat-id clock-value]
(>= (get-in db [:chats chat-id :deleted-at-clock-value]) clock-value))
(defn add-timeline-message [acc chat-id message-id message]
(-> acc
(update-in [:db :messages chat-id] assoc message-id message)
(update-in [:db :message-lists chat-id] message-list/add message)))
;;TODO this is too expensive, probably we could mark message somehow and just hide it in the UI
(fx/defn rebuild-message-list
[{:keys [db]} chat-id]
{:db (assoc-in db [:message-lists chat-id]
(message-list/add-many nil (vals (get-in db [:messages chat-id]))))})
(fx/defn hidden-message-marked-as-seen
{:events [::hidden-message-marked-as-seen]}
[{:keys [db] :as cofx} chat-id _ hidden-message-count]
(when (= 1 hidden-message-count)
{:db (update-in db [:chats chat-id]
update
:unviewed-messages-count dec)}))
(fx/defn hide-message
(defn hide-message
"Hide chat message, rebuild message-list"
[{:keys [db] :as cofx} chat-id {:keys [seen message-id]}]
(fx/merge cofx
{:db (update-in db [:messages chat-id] dissoc message-id)}
(data-store.messages/mark-messages-seen chat-id [message-id] #(re-frame/dispatch [::hidden-message-marked-as-seen %1 %2 %3]))
(rebuild-message-list chat-id)))
[{:keys [db]} chat-id message-id]
;;TODO this is too expensive, probably we could mark message somehow and just hide it in the UI
(rebuild-message-list {:db (update-in db [:messages chat-id] dissoc message-id)} chat-id))
(fx/defn add-message
[{:keys [db] :as cofx}
{{:keys [chat-id message-id replace timestamp from] :as message} :message
:keys [seen-by-user?]}]
(let [current-public-key (multiaccounts.model/current-public-key cofx)
message-to-be-removed (when replace
(get-in db [:messages chat-id replace]))
prepared-message (prepare-message message seen-by-user?)]
(fx/merge cofx
(when message-to-be-removed
(hide-message chat-id message-to-be-removed))
(fn [{:keys [db]}]
{:db (cond-> (-> db
;; We should not be always adding to the list, as it does not make sense
;; if the chat has not been initialized, but run into
;; some troubles disabling it, so next time
(update-in [:messages chat-id] assoc message-id prepared-message)
(update-in [:message-lists chat-id] message-list/add prepared-message))
(and (not seen-by-user?)
(not= from current-public-key)
(not (get-in db [:chats chat-id :profile-public-key]))
(not (get-in db [:chats chat-id :timeline?])))
(update-in [:chats chat-id :loaded-unviewed-messages-ids]
(fnil conj #{}) message-id))}))))
(fx/defn join-times-messages-checked
"The key :might-have-join-time-messages? in public chats signals that
the public chat is freshly (re)created and requests for messages to the
mailserver for the topic has not completed yet. Likewise, the key
:join-time-mail-request-id is associated a little bit after, to signal that
the request to mailserver was a success. When request is signalled complete
by mailserver, corresponding event :chat.ui/join-times-messages-checked
dissociates these two fileds via this function, thereby signalling that the
public chat is not fresh anymore."
{:events [:chat/join-times-messages-checked]}
[{:keys [db] :as cofx} chat-ids]
(reduce (fn [acc chat-id]
(cond-> acc
(:might-have-join-time-messages? (chat-model/get-chat cofx chat-id))
(update :db #(chat-model/dissoc-join-time-fields % chat-id))))
{:db db}
chat-ids))
(fx/defn add-sender-to-chat-users
[{:keys [db]} {:keys [chat-id alias name identicon from]}]
(when (and alias (not= alias ""))
(let [nickname (get-in db [:contacts/contacts from :nickname])]
{:db (update-in db [:chats chat-id :users] assoc
from
(mentions/add-searchable-phrases
{:alias alias
:name (or name alias)
:identicon identicon
:public-key from
:nickname nickname}))})))
(fx/defn add-senders-to-chat-users
{:events [:chat/add-senders-to-chat-users]}
[{:keys [db]} messages]
(reduce (fn [acc {:keys [chat-id alias name identicon from]}]
(update-in acc [:db :chats chat-id :users] assoc
from
(mentions/add-searchable-phrases
{:alias alias
:name (or name alias)
:identicon identicon
:public-key from
:nickname (get-in db [:contacts/contacts from :nickname])})))
{:db db}
messages))
(fx/defn add-received-message
[{:keys [db] :as cofx}
{:keys [chat-id clock-value] :as message}]
(let [{:keys [loaded-chat-id view-id current-chat-id]} db
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
current-chat? (= chat-id loaded-chat-id)]
(when current-chat?
(fx/merge
cofx
;; If we don't have any hidden message or the hidden message is before
;; this one, we add the message to the UI
(if (or (not @view.state/first-not-visible-item)
(<= (:clock-value @view.state/first-not-visible-item)
clock-value))
(add-message {:message message
:seen-by-user? (and current-chat?
(= view-id :chat))})
;; Not in the current view, set all-loaded to false
;; and offload to db and update cursor if necessary
{:db (cond-> (assoc-in db [:chats chat-id :all-loaded?] false)
(>= clock-value cursor-clock-value)
(update-in [:chats chat-id] assoc
:cursor (chat-loading/clock-value->cursor clock-value)
:cursor-clock-value clock-value))})
(add-sender-to-chat-users message)))))
(defn timeline-message? [db chat-id]
(and
(get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
(or
(= chat-id (chat-model/my-profile-chat-topic db))
(when-let [pub-key (get-in db [:chats chat-id :profile-public-key])]
(contact.db/added? db pub-key)))))
(defn- message-loaded?
[{:keys [db]} {:keys [chat-id message-id]}]
(get-in db [:messages chat-id message-id]))
(defn get-timeline-message [db chat-id message-js]
(when (timeline-message? db chat-id)
(data-store.messages/<-rpc (types/js->clj message-js))))
(defn- earlier-than-deleted-at?
[{:keys [db]} {:keys [chat-id clock-value]}]
(let [{:keys [deleted-at-clock-value]}
(get-in db [:chats chat-id])]
(>= deleted-at-clock-value clock-value)))
(defn add-message [{:keys [db] :as acc} message-js chat-id message-id cursor-clock-value]
(let [{:keys [alias replace from clock-value] :as message}
(data-store.messages/<-rpc (types/js->clj message-js))]
(if (message-loaded? db chat-id message-id)
;; If the message is already loaded, it means it's an update, that
;; happens when a message that was missing a reply had the reply
;; coming through, in which case we just insert the new message
(assoc-in acc [:db :messages chat-id message-id] message)
(cond-> acc
;;add new message to db
:always
(update-in [:db :messages chat-id] assoc message-id message)
:always
(update-in [:db :message-lists chat-id] message-list/add message)
(fx/defn update-unviewed-count
[{:keys [db] :as cofx} {:keys [chat-id from message-type message-id new?]}]
(when-not (= message-type constants/message-type-private-group-system-message)
(let [{:keys [current-chat-id view-id]} db
chat-view? (= :chat view-id)
current-count (get-in db [:chats chat-id :unviewed-messages-count])]
(cond
(= from (multiaccounts.model/current-public-key cofx))
;; nothing to do
nil
(or (not cursor-clock-value) (< clock-value cursor-clock-value))
(update-in [:db :pagination-info chat-id] assoc
:cursor (chat.loading/clock-value->cursor clock-value)
:cursor-clock-value clock-value)
(and chat-view? (= current-chat-id chat-id))
(fx/merge cofx
(data-store.messages/mark-messages-seen current-chat-id [message-id] nil))
;;conj sender for add-sender-to-chat-users
(and (not (string/blank? alias))
(not (get-in db [:chats chat-id :users from])))
(update :senders assoc from message)
new?
{:db (update-in db [:chats chat-id]
assoc
:unviewed-messages-count (inc current-count))}))))
(not (string/blank? replace))
;;TODO this is expensive
(hide-message chat-id replace)))))
(fx/defn check-for-incoming-tx
[cofx {{:keys [transaction-hash]} :command-parameters}]
(when (and transaction-hash
(not (string/blank? transaction-hash)))
;; NOTE(rasom): dispatch later is needed because of circular dependency
{:dispatch-later
[{:dispatch [:watch-tx transaction-hash]
:ms 20}]}))
(defn reduce-js-messages [{:keys [db] :as acc} ^js message-js]
(let [chat-id (.-localChatId message-js)
clock-value (.-clock message-js)
message-id (.-id message-js)
current-chat-id (:current-chat-id db)
cursor-clock-value (get-in db [:pagination-info current-chat-id :cursor-clock-value])]
;;ignore not opened chats and earlier clock
(if (and (get-in db [:pagination-info chat-id :messages-initialized?])
;;TODO why do we need this ?
(not (earlier-than-deleted-at? db chat-id clock-value)))
(if (or (not @view.state/first-not-visible-item)
(<= (:clock-value @view.state/first-not-visible-item)
clock-value))
(add-message acc message-js chat-id message-id cursor-clock-value)
;; Not in the current view, set all-loaded to false
;; and offload to db and update cursor if necessary
;;TODO if we'll offload messages , it will conflict with end reached, so probably if we reached the end of visible area,
;; we need to drop other messages with (< clock-value cursor-clock-value) from response-js so we don't update
;; :cursor-clock-value because it will be changed when we loadMore message
{:db (cond-> (assoc-in db [:pagination-info chat-id :all-loaded?] false)
(> clock-value cursor-clock-value)
;;TODO cut older messages from messages-list
(update-in [:pagination-info chat-id] assoc
:cursor (chat.loading/clock-value->cursor clock-value)
:cursor-clock-value clock-value))})
acc)))
(fx/defn receive-one
{:events [::receive-one]}
[{:keys [db] :as cofx} {:keys [message-id chat-id] :as message}]
(fx/merge cofx
;;If its a profile updates we want to add this message to the timeline as well
#(when (get-in cofx [:db :chats chat-id :profile-public-key])
{:dispatch-n [[::receive-one (assoc message :chat-id chat-model/timeline-chat-id)]]})
#(when-not (earlier-than-deleted-at? cofx message)
(if (message-loaded? cofx message)
;; If the message is already loaded, it means it's an update, that
;; happens when a message that was missing a reply had the reply
;; coming through, in which case we just insert the new message
{:db (assoc-in db [:messages chat-id message-id] message)}
(fx/merge cofx
(add-received-message message)
(update-unviewed-count message)
(chat-model/join-time-messages-checked chat-id)
(check-for-incoming-tx message))))))
(defn receive-many [{:keys [db]} ^js response-js]
(let [current-chat-id (:current-chat-id db)
messages-js ^js (.splice (.-messages response-js) 0 (if platform/low-device? 3 10))
{:keys [db chats senders]}
(reduce reduce-js-messages
{:db db :chats #{} :senders {} :transactions #{}}
messages-js)]
;;we want to render new messages as soon as possible
;;so we dispatch later all other events which can be handled async
{:db db
;;TODO currently we process every message, we need to precess them by batches
;;or better move processing to status-go
#_((fx/defn add-received-messages
[{:keys [db] :as cofx} grouped-messages]
(when-let [messages (get grouped-messages (:loaded-chat-id db))]
(apply fx/merge cofx (map add-received-message messages))))
:utils/dispatch-later
(concat [{:ms 20 :dispatch [:process-response response-js]}]
(when (and current-chat-id
(get chats current-chat-id)
(not (chat-model/profile-chat? {:db db} current-chat-id)))
[{:ms 100 :dispatch [:chat/mark-all-as-read (:current-chat-id db)]}])
(when (seq senders)
[{:ms 100 :dispatch [:chat/add-senders-to-chat-users (vals senders)]}]))}))
(defn reduce-count-messages [me]
(fn [acc chat-id messages]
(assoc acc chat-id
(remove #(or
(= (:message-type %)
constants/message-type-private-group-system-message)
(= (:from %) me))
messages))))
(defn reduce-js-statuses [db ^js message-js]
(let [chat-id (.-localChatId message-js)
profile-initialized (get-in db [:pagination-info chat-id :messages-initialized?])
timeline-message (timeline-message? db chat-id)]
(if (or profile-initialized timeline-message)
(let [{:keys [message-id] :as message} (data-store.messages/<-rpc (types/js->clj message-js))]
(cond-> db
profile-initialized
(update-in [:messages chat-id] assoc message-id message)
profile-initialized
(update-in [:message-lists chat-id] message-list/add message)
timeline-message
(update-in [:messages constants/timeline-chat-id] assoc message-id message)
timeline-message
(update-in [:message-lists constants/timeline-chat-id] message-list/add message)))
db)))
(defn reduce-chat-messages [chat-view? current-chat-id]
(fn [acc chat-id messages]
(if (and chat-view? (= current-chat-id chat-id))
(data-store.messages/mark-messages-seen acc current-chat-id (map :message-id messages) nil)
(update-in acc [:db :chats chat-id :unviewed-messages-count] + (count messages)))))
(fx/defn process-statuses
{:events [:process-statuses]}
[{:keys [db]} statuses]
{:db (reduce reduce-js-statuses db statuses)})
(fx/defn update-unviewed-counts
[{:keys [db] :as cofx} grouped-messages]
(let [{:keys [current-chat-id view-id]} db
me (multiaccounts.model/current-public-key cofx)
messages (reduce-kv (reduce-count-messages me)
{}
grouped-messages)]
(when (seq messages)
(reduce-kv (reduce-chat-messages (= :chat view-id) current-chat-id) {:db db} messages))))
(fx/defn receive [cofx messages]
(when-let [grouped-messages
(->> (into []
(comp
(map #(assoc % :chat-id (extract-chat-id cofx %)))
(remove #(earlier-than-deleted-at? cofx %)))
messages)
(group-by :chat-id))]
(when (seq grouped-messages)
(fx/merge cofx
(add-received-messages grouped-messages)
(update-unviewed-counts grouped-messages)
(chat-model/join-time-messages-checked-for-chats (keys grouped-messages)))))))
;;;; Send message
(fx/defn update-db-message-status
[{:keys [db] :as cofx} chat-id message-id status]
(when (get-in db [:messages chat-id message-id])
@ -242,13 +216,9 @@
(rebuild-message-list chat-id)))
(fx/defn send-message
[{:keys [db now] :as cofx} message]
[cofx message]
(protocol/send-chat-messages cofx [message]))
(fx/defn send-messages
[{:keys [db now] :as cofx} messages]
[cofx messages]
(protocol/send-chat-messages cofx messages))
(fx/defn toggle-expand-message
[{:keys [db]} chat-id message-id]
{:db (update-in db [:messages chat-id message-id :expanded?] not)})

View File

@ -4,6 +4,7 @@
["functional-red-black-tree" :as rb-tree]))
(defn- add-datemark [{:keys [whisper-timestamp] :as msg}]
;;TODO this is slow
(assoc msg :datemark (time/day-relative whisper-timestamp)))
(defn- add-timestamp [{:keys [whisper-timestamp] :as msg}]
@ -132,12 +133,10 @@
(let [^js iter (.find tree message)
^js previous-message (when (.-hasPrev iter)
(get-prev-element iter))
^js next-message (when (.-hasNext iter)
(get-next-element iter))
^js next-message (when (.-hasNext iter)
(get-next-element iter))
^js message-with-pos-data (add-group-info message previous-message next-message)]
(cond->
(.update iter message-with-pos-data)
(cond-> (.update iter message-with-pos-data)
next-message
(-> ^js (.find next-message)
(.update (update-next-message message-with-pos-data next-message)))
@ -170,8 +169,7 @@
(update-message tree prepared-message)))
(defn add [message-list message]
(insert-message (or message-list (rb-tree compare-fn))
(prepare-message message)))
(insert-message (or message-list (rb-tree compare-fn)) (prepare-message message)))
(defn add-many [message-list messages]
(reduce add

View File

@ -13,8 +13,7 @@
{:db (update-in db [:chats chat-id] assoc
:unviewed-messages-count (subtract-seen-messages
unviewed-messages-count
loaded-unviewed-messages-ids)
:loaded-unviewed-messages-ids #{})}))
loaded-unviewed-messages-ids))}))
(fx/defn mark-messages-seen
"Marks all unviewed loaded messages as seen in particular chat"
@ -28,11 +27,4 @@
db
loaded-unviewed-ids)}
(messages-store/mark-messages-seen chat-id loaded-unviewed-ids nil)
(update-chats-unviewed-messages-count {:chat-id chat-id})))))
(fx/defn ui-mark-messages-seen
{:events [:chat.ui/mark-messages-seen]}
[{:keys [db] :as cofx} view-id]
(fx/merge cofx
{:db (assoc db :view-id view-id)}
(mark-messages-seen cofx (:current-chat-id db))))
(update-chats-unviewed-messages-count {:chat-id chat-id})))))

View File

@ -1,155 +1,135 @@
(ns status-im.chat.models.message-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.models.loading :as chat-loading]
[status-im.chat.models.message :as message]
[status-im.chat.models.message-list :as models.message-list]
[status-im.ui.screens.chat.state :as view.state]
[status-im.utils.datetime :as time]))
[status-im.chat.models.loading :as loading]
[status-im.utils.datetime :as time]
[status-im.ui.screens.chat.state :as view.state]))
(deftest add-received-message-test
(with-redefs [message/add-message (constantly :added)]
(with-redefs [message/add-message #(identity %1)]
(let [chat-id "chat-id"
clock-value 10
cursor-clock-value (dec clock-value)
cursor (chat-loading/clock-value->cursor cursor-clock-value)
cursor (loading/clock-value->cursor cursor-clock-value)
cofx {:now 0
:db {:loaded-chat-id chat-id
:current-chat-id chat-id
:all-loaded? true
:chats {chat-id {:cursor cursor
:cursor-clock-value cursor-clock-value}}}}
message {:chat-id chat-id
:clock-value clock-value
:alias "alias"
:name "name"
:identicon "identicon"
:from "from"}]
(testing "not current-chat"
(is (nil? (message/add-received-message
(update cofx :db dissoc :loaded-chat-id)
message))))
:db {:current-chat-id chat-id
:pagination-info {chat-id {:messages-initialized? true
:cursor cursor
:cursor-clock-value cursor-clock-value}}}}
message #js {:localChatId chat-id
:clock clock-value
:alias "alias"
:name "name"
:identicon "identicon"
:from "from"}]
;; <- cursor
;; <- message
;; <- top of the chat
(testing "there's no hidden item"
(with-redefs [view.state/first-not-visible-item (atom nil)]
(is (= {:db {:loaded-chat-id "chat-id",
:current-chat-id "chat-id",
:all-loaded? true,
:chats
(is (= {:db {:current-chat-id "chat-id",
:pagination-info
{"chat-id"
{:cursor
{:messages-initialized? true
:cursor
"00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000",
:cursor-clock-value 9,
:users
{"from" {:alias "alias",
:name "name",
:identicon "identicon",
:public-key "from"
:nickname nil
:searchable-phrases ["alias" "name"]}}}}}}
(message/add-received-message
cofx
message)))))
:cursor-clock-value 9}}}}
(dissoc (message/receive-many
cofx
#js {:messages (to-array [message])})
:utils/dispatch-later)))))
;; <- cursor
;; <- first-hidden-item
;; <- message
;; <- top of the chat
(testing "the hidden item has a clock value less than the current"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
(is (= {:db {:loaded-chat-id "chat-id",
:current-chat-id "chat-id",
:all-loaded? true,
:chats
(is (= {:db {:current-chat-id "chat-id",
:pagination-info
{"chat-id"
{:cursor
{:messages-initialized? true
:cursor
"00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000",
:cursor-clock-value 9,
:users
{"from" {:alias "alias",
:name "name",
:identicon "identicon",
:public-key "from"
:nickname nil
:searchable-phrases ["alias" "name"]}}}}}}
(message/add-received-message
cofx
message)))))
:cursor-clock-value 9}}}}
(dissoc (message/receive-many
cofx
#js {:messages (to-array [message])})
:utils/dispatch-later)))))
;; <- cursor
;; <- message
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls between the first-hidden-item and cursor"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [result (message/add-received-message
cofx
message)]
(let [result (dissoc (message/receive-many
cofx
#js {:messages (to-array [message])})
:utils/dispatch-later)]
(testing "it sets all-loaded? to false"
(is (not (get-in result [:db :chats chat-id :all-loaded?]))))
(is (not (get-in result [:db :pagination-info chat-id :all-loaded?]))))
(testing "it updates cursor-clock-value & cursor"
(is (= clock-value (get-in result [:db :chats chat-id :cursor-clock-value])))
(is (= (chat-loading/clock-value->cursor clock-value) (get-in result [:db :chats chat-id :cursor])))))))
(is (= clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value])))
(is (= (loading/clock-value->cursor clock-value) (get-in result [:db :pagination-info chat-id :cursor])))))))
;; <- message
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls between the first-hidden-item and cursor is nil"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [result (message/add-received-message
(update-in cofx [:db :chats chat-id] dissoc :cursor :cursor-clock-value)
message)]
(let [result (dissoc (message/receive-many
(update-in cofx [:db :pagination-info chat-id] dissoc :cursor :cursor-clock-value)
#js {:messages (to-array [message])})
:utils/dispatch-later)]
(testing "it sets all-loaded? to false"
(is (not (get-in result [:db :chats chat-id :all-loaded?]))))
(is (not (get-in result [:db :pagination-info chat-id :all-loaded?]))))
(testing "it updates cursor-clock-value & cursor"
(is (= clock-value (get-in result [:db :chats chat-id :cursor-clock-value])))
(is (= (chat-loading/clock-value->cursor clock-value) (get-in result [:db :chats chat-id :cursor])))))))
(is (= clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value])))
(is (= (loading/clock-value->cursor clock-value) (get-in result [:db :pagination-info chat-id :cursor])))))))
;; <- message
;; <- cursor
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls before both the first-hidden-item and cursor"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [result (message/add-received-message
cofx
(update message :clock-value #(- % 2)))]
(let [message #js {:localChatId chat-id
:clock (- clock-value 2)
:alias "alias"
:name "name"
:identicon "identicon"
:from "from"}
result (dissoc (message/receive-many
cofx
#js {:messages (to-array [message])})
:utils/dispatch-later)]
(testing "it sets all-loaded? to false"
(is (not (get-in result [:db :chats chat-id :all-loaded?]))))
(is (not (get-in result [:db :pagination-info chat-id :all-loaded?]))))
(testing "it does not update cursor-clock-value & cursor"
(is (= cursor-clock-value (get-in result [:db :chats chat-id :cursor-clock-value])))
(is (= cursor (get-in result [:db :chats chat-id :cursor]))))))))))
(is (= cursor-clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value])))
(is (= cursor (get-in result [:db :pagination-info chat-id :cursor]))))))))))
(deftest message-loaded?
(testing "it returns false when it's not in loaded message"
(is (not (#'status-im.chat.models.message/message-loaded? {:db {:chats {"a" {}}}}
{:message-id "message-id"
:from "a"
:clock-value 1
:chat-id "a"}))))
(is (not (#'status-im.chat.models.message/message-loaded? {:messages {"a" {}}} "a" "message-id"))))
(testing "it returns true when it's already in the loaded message"
(is (#'status-im.chat.models.message/message-loaded? {:db
{:messages {"a" {"message-id" {}}}}}
{:message-id "message-id"
:from "a"
:clock-value 1
:chat-id "a"}))))
(is (#'status-im.chat.models.message/message-loaded? {:messages {"a" {"message-id" {}}}} "a" "message-id"))))
(deftest earlier-than-deleted-at?
(testing "it returns true when the clock-value is the same as the deleted-clock-value in chat"
(is (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
{:message-id "message-id"
:from "a"
:clock-value 1
:chat-id "a"})))
(is (#'status-im.chat.models.message/earlier-than-deleted-at?
{:chats {"a" {:deleted-at-clock-value 1}}}
"a"
1)))
(testing "it returns false when the clock-value is greater than the deleted-clock-value in chat"
(is (not (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
{:message-id "message-id"
:from "a"
:clock-value 2
:chat-id "a"}))))
(is (not (#'status-im.chat.models.message/earlier-than-deleted-at?
{:chats {"a" {:deleted-at-clock-value 1}}}
"a"
2))))
(testing "it returns true when the clock-value is less than the deleted-clock-value in chat"
(is (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
{:message-id "message-id"
:from "a"
:clock-value 0
:chat-id "a"}))))
(is (#'status-im.chat.models.message/earlier-than-deleted-at?
{:chats {"a" {:deleted-at-clock-value 1}}}
"a"
0))))
(deftest delete-message
(with-redefs [time/day-relative (constantly "day-relative")

View File

@ -4,8 +4,7 @@
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]
[status-im.transport.message.protocol :as message.protocol]
[status-im.data-store.reactions :as data-store.reactions]
[status-im.chat.models :as chat]))
[status-im.data-store.reactions :as data-store.reactions]))
(defn update-reaction [acc retracted chat-id message-id emoji-id emoji-reaction-id reaction]
;; NOTE(Ferossgp): For a better performance, better to not keep in db all retracted reactions
@ -23,7 +22,7 @@
:as reaction}]
(cond-> (update-reaction acc retracted chat-id message-id emoji-id emoji-reaction-id reaction)
(get-in chats [chat-id :profile-public-key])
(update-reaction retracted chat/timeline-chat-id message-id emoji-id emoji-reaction-id reaction)))
(update-reaction retracted constants/timeline-chat-id message-id emoji-id emoji-reaction-id reaction)))
reactions
new-reactions)))
@ -39,27 +38,25 @@
{:db (update db :reactions (process-reactions (:chats db)) reactions)}))
(fx/defn load-more-reactions
[{:keys [db] :as cofx} cursor]
(when-let [current-chat-id (:current-chat-id db)]
(when-let [session-id (get-in db [:pagination-info current-chat-id :messages-initialized?])]
(data-store.reactions/reactions-by-chat-id-rpc
current-chat-id
cursor
constants/default-number-of-messages
#(re-frame/dispatch [::reactions-loaded current-chat-id session-id %])
#(log/error "failed loading reactions" current-chat-id %)))))
{:events [:load-more-reactions]}
[{:keys [db]} cursor chat-id]
(when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])]
(data-store.reactions/reactions-by-chat-id-rpc
chat-id
cursor
constants/default-number-of-messages
#(re-frame/dispatch [::reactions-loaded chat-id session-id %])
#(log/error "failed loading reactions" chat-id %))))
(fx/defn reactions-loaded
{:events [::reactions-loaded]}
[{{:keys [current-chat-id] :as db} :db}
[{db :db}
chat-id
session-id
reactions]
(when-not (or (nil? current-chat-id)
(not= chat-id current-chat-id)
(and (get-in db [:pagination-info current-chat-id :messages-initialized?])
(not= session-id
(get-in db [:pagination-info current-chat-id :messages-initialized?]))))
(when-not (and (get-in db [:pagination-info chat-id :messages-initialized?])
(not= session-id
(get-in db [:pagination-info chat-id :messages-initialized?])))
(let [reactions-w-chat-id (map #(assoc % :chat-id chat-id) reactions)]
{:db (update db :reactions (process-reactions (:chats db)) reactions-w-chat-id)})))

View File

@ -1,3 +1,4 @@
;;this ns is needed because of cycled deps
(ns status-im.chat.models.transport
(:require [status-im.utils.fx :as fx]
[status-im.transport.message.core :as transport.message]
@ -9,5 +10,5 @@
(let [message (get-in db [:messages chat-id message-id])]
(fx/merge
cofx
(transport.message/set-message-envelope-hash chat-id message-id (:message-type message) 1)
(transport.message/set-message-envelope-hash chat-id message-id (:message-type message))
(chat.message/resend-message chat-id message-id))))

View File

@ -151,7 +151,6 @@
:messages {"status" {"4" {} "5" {} "6" {}}}
:chats {"status" {:public? true
:group-chat true
:loaded-unviewed-messages-ids #{"6" "5" "4"}}
"opened" {:loaded-unviewed-messages-ids #{}}
"1-1" {:loaded-unviewed-messages-ids #{"6" "5" "4"}}}})
:group-chat true}
"opened" {}
"1-1" {}}})

View File

@ -24,18 +24,21 @@
{:db (dissoc db :commands/select-account)
::json-rpc/call [{:method (json-rpc/call-ext-method "acceptRequestAddressForTransaction")
:params [message-id address]
:on-success #(re-frame/dispatch [:transport/message-sent % 1])}]})
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})
(fx/defn handle-decline-request-address-for-transaction
{:events [::decline-request-address-for-transaction]}
[cofx message-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "declineRequestAddressForTransaction")
:params [message-id]
:on-success #(re-frame/dispatch [:transport/message-sent % 1])}]})
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})
(fx/defn handle-decline-request-transaction
{:events [::decline-request-transaction]}
[cofx message-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "declineRequestTransaction")
:params [message-id]
:on-success #(re-frame/dispatch [:transport/message-sent % 1])}]})
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})

View File

@ -175,8 +175,9 @@
:text "Upgrade here to see an invitation to community"
:communityId community-id
:contentType constants/content-type-community}]
:js-response true
:on-success
#(re-frame/dispatch [:transport/message-sent % 1])
#(re-frame/dispatch [:transport/message-sent %])
:on-failure #(log/error "failed to send a message" %)}]})
(fx/defn invite-users
@ -242,7 +243,7 @@
[{:keys [db]}]
(let [{:keys [name description membership]} (get db :communities/create)
my-public-key (get-in db [:multiaccount :public-key])]
(log/error "Edit community is not yet implemented")
(log/error "Edit community is not yet implemented")))
;; {::json-rpc/call [{:method "wakuext_editCommunity"
;; :params [{:identity {:display_name name
;; :description description}
@ -251,7 +252,7 @@
;; :on-error #(do
;; (log/error "failed to create community" %)
;; (re-frame/dispatch [::failed-to-edit-community %]))}]}
))
(fx/defn create-channel
{:events [::create-channel-confirmation-pressed]}

View File

@ -132,3 +132,5 @@
(def ^:const keycard-integration-link "https://status.im/keycard-integration")
(def ^:const status-community-id "0x039b2da47552aa117a96ea8f1d4d108ba66637c7517a3c94a57b99dbb8a002eda2")
(def ^:const timeline-chat-id "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a")

View File

@ -43,7 +43,7 @@
public-key)
(assoc :last-updated now)
(update :system-tags (fnil conj #{}) :contact/blocked))
from-one-to-one-chat? (not (get-in db [:chats (:inactive-chat-id db) :group-chat]))]
from-one-to-one-chat? (not (get-in db [:chats (:current-chat-id db) :group-chat]))]
(fx/merge cofx
{:db (-> db
;; add the contact to blocked contacts

View File

@ -8,7 +8,8 @@
[status-im.navigation :as navigation]
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]
[clojure.string :as string]))
[clojure.string :as string]
[status-im.constants :as constants]))
(fx/defn load-contacts
{:events [::contacts-loaded]}
@ -81,7 +82,8 @@
(fnil #(conj % :contact/added) #{})))]
(fx/merge cofx
{:db (dissoc db :contacts/new-identity)
:dispatch [:start-profile-chat public-key]}
:dispatch-n [[:start-profile-chat public-key]
[:offload-messages constants/timeline-chat-id]]}
(upsert-contact contact)
(send-contact-request contact)
(mailserver/process-next-messages-request)))))
@ -95,7 +97,7 @@
(fnil #(disj % :contact/added) #{}))]
(fx/merge cofx
{:db (assoc-in db [:contacts/contacts public-key] new-contact)
:dispatch [:chat/remove-update-chat public-key]}
:dispatch [:offload-messages constants/timeline-chat-id]}
(contacts-store/save-contact new-contact))))
(fx/defn create-contact

View File

@ -83,8 +83,7 @@
:last-clock-value :lastClockValue
:profile-public-key :profile})
(dissoc :public? :group-chat :messages
:might-have-join-time-messages?
:loaded-unviewed-messages-ids :chat-type
:might-have-join-time-messages? :chat-type
:contacts :admins :members-joined)))
(defn <-rpc [chat]

View File

@ -16,7 +16,6 @@
:unviewed-messages-count 2
:is-active true
:chat-id "chat-id"
:loaded-unviewed-messages-ids []
:timestamp 2}
expected-chat {:id "chat-id"
:color "color"

View File

@ -57,8 +57,8 @@
limit
on-success
on-failure]
{::json-rpc/call [{:method (json-rpc/call-ext-method "chatMessages")
:params [chat-id cursor limit]
{::json-rpc/call [{:method (json-rpc/call-ext-method "chatMessages")
:params [chat-id cursor limit]
:on-success (fn [result]
(on-success (update result :messages #(map <-rpc %))))
:on-failure on-failure}]})

View File

@ -200,7 +200,7 @@
(str "wakuext_" method))
(defn call
[{:keys [method params on-success on-error] :as arg}]
[{:keys [method params on-success on-error js-response] :as arg}]
(if-let [method-options (json-rpc-api method)]
(let [params (or params [])
{:keys [id on-result subscription?]
@ -226,15 +226,16 @@
(fn [response]
(if (string/blank? response)
(on-error {:message "Blank response"})
(let [{:keys [error result]} (types/json->clj response)]
(if error
(on-error error)
(let [response-js (types/json->js response)]
(if (.-error response-js)
(on-error (types/js->clj (.-error response-js)))
(if subscription?
(re-frame/dispatch
[:ethereum.callback/subscription-success
result on-success])
(on-success (on-result result))))))))))
(types/js->clj (.-result response-js)) on-success])
(on-success (on-result (if js-response
(.-result response-js)
(types/js->clj (.-result response-js)))))))))))))
(log/warn "method" method "not found" arg)))
(defn eth-call

View File

@ -1,16 +1,9 @@
(ns status-im.events
(:require [re-frame.core :as re-frame]
[status-im.chat.models :as chat]
[status-im.chat.models.message :as chat.message]
[status-im.chat.models.reactions :as chat.reactions]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]
[status-im.data-store.reactions :as data-store.reactions]
[status-im.group-chats.core :as group-chats]
[status-im.i18n.i18n :as i18n]
[status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.transport.message.core :as transport.message]
[status-im.ui.components.react :as react]
[status-im.utils.fx :as fx]
status-im.utils.logging.core
@ -22,8 +15,6 @@
[status-im.native-module.core :as status]
[status-im.ui.components.permissions :as permissions]
[status-im.utils.utils :as utils]
[status-im.data-store.invitations :as data-store.invitations]
[status-im.contact.db :as contact.db]
[status-im.ethereum.json-rpc :as json-rpc]
clojure.set
status-im.currency.core
@ -55,7 +46,6 @@
status-im.mailserver.constants
status-im.ethereum.subscriptions
status-im.fleet.core
status-im.chat.models.message-seen
status-im.contact.block
status-im.contact.core
status-im.contact.chat
@ -101,30 +91,6 @@
(fn []
(dimensions/add-event-listener)))
;;TODO: map fx/merge is slow, we shouldn't do it, its much better to have one fx for vector of items
(fx/defn handle-update
{:events [:transport/reaction-sent :transport/retraction-sent :transport/invitation-sent]}
[cofx {:keys [chats messages emojiReactions invitations]}]
(let [chats (map data-store.chats/<-rpc chats)
messages (map data-store.messages/<-rpc messages)
message-fxs (map chat.message/receive-one messages)
emoji-reactions (map data-store.reactions/<-rpc emojiReactions)
emoji-react-fxs (map chat.reactions/receive-one emoji-reactions)
invitations-fxs [(group-chats/handle-invitations
(map data-store.invitations/<-rpc invitations))]
chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)]
(apply fx/merge cofx (concat chat-fxs message-fxs emoji-react-fxs invitations-fxs))))
(fx/defn transport-message-sent
{:events [:transport/message-sent]}
[cofx response messages-count]
(let [set-hash-fxs (map (fn [{:keys [localChatId id messageType]}]
(transport.message/set-message-envelope-hash localChatId id messageType messages-count))
(:messages response))]
(apply fx/merge cofx
(conj set-hash-fxs
(handle-update response)))))
(fx/defn dismiss-keyboard
{:events [:dismiss-keyboard]}
[_]
@ -210,35 +176,15 @@
[{:keys [db]} dimensions]
{:db (assoc db :dimensions/window (dimensions/window dimensions))})
(fx/defn reset-current-profile-chat [{:keys [db] :as cofx} public-key]
(let [chat-id (chat/profile-chat-topic public-key)]
(when-not (= (:current-chat-id db) chat-id)
(fx/merge cofx
(chat/start-profile-chat public-key)
(chat/offload-all-messages)
(chat/preload-chat-data chat-id)))))
(fx/defn init-timeline-chat
{:events [:init-timeline-chat]}
[{:keys [db] :as cofx}]
(when-not (get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
(chat/preload-chat-data cofx constants/timeline-chat-id)))
(fx/defn reset-current-timeline-chat [{:keys [db] :as cofx}]
(let [profile-chats (conj (map :public-key (contact.db/get-active-contacts (:contacts/contacts db)))
(get-in db [:multiaccount :public-key]))]
(when-not (= (:current-chat-id db) chat/timeline-chat-id)
(fx/merge cofx
(fn [cofx]
(apply fx/merge cofx (map chat/start-profile-chat profile-chats)))
(chat/start-timeline-chat)
(chat/offload-all-messages)
(chat/preload-chat-data chat/timeline-chat-id)))))
(fx/defn reset-current-chat [{:keys [db] :as cofx} chat-id]
(when-not (= (:current-chat-id db) chat-id)
(fx/merge cofx
(chat/offload-all-messages)
(chat/preload-chat-data chat-id))))
;; NOTE: Will be removed with the keycard PR
(fx/defn on-will-focus
{:events [:screens/on-will-focus]}
[{:keys [db] :as cofx} view-id]
[cofx view-id]
(fx/merge cofx
#(case view-id
:keycard-settings (keycard/settings-screen-did-load %)
@ -247,30 +193,8 @@
:keycard-login-pin (keycard/enter-pin-screen-did-load %)
:add-new-account-pin (keycard/enter-pin-screen-did-load %)
:keycard-authentication-method (keycard/authentication-method-screen-did-load %)
(:chat :group-chat-profile) (reset-current-chat % (get db :inactive-chat-id))
:multiaccounts (keycard/multiaccounts-screen-did-load %)
(:wallet-stack :wallet) (wallet/wallet-will-focus %)
(:status :status-stack)
(reset-current-timeline-chat %)
:profile
(reset-current-profile-chat % (get-in % [:db :contacts/identity]))
nil)))
(fx/defn tab-will-change
{:events [:screens/tab-will-change]}
[{:keys [db] :as cofx} view-id]
(fx/merge cofx
#(case view-id
;;when we back to chat we want to show inactive chat
:chat
(reset-current-chat % (get db :inactive-chat-id))
(:status :status-stack)
(reset-current-timeline-chat %)
:profile
(reset-current-profile-chat % (get-in % [:db :contacts/identity]))
nil)))
;;TODO :replace by named events

View File

@ -4,9 +4,6 @@
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.models :as models.chat]
[status-im.chat.models.message :as models.message]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.group-chats.db :as group-chats.db]
[status-im.multiaccounts.model :as multiaccounts.model]
@ -16,13 +13,27 @@
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]))
(fx/defn navigate-chat-updated
{:events [:navigate-chat-updated]}
[cofx chat-id]
(if (get-in cofx [:db :chats chat-id :is-active])
(models.chat/navigate-to-chat cofx chat-id)
(navigation/navigate-to-cofx cofx :home {})))
(fx/defn handle-chat-update
{:events [:chat-updated]}
[_ response]
{:dispatch-n [[:sanitize-messages-and-process-response response]
[:navigate-chat-updated (.-id (aget (.-chats response) 0))]]})
(fx/defn remove-member
"Format group update message and sign membership"
{:events [:group-chats.ui/remove-member-pressed]}
[_ chat-id member]
{::json-rpc/call [{:method (json-rpc/call-ext-method "removeMemberFromGroupChat")
:params [nil chat-id member]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn set-up-filter
"Listen/Tear down the shared topic/contact-codes. Stop listening for members who
@ -39,34 +50,13 @@
(transport.filters/upsert-group-chat-topics)
(transport.filters/load-members members)))))
(fx/defn handle-chat-update
{:events [::chat-updated]}
[cofx {:keys [chats messages]}]
(let [{:keys [chat-id] :as chat} (-> chats
first
(data-store.chats/<-rpc))
previous-chat (get-in cofx [:chats chat-id])
set-up-filter-fx (set-up-filter chat-id previous-chat)
chat-fx (models.chat/ensure-chat
(dissoc chat :unviewed-messages-count))
messages-fx (map #(models.message/receive-one
(data-store.messages/<-rpc %))
messages)
navigate-fx #(if (get-in % [:db :chats chat-id :is-active])
(models.chat/navigate-to-chat % chat-id)
(navigation/navigate-to-cofx % :home {}))]
(apply fx/merge cofx (concat [chat-fx]
messages-fx
[navigate-fx]))))
(fx/defn join-chat
{:events [:group-chats.ui/join-pressed]}
[_ chat-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "confirmJoiningGroup")
:params [chat-id]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn create
{:events [:group-chats.ui/create-pressed]
@ -75,7 +65,8 @@
(let [selected-contacts (:group/selected-contacts db)]
{::json-rpc/call [{:method (json-rpc/call-ext-method "createGroupChatWithMembers")
:params [nil group-name (into [] selected-contacts)]
:on-success #(re-frame/dispatch [::chat-updated %])}]}))
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]}))
(fx/defn create-from-link
[cofx {:keys [chat-id invitation-admin chat-name]}]
@ -83,14 +74,16 @@
(models.chat/navigate-to-chat cofx chat-id)
{::json-rpc/call [{:method (json-rpc/call-ext-method "createGroupChatFromInvitation")
:params [chat-name chat-id invitation-admin]
:on-success #(re-frame/dispatch [::chat-updated %])}]}))
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]}))
(fx/defn make-admin
{:events [:group-chats.ui/make-admin-pressed]}
[_ chat-id member]
{::json-rpc/call [{:method (json-rpc/call-ext-method "addAdminsToGroupChat")
:params [nil chat-id [member]]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn add-members
"Add members to a group chat"
@ -98,7 +91,8 @@
[{{:keys [current-chat-id selected-participants]} :db :as cofx}]
{::json-rpc/call [{:method (json-rpc/call-ext-method "addMembersToGroupChat")
:params [nil current-chat-id selected-participants]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn add-members-from-invitation
"Add members to a group chat"
@ -107,7 +101,8 @@
{:db (assoc-in db [:group-chat/invitations id :state] constants/invitation-state-approved)
::json-rpc/call [{:method (json-rpc/call-ext-method "addMembersToGroupChat")
:params [nil current-chat-id [participant]]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn leave
"Leave chat"
@ -115,7 +110,8 @@
[{:keys [db] :as cofx} chat-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "leaveGroupChat")
:params [nil chat-id true]
:on-success #(re-frame/dispatch [::chat-updated %])}]})
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]})
(fx/defn remove
"Remove chat"
@ -142,7 +138,8 @@
{:db (assoc-in db [:chats chat-id :name] new-name)
::json-rpc/call [{:method (json-rpc/call-ext-method "changeGroupChatName")
:params [nil chat-id new-name]
:on-success #(re-frame/dispatch [::chat-updated %])}]}))
:js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]}))
(fx/defn membership-retry
{:events [:group-chats.ui/membership-retry]}
@ -163,7 +160,7 @@
{:db (assoc-in db [:chat/memberships current-chat-id] nil)
::json-rpc/call [{:method (json-rpc/call-ext-method "sendGroupChatInvitationRequest")
:params [nil current-chat-id invitation-admin message]
:on-success #(re-frame/dispatch [:transport/invitation-sent %])}]}))
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}))
(fx/defn send-group-chat-membership-rejection
"Send group chat membership rejection"
@ -171,7 +168,7 @@
[cofx invitation-id]
{::json-rpc/call [{:method (json-rpc/call-ext-method "sendGroupChatInvitationRejection")
:params [nil invitation-id]
:on-success #(re-frame/dispatch [:transport/invitation-sent %])}]})
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})
(fx/defn handle-invitations
[{db :db} invitations]

View File

@ -1256,7 +1256,9 @@
{})
(dismiss-connection-error false))))
(fx/defn load-gaps-fx [{:keys [db] :as cofx} chat-id]
(fx/defn load-gaps-fx
{:events [:load-gaps]}
[{:keys [db] :as cofx} chat-id]
(when-not (get-in db [:gaps-loaded? chat-id])
(let [success-fn #(re-frame/dispatch [::gaps-loaded %1 %2])]
(data-store.mailservers/load-gaps cofx chat-id success-fn))))

View File

@ -169,9 +169,3 @@
(extract-topics (:mailserver/topics db)
chat-id
(not (get-in (:chats db) [chat-id :public?]))))
(defn topics-for-current-chat
"return a list of topics used by the current-chat, include discovery if
private group chat or one-to-one"
[{:keys [current-chat-id] :as db}]
(topics-for-chat db current-chat-id))

View File

@ -32,7 +32,8 @@
[status-im.data-store.invitations :as data-store.invitations]
[status-im.chat.models.link-preview :as link-preview]
[status-im.utils.mobile-sync :as utils.mobile-sync]
[status-im.async-storage.core :as async-storage]))
[status-im.async-storage.core :as async-storage]
[status-im.chat.models :as chat.models]))
(re-frame/reg-fx
::login
@ -317,6 +318,8 @@
(finish-keycard-setup)
(transport/start-messenger)
(communities/fetch)
(chat.models/start-timeline-chat)
(chat.models/start-profile-chat (:public-key multiaccount))
(multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount)))))

View File

@ -138,6 +138,7 @@
{:keys [identicon]} :contact
contact-id :contact-id}]
(when (and chat-type chat-id)
;;TODO : DON'T USE SUBS IN EVENTS
(let [contact-name @(re-frame/subscribe
[:contacts/contact-name-by-identity contact-id])
group-chat? (not= chat-type constants/one-to-one-chat-type)

View File

@ -55,7 +55,7 @@
"node.login" (status-node-started cofx (js->clj event-js :keywordize-keys true))
"envelope.sent" (transport.message/update-envelopes-status cofx (:ids (js->clj event-js :keywordize-keys true)) :sent)
"envelope.expired" (transport.message/update-envelopes-status cofx (:ids (js->clj event-js :keywordize-keys true)) :not-sent)
"message.delivered" (let [{:keys [chatID messageID] :as event-cljs} (js->clj event-js :keywordize-keys true)]
"message.delivered" (let [{:keys [chatID messageID]} (js->clj event-js :keywordize-keys true)]
(models.message/update-db-message-status cofx chatID messageID :delivered))
"mailserver.request.completed" (mailserver/handle-request-completed cofx (js->clj event-js :keywordize-keys true))
"mailserver.request.expired" (when (multiaccounts.model/logged-in? cofx)
@ -64,7 +64,7 @@
"subscriptions.data" (ethereum.subscriptions/handle-signal cofx (js->clj event-js :keywordize-keys true))
"subscriptions.error" (ethereum.subscriptions/handle-error cofx (js->clj event-js :keywordize-keys true))
"whisper.filter.added" (transport.filters/handle-negotiated-filter cofx (js->clj event-js :keywordize-keys true))
"messages.new" (transport.message/process-response cofx event-js)
"messages.new" (transport.message/sanitize-messages-and-process-response cofx event-js)
"wallet" (ethereum.subscriptions/new-wallet-event cofx (js->clj event-js :keywordize-keys true))
"local-notifications" (local-notifications/process cofx (js->clj event-js :keywordize-keys true))
(log/debug "Event " type " not handled"))))

View File

@ -240,8 +240,9 @@
:params [chat-id (str value) contract transaction-hash
(or (:result (types/json->clj signature))
(ethereum/normalized-hex signature))]
:js-response true
:on-success
#(re-frame/dispatch [:transport/message-sent % 1])}]})
#(re-frame/dispatch [:transport/message-sent %])}]})
(fx/defn send-accept-request-transaction-message
{:events [:sign/send-accept-transaction-message]}
@ -250,8 +251,9 @@
:params [transaction-hash message-id
(or (:result (types/json->clj signature))
(ethereum/normalized-hex signature))]
:js-response true
:on-success
#(re-frame/dispatch [:transport/message-sent % 1])}]})
#(re-frame/dispatch [:transport/message-sent %])}]})
(fx/defn transaction-result
[{:keys [db] :as cofx} result tx-obj]
@ -429,7 +431,8 @@
amount
(when-not (= symbol :ETH)
address)]
:on-success #(re-frame/dispatch [:transport/message-sent % 1])}]})))
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})))
(fx/defn sign-transaction-button-clicked-from-request
{:events [:wallet.ui/sign-transaction-button-clicked-from-request]}

View File

@ -116,7 +116,6 @@
(reg-root-key-sub :group-chat/invitations :group-chat/invitations)
(reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions)
(reg-root-key-sub :chat/inputs-with-mentions :chat/inputs-with-mentions)
(reg-root-key-sub :inactive-chat-id :inactive-chat-id)
;;browser
(reg-root-key-sub :browsers :browser/browsers)
(reg-root-key-sub :browser/options :browser/options)
@ -758,7 +757,7 @@
chats)))
(re-frame/reg-sub
::chat
:chat-by-id
:<- [::chats]
(fn [chats [_ chat-id]]
(get chats chat-id)))
@ -777,6 +776,19 @@
(fn [[chat-id inputs]]
(get inputs chat-id)))
(re-frame/reg-sub
:chats/timeline-chat-input
:<- [:chat/inputs]
:<- [:multiaccount/public-key]
(fn [[inputs public-key]]
(get inputs (chat.models/profile-chat-topic public-key))))
(re-frame/reg-sub
:chats/timeline-chat-input-text
:<- [:chats/timeline-chat-input]
(fn [input]
(:input-text input)))
(re-frame/reg-sub
:chats/current-chat-input-text
:<- [:chats/current-chat-inputs]
@ -794,13 +806,11 @@
:chats/current-chat
:<- [:chats/current-raw-chat]
:<- [:multiaccount/public-key]
:<- [:inactive-chat-id]
:<- [:communities/current-community]
(fn [[{:keys [group-chat] :as current-chat}
my-public-key
inactive-chat-id
community]]
(when (and current-chat (= (:chat-id current-chat) inactive-chat-id))
(when current-chat
(cond-> current-chat
(chat.models/public-chat? current-chat)
(assoc :show-input? true)
@ -846,27 +856,24 @@
(chat.models/public-chat? current-chat)))
(re-frame/reg-sub
:chats/current-chat-messages
:chats/chat-messages
:<- [::messages]
:<- [:chats/current-chat-id]
(fn [[messages chat-id]]
(fn [messages [_ chat-id]]
(get messages chat-id {})))
(re-frame/reg-sub
:chats/message-reactions
:<- [:multiaccount/public-key]
:<- [::reactions]
:<- [:chats/current-chat-id]
(fn [[current-public-key reactions current-chat-id] [_ message-id chat-id]]
(fn [[current-public-key reactions] [_ message-id chat-id]]
(models.reactions/message-reactions
current-public-key
(get-in reactions [(or chat-id current-chat-id) message-id]))))
(get-in reactions [chat-id message-id]))))
(re-frame/reg-sub
:chats/messages-gaps
:<- [:mailserver/gaps]
:<- [:chats/current-chat-id]
(fn [[gaps chat-id]]
(fn [gaps [_ chat-id]]
(sort-by :from (vals (get gaps chat-id)))))
(re-frame/reg-sub
@ -878,8 +885,7 @@
(re-frame/reg-sub
:chats/range
:<- [:mailserver/ranges]
:<- [:chats/current-chat-id]
(fn [[ranges chat-id]]
(fn [ranges [_ chat-id]]
(get ranges chat-id)))
(re-frame/reg-sub
@ -893,21 +899,25 @@
(re-frame/reg-sub
:chats/all-loaded?
:<- [::pagination-info]
:<- [:chats/current-chat-id]
(fn [[pagination-info chat-id]]
(fn [pagination-info [_ chat-id]]
(get-in pagination-info [chat-id :all-loaded?])))
(re-frame/reg-sub
:chats/loading-messages?
:<- [::pagination-info]
(fn [pagination-info [_ chat-id]]
(get-in pagination-info [chat-id :loading-messages?])))
(re-frame/reg-sub
:chats/public?
:<- [:chats/current-raw-chat]
(fn [chat]
(:public? chat)))
:<- [::chats]
(fn [chats [_ chat-id]]
(get-in chats [chat-id :public?])))
(re-frame/reg-sub
:chats/message-list
:<- [::message-lists]
:<- [:chats/current-chat-id]
(fn [[message-lists chat-id]]
(fn [message-lists [_ chat-id]]
(get message-lists chat-id)))
(defn hydrate-messages
@ -920,21 +930,23 @@
message-list))
(re-frame/reg-sub
:chats/current-chat-no-messages?
:<- [:chats/current-chat-messages]
:chats/chat-no-messages?
(fn [[_ chat-id] _]
(re-frame/subscribe [:chats/chat-messages chat-id]))
(fn [messages]
(empty? messages)))
(re-frame/reg-sub
:chats/current-chat-messages-stream
:<- [:chats/message-list]
:<- [:chats/current-chat-messages]
:<- [:chats/messages-gaps]
:<- [:chats/range]
:<- [:chats/all-loaded?]
:<- [:chats/public?]
:chats/chat-messages-stream
(fn [[_ chat-id] _]
[(re-frame/subscribe [:chats/message-list chat-id])
(re-frame/subscribe [:chats/chat-messages chat-id])
(re-frame/subscribe [:chats/messages-gaps chat-id])
(re-frame/subscribe [:chats/range chat-id])
(re-frame/subscribe [:chats/all-loaded? chat-id])
(re-frame/subscribe [:chats/public? chat-id])])
(fn [[message-list messages messages-gaps range all-loaded? public?]]
;;TODO (perf) we need to move all these to status-go
;;TODO (perf)
(-> (models.message-list/->seq message-list)
(chat.db/add-datemarks)
(hydrate-messages messages)
@ -942,13 +954,17 @@
(re-frame/reg-sub
:chats/timeline-messages-stream
:<- [:chats/message-list]
:<- [:chats/current-chat-messages]
:<- [:chats/current-raw-chat]
(fn [[message-list messages {:keys [timeline?]}]]
(when timeline?
(-> (models.message-list/->seq message-list)
(hydrate-messages messages)))))
:<- [:chats/message-list constants/timeline-chat-id]
:<- [:chats/chat-messages constants/timeline-chat-id]
(fn [[message-list messages]]
(-> (models.message-list/->seq message-list)
(hydrate-messages messages))))
(re-frame/reg-sub
:chats/current-profile-chat
:<- [:contacts/current-contact-identity]
(fn [identity]
(chat.models/profile-chat-topic identity)))
(re-frame/reg-sub
:chats/photo-path
@ -997,6 +1013,12 @@
(fn [{:keys [metadata]}]
(:sending-image metadata)))
(re-frame/reg-sub
:chats/timeline-sending-image
:<- [:chats/timeline-chat-input]
(fn [{:keys [metadata]}]
(:sending-image metadata)))
(re-frame/reg-sub
:public-chat.new/topic-error-message
:<- [:public-group-topic]
@ -1035,7 +1057,7 @@
(re-frame/reg-sub
:group-chat/inviter-info
(fn [[_ chat-id] _]
[(re-frame/subscribe [::chat chat-id])
[(re-frame/subscribe [:chat-by-id chat-id])
(re-frame/subscribe [:multiaccount/public-key])])
(fn [[chat my-public-key]]
{:joined? (group-chats.db/joined? my-public-key chat)
@ -2028,9 +2050,8 @@
(re-frame/reg-sub
:chats/fetching-gap-in-progress?
:<- [:chats/current-chat-id]
:<- [:mailserver/fetching-gaps-in-progress]
(fn [[chat-id gaps] [_ ids]]
(fn [gaps [_ ids chat-id]]
(seq (select-keys (get gaps chat-id) ids))))
(re-frame/reg-sub

View File

@ -7,44 +7,19 @@
[status-im.communities.core :as models.communities]
[status-im.pairing.core :as models.pairing]
[status-im.transport.filters.core :as models.filters]
[status-im.data-store.messages :as data-store.messages]
[status-im.data-store.reactions :as data-store.reactions]
[status-im.data-store.contacts :as data-store.contacts]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.invitations :as data-store.invitations]
[status-im.group-chats.core :as models.group]
[status-im.utils.fx :as fx]
[status-im.utils.types :as types]))
(fx/defn handle-chats [cofx chats]
(models.chat/ensure-chats cofx chats))
(fx/defn handle-contacts [cofx contacts]
(models.contact/ensure-contacts cofx contacts))
(fx/defn handle-message [cofx message]
(models.message/receive-one cofx message))
(fx/defn handle-community [cofx community]
(models.communities/handle-community cofx community))
(fx/defn handle-request-to-join-community [cofx request]
(models.communities/handle-request-to-join cofx request))
(fx/defn handle-reactions [cofx reactions]
(models.reactions/receive-signal cofx reactions))
(fx/defn handle-invitations [cofx invitations]
(models.group/handle-invitations cofx invitations))
(fx/defn handle-filters [cofx filters]
(models.filters/handle-filters cofx filters))
(fx/defn handle-filters-removed [cofx filters]
(models.filters/handle-filters-removed cofx filters))
[status-im.utils.types :as types]
[status-im.constants :as constants]
[status-im.multiaccounts.model :as multiaccounts.model]
[clojure.string :as string]))
(fx/defn process-response
{:events [::process]}
{:events [:process-response]}
[cofx ^js response-js]
(let [^js communities (.-communities response-js)
^js requests-to-join-community (.-requestsToJoinCommunity response-js)
@ -56,76 +31,142 @@
^js filters (.-filters response-js)
^js removed-filters (.-removedFilters response-js)
^js invitations (.-invitations response-js)]
(cond
(seq chats)
(do
(js-delete response-js "chats")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.chat/ensure-chats (map #(-> %
(data-store.chats/<-rpc)
;;TODO why here?
(dissoc :unviewed-messages-count))
(types/js->clj chats)))))
(seq messages)
(models.message/receive-many cofx response-js)
(seq installations)
(let [installations-clj (types/js->clj installations)]
(js-delete response-js "installations")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.pairing/handle-installations installations-clj)))
(seq contacts)
(let [contacts-clj (types/js->clj contacts)]
(js-delete response-js "contacts")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-contacts (map data-store.contacts/<-rpc contacts-clj))))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.contact/ensure-contacts (map data-store.contacts/<-rpc contacts-clj))))
(seq communities)
(let [community (.pop communities)]
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-community (types/js->clj community))))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.communities/handle-community (types/js->clj community))))
(seq requests-to-join-community)
(let [request (.pop requests-to-join-community)]
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-request-to-join-community (types/js->clj request))))
(seq chats)
(let [chats-clj (types/js->clj chats)]
(js-delete response-js "chats")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-chats (map #(-> %
(data-store.chats/<-rpc)
(dissoc :unviewed-messages-count))
chats-clj))))
(seq messages)
(let [message (.pop messages)]
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-message (-> message (types/js->clj) (data-store.messages/<-rpc)))))
(models.communities/handle-request-to-join (types/js->clj request))))
(seq emoji-reactions)
(let [reactions (types/js->clj emoji-reactions)]
(js-delete response-js "emojiReactions")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-reactions (map data-store.reactions/<-rpc reactions))))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.reactions/receive-signal (map data-store.reactions/<-rpc reactions))))
(seq invitations)
(let [invitations (types/js->clj invitations)]
(js-delete response-js "invitations")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-invitations (map data-store.invitations/<-rpc invitations))))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.group/handle-invitations (map data-store.invitations/<-rpc invitations))))
(seq filters)
(let [filters (types/js->clj filters)]
(js-delete response-js "filters")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-filters filters)))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.filters/handle-filters filters)))
(seq removed-filters)
(let [removed-filters (types/js->clj removed-filters)]
(js-delete response-js "removedFilters")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-filters-removed filters))))))
{:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]}
(models.filters/handle-filters-removed filters))))))
(defn group-by-and-update-unviewed-counts
"group messages by current chat, profile updates, transactions and update unviewed counters in db for not curent chats"
[{:keys [current-chat-id db] :as acc} ^js message-js]
(let [chat-id (.-localChatId message-js)
message-type (.-messageType message-js)
from (.-from message-js)
new (.-new message-js)
current (= current-chat-id chat-id)
profile (models.chat/profile-chat? {:db db} chat-id)
tx-hash (and (.-commandParameters message-js) (.-commandParameters.transactionHash message-js))]
(cond-> acc
current
(update :messages conj message-js)
profile
(update :statuses conj message-js)
;;update counter
(and (not current)
new
(not profile)
(not (= message-type constants/message-type-private-group-system-message))
(not (= from (multiaccounts.model/current-public-key {:db db}))))
(update-in [:db :chats chat-id :unviewed-messages-count] inc)
;;conj incoming transaction for :watch-tx
(not (string/blank? tx-hash))
(update :transactions conj tx-hash)
:always
(update :chats conj chat-id))))
(defn sort-js-messages!
"sort messages, so we can start process latest first,in that case we only need to process frist 20 and drop others"
[response-js messages]
(if (seq messages)
(set! (.-messages response-js)
(.sort (to-array messages)
(fn [a b]
(- (.-clock b) (.-clock a)))))
(js-delete response-js "messages")))
(fx/defn sanitize-messages-and-process-response
"before processing we want to filter and sort messages, so we can process first only messages which will be showed"
{:events [:sanitize-messages-and-process-response]}
[{:keys [db] :as cofx} ^js response-js]
(let [current-chat-id (:current-chat-id db)
{:keys [db messages transactions chats statuses]}
(reduce group-by-and-update-unviewed-counts
{:db db :chats #{} :transactions #{} :statuses [] :messages []
:current-chat-id current-chat-id}
(.-messages response-js))]
(sort-js-messages! response-js messages)
(fx/merge cofx
{:db db
:utils/dispatch-later (concat []
(when (seq statuses)
[{:ms 100 :dispatch [:process-statuses statuses]}])
(when (seq transactions)
(for [transaction-hash transactions]
{:ms 100 :dispatch [:watch-tx transaction-hash]}))
(when (seq chats)
[{:ms 100 :dispatch [:chat/join-times-messages-checked chats]}]))}
(process-response response-js))))
(fx/defn remove-hash
[{:keys [db] :as cofx} envelope-hash]
[{:keys [db]} envelope-hash]
{:db (update db :transport/message-envelopes dissoc envelope-hash)})
(fx/defn check-confirmations
@ -166,7 +207,7 @@
(fx/defn set-message-envelope-hash
"message-type is used for tracking"
[{:keys [db] :as cofx} chat-id message-id message-type messages-count]
[{:keys [db] :as cofx} chat-id message-id message-type]
;; Check first if the confirmation has already arrived
(let [statuses (get-in db [:transport/message-confirmations message-id])
check-confirmations-fx (map
@ -180,5 +221,15 @@
{:chat-id chat-id
:message-type message-type})
(update-in [:transport/message-ids->confirmations message-id]
#(or % {:pending-confirmations messages-count})))})]
#(or % {:pending-confirmations 1})))})]
(apply fx/merge cofx (conj check-confirmations-fx add-envelope-data))))
(fx/defn transport-message-sent
{:events [:transport/message-sent]}
[cofx response-js]
(let [set-hash-fxs (map (fn [{:keys [localChatId id messageType]}]
(set-message-envelope-hash localChatId id messageType))
(types/js->clj (.-messages response-js)))]
(apply fx/merge cofx
(conj set-hash-fxs
#(sanitize-messages-and-process-response % response-js)))))

View File

@ -26,26 +26,25 @@
:sticker sticker
:contentType content-type})
(fx/defn send-chat-messages [cofx messages]
{::json-rpc/call
[{:method (json-rpc/call-ext-method "sendChatMessages")
:params [(mapv build-message messages)]
:on-success
#(re-frame/dispatch [:transport/message-sent % 1])
:on-failure #(log/error "failed to send a message" %)}]})
(fx/defn send-chat-messages [_ messages]
{::json-rpc/call [{:method (json-rpc/call-ext-method "sendChatMessages")
:params [(mapv build-message messages)]
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])
:on-failure #(log/error "failed to send a message" %)}]})
(fx/defn send-reaction [cofx {:keys [message-id chat-id emoji-id]}]
(fx/defn send-reaction [_ {:keys [message-id chat-id emoji-id]}]
{::json-rpc/call [{:method (json-rpc/call-ext-method
"sendEmojiReaction")
:params [chat-id message-id emoji-id]
:on-success
#(re-frame/dispatch [:transport/reaction-sent %])
:js-response true
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])
:on-failure #(log/error "failed to send a reaction" %)}]})
(fx/defn send-retract-reaction [cofx {:keys [emoji-reaction-id] :as reaction}]
(fx/defn send-retract-reaction [_ {:keys [emoji-reaction-id] :as reaction}]
{::json-rpc/call [{:method (json-rpc/call-ext-method
"sendEmojiReactionRetraction")
:params [emoji-reaction-id]
:on-success
#(re-frame/dispatch [:transport/retraction-sent %])
:js-response true
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])
:on-failure #(log/error "failed to send a reaction retraction" %)}]})

View File

@ -49,47 +49,47 @@
update? (atom nil)
current-obj (reagent/atom nil)]
(reagent/create-class
{:component-will-mount (fn [args]
(let [[_ obj _ _] (.-argv (.-props args))]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
{:UNSAFE_componentWillMount (fn [args]
(let [[_ obj _ _] (.-argv (.-props args))]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600))
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))))))
:component-will-update (fn [_ [_ obj _ _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600))
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))))))
:UNSAFE_componentWillUpdate (fn [_ [_ obj _ _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600))
(hide-panel-anim bottom-anim-value alpha-value (- window-height))))))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600))
(hide-panel-anim bottom-anim-value alpha-value (- window-height))))))
:reagent-render (fn []
(when @current-obj
[react/keyboard-avoiding-view {:style {:position :absolute :top 0 :bottom 0 :left 0 :right 0}}

View File

@ -1,6 +1,5 @@
(ns status-im.ui.components.connectivity.view
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.colors :as colors]
@ -67,7 +66,7 @@
(defview loading-indicator []
(letsubs [ui-status-properties [:connectivity/ui-status-properties]
window-width (reagent/atom 0)]
window-width [:dimensions/window-width]]
(when (:loading-indicator? ui-status-properties)
[loading-indicator-anim @window-width])))

View File

@ -79,7 +79,7 @@
(def tabs
(reagent/adapt-react-class
(fn [props]
(let [{:keys [navigate index route state popToTop]} (bean props)
(let [{:keys [navigate index route popToTop]} (bean props)
{:keys [keyboard-shown]
:or {keyboard-shown false}} (when platform/android? (rn/use-keyboard))
{:keys [bottom]} (safe-area/use-safe-area)
@ -105,10 +105,7 @@
:label title
:on-press #(if (= (str index) (str route-index))
(popToTop)
(let [view-id (navigation/get-index-route-name route-index (bean state))]
(re-frame/dispatch-sync [:screens/tab-will-change view-id])
(reagent/flush)
(navigate (name nav-stack))))
(navigate (name nav-stack)))
:accessibility-label accessibility-label
:count-subscription count-subscription
:active? (= (str index) (str route-index))

View File

@ -40,41 +40,41 @@
alpha-value (anim/create-value 0)
current-permission (reagent/atom nil)
update? (reagent/atom nil)]
{:component-will-update (fn [_ [_ _ {:keys [requested-permission]}]]
(cond
@update?
;; the component has been updated with a new permission, we show the panel
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
{:UNSAFE_componentWillUpdate (fn [_ [_ _ {:keys [requested-permission]}]]
(cond
@update?
;; the component has been updated with a new permission, we show the panel
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-permission requested-permission)
;; a permission has been accepted/denied by the user, and there is
;; another permission that needs to be processed by the user
;; we hide the processed permission with an animation and update
;; `current-permission` with a delay so that the information is still
;; available during the animation
(do (reset! update? true)
(js/setTimeout #(reset! current-permission
(permission-details requested-permission))
600)
(hide-panel-anim bottom-anim-value alpha-value))
(and @current-permission requested-permission)
;; a permission has been accepted/denied by the user, and there is
;; another permission that needs to be processed by the user
;; we hide the processed permission with an animation and update
;; `current-permission` with a delay so that the information is still
;; available during the animation
(do (reset! update? true)
(js/setTimeout #(reset! current-permission
(permission-details requested-permission))
600)
(hide-panel-anim bottom-anim-value alpha-value))
requested-permission
;; the dapp is asking for a permission, we put it in current-permission
;; and start the show-animation
(do (reset! current-permission
(get browser.permissions/supported-permissions
requested-permission))
(show-panel-anim bottom-anim-value alpha-value))
requested-permission
;; the dapp is asking for a permission, we put it in current-permission
;; and start the show-animation
(do (reset! current-permission
(get browser.permissions/supported-permissions
requested-permission))
(show-panel-anim bottom-anim-value alpha-value))
:else
;; a permission has been accepted/denied by the user, and there is
;; no other permission that needs to be processed by the user
;; we hide the processed permission with an animation and update
;; `current-permission` with a delay so that the information is still
;; available during the animation
(do (js/setTimeout #(reset! current-permission nil) 500)
(hide-panel-anim bottom-anim-value alpha-value))))}
:else
;; a permission has been accepted/denied by the user, and there is
;; no other permission that needs to be processed by the user
;; we hide the processed permission with an animation and update
;; `current-permission` with a delay so that the information is still
;; available during the animation
(do (js/setTimeout #(reset! current-permission nil) 500)
(hide-panel-anim bottom-anim-value alpha-value))))}
(when @current-permission
(let [{:keys [title description type icon]} @current-permission]
[react/view styles/permissions-panel-container

View File

@ -146,37 +146,36 @@
(let [mentionable-users @(re-frame/subscribe [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)]
[rn/view {:style (styles/text-input-wrapper)}
[rn/text-input
{:style (styles/text-input)
:ref text-input-ref
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
:text-align-vertical :center
:multiline true
:editable (not cooldown-enabled?)
:blur-on-submit false
:auto-focus false
:on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
:underline-color-android :transparent
:auto-capitalize :sentences
: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])]]))
[rn/text-input
{:style (styles/text-input)
:ref text-input-ref
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
:text-align-vertical :center
:multiline true
:editable (not cooldown-enabled?)
:blur-on-submit false
:auto-focus false
:on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
:underline-color-android :transparent
:auto-capitalize :sentences
: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])]))
(defn mention-item
[[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref]

View File

@ -38,6 +38,8 @@
(merge typography/font-regular
typography/base
{:flex 1
:min-height 34
:max-height 144
:margin 0
:border-width 0
:flex-shrink 1

View File

@ -7,7 +7,7 @@
[status-im.ui.screens.chat.styles.input.gap :as style]))
(defn on-press
[ids first-gap? idx list-ref]
[ids first-gap? idx list-ref chat-id]
(fn []
(when (and list-ref @list-ref)
(.scrollToIndex ^js @list-ref
@ -15,24 +15,25 @@
:viewOffset 20
:viewPosition 0.5}))
(if first-gap?
(re-frame/dispatch [:chat.ui/fetch-more])
(re-frame/dispatch [:chat.ui/fill-gaps ids]))))
(re-frame/dispatch [:chat.ui/fetch-more chat-id])
(re-frame/dispatch [:chat.ui/fill-gaps ids chat-id]))))
(views/defview gap
[{:keys [gaps first-gap?]} idx list-ref timeline]
(views/letsubs [range [:chats/range]
{:keys [might-have-join-time-messages?]} [:chats/current-raw-chat]
[{:keys [gaps first-gap?]} idx list-ref timeline chat-id]
(views/letsubs [range [:chats/range chat-id]
{:keys [might-have-join-time-messages?]} [:chat-by-id chat-id]
in-progress? [:chats/fetching-gap-in-progress?
(if first-gap?
[:first-gap]
(:ids gaps))]
(:ids gaps))
chat-id]
connected? [:mailserver/connected?]]
(let [ids (:ids gaps)]
(when-not (and first-gap? might-have-join-time-messages?)
[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))
(on-press ids first-gap? idx list-ref chat-id))
:style style/touchable}
[react/view {:style style/label-container}
(if in-progress?

View File

@ -120,7 +120,7 @@
outgoing colors/mention-outgoing
:else colors/mention-incoming)}
:on-press (when-not (= content-type constants/content-type-system-text)
#(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact literal]))}
#(re-frame/dispatch [:chat.ui/show-profile literal]))}
[mention-element literal]])
"status-tag"
(conj acc [react/text-class
@ -286,17 +286,16 @@
[react/view (style/message-author-userpic outgoing)
(when first-in-group?
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])])
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
(when display-username?
[react/touchable-opacity {:style style/message-author-touchable
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]])
;;MESSAGE CONTENT
[react/view
content]
content
[link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]]
; delivery status
[react/view (style/delivery-status outgoing)
@ -475,7 +474,7 @@
(on-long-press
(when-not outgoing
[{:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/view-details)}])))})
[react/image {:style {:margin 10 :width 140 :height 140}
;;TODO (perf) move to event
@ -512,7 +511,7 @@
(defn chat-message [message space-keeper]
[reactions/with-reaction-picker
{:message message
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)])
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
:picker-on-open (fn []
(space-keeper true))
:picker-on-close (fn []

View File

@ -62,35 +62,35 @@
(reset! position pos)
(reset! visible true))]
[:<>
[animated/view {:style {:opacity (animated/mix animation 1 0)}}
[rn/view {:ref ref
:collapsable false}
[render message {:modal false
:on-long-press (fn [act]
(when (or (not outgoing)
(and outgoing (= outgoing-status :sent)))
(reset! actions act)
(get-picker-position ref on-open)))}]]
[rn/view {:ref ref
:style {:opacity (if @visible 0 1)}
:collapsable false}
[render message {:modal false
:on-long-press (fn [act]
(when (or (not outgoing)
(and outgoing (= outgoing-status :sent)))
(reset! actions act)
(get-picker-position ref on-open)))}]
[reaction-row/message-reactions message reactions timeline]]
[rn/modal {:visible @visible
:on-request-close on-close
:on-show (fn []
(js/requestAnimationFrame
#(animated/set-value animated-state 1)))
:transparent true}
[reaction-picker/modal {:outgoing (:outgoing message)
:display-photo (:display-photo? message)
:animation animation
:spring spring-animation
:top (:top @position)
:message-height (:height @position)
:on-close on-close
:actions @actions
:own-reactions own-reactions
:timeline timeline
:send-emoji (fn [emoji]
(on-close)
(js/setTimeout #(on-emoji-press emoji)
reaction-picker/animation-duration))}
[render message {:modal true
:close-modal on-close}]]]]))))
(when @visible
[rn/modal {:on-request-close on-close
:on-show (fn []
(js/requestAnimationFrame
#(animated/set-value animated-state 1)))
:transparent true}
[reaction-picker/modal {:outgoing (:outgoing message)
:display-photo (:display-photo? message)
:animation animation
:spring spring-animation
:top (:top @position)
:message-height (:height @position)
:on-close on-close
:actions @actions
:own-reactions own-reactions
:timeline timeline
:send-emoji (fn [emoji]
(on-close)
(js/setTimeout #(on-emoji-press emoji)
reaction-picker/animation-duration))}
[render message {:modal true
:close-modal on-close}]]])]))))

View File

@ -2,5 +2,7 @@
(defonce first-not-visible-item (atom nil))
(defn reset []
(defonce scrolling (atom nil))
(defn reset-visible-item []
(reset! first-not-visible-item nil))

View File

@ -82,8 +82,8 @@
(defview stickers-paging-panel [installed-packs selected-pack]
(letsubs [ref (atom nil)
width [:dimensions/window-width]]
{:component-will-update (fn [_ [_ installed-packs selected-pack]]
(update-scroll-position @ref installed-packs selected-pack width true))
{:UNSAFE_componentWillUpdate (fn [_ [_ installed-packs selected-pack]]
(update-scroll-position @ref installed-packs selected-pack width true))
:component-did-mount #(update-scroll-position @ref installed-packs selected-pack width false)}
[react/scroll-view {:style {:flex 1}
:horizontal true

View File

@ -39,18 +39,19 @@
chat-type
chat-name
public?]}]
[react/view {:style st/toolbar-container}
[react/view {:margin-right 10}
[chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color]]
[react/view {:style st/chat-name-view}
(if group-chat
[react/text {:style st/chat-name-text
:number-of-lines 1
:accessibility-label :chat-name-text}
chat-name]
[one-to-one-name chat-id])
(when-not group-chat
[contact-indicator chat-id])
(when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type))
[group-last-activity {:contacts contacts
:public? public?}])]])
(when chat-id
[react/view {:style st/toolbar-container}
[react/view {:margin-right 10}
[chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color]]
[react/view {:style st/chat-name-view}
(if group-chat
[react/text {:style st/chat-name-text
:number-of-lines 1
:accessibility-label :chat-name-text}
chat-name]
[one-to-one-name chat-id])
(when-not group-chat
[contact-indicator chat-id])
(when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type))
[group-last-activity {:contacts contacts
:public? public?}])]]))

View File

@ -11,7 +11,6 @@
[status-im.ui.screens.chat.sheets :as sheets]
[quo.animated :as animated]
[quo.react-native :as rn]
[quo.platform :as platform]
[status-im.ui.screens.chat.audio-message.views :as audio-message]
[quo.react :as quo.react]
[status-im.ui.screens.chat.message.message :as message]
@ -20,7 +19,6 @@
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.ui.screens.chat.image.views :as image]
[status-im.ui.screens.chat.state :as state]
[status-im.utils.debounce :as debounce]
[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]
@ -32,20 +30,20 @@
[status-im.ui.components.toolbar :as toolbar]
[quo.core :as quo]
[clojure.string :as string]
[status-im.constants :as constants]))
[status-im.constants :as constants]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]))
(defn topbar []
(let [current-chat @(re-frame/subscribe [:current-chat/metadata])]
[topbar/topbar
{:content [toolbar-content/toolbar-content-view current-chat]
:navigation {:on-press #(re-frame/dispatch [:navigate-to :home])}
: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 [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 invitation-requests [chat-id admins]
(let [current-pk @(re-frame/subscribe [:multiaccount/public-key])
@ -153,60 +151,7 @@
first-not-visible (aget (.-data ^js (.-props ^js @messages-list-ref)) (inc index))]
(when (and first-not-visible
(= :message (:type first-not-visible)))
first-not-visible)))))
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
(defn render-fn [{:keys [outgoing type] :as message} idx _ {:keys [group-chat public? current-public-key space-keeper]}]
[react/view {:style (when platform/android? {:scaleY -1})}
(if (= type :datemark)
[message-datemark/chat-datemark (:value message)]
(if (= type :gap)
[gap/gap message idx messages-list-ref false]
; message content
[message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:current-public-key current-public-key)
space-keeper]))])
(defn messages-view
[{:keys [chat bottom-space pan-responder space-keeper]}]
(let [{:keys [group-chat chat-id chat-type public? invitation-admin]} chat
messages @(re-frame/subscribe [:chats/current-chat-messages-stream])
no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?])
current-public-key @(re-frame/subscribe [:multiaccount/public-key])]
[list/flat-list
(merge
pan-responder
{:key-fn #(or (:message-id %) (:value %))
:ref #(reset! messages-list-ref %)
:header (when (= chat-type constants/private-group-chat-type)
[react/view {:style (when platform/android? {:scaleY -1})}
[chat.group/group-chat-footer chat-id invitation-admin]])
:footer [react/view {:style (when platform/android? {:scaleY -1})}
[chat-intro-header-container chat no-messages?]
(when (= chat-type constants/one-to-one-chat-type)
[invite.chat/reward-messages])]
:data messages
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})
:render-data {:group-chat group-chat
:public? public?
:current-public-key current-public-key
:space-keeper space-keeper}
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
:on-scroll-to-index-failed #() ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scrollIndicatorInsets {:top bottom-space}
:keyboardDismissMode "interactive"
:keyboard-should-persist-taps :handled})]))
first-not-visible))))))
(defn bottom-sheet [input-bottom-sheet]
(case input-bottom-sheet
@ -258,72 +203,155 @@
:on-press #(re-frame/dispatch [:send-group-chat-membership-request])}
(i18n/label :t/request-membership)]}])]))
(defn get-space-keeper-ios [bottom-space panel-space active-panel text-input-ref]
(fn [state]
;; NOTE: Only iOs now because we use soft input resize screen on android
(when platform/ios?
(cond
(and state
(< @bottom-space @panel-space)
(not @active-panel))
(reset! bottom-space @panel-space)
(and (not state)
(< @panel-space @bottom-space))
(do
(some-> ^js (quo.react/current-ref text-input-ref) .focus)
(reset! panel-space @bottom-space)
(reset! bottom-space 0))))))
(defn get-set-active-panel [active-panel]
(fn [panel]
(rn/configure-next
(:ease-opacity-200 rn/custom-animations))
(reset! active-panel panel)
(reagent/flush)
(when panel
(js/setTimeout #(react/dismiss-keyboard!) 100))))
(defn list-footer [{:keys [chat-id chat-type] :as chat}]
(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])]
[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}
[react/activity-indicator {:animating true}]]
[chat-intro-header-container chat no-messages?])
(when (= chat-type constants/one-to-one-chat-type)
[invite.chat/reward-messages])]))
(defn list-header [{:keys [chat-id chat-type invitation-admin]}]
(when (= chat-type constants/private-group-chat-type)
[react/view {:style (when platform/android? {:scaleY -1})}
[chat.group/group-chat-footer chat-id invitation-admin]]))
(defn render-fn [{:keys [outgoing type] :as message}
idx
_
{:keys [group-chat public? current-public-key space-keeper chat-id]}]
[react/view {:style (when platform/android? {:scaleY -1})}
(if (= type :datemark)
[message-datemark/chat-datemark (:value message)]
(if (= type :gap)
[gap/gap message idx messages-list-ref false chat-id]
; message content
[message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:current-public-key current-public-key)
space-keeper]))])
(defn messages-view
[{:keys [chat bottom-space pan-responder space-keeper]}]
(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])]
[list/flat-list
(merge
pan-responder
{:key-fn #(or (:message-id %) (:value %))
:ref #(reset! messages-list-ref %)
:header [list-header chat]
:footer [list-footer chat]
:data messages
:render-data {:group-chat group-chat
:public? public?
:current-public-key current-public-key
:space-keeper space-keeper
:chat-id chat-id}
: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
: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)
;;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 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 (partial reset! panel-space)
on-update #(when-not (zero? %) (reset! panel-space %))
pan-responder (accessory/create-pan-responder position-y pan-state)
space-keeper (fn [state]
;; NOTE: Only iOs now because we use soft input resize screen on android
(when platform/ios?
(cond
(and state
(< @bottom-space @panel-space)
(not @active-panel))
(reset! bottom-space @panel-space)
(and (not state)
(< @panel-space @bottom-space))
(do
(some-> ^js (quo.react/current-ref text-input-ref) .focus)
(reset! panel-space @bottom-space)
(reset! bottom-space 0)))))
set-active-panel (fn [panel]
(rn/configure-next
(:ease-opacity-200 rn/custom-animations))
(reset! active-panel panel)
(reagent/flush)
(when panel
(js/setTimeout #(react/dismiss-keyboard!) 100)))
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 %])]
(fn []
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat}
@(re-frame/subscribe [:chats/current-chat])]
(when current-chat
[react/view {:style {:flex 1}}
[connectivity/loading-indicator]
[topbar]
[react/view {:style {:flex 1}}
[:<>
[connectivity/loading-indicator]
[topbar current-chat]
[:<>
(when current-chat
(if group-chat
[invitation-requests chat-id admins]
[add-contact-bar chat-id])
[messages-view {:chat current-chat
:bottom-space (max @bottom-space @panel-space)
:pan-responder pan-responder
:space-keeper space-keeper}]]
(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])
(when show-input?
[accessory/view {:y position-y
:pan-state pan-state
:has-panel (boolean @active-panel)
:on-close #(set-active-panel nil)
: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}]
[bottom-sheet @active-panel]])])))))
[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}]]
(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])
(when show-input?
[accessory/view {:y position-y
:pan-state pan-state
:has-panel (boolean @active-panel)
:on-close #(set-active-panel nil)
: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}]
[bottom-sheet @active-panel]])]))))

View File

@ -169,9 +169,8 @@
(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)))
(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]
@ -194,9 +193,9 @@
(defn home []
[react/keyboard-avoiding-view {:style styles/home-container}
[topbar/topbar {:title (i18n/label :t/chat)
:navigation :none
:right-component [connectivity/connectivity-button]}]
[topbar/topbar {:title (i18n/label :t/chat)
:navigation :none
:right-component [connectivity/connectivity-button]}]
[connectivity/loading-indicator]
[chats-list]
[plus-button]])

View File

@ -174,13 +174,10 @@
[message-content-text {:content (:content last-message)
:content-type (:content-type last-message)}]]
[unviewed-indicator home-item]]
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
(re-frame/dispatch [:search/home-filter-changed nil])
(if public?
(re-frame/dispatch [:chat.ui/mark-public-all-read chat-id])
(re-frame/dispatch [:chat.ui/mark-messages-seen :chat])))
:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
(re-frame/dispatch [:search/home-filter-changed nil]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions home-item])}])}]))

View File

@ -69,7 +69,7 @@
"hardwareBackPress"
request-close)))]
(reagent/create-class
{:component-will-update
{:UNSAFE_componentWillUpdate
(fn [_ [_ popover _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(cond

View File

@ -20,8 +20,7 @@
[clojure.string :as string]
[quo.components.list.item :as list-item]
[status-im.ui.components.list.views :as list]
[status-im.ui.screens.status.views :as status.views]
[status-im.ui.screens.chat.views :as chat.views])
[status-im.ui.screens.status.views :as status.views])
(:require-macros [status-im.utils.views :as views]))
(defn actions
@ -156,60 +155,54 @@
:number-of-lines 2}
label]]])
(defn status []
(let [messages @(re-frame/subscribe [:chats/current-chat-messages-stream])
no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?])
{:keys [profile-public-key]} @(re-frame/subscribe [:chats/current-raw-chat])]
(when profile-public-key
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:header (when no-messages?
[react/view {:padding-horizontal 32 :margin-top 32}
[react/view (styles/updates-descr-cont)
[react/text {:style {:color colors/gray :line-height 22}}
(i18n/label :t/status-updates-descr)]]])
:ref #(reset! status.views/messages-list-ref %)
:on-viewable-items-changed chat.views/on-viewable-items-changed
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
:on-scroll-to-index-failed #() ;;don't remove this
:render-fn status.views/render-message
:data messages}])))
(views/defview profile []
(views/letsubs [{:keys [public-key name ens-verified]
:as contact} [:contacts/current-contact]]
(let [muted? (:muted @(re-frame/subscribe [:chats/chat public-key]))
[first-name second-name] (multiaccounts/contact-two-names contact true)
on-share #(re-frame/dispatch [:show-popover (merge
{:view :share-chat-key
:address public-key}
(when (and ens-verified name)
{:ens-name name}))])]
(when contact
[react/view {:flex 1}
[quo/animated-header
{:use-insets false
:right-accessories [{:icon :main-icons/share
:accessibility-label :share-button
:on-press on-share}]
:left-accessories [{:icon :main-icons/close
:accessibility-label :back-button
:on-press #(re-frame/dispatch [:navigate-back])}]
:extended-header (profile-header/extended-header
{:on-press on-share
:bottom-separator false
:title first-name
:photo (multiaccounts/displayed-photo contact)
:monospace (not ens-verified)
:subtitle second-name})}
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}]
[nickname-settings contact]
[react/view {:height 1 :background-color colors/gray-lighter}]
[react/view {:padding-top 17 :flex-direction :row :align-items :stretch :flex 1}
(for [{:keys [label] :as action} (actions contact muted?)
:when label]
^{:key label}
[button-item action])]
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 16}]
[status]]]))))
(defn profile []
(let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe [:contacts/current-contact])
current-chat-id @(re-frame/subscribe [:chats/current-profile-chat])
messages @(re-frame/subscribe [:chats/chat-messages-stream current-chat-id])
no-messages? @(re-frame/subscribe [:chats/chat-no-messages? current-chat-id])
muted? (:muted @(re-frame/subscribe [:chats/chat public-key]))
[first-name second-name] (multiaccounts/contact-two-names contact true)
on-share #(re-frame/dispatch [:show-popover (merge
{:view :share-chat-key
:address public-key}
(when (and ens-verified name)
{:ens-name name}))])]
(when contact
[:<>
[quo/header {:right-accessories [{:icon :main-icons/share
:accessibility-label :share-button
:on-press on-share}]
:left-accessories [{:icon :main-icons/close
:accessibility-label :back-button
:on-press #(re-frame/dispatch [:navigate-back])}]}]
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:header [:<>
[(profile-header/extended-header
{:on-press on-share
:bottom-separator false
:title first-name
:photo (multiaccounts/displayed-photo contact)
:monospace (not ens-verified)
:subtitle second-name})]
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}]
[nickname-settings contact]
[react/view {:height 1 :background-color colors/gray-lighter}]
[react/view {:padding-top 17 :flex-direction :row :align-items :stretch :flex 1}
(for [{:keys [label] :as action} (actions contact muted?)
:when label]
^{:key label}
[button-item action])]
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 16}]
(when no-messages?
[react/view {:padding-horizontal 32 :margin-top 32}
[react/view (styles/updates-descr-cont)
[react/text {:style {:color colors/gray :line-height 22}}
(i18n/label :t/status-updates-descr)]]])]
:ref #(reset! status.views/messages-list-ref %)
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages current-chat-id])
:on-scroll-to-index-failed #() ;;don't remove this
:render-data {:chat-id current-chat-id}
:render-fn status.views/render-message
:data messages}]])))

View File

@ -25,8 +25,8 @@
(defonce communities-stack (navigation/create-stack))
(defn chat-stack []
[stack {:initial-route-name :home
:header-mode :none}
[stack {:initial-route-name :home
:header-mode :none}
[{:name :home
:style {:padding-bottom tabbar.styles/tabs-diff}
:component home/home}

View File

@ -9,6 +9,7 @@
[stack {:initial-route-name :status
:header-mode :none}
[{:name :status
:on-focus [:init-timeline-chat]
:insets {:top true}
:style {:padding-bottom tabbar.styles/tabs-diff}
:component status.views/timeline}]])
:component status.views/timeline}]])

View File

@ -19,16 +19,16 @@
[react/view styles/buttons
[pressable/pressable {:type :scale
:accessibility-label :take-picture
:on-press #(re-frame/dispatch [:chat.ui/show-image-picker-camera])}
:on-press #(re-frame/dispatch [:chat.ui/show-image-picker-camera-timeline])}
[icons/icon :main-icons/camera]]
[react/view {:style {:padding-top 8}}
[pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/open-image-picker])
[pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/open-image-picker-timeline])
:accessibility-label :open-gallery
:type :scale}
[icons/icon :main-icons/gallery]]]])
(defn image-preview [uri]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick uri])}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick-timeline uri])}
[react/image {:style styles/image
:source {:uri uri}}]])
@ -52,8 +52,8 @@
scroll (reagent/atom nil)
autoscroll? (reagent/atom false)
scroll-height (reagent/atom nil)
input-text (re-frame/subscribe [:chats/current-chat-input-text])
sending-image (re-frame/subscribe [:chats/sending-image])]
input-text (re-frame/subscribe [:chats/timeline-chat-input-text])
sending-image (re-frame/subscribe [:chats/timeline-sending-image])]
(fn []
(let [{:keys [uri]} (first (vals @sending-image))]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
@ -83,7 +83,7 @@
@scroll-height
-40)]
(.scrollTo @scroll #js {:y height :animated true})))
:on-change-text #(re-frame/dispatch [:chat.ui/set-chat-input-text %])
:on-change-text #(re-frame/dispatch [:chat.ui/set-timeline-input-text %])
:default-value @input-text
:placeholder (i18n/label :t/whats-on-your-mind)}]
(when uri

View File

@ -11,7 +11,6 @@
[status-im.ui.components.list.views :as list]
[status-im.i18n.i18n :as i18n]
[status-im.ui.screens.status.styles :as styles]
[status-im.ui.screens.chat.views :as chat.views]
[status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.photos :as photos]
@ -65,7 +64,7 @@
:background-color :transparent
:border-color colors/black-transparent}]
(when show-close?
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image])
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image-timeline])
:accessibility-label :cancel-send-image
:style {:right 4 :top 12 :position :absolute}}
[react/view {:width 24
@ -110,7 +109,7 @@
{:border-radius 16}))
[react/touchable-highlight
{:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
(re-frame/dispatch [:chat.ui/show-profile from]))}
[react/view {:padding-top 2 :padding-right 8}
(if outgoing
[photos/account-photo account]
@ -120,7 +119,7 @@
:justify-content :space-between}
[react/touchable-highlight
{:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
(re-frame/dispatch [:chat.ui/show-profile from]))}
(if outgoing
[message/message-my-name {:profile? true :you? false}]
[message/message-author-name from {:profile? true}])]
@ -134,14 +133,14 @@
[message/render-parsed-text (assoc message :outgoing false) (:parsed-text content)]])
[link-preview/link-preview-wrapper (:links content) outgoing true]]]]))
(defn render-message [{:keys [type] :as message} idx _ {:keys [timeline account]}]
(defn render-message [{:keys [type] :as message} idx _ {:keys [timeline account chat-id]}]
(if (= type :datemark)
nil
(if (= type :gap)
(if timeline
nil
[gap/gap message idx messages-list-ref true])
; message content
[gap/gap message idx messages-list-ref true chat-id])
;; for timeline for reactions we need to use :from as chat-id
(let [chat-id (chat/profile-chat-topic (:from message))]
[react/view (merge {:accessibility-label :chat-item}
(when (:last-in-group? message)
@ -152,7 +151,7 @@
[reactions/with-reaction-picker
{:message message
:timeline true
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)])
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) constants/timeline-chat-id])
:picker-on-open (fn [])
:picker-on-close (fn [])
:send-emoji (fn [{:keys [emoji-id]}]
@ -182,37 +181,38 @@
(defn timeline []
(let [messages @(re-frame/subscribe [:chats/timeline-messages-stream])
no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?])
loading-messages? @(re-frame/subscribe [:chats/loading-messages? constants/timeline-chat-id])
no-messages? @(re-frame/subscribe [:chats/chat-no-messages? constants/timeline-chat-id])
account @(re-frame/subscribe [:multiaccount])]
[react/view {:flex 1}
;;TODO implement in the next iteration
#_[tabs]
[react/view {:height 1
:background-color colors/gray-lighter}]
(if no-messages?
[react/view {:padding-horizontal 32
:margin-top 64}
[react/image {:style {:width 140
:height 140
:align-self :center}
:source {:uri (contenthash/url image-hash)}}]
[react/view (styles/descr-container)
[react/text {:style {:color colors/gray
:line-height 22}}
(if (= :timeline (:tab @state))
(i18n/label :t/statuses-descr)
(i18n/label :t/statuses-my-status-descr))]]]
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:render-data {:timeline (= :timeline (:tab @state))
:account account}
:render-fn render-message
:data messages
:on-viewable-items-changed chat.views/on-viewable-items-changed
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
;;don't remove :on-scroll-to-index-failed
:on-scroll-to-index-failed #()
:header [react/view {:height 8}]
:footer [react/view {:height 68}]}])
(if loading-messages?
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/activity-indicator {:animating true}]]
(if no-messages?
[react/view {:padding-horizontal 32
:margin-top 64}
[react/image {:style {:width 140
:height 140
:align-self :center}
:source {:uri (contenthash/url image-hash)}}]
[react/view (styles/descr-container)
[react/text {:style {:color colors/gray
:line-height 22}}
(if (= :timeline (:tab @state))
(i18n/label :t/statuses-descr)
(i18n/label :t/statuses-my-status-descr))]]]
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:render-data {:timeline (= :timeline (:tab @state))
:account account}
:render-fn render-message
:data messages
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages constants/timeline-chat-id])
;;don't remove :on-scroll-to-index-failed
:on-scroll-to-index-failed #()
:header [react/view {:height 8}]
:footer [react/view {:height 68}]}]))
[components.plus-button/plus-button
{:on-press #(re-frame/dispatch [:navigate-to :my-status])}]]))

View File

@ -33,3 +33,5 @@
(defn android-version>= [v]
(and android? (>= version v)))
(def low-device? (and android? (< version 29)))

View File

@ -23,6 +23,13 @@
(catch js/Error _
(when (string? json) json)))))
(defn json->js [json]
(when-not (= json "undefined")
(try
(.parse js/JSON json)
(catch js/Error _
(when (string? json) json)))))
(def serialize clj->json)
(defn deserialize [o] (try (json->clj o)
(catch :default _ nil)))

View File

@ -98,7 +98,8 @@
:utils/dispatch-later
(fn [params]
(doseq [{:keys [ms dispatch]} params]
(set-timeout #(re-frame/dispatch dispatch) ms))))
(when (and ms dispatch)
(set-timeout #(re-frame/dispatch dispatch) ms)))))
(defn clear-timeout [id]
(.clearTimeout background-timer id))

View File

@ -383,7 +383,8 @@
(when-not (= symbol :ETH)
address)
from-address]
:on-success #(re-frame/dispatch [:transport/message-sent % 1])}]})))
:js-response true
:on-success #(re-frame/dispatch [:transport/message-sent %])}]})))
(fx/defn accept-request-transaction-button-clicked-from-command
{:events [:wallet.ui/accept-request-transaction-button-clicked-from-command]}