Emoji reactions
Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
|
@ -59,6 +59,7 @@
|
||||||
"react-native-svg": "^9.8.4",
|
"react-native-svg": "^9.8.4",
|
||||||
"react-native-touch-id": "^4.4.1",
|
"react-native-touch-id": "^4.4.1",
|
||||||
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#v10.2.3_5",
|
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#v10.2.3_5",
|
||||||
|
"tdigest": "^0.1.1",
|
||||||
"web3-utils": "^1.2.1"
|
"web3-utils": "^1.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
|
@ -64,6 +64,19 @@
|
||||||
:ease-in-out (bezier 0.42 0 0.58 1)
|
:ease-in-out (bezier 0.42 0 0.58 1)
|
||||||
:keyboard (bezier 0.17 0.59 0.4 0.77)})
|
:keyboard (bezier 0.17 0.59 0.4 0.77)})
|
||||||
|
|
||||||
|
(def springs {:lazy {:damping 50
|
||||||
|
:mass 0.3
|
||||||
|
:stiffness 120
|
||||||
|
:overshootClamping true
|
||||||
|
:bouncyFactor 1}
|
||||||
|
:jump {:damping 13
|
||||||
|
:mass 0.5
|
||||||
|
:stiffness 170
|
||||||
|
:overshootClamping false
|
||||||
|
:bouncyFactor 1
|
||||||
|
:restSpeedThreshold 0.001
|
||||||
|
:restDisplacementThreshold 0.001}})
|
||||||
|
|
||||||
(defn set-value [anim val]
|
(defn set-value [anim val]
|
||||||
(ocall anim "setValue" val))
|
(ocall anim "setValue" val))
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,6 @@
|
||||||
[quo.components.controls.styles :as styles]
|
[quo.components.controls.styles :as styles]
|
||||||
[status-im.ui.components.icons.vector-icons :as icons]))
|
[status-im.ui.components.icons.vector-icons :as icons]))
|
||||||
|
|
||||||
(def spring-config {:damping 50
|
|
||||||
:mass 0.3
|
|
||||||
:stiffness 120
|
|
||||||
:overshootClamping true
|
|
||||||
:bouncyFactor 1})
|
|
||||||
|
|
||||||
(defn control-builder [component]
|
(defn control-builder [component]
|
||||||
(fn [props]
|
(fn [props]
|
||||||
(let [{:keys [value onChange disabled]}
|
(let [{:keys [value onChange disabled]}
|
||||||
|
@ -29,7 +23,7 @@
|
||||||
[])
|
[])
|
||||||
transition (react/use-memo
|
transition (react/use-memo
|
||||||
(fn []
|
(fn []
|
||||||
(animated/with-spring-transition state spring-config))
|
(animated/with-spring-transition state (:lazy animated/springs)))
|
||||||
[])
|
[])
|
||||||
press-end (fn []
|
press-end (fn []
|
||||||
(when (and (not disabled) onChange)
|
(when (and (not disabled) onChange)
|
||||||
|
|
|
@ -168,7 +168,7 @@
|
||||||
(not on-long-press))
|
(not on-long-press))
|
||||||
rn/view
|
rn/view
|
||||||
animated animated/pressable
|
animated animated/pressable
|
||||||
:else gh/touchable-hightlight)]
|
:else gh/touchable-highlight)]
|
||||||
[rn/view {:background-color (if (and (= accessory :radio) active)
|
[rn/view {:background-color (if (and (= accessory :radio) active)
|
||||||
active-background
|
active-background
|
||||||
passive-background)}
|
passive-background)}
|
||||||
|
|
|
@ -169,7 +169,7 @@
|
||||||
:on-press #(reset! visible true)}
|
:on-press #(reset! visible true)}
|
||||||
|
|
||||||
:else after)
|
:else after)
|
||||||
secure (and secure-text-entry
|
secure (and (true? secure-text-entry)
|
||||||
(or platform/android? (not @visible)))
|
(or platform/android? (not @visible)))
|
||||||
on-cancel (fn []
|
on-cancel (fn []
|
||||||
(when on-cancel
|
(when on-cancel
|
||||||
|
|
|
@ -32,11 +32,11 @@
|
||||||
(def touchable-without-feedback
|
(def touchable-without-feedback
|
||||||
(reagent/adapt-react-class touchable-without-feedback-class))
|
(reagent/adapt-react-class touchable-without-feedback-class))
|
||||||
|
|
||||||
(def touchable-hightlight-class (reagent/adapt-react-class TouchableHighlight))
|
(def touchable-highlight-class (reagent/adapt-react-class TouchableHighlight))
|
||||||
|
|
||||||
(defn touchable-hightlight [props & children]
|
(defn touchable-highlight [props & children]
|
||||||
(into [touchable-hightlight-class (merge {:underlay-color (:interactive-02 @colors/theme)}
|
(into [touchable-highlight-class (merge {:underlay-color (:interactive-02 @colors/theme)}
|
||||||
props)]
|
props)]
|
||||||
children))
|
children))
|
||||||
|
|
||||||
(def touchable-opacity
|
(def touchable-opacity
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
[status-im.transport.filters.core :as filters]
|
[status-im.transport.filters.core :as filters]
|
||||||
[status-im.mailserver.core :as mailserver]
|
[status-im.mailserver.core :as mailserver]
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
|
[status-im.chat.models.reactions :as reactions]
|
||||||
[status-im.chat.models.message-list :as message-list]
|
[status-im.chat.models.message-list :as message-list]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[status-im.chat.models.message-seen :as message-seen]))
|
[status-im.chat.models.message-seen :as message-seen]))
|
||||||
|
@ -133,6 +134,7 @@
|
||||||
#(re-frame/dispatch [::failed-loading-messages current-chat-id session-id %]))]
|
#(re-frame/dispatch [::failed-loading-messages current-chat-id session-id %]))]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
load-messages-fx
|
load-messages-fx
|
||||||
|
(reactions/load-more-reactions cursor)
|
||||||
(mailserver/load-gaps-fx current-chat-id)))))))
|
(mailserver/load-gaps-fx current-chat-id)))))))
|
||||||
|
|
||||||
(fx/defn load-messages
|
(fx/defn load-messages
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
(ns status-im.chat.models.reactions
|
||||||
|
(:require [status-im.constants :as constants]
|
||||||
|
[re-frame.core :as re-frame]
|
||||||
|
[status-im.utils.fx :as fx]
|
||||||
|
[status-im.waku.core :as waku]
|
||||||
|
[taoensso.timbre :as log]
|
||||||
|
[status-im.transport.message.protocol :as message.protocol]
|
||||||
|
[status-im.data-store.reactions :as data-store.reactions]))
|
||||||
|
|
||||||
|
(defn process-reactions
|
||||||
|
[reactions new-reactions]
|
||||||
|
;; TODO(Ferossgp): handling own reaction in subscription could be expensive,
|
||||||
|
;; for better performance we can here separate own reaction into 2 maps
|
||||||
|
(reduce
|
||||||
|
(fn [acc {:keys [chat-id message-id emoji-id emoji-reaction-id retracted]
|
||||||
|
:as reaction}]
|
||||||
|
;; NOTE(Ferossgp): For a better performance, better to not keep in db all retracted reactions
|
||||||
|
;; retraction will always come after the reaction so there shouldn't be a conflict
|
||||||
|
(if retracted
|
||||||
|
(update-in acc [chat-id message-id emoji-id] dissoc emoji-reaction-id)
|
||||||
|
(assoc-in acc [chat-id message-id emoji-id emoji-reaction-id] reaction)))
|
||||||
|
reactions
|
||||||
|
new-reactions))
|
||||||
|
|
||||||
|
(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)))
|
||||||
|
|
||||||
|
(fx/defn receive-signal
|
||||||
|
[{:keys [db] :as cofx} reactions]
|
||||||
|
(let [reactions (filter (partial earlier-than-deleted-at? cofx) reactions)]
|
||||||
|
{:db (update db :reactions process-reactions 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
|
||||||
|
(waku/enabled? cofx)
|
||||||
|
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 %)))))
|
||||||
|
|
||||||
|
(fx/defn reactions-loaded
|
||||||
|
{:events [::reactions-loaded]}
|
||||||
|
[{{:keys [current-chat-id] :as 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?]))))
|
||||||
|
(let [reactions-w-chat-id (map #(assoc % :chat-id chat-id) reactions)]
|
||||||
|
{:db (update db :reactions process-reactions reactions-w-chat-id)})))
|
||||||
|
|
||||||
|
|
||||||
|
;; Send reactions
|
||||||
|
|
||||||
|
|
||||||
|
(fx/defn send-emoji-reaction
|
||||||
|
{:events [::send-emoji-reaction]}
|
||||||
|
[{{:keys [current-chat-id] :as db} :db :as cofx} reaction]
|
||||||
|
(message.protocol/send-reaction cofx
|
||||||
|
(assoc reaction :chat-id current-chat-id)))
|
||||||
|
|
||||||
|
(fx/defn send-retract-emoji-reaction
|
||||||
|
{:events [::send-emoji-reaction-retraction]}
|
||||||
|
[{{:keys [current-chat-id reactions] :as db} :db :as cofx} reaction]
|
||||||
|
(message.protocol/send-retract-reaction cofx
|
||||||
|
(assoc reaction :chat-id current-chat-id)))
|
||||||
|
|
||||||
|
(fx/defn receive-one
|
||||||
|
{:events [::receive-one]}
|
||||||
|
[{:keys [db]} reaction]
|
||||||
|
{:db (update db :reactions process-reactions [reaction])})
|
||||||
|
|
||||||
|
(defn message-reactions [current-public-key reactions]
|
||||||
|
(reduce
|
||||||
|
(fn [acc [emoji-id reactions]]
|
||||||
|
(if (pos? (count reactions))
|
||||||
|
(let [own (first (filter (fn [[_ {:keys [from]}]]
|
||||||
|
(= from current-public-key)) reactions))]
|
||||||
|
(conj acc {:emoji-id emoji-id
|
||||||
|
:own (boolean (seq own))
|
||||||
|
:emoji-reaction-id (:emoji-reaction-id (second own))
|
||||||
|
:quantity (count reactions)}))
|
||||||
|
acc))
|
||||||
|
[]
|
||||||
|
reactions))
|
|
@ -1,5 +1,6 @@
|
||||||
(ns status-im.constants
|
(ns status-im.constants
|
||||||
(:require [status-im.ethereum.core :as ethereum]
|
(:require [status-im.ethereum.core :as ethereum]
|
||||||
|
[status-im.react-native.resources :as resources]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.utils.config :as config]))
|
[status-im.utils.config :as config]))
|
||||||
|
|
||||||
|
@ -16,6 +17,20 @@
|
||||||
(def content-type-image 7)
|
(def content-type-image 7)
|
||||||
(def content-type-audio 8)
|
(def content-type-audio 8)
|
||||||
|
|
||||||
|
(def emoji-reaction-love 1)
|
||||||
|
(def emoji-reaction-thumbs-up 2)
|
||||||
|
(def emoji-reaction-thumbs-down 3)
|
||||||
|
(def emoji-reaction-laugh 4)
|
||||||
|
(def emoji-reaction-sad 5)
|
||||||
|
(def emoji-reaction-angry 6)
|
||||||
|
|
||||||
|
(def reactions {emoji-reaction-love (:love resources/reactions)
|
||||||
|
emoji-reaction-thumbs-up (:thumbs-up resources/reactions)
|
||||||
|
emoji-reaction-thumbs-down (:thumbs-down resources/reactions)
|
||||||
|
emoji-reaction-laugh (:laugh resources/reactions)
|
||||||
|
emoji-reaction-sad (:sad resources/reactions)
|
||||||
|
emoji-reaction-angry (:angry resources/reactions)})
|
||||||
|
|
||||||
(def message-type-one-to-one 1)
|
(def message-type-one-to-one 1)
|
||||||
(def message-type-public-group 2)
|
(def message-type-public-group 2)
|
||||||
(def message-type-private-group 3)
|
(def message-type-private-group 3)
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
(.addListener ^js react/keyboard
|
(.addListener ^js react/keyboard
|
||||||
(if platform/ios?
|
(if platform/ios?
|
||||||
"keyboardWillHide"
|
"keyboardWillHide"
|
||||||
"keyboardWDidHide")
|
"keyboardDidHide")
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(re-frame/dispatch-sync [:set :keyboard-height 0])))
|
(re-frame/dispatch-sync [:set :keyboard-height 0])))
|
||||||
(.addEventListener ^js react/app-state "change" app-state-change-handler)
|
(.addEventListener ^js react/app-state "change" app-state-change-handler)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
(ns status-im.data-store.reactions
|
||||||
|
(:require [clojure.set :as clojure.set]
|
||||||
|
[status-im.ethereum.json-rpc :as json-rpc]))
|
||||||
|
|
||||||
|
(defn ->rpc [message]
|
||||||
|
(-> message
|
||||||
|
(clojure.set/rename-keys {:message-id :messageId
|
||||||
|
:emoji-id :emojiId
|
||||||
|
:chat-id :localChatId
|
||||||
|
:message-type :messageType
|
||||||
|
:emoji-reaction-id :id})))
|
||||||
|
|
||||||
|
(defn <-rpc [message]
|
||||||
|
(-> message
|
||||||
|
(dissoc :chat_id)
|
||||||
|
(clojure.set/rename-keys {:messageId :message-id
|
||||||
|
:localChatId :chat-id
|
||||||
|
:emojiId :emoji-id
|
||||||
|
:messageType :message-type
|
||||||
|
:id :emoji-reaction-id})))
|
||||||
|
|
||||||
|
(defn reactions-by-chat-id-rpc [waku-enabled?
|
||||||
|
chat-id
|
||||||
|
cursor
|
||||||
|
limit
|
||||||
|
on-success
|
||||||
|
on-failure]
|
||||||
|
{::json-rpc/call [{:method (json-rpc/call-ext-method waku-enabled? "emojiReactionsByChatID")
|
||||||
|
:params [chat-id cursor limit]
|
||||||
|
:on-success (fn [result]
|
||||||
|
(on-success (map <-rpc result)))
|
||||||
|
:on-failure on-failure}]})
|
|
@ -74,6 +74,9 @@
|
||||||
"shhext_prepareContent" {}
|
"shhext_prepareContent" {}
|
||||||
"shhext_blockContact" {}
|
"shhext_blockContact" {}
|
||||||
"shhext_updateMailservers" {}
|
"shhext_updateMailservers" {}
|
||||||
|
"shhext_sendEmojiReaction" {}
|
||||||
|
"shhext_sendEmojiReactionRetraction" {}
|
||||||
|
"shhext_emojiReactionsByChatID" {}
|
||||||
;;TODO not used anywhere?
|
;;TODO not used anywhere?
|
||||||
"shhext_deleteChat" {}
|
"shhext_deleteChat" {}
|
||||||
"shhext_saveContact" {}
|
"shhext_saveContact" {}
|
||||||
|
@ -125,6 +128,9 @@
|
||||||
"wakuext_prepareContent" {}
|
"wakuext_prepareContent" {}
|
||||||
"wakuext_blockContact" {}
|
"wakuext_blockContact" {}
|
||||||
"wakuext_updateMailservers" {}
|
"wakuext_updateMailservers" {}
|
||||||
|
"wakuext_sendEmojiReaction" {}
|
||||||
|
"wakuext_sendEmojiReactionRetraction" {}
|
||||||
|
"wakuext_emojiReactionsByChatID" {}
|
||||||
;;TODO not used anywhere?
|
;;TODO not used anywhere?
|
||||||
"wakuext_deleteChat" {}
|
"wakuext_deleteChat" {}
|
||||||
"wakuext_saveContact" {}
|
"wakuext_saveContact" {}
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
[status-im.chat.models.input :as chat.input]
|
[status-im.chat.models.input :as chat.input]
|
||||||
[status-im.chat.models.loading :as chat.loading]
|
[status-im.chat.models.loading :as chat.loading]
|
||||||
[status-im.chat.models.message :as chat.message]
|
[status-im.chat.models.message :as chat.message]
|
||||||
|
[status-im.chat.models.reactions :as chat.reactions]
|
||||||
[status-im.chat.models.message-seen :as message-seen]
|
[status-im.chat.models.message-seen :as message-seen]
|
||||||
[status-im.contact.block :as contact.block]
|
[status-im.contact.block :as contact.block]
|
||||||
[status-im.contact.core :as contact]
|
[status-im.contact.core :as contact]
|
||||||
[status-im.data-store.chats :as data-store.chats]
|
[status-im.data-store.chats :as data-store.chats]
|
||||||
[status-im.data-store.messages :as data-store.messages]
|
[status-im.data-store.messages :as data-store.messages]
|
||||||
|
[status-im.data-store.reactions :as data-store.reactions]
|
||||||
[status-im.ethereum.subscriptions :as ethereum.subscriptions]
|
[status-im.ethereum.subscriptions :as ethereum.subscriptions]
|
||||||
[status-im.fleet.core :as fleet]
|
[status-im.fleet.core :as fleet]
|
||||||
[status-im.group-chats.core :as group-chats]
|
[status-im.group-chats.core :as group-chats]
|
||||||
|
@ -794,12 +796,14 @@
|
||||||
(fn [_ [_ err]]
|
(fn [_ [_ err]]
|
||||||
(log/error :send-status-message-error err)))
|
(log/error :send-status-message-error err)))
|
||||||
|
|
||||||
(fx/defn handle-update [cofx {:keys [chats messages] :as response}]
|
(fx/defn handle-update [cofx {:keys [chats messages emojiReactions] :as response}]
|
||||||
(let [chats (map data-store.chats/<-rpc chats)
|
(let [chats (map data-store.chats/<-rpc chats)
|
||||||
messages (map data-store.messages/<-rpc messages)
|
messages (map data-store.messages/<-rpc messages)
|
||||||
message-fxs (map chat.message/receive-one messages)
|
message-fxs (map chat.message/receive-one messages)
|
||||||
chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)]
|
emoji-reactions (map data-store.reactions/<-rpc emojiReactions)
|
||||||
(apply fx/merge cofx (concat chat-fxs message-fxs))))
|
emoji-react-fxs (map chat.reactions/receive-one emoji-reactions)
|
||||||
|
chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)]
|
||||||
|
(apply fx/merge cofx (concat chat-fxs message-fxs emoji-react-fxs))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:transport/message-sent
|
:transport/message-sent
|
||||||
|
@ -811,6 +815,16 @@
|
||||||
(conj set-hash-fxs
|
(conj set-hash-fxs
|
||||||
(handle-update response))))))
|
(handle-update response))))))
|
||||||
|
|
||||||
|
(fx/defn reaction-sent
|
||||||
|
{:events [:transport/reaction-sent]}
|
||||||
|
[cofx response]
|
||||||
|
(handle-update cofx response))
|
||||||
|
|
||||||
|
(fx/defn retraction-sent
|
||||||
|
{:events [:transport/retraction-sent]}
|
||||||
|
[cofx response]
|
||||||
|
(handle-update cofx response))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:transport.callback/node-info-fetched
|
:transport.callback/node-info-fetched
|
||||||
(fn [cofx [_ node-info]]
|
(fn [cofx [_ node-info]]
|
||||||
|
|
|
@ -51,3 +51,11 @@
|
||||||
(get @loaded-images k)
|
(get @loaded-images k)
|
||||||
(get (swap! loaded-images assoc k
|
(get (swap! loaded-images assoc k
|
||||||
(get ui k)) k)))
|
(get ui k)) k)))
|
||||||
|
|
||||||
|
(def reactions
|
||||||
|
{:love (js/require "../resources/images/reactions/love.png")
|
||||||
|
:angry (js/require "../resources/images/reactions/angry.png")
|
||||||
|
:sad (js/require "../resources/images/reactions/sad.png")
|
||||||
|
:laugh (js/require "../resources/images/reactions/laugh.png")
|
||||||
|
:thumbs-up (js/require "../resources/images/reactions/thumbs-up.png")
|
||||||
|
:thumbs-down (js/require "../resources/images/reactions/thumbs-down.png")})
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
[status-im.multiaccounts.db :as multiaccounts.db]
|
[status-im.multiaccounts.db :as multiaccounts.db]
|
||||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||||
[status-im.multiaccounts.recover.core :as recover]
|
[status-im.multiaccounts.recover.core :as recover]
|
||||||
|
[status-im.chat.models.reactions :as models.reactions]
|
||||||
[status-im.pairing.core :as pairing]
|
[status-im.pairing.core :as pairing]
|
||||||
[status-im.signing.gas :as signing.gas]
|
[status-im.signing.gas :as signing.gas]
|
||||||
#_[status-im.tribute-to-talk.core :as tribute-to-talk]
|
#_[status-im.tribute-to-talk.core :as tribute-to-talk]
|
||||||
|
@ -182,6 +183,7 @@
|
||||||
(reg-root-key-sub :multiaccounts/loading :multiaccounts/loading)
|
(reg-root-key-sub :multiaccounts/loading :multiaccounts/loading)
|
||||||
|
|
||||||
(reg-root-key-sub ::messages :messages)
|
(reg-root-key-sub ::messages :messages)
|
||||||
|
(reg-root-key-sub ::reactions :reactions)
|
||||||
(reg-root-key-sub ::message-lists :message-lists)
|
(reg-root-key-sub ::message-lists :message-lists)
|
||||||
(reg-root-key-sub ::pagination-info :pagination-info)
|
(reg-root-key-sub ::pagination-info :pagination-info)
|
||||||
|
|
||||||
|
@ -637,6 +639,16 @@
|
||||||
(fn [[messages chat-id]]
|
(fn [[messages chat-id]]
|
||||||
(get 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 chat-id] [_ message-id]]
|
||||||
|
(models.reactions/message-reactions
|
||||||
|
current-public-key
|
||||||
|
(get-in reactions [chat-id message-id]))))
|
||||||
|
|
||||||
(re-frame/reg-sub
|
(re-frame/reg-sub
|
||||||
:chats/messages-gaps
|
:chats/messages-gaps
|
||||||
:<- [:mailserver/gaps]
|
:<- [:mailserver/gaps]
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
status-im.transport.message.core
|
status-im.transport.message.core
|
||||||
(:require [status-im.chat.models.message :as models.message]
|
(:require [status-im.chat.models.message :as models.message]
|
||||||
[status-im.chat.models :as models.chat]
|
[status-im.chat.models :as models.chat]
|
||||||
|
[status-im.chat.models.reactions :as models.reactions]
|
||||||
[status-im.contact.core :as models.contact]
|
[status-im.contact.core :as models.contact]
|
||||||
[status-im.pairing.core :as models.pairing]
|
[status-im.pairing.core :as models.pairing]
|
||||||
[status-im.data-store.messages :as data-store.messages]
|
[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.contacts :as data-store.contacts]
|
||||||
[status-im.data-store.chats :as data-store.chats]
|
[status-im.data-store.chats :as data-store.chats]
|
||||||
[status-im.utils.handlers :as handlers]
|
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
[status-im.utils.types :as types]))
|
[status-im.utils.types :as types]))
|
||||||
|
|
||||||
|
@ -20,11 +21,17 @@
|
||||||
(fx/defn handle-message [cofx message]
|
(fx/defn handle-message [cofx message]
|
||||||
(models.message/receive-one cofx message))
|
(models.message/receive-one cofx message))
|
||||||
|
|
||||||
(fx/defn process-response [cofx ^js response-js]
|
(fx/defn handle-reactions [cofx reactions]
|
||||||
|
(models.reactions/receive-signal cofx reactions))
|
||||||
|
|
||||||
|
(fx/defn process-response
|
||||||
|
{:events [::process]}
|
||||||
|
[cofx ^js response-js]
|
||||||
(let [^js chats (.-chats response-js)
|
(let [^js chats (.-chats response-js)
|
||||||
^js contacts (.-contacts response-js)
|
^js contacts (.-contacts response-js)
|
||||||
^js installations (.-installations response-js)
|
^js installations (.-installations response-js)
|
||||||
^js messages (.-messages response-js)]
|
^js messages (.-messages response-js)
|
||||||
|
^js emoji-reactions (.-emojiReactions response-js)]
|
||||||
(cond
|
(cond
|
||||||
(seq installations)
|
(seq installations)
|
||||||
(let [installations-clj (types/js->clj installations)]
|
(let [installations-clj (types/js->clj installations)]
|
||||||
|
@ -54,12 +61,14 @@
|
||||||
(let [message (.pop messages)]
|
(let [message (.pop messages)]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
|
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
|
||||||
(handle-message (-> message (types/js->clj) (data-store.messages/<-rpc))))))))
|
(handle-message (-> message (types/js->clj) (data-store.messages/<-rpc)))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(seq emoji-reactions)
|
||||||
::process
|
(let [reactions (types/js->clj emoji-reactions)]
|
||||||
(fn [cofx [_ response-js]]
|
(js-delete response-js "emojiReactions")
|
||||||
(process-response cofx response-js)))
|
(fx/merge cofx
|
||||||
|
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
|
||||||
|
(handle-reactions (map data-store.reactions/<-rpc reactions)))))))
|
||||||
|
|
||||||
(fx/defn remove-hash
|
(fx/defn remove-hash
|
||||||
[{:keys [db] :as cofx} envelope-hash]
|
[{:keys [db] :as cofx} envelope-hash]
|
||||||
|
|
|
@ -31,3 +31,21 @@
|
||||||
:on-success
|
:on-success
|
||||||
#(re-frame/dispatch [:transport/message-sent % 1])
|
#(re-frame/dispatch [:transport/message-sent % 1])
|
||||||
:on-failure #(log/error "failed to send a message" %)}]})
|
:on-failure #(log/error "failed to send a message" %)}]})
|
||||||
|
|
||||||
|
(fx/defn send-reaction [cofx {:keys [message-id chat-id emoji-id]}]
|
||||||
|
{::json-rpc/call [{:method (json-rpc/call-ext-method
|
||||||
|
(get-in cofx [:db :multiaccount :waku-enabled])
|
||||||
|
"sendEmojiReaction")
|
||||||
|
:params [chat-id message-id emoji-id]
|
||||||
|
:on-success
|
||||||
|
#(re-frame/dispatch [:transport/reaction-sent %])
|
||||||
|
:on-failure #(log/error "failed to send a reaction" %)}]})
|
||||||
|
|
||||||
|
(fx/defn send-retract-reaction [cofx {:keys [emoji-reaction-id] :as reaction}]
|
||||||
|
{::json-rpc/call [{:method (json-rpc/call-ext-method
|
||||||
|
(get-in cofx [:db :multiaccount :waku-enabled])
|
||||||
|
"sendEmojiReactionRetraction")
|
||||||
|
:params [emoji-reaction-id]
|
||||||
|
:on-success
|
||||||
|
#(re-frame/dispatch [:transport/retraction-sent %])
|
||||||
|
:on-failure #(log/error "failed to send a reaction retraction" %)}]})
|
||||||
|
|
|
@ -134,14 +134,9 @@
|
||||||
:set-active set-active-panel}])]]]])
|
:set-active set-active-panel}])]]]])
|
||||||
|
|
||||||
(defn chat-toolbar []
|
(defn chat-toolbar []
|
||||||
(let [text-input-ref (react/create-ref)
|
(let [previous-layout (atom nil)
|
||||||
input-focus (fn []
|
|
||||||
(some-> ^js (react/current-ref text-input-ref) .focus))
|
|
||||||
clear-input (fn []
|
|
||||||
(some-> ^js (react/current-ref text-input-ref) .clear))
|
|
||||||
previous-layout (atom nil)
|
|
||||||
had-reply (atom nil)]
|
had-reply (atom nil)]
|
||||||
(fn [{:keys [active-panel set-active-panel]}]
|
(fn [{:keys [active-panel set-active-panel text-input-ref]}]
|
||||||
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
||||||
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
||||||
mainnet? @(re-frame/subscribe [:mainnet?])
|
mainnet? @(re-frame/subscribe [:mainnet?])
|
||||||
|
@ -151,6 +146,10 @@
|
||||||
public? @(re-frame/subscribe [:current-chat/public?])
|
public? @(re-frame/subscribe [:current-chat/public?])
|
||||||
reply @(re-frame/subscribe [:chats/reply-message])
|
reply @(re-frame/subscribe [:chats/reply-message])
|
||||||
sending-image @(re-frame/subscribe [:chats/sending-image])
|
sending-image @(re-frame/subscribe [:chats/sending-image])
|
||||||
|
input-focus (fn []
|
||||||
|
(some-> ^js (react/current-ref text-input-ref) .focus))
|
||||||
|
clear-input (fn []
|
||||||
|
(some-> ^js (react/current-ref text-input-ref) .clear))
|
||||||
empty-text (string/blank? (string/trim (or input-text "")))
|
empty-text (string/blank? (string/trim (or input-text "")))
|
||||||
show-send (and (or (not empty-text)
|
show-send (and (or (not empty-text)
|
||||||
sending-image)
|
sending-image)
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
[status-im.ui.components.react :as react]
|
[status-im.ui.components.react :as react]
|
||||||
[status-im.ui.components.slider :as slider]))
|
[status-im.ui.components.slider :as slider]))
|
||||||
|
|
||||||
(defn message-press-handlers [_]
|
|
||||||
;;TBI save audio file?
|
|
||||||
)
|
|
||||||
|
|
||||||
(defonce player-ref (atom nil))
|
(defonce player-ref (atom nil))
|
||||||
(defonce current-player-message-id (atom nil))
|
(defonce current-player-message-id (atom nil))
|
||||||
(defonce current-active-state-ref-ref (atom nil))
|
(defonce current-active-state-ref-ref (atom nil))
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
(:require [re-frame.core :as re-frame]
|
(:require [re-frame.core :as re-frame]
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
|
[quo.platform :as platform]
|
||||||
[status-im.ui.components.colors :as colors]
|
[status-im.ui.components.colors :as colors]
|
||||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||||
[status-im.ui.components.react :as react]
|
[status-im.ui.components.react :as react]
|
||||||
[status-im.ui.screens.chat.message.audio :as message.audio]
|
[status-im.ui.screens.chat.message.audio :as message.audio]
|
||||||
|
[status-im.chat.models.reactions :as models.reactions]
|
||||||
[status-im.ui.screens.chat.message.command :as message.command]
|
[status-im.ui.screens.chat.message.command :as message.command]
|
||||||
[status-im.ui.screens.chat.photos :as photos]
|
[status-im.ui.screens.chat.photos :as photos]
|
||||||
[status-im.ui.screens.chat.sheets :as sheets]
|
[status-im.ui.screens.chat.sheets :as sheets]
|
||||||
|
@ -13,6 +15,7 @@
|
||||||
[status-im.ui.screens.chat.utils :as chat.utils]
|
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||||
[status-im.utils.contenthash :as contenthash]
|
[status-im.utils.contenthash :as contenthash]
|
||||||
[status-im.utils.security :as security]
|
[status-im.utils.security :as security]
|
||||||
|
[status-im.ui.screens.chat.message.reactions :as reactions]
|
||||||
[reagent.core :as reagent])
|
[reagent.core :as reagent])
|
||||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||||
|
|
||||||
|
@ -29,13 +32,6 @@
|
||||||
outgoing
|
outgoing
|
||||||
(:rtl? content))} timestamp-str]))
|
(:rtl? content))} timestamp-str]))
|
||||||
|
|
||||||
(defn message-bubble-wrapper
|
|
||||||
[message message-content appender]
|
|
||||||
[react/view
|
|
||||||
(style/message-view message)
|
|
||||||
message-content
|
|
||||||
appender])
|
|
||||||
|
|
||||||
(defview quoted-message
|
(defview quoted-message
|
||||||
[_ {:keys [from text image]} outgoing current-public-key public?]
|
[_ {:keys [from text image]} outgoing current-public-key public?]
|
||||||
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
||||||
|
@ -106,14 +102,6 @@
|
||||||
|
|
||||||
(conj acc literal)))
|
(conj acc literal)))
|
||||||
|
|
||||||
(defview message-content-status [{:keys [content content-type]}]
|
|
||||||
[react/view style/status-container
|
|
||||||
[react/text {:style (style/status-text)}
|
|
||||||
(reduce
|
|
||||||
(fn [acc e] (render-inline (:text content) false content-type acc e))
|
|
||||||
[react/text-class {:style (style/status-text)}]
|
|
||||||
(-> content :parsed-text peek :children))]])
|
|
||||||
|
|
||||||
(defn render-block [{:keys [content outgoing content-type]} acc
|
(defn render-block [{:keys [content outgoing content-type]} acc
|
||||||
{:keys [type ^js literal children]}]
|
{:keys [type ^js literal children]}]
|
||||||
(case type
|
(case type
|
||||||
|
@ -153,51 +141,15 @@
|
||||||
;; Append timestamp to new block
|
;; Append timestamp to new block
|
||||||
(conj elements timestamp))))
|
(conj elements timestamp))))
|
||||||
|
|
||||||
(defn text-message-press-handlers [message]
|
|
||||||
{:on-press (fn [_]
|
|
||||||
(react/dismiss-keyboard!))
|
|
||||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
|
||||||
{:content (sheets/message-long-press message)
|
|
||||||
:height 192}])})
|
|
||||||
|
|
||||||
(defn text-message
|
|
||||||
[{:keys [content outgoing current-public-key public?] :as message}]
|
|
||||||
[react/touchable-highlight (text-message-press-handlers message)
|
|
||||||
[message-bubble-wrapper message
|
|
||||||
(let [response-to (:response-to content)]
|
|
||||||
[react/view
|
|
||||||
(when (and (seq response-to) (:quoted-message message))
|
|
||||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
|
||||||
[render-parsed-text-with-timestamp message (:parsed-text content)]])
|
|
||||||
[message-timestamp message true]]])
|
|
||||||
|
|
||||||
(defn unknown-content-type
|
(defn unknown-content-type
|
||||||
[{:keys [outgoing content-type content] :as message}]
|
[{:keys [outgoing content-type content] :as message}]
|
||||||
[message-bubble-wrapper message
|
[react/view (style/message-view message)
|
||||||
[react/text
|
[react/text
|
||||||
{:style {:color (if outgoing colors/white-persist colors/black)}}
|
{:style {:color (if outgoing colors/white-persist colors/black)}}
|
||||||
(if (seq (:text content))
|
(if (seq (:text content))
|
||||||
(:text content)
|
(:text content)
|
||||||
(str "Unhandled content-type " content-type))]])
|
(str "Unhandled content-type " content-type))]])
|
||||||
|
|
||||||
(defn system-text-message
|
|
||||||
[{:keys [content] :as message}]
|
|
||||||
[message-bubble-wrapper message
|
|
||||||
[react/view
|
|
||||||
[render-parsed-text message (:parsed-text content)]]])
|
|
||||||
|
|
||||||
(defn emoji-message
|
|
||||||
[{:keys [content current-public-key outgoing public?] :as message}]
|
|
||||||
(let [response-to (:response-to content)]
|
|
||||||
[react/touchable-highlight (text-message-press-handlers message)
|
|
||||||
[message-bubble-wrapper message
|
|
||||||
[react/view {:style (style/style-message-text outgoing)}
|
|
||||||
(when (and (seq response-to) (:quoted-message message))
|
|
||||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
|
||||||
[react/text {:style (style/emoji-message message)}
|
|
||||||
(:text content)]]
|
|
||||||
[message-timestamp message]]]))
|
|
||||||
|
|
||||||
(defn message-not-sent-text
|
(defn message-not-sent-text
|
||||||
[chat-id message-id]
|
[chat-id message-id]
|
||||||
[react/touchable-highlight
|
[react/touchable-highlight
|
||||||
|
@ -220,43 +172,39 @@
|
||||||
(= outgoing-status :not-sent))
|
(= outgoing-status :not-sent))
|
||||||
[message-not-sent-text chat-id message-id]))
|
[message-not-sent-text chat-id message-id]))
|
||||||
|
|
||||||
(defview message-author-name [from alias]
|
(defview message-author-name [from alias modal]
|
||||||
(letsubs [contact-name [:contacts/raw-contact-name-by-identity from]]
|
(letsubs [contact-name [:contacts/raw-contact-name-by-identity from]]
|
||||||
(chat.utils/format-author (or contact-name alias) style/message-author-name-container)))
|
(chat.utils/format-author (or contact-name alias) modal)))
|
||||||
|
|
||||||
(defn message-content-wrapper
|
(defn message-content-wrapper
|
||||||
"Author, userpic and delivery wrapper"
|
"Author, userpic and delivery wrapper"
|
||||||
[{:keys [alias first-in-group? display-photo? identicon display-username?
|
[{:keys [alias first-in-group? display-photo? identicon display-username?
|
||||||
from outgoing]
|
from outgoing]
|
||||||
:as message} content]
|
:as message} content {:keys [modal close-modal]}]
|
||||||
[react/view {:style (style/message-wrapper message)
|
[react/view {:style (style/message-wrapper message)
|
||||||
|
:pointer-events :box-none
|
||||||
:accessibility-label :chat-item}
|
:accessibility-label :chat-item}
|
||||||
[react/view (style/message-body message)
|
[react/view {:style (style/message-body message)
|
||||||
|
:pointer-events :box-none}
|
||||||
(when display-photo?
|
(when display-photo?
|
||||||
; userpic
|
|
||||||
[react/view (style/message-author-userpic outgoing)
|
[react/view (style/message-author-userpic outgoing)
|
||||||
(when first-in-group?
|
(when first-in-group?
|
||||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
|
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
|
||||||
|
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||||
[photos/member-identicon identicon]])])
|
[photos/member-identicon identicon]])])
|
||||||
; username
|
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
||||||
[react/view (style/message-author-wrapper outgoing display-photo?)
|
|
||||||
(when display-username?
|
(when display-username?
|
||||||
[react/touchable-opacity {:style style/message-author-touchable
|
[react/touchable-opacity {:style style/message-author-touchable
|
||||||
:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
|
:on-press #(do (when modal (close-modal))
|
||||||
[message-author-name from alias]])
|
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||||
|
[message-author-name from alias modal]])
|
||||||
;;MESSAGE CONTENT
|
;;MESSAGE CONTENT
|
||||||
content]]
|
[react/view
|
||||||
|
content]]]
|
||||||
; delivery status
|
; delivery status
|
||||||
[react/view (style/delivery-status outgoing)
|
[react/view (style/delivery-status outgoing)
|
||||||
[message-delivery-status message]]])
|
[message-delivery-status message]]])
|
||||||
|
|
||||||
(defn system-message-content-wrapper
|
|
||||||
[message child]
|
|
||||||
[react/view {:style (style/message-wrapper-base message)
|
|
||||||
:accessibility-label :chat-item}
|
|
||||||
[react/view (style/system-message-body message)
|
|
||||||
[react/view child]]])
|
|
||||||
|
|
||||||
(defn message-content-image [{:keys [content outgoing]}]
|
(defn message-content-image [{:keys [content outgoing]}]
|
||||||
(let [dimensions (reagent/atom [260 260])
|
(let [dimensions (reagent/atom [260 260])
|
||||||
uri (:image content)]
|
uri (:image content)]
|
||||||
|
@ -271,57 +219,146 @@
|
||||||
:resize-mode :contain
|
:resize-mode :contain
|
||||||
:source {:uri uri}}]])))
|
:source {:uri uri}}]])))
|
||||||
|
|
||||||
(defn image-message-press-handlers [{:keys [content] :as message}]
|
(defmulti ->message :content-type)
|
||||||
{:on-press (fn [_]
|
|
||||||
(when (:image content)
|
|
||||||
(re-frame/dispatch [:navigate-to :image-preview message]))
|
|
||||||
(react/dismiss-keyboard!))
|
|
||||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
|
||||||
{:content (sheets/image-long-press message false)
|
|
||||||
:height 160}])})
|
|
||||||
|
|
||||||
(defn sticker-message-press-handlers [{:keys [content] :as message}]
|
(defmethod ->message constants/content-type-command
|
||||||
(let [pack (get-in content [:sticker :pack])]
|
|
||||||
{:on-press (fn [_]
|
|
||||||
(when pack
|
|
||||||
(re-frame/dispatch [:stickers/open-sticker-pack pack]))
|
|
||||||
(react/dismiss-keyboard!))
|
|
||||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
|
||||||
{:content (sheets/sticker-long-press message)
|
|
||||||
:height 64}])}))
|
|
||||||
|
|
||||||
(defn message-content-audio
|
|
||||||
[message]
|
[message]
|
||||||
[react/touchable-highlight (message.audio/message-press-handlers message)
|
[message.command/command-content message-content-wrapper message])
|
||||||
[message-bubble-wrapper message
|
|
||||||
[message.audio/message-content message [message-timestamp message false]]]])
|
|
||||||
|
|
||||||
(defn chat-message [{:keys [public? content content-type] :as message}]
|
(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}]
|
||||||
(if (= content-type constants/content-type-command)
|
[react/view {:accessibility-label :chat-item}
|
||||||
[message.command/command-content message-content-wrapper message]
|
[react/view (style/system-message-body message)
|
||||||
(if (= content-type constants/content-type-system-text)
|
[react/view (style/message-view message)
|
||||||
[system-message-content-wrapper message [system-text-message message]]
|
[react/view
|
||||||
[message-content-wrapper
|
[render-parsed-text message (:parsed-text content)]]]]])
|
||||||
message
|
|
||||||
(if (= content-type constants/content-type-text)
|
(defmethod ->message constants/content-type-text
|
||||||
;; text message
|
[{:keys [content outgoing current-public-key public?] :as message} {:keys [on-long-press modal]
|
||||||
[text-message message]
|
:as reaction-picker}]
|
||||||
(if (= content-type constants/content-type-status)
|
[message-content-wrapper message
|
||||||
[message-content-status message]
|
[react/touchable-highlight (when-not modal
|
||||||
(if (= content-type constants/content-type-emoji)
|
{:on-press (fn [_]
|
||||||
[emoji-message message]
|
(react/dismiss-keyboard!))
|
||||||
(if (= content-type constants/content-type-sticker)
|
:on-long-press (fn []
|
||||||
[react/touchable-highlight (sticker-message-press-handlers message)
|
(on-long-press
|
||||||
[react/image {:style {:margin 10 :width 140 :height 140}
|
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||||
;;TODO (perf) move to event
|
:label (i18n/label :t/message-reply)}
|
||||||
:source {:uri (contenthash/url (-> content :sticker :hash))}}]]
|
{:on-press #(react/copy-to-clipboard (get content :text))
|
||||||
(if (and (= content-type constants/content-type-image)
|
:label (i18n/label :t/sharing-copy-to-clipboard)}]))})
|
||||||
;; Disabling images for public-chats
|
[react/view (style/message-view message)
|
||||||
(not public?))
|
(let [response-to (:response-to content)]
|
||||||
[react/touchable-highlight (image-message-press-handlers message)
|
[react/view
|
||||||
[message-content-image message]]
|
(when (and (seq response-to) (:quoted-message message))
|
||||||
(if (and (= content-type constants/content-type-audio)
|
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
||||||
;; Disabling audio for public-chats
|
[render-parsed-text-with-timestamp message (:parsed-text content)]])
|
||||||
(not public?))
|
[message-timestamp message true]]]
|
||||||
[message-content-audio message]
|
reaction-picker])
|
||||||
[unknown-content-type message]))))))])))
|
|
||||||
|
(defmethod ->message constants/content-type-status
|
||||||
|
[{:keys [content content-type] :as message}]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[react/view style/status-container
|
||||||
|
[react/text {:style (style/status-text)}
|
||||||
|
(reduce
|
||||||
|
(fn [acc e] (render-inline (:text content) false content-type acc e))
|
||||||
|
[react/text-class {:style (style/status-text)}]
|
||||||
|
(-> content :parsed-text peek :children))]]])
|
||||||
|
|
||||||
|
(defmethod ->message constants/content-type-emoji
|
||||||
|
[{:keys [content current-public-key outgoing public?] :as message} {:keys [on-long-press modal]
|
||||||
|
:as reaction-picker}]
|
||||||
|
(let [response-to (:response-to content)]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[react/touchable-highlight (when-not modal
|
||||||
|
{:on-press (fn []
|
||||||
|
(react/dismiss-keyboard!))
|
||||||
|
:on-long-press (fn []
|
||||||
|
(on-long-press
|
||||||
|
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||||
|
:label (i18n/label :t/message-reply)}
|
||||||
|
{:on-press #(react/copy-to-clipboard (get content :text))
|
||||||
|
:label (i18n/label :t/sharing-copy-to-clipboard)}]))})
|
||||||
|
[react/view (style/message-view message)
|
||||||
|
[react/view {:style (style/style-message-text outgoing)}
|
||||||
|
(when (and (seq response-to) (:quoted-message message))
|
||||||
|
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
||||||
|
[react/text {:style (style/emoji-message message)}
|
||||||
|
(:text content)]]
|
||||||
|
[message-timestamp message]]]
|
||||||
|
reaction-picker]))
|
||||||
|
|
||||||
|
(defmethod ->message constants/content-type-sticker
|
||||||
|
[{:keys [content from outgoing]
|
||||||
|
:as message}
|
||||||
|
{:keys [on-long-press modal]
|
||||||
|
:as reaction-picker}]
|
||||||
|
(let [pack (get-in content [:sticker :pack])]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[react/touchable-highlight (when-not modal
|
||||||
|
{:on-press (fn [_]
|
||||||
|
(when pack
|
||||||
|
(re-frame/dispatch [:stickers/open-sticker-pack pack]))
|
||||||
|
(react/dismiss-keyboard!))
|
||||||
|
:on-long-press (fn []
|
||||||
|
(on-long-press
|
||||||
|
(when-not outgoing
|
||||||
|
[{:on-press #(when pack
|
||||||
|
(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
|
||||||
|
:source {:uri (contenthash/url (-> content :sticker :hash))}}]]
|
||||||
|
reaction-picker]))
|
||||||
|
|
||||||
|
(defmethod ->message constants/content-type-image [{:keys [content] :as message} {:keys [on-long-press modal]
|
||||||
|
:as reaction-picker}]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[react/touchable-highlight (when-not modal
|
||||||
|
{:on-press (fn [_]
|
||||||
|
(when (:image content)
|
||||||
|
(re-frame/dispatch [:navigate-to :image-preview message]))
|
||||||
|
(react/dismiss-keyboard!))
|
||||||
|
:on-long-press (fn []
|
||||||
|
(on-long-press
|
||||||
|
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||||
|
:label (i18n/label :t/message-reply)}
|
||||||
|
{:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
|
||||||
|
:label (i18n/label :t/save)}]))})
|
||||||
|
[message-content-image message]]
|
||||||
|
reaction-picker])
|
||||||
|
|
||||||
|
(defmethod ->message constants/content-type-audio [message {:keys [on-long-press modal]
|
||||||
|
:as reaction-picker}]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[react/touchable-highlight (when-not modal
|
||||||
|
{:on-long-press
|
||||||
|
(fn [] (on-long-press []))})
|
||||||
|
[react/view (style/message-view message)
|
||||||
|
[message.audio/message-content message [message-timestamp message false]]]]
|
||||||
|
reaction-picker])
|
||||||
|
|
||||||
|
(defmethod ->message :default [message]
|
||||||
|
[message-content-wrapper message
|
||||||
|
[unknown-content-type message]])
|
||||||
|
|
||||||
|
(defn chat-message [message set-active-panel]
|
||||||
|
[reactions/with-reaction-picker
|
||||||
|
{:message message
|
||||||
|
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)])
|
||||||
|
:picker-on-open (fn []
|
||||||
|
;; NOTE(Ferossgp): Because of soft-input adjustResize there are some problems on android
|
||||||
|
(when (and platform/ios? (pos? @(re-frame/subscribe [:keyboard-height])))
|
||||||
|
(set-active-panel :keep-space)))
|
||||||
|
:picker-on-close (fn []
|
||||||
|
(when platform/ios?
|
||||||
|
(set-active-panel nil)))
|
||||||
|
:send-emoji (fn [{:keys [emoji-id]}]
|
||||||
|
(re-frame/dispatch [::models.reactions/send-emoji-reaction
|
||||||
|
{:message-id (:message-id message)
|
||||||
|
:emoji-id emoji-id}]))
|
||||||
|
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
|
||||||
|
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction
|
||||||
|
{:message-id (:message-id message)
|
||||||
|
:emoji-id emoji-id
|
||||||
|
:emoji-reaction-id emoji-reaction-id}]))
|
||||||
|
:render ->message}])
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
(ns status-im.ui.screens.chat.message.reactions
|
||||||
|
(:require [status-im.ui.screens.chat.message.reactions-picker :as reaction-picker]
|
||||||
|
[status-im.ui.screens.chat.message.reactions-row :as reaction-row]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[quo.react-native :as rn]
|
||||||
|
[quo.react :as react]
|
||||||
|
[quo.animated :as animated]))
|
||||||
|
|
||||||
|
(defn measure-in-window [ref cb]
|
||||||
|
(.measureInWindow ^js ref cb))
|
||||||
|
|
||||||
|
(defn get-picker-position [^js ref cb]
|
||||||
|
(some-> ref
|
||||||
|
react/current-ref
|
||||||
|
(measure-in-window
|
||||||
|
(fn [x y width height]
|
||||||
|
(cb {:top y
|
||||||
|
:left x
|
||||||
|
:width width
|
||||||
|
:height height})))))
|
||||||
|
|
||||||
|
(defn- extract-id [reactions id]
|
||||||
|
(->> reactions
|
||||||
|
(filter (fn [{:keys [emoji-id]}] (= emoji-id id)))
|
||||||
|
first
|
||||||
|
:emoji-reaction-id))
|
||||||
|
|
||||||
|
(defn with-reaction-picker []
|
||||||
|
(let [ref (react/create-ref)
|
||||||
|
animated-state (animated/value 0)
|
||||||
|
spring-animation (animated/with-spring-transition
|
||||||
|
animated-state
|
||||||
|
(:jump animated/springs))
|
||||||
|
animation (animated/with-timing-transition
|
||||||
|
animated-state
|
||||||
|
{:duration reaction-picker/animation-duration
|
||||||
|
:easing (:ease-in-out animated/easings)})
|
||||||
|
visible (reagent/atom false)
|
||||||
|
actions (reagent/atom nil)
|
||||||
|
position (reagent/atom {})]
|
||||||
|
(fn [{:keys [message reactions outgoing outgoing-status render send-emoji retract-emoji picker-on-open picker-on-close]}]
|
||||||
|
(let [own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
|
||||||
|
(if own (conj acc emoji-id) acc))
|
||||||
|
[] reactions)
|
||||||
|
on-emoji-press (fn [emoji-id]
|
||||||
|
(let [active ((set own-reactions) emoji-id)]
|
||||||
|
(if active
|
||||||
|
(retract-emoji {:emoji-id emoji-id
|
||||||
|
:emoji-reaction-id (extract-id reactions emoji-id)})
|
||||||
|
(send-emoji {:emoji-id emoji-id}))))
|
||||||
|
on-close (fn []
|
||||||
|
(animated/set-value animated-state 0)
|
||||||
|
(js/setTimeout
|
||||||
|
(fn []
|
||||||
|
(reset! actions nil)
|
||||||
|
(reset! visible false)
|
||||||
|
(picker-on-close))
|
||||||
|
reaction-picker/animation-duration))
|
||||||
|
on-open (fn [pos]
|
||||||
|
(picker-on-open)
|
||||||
|
(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)))}]]
|
||||||
|
[reaction-row/message-reactions message reactions]]
|
||||||
|
[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
|
||||||
|
:send-emoji (fn [emoji]
|
||||||
|
(on-close)
|
||||||
|
(js/setTimeout #(on-emoji-press emoji)
|
||||||
|
reaction-picker/animation-duration))}
|
||||||
|
[render message {:modal true
|
||||||
|
:close-modal on-close}]]]]))))
|
|
@ -0,0 +1,105 @@
|
||||||
|
(ns status-im.ui.screens.chat.message.reactions-picker
|
||||||
|
(:require [cljs-bean.core :as bean]
|
||||||
|
[status-im.ui.screens.chat.message.styles :as styles]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[quo.react-native :as rn]
|
||||||
|
[quo.react :as react]
|
||||||
|
[quo.animated :as animated]
|
||||||
|
[quo.components.safe-area :as safe-area]
|
||||||
|
[quo.core :as quo]))
|
||||||
|
|
||||||
|
(def tabbar-height 36)
|
||||||
|
(def text-input-height 54)
|
||||||
|
|
||||||
|
(def animation-duration 150)
|
||||||
|
|
||||||
|
(def scale 0.8)
|
||||||
|
(def translate-x 27)
|
||||||
|
(def translate-y -24)
|
||||||
|
|
||||||
|
(defn picker [{:keys [outgoing actions own-reactions on-close send-emoji]}]
|
||||||
|
[rn/view {:style (styles/container-style {:outgoing outgoing})}
|
||||||
|
[rn/view {:style (styles/reactions-picker-row)}
|
||||||
|
(doall
|
||||||
|
(for [[id resource] constants/reactions
|
||||||
|
:let [active (own-reactions id)]]
|
||||||
|
^{:key id}
|
||||||
|
[rn/touchable-opacity {:on-press #(send-emoji id)}
|
||||||
|
[rn/view {:style (styles/reaction-button active)}
|
||||||
|
[rn/image {:source resource
|
||||||
|
:style {:height 32
|
||||||
|
:width 32}}]]]))]
|
||||||
|
(when (seq actions)
|
||||||
|
[rn/view {:style (styles/quick-actions-row)}
|
||||||
|
(for [action actions
|
||||||
|
:let [{:keys [label on-press]} (bean/bean action)]]
|
||||||
|
^{:key label}
|
||||||
|
[rn/touchable-opacity {:on-press (fn []
|
||||||
|
(on-close)
|
||||||
|
(js/setTimeout on-press animation-duration))}
|
||||||
|
[quo/button {:type :secondary}
|
||||||
|
label]])])])
|
||||||
|
|
||||||
|
(def modal
|
||||||
|
(reagent/adapt-react-class
|
||||||
|
(fn [props]
|
||||||
|
(let [{outgoing :outgoing
|
||||||
|
animation :animation
|
||||||
|
spring :spring
|
||||||
|
top :top
|
||||||
|
message-height :messageHeight
|
||||||
|
display-photo :displayPhoto
|
||||||
|
on-close :onClose
|
||||||
|
actions :actions
|
||||||
|
send-emoji :sendEmoji
|
||||||
|
own-reactions :ownReactions
|
||||||
|
children :children}
|
||||||
|
(bean/bean props)
|
||||||
|
{bottom-inset :bottom} (safe-area/use-safe-area)
|
||||||
|
{window-height :height} (rn/use-window-dimensions)
|
||||||
|
|
||||||
|
{picker-height :height
|
||||||
|
on-picker-layout :on-layout} (rn/use-layout)
|
||||||
|
|
||||||
|
full-height (+ message-height picker-height top)
|
||||||
|
max-height (- window-height bottom-inset tabbar-height text-input-height)
|
||||||
|
top-delta (max 0 (- full-height max-height))
|
||||||
|
translation-x (if outgoing
|
||||||
|
translate-x
|
||||||
|
(* -1 translate-x))]
|
||||||
|
(reagent/as-element
|
||||||
|
[:<>
|
||||||
|
[rn/view {:style {:position :absolute
|
||||||
|
:flex 1
|
||||||
|
:top 0
|
||||||
|
:bottom 0
|
||||||
|
:left 0
|
||||||
|
:right 0}}
|
||||||
|
[rn/touchable-without-feedback
|
||||||
|
{:on-press on-close}
|
||||||
|
[animated/view {:style {:flex 1
|
||||||
|
:opacity animation
|
||||||
|
:background-color "rgba(0,0,0,0.5)"}}]]]
|
||||||
|
[animated/view {:pointer-events :box-none
|
||||||
|
:style {:top (- top top-delta)
|
||||||
|
:left 0
|
||||||
|
:right 0
|
||||||
|
:position :absolute
|
||||||
|
:opacity animation
|
||||||
|
:transform [{:translateY (animated/mix animation top-delta 0)}]}}
|
||||||
|
(into [:<>] (react/get-children children))
|
||||||
|
[animated/view {:on-layout on-picker-layout
|
||||||
|
:pointer-events :box-none
|
||||||
|
:style (merge (styles/picker-wrapper-style {:display-photo? display-photo
|
||||||
|
:outgoing outgoing})
|
||||||
|
{:opacity animation
|
||||||
|
:transform [{:translateX (animated/mix spring translation-x 0)}
|
||||||
|
{:translateY (animated/mix spring translate-y 0)}
|
||||||
|
{:scale (animated/mix spring scale 1)}]})}
|
||||||
|
[picker {:outgoing outgoing
|
||||||
|
:actions actions
|
||||||
|
:on-close on-close
|
||||||
|
:own-reactions (into #{} (js->clj own-reactions))
|
||||||
|
:send-emoji send-emoji
|
||||||
|
:animation animation}]]]])))))
|
|
@ -0,0 +1,22 @@
|
||||||
|
(ns status-im.ui.screens.chat.message.reactions-row
|
||||||
|
(:require [status-im.constants :as constants]
|
||||||
|
[status-im.ui.screens.chat.message.styles :as styles]
|
||||||
|
[quo.react-native :as rn]
|
||||||
|
[quo.core :as quo]))
|
||||||
|
|
||||||
|
(defn reaction [{:keys [outgoing]} {:keys [own emoji-id quantity]}]
|
||||||
|
[rn/view {:style (styles/reaction-style {:outgoing outgoing
|
||||||
|
:own own})}
|
||||||
|
[rn/image {:source (get constants/reactions emoji-id)
|
||||||
|
:style {:width 16
|
||||||
|
:height 16
|
||||||
|
:margin-right 4}}]
|
||||||
|
[quo/text {:style (styles/reaction-quantity-style {:own own})}
|
||||||
|
quantity]])
|
||||||
|
|
||||||
|
(defn message-reactions [message reactions]
|
||||||
|
(when (seq reactions)
|
||||||
|
[rn/view {:style (styles/reactions-row message)}
|
||||||
|
(for [emoji-reaction reactions]
|
||||||
|
^{:key (str emoji-reaction)}
|
||||||
|
[reaction message emoji-reaction])]))
|
|
@ -0,0 +1,86 @@
|
||||||
|
(ns status-im.ui.screens.chat.message.styles
|
||||||
|
(:require [quo.design-system.colors :as colors]
|
||||||
|
[status-im.ui.screens.chat.styles.photos :as photos]))
|
||||||
|
|
||||||
|
(defn picker-wrapper-style [{:keys [display-photo? outgoing]}]
|
||||||
|
(merge {:flex-direction :row
|
||||||
|
:flex 1
|
||||||
|
:padding-top 4
|
||||||
|
:padding-right 8}
|
||||||
|
(if outgoing
|
||||||
|
{:justify-content :flex-end}
|
||||||
|
{:justify-content :flex-start})
|
||||||
|
(if display-photo?
|
||||||
|
{:padding-left (+ 16 photos/default-size)}
|
||||||
|
{:padding-left 8})))
|
||||||
|
|
||||||
|
(defn container-style [{:keys [outgoing]}]
|
||||||
|
(merge {:border-top-left-radius 16
|
||||||
|
:border-top-right-radius 16
|
||||||
|
:border-bottom-right-radius 16
|
||||||
|
:border-bottom-left-radius 16
|
||||||
|
:background-color (:ui-background @colors/theme)}
|
||||||
|
(if outgoing
|
||||||
|
{:border-top-right-radius 4}
|
||||||
|
{:border-top-left-radius 4})))
|
||||||
|
|
||||||
|
(defn reactions-picker-row []
|
||||||
|
{:flex-direction :row
|
||||||
|
:padding-vertical 8
|
||||||
|
:padding-horizontal 8})
|
||||||
|
|
||||||
|
(defn quick-actions-row []
|
||||||
|
{:flex-direction :row
|
||||||
|
:justify-content :space-evenly
|
||||||
|
:border-top-width 1
|
||||||
|
:border-top-color (:ui-01 @colors/theme)})
|
||||||
|
|
||||||
|
(defn reaction-style [{:keys [outgoing own]}]
|
||||||
|
(merge {:border-top-left-radius 10
|
||||||
|
:border-top-right-radius 10
|
||||||
|
:border-bottom-right-radius 10
|
||||||
|
:border-bottom-left-radius 10
|
||||||
|
:flex-direction :row
|
||||||
|
:margin-vertical 2
|
||||||
|
:padding-right 8
|
||||||
|
:padding-left 2
|
||||||
|
:padding-vertical 2}
|
||||||
|
(if own
|
||||||
|
{:background-color (:interactive-01 @colors/theme)}
|
||||||
|
{:background-color (:interactive-02 @colors/theme)})
|
||||||
|
(if outgoing
|
||||||
|
{:border-top-right-radius 2
|
||||||
|
:margin-left 4}
|
||||||
|
{:border-top-left-radius 2
|
||||||
|
:margin-right 4})))
|
||||||
|
|
||||||
|
(defn reaction-quantity-style [{:keys [own]}]
|
||||||
|
{:font-size 12
|
||||||
|
:line-height 16
|
||||||
|
:color (if own
|
||||||
|
colors/white
|
||||||
|
(:text-01 @colors/theme))})
|
||||||
|
|
||||||
|
(defn reactions-row [{:keys [outgoing display-photo?]}]
|
||||||
|
(merge {:flex-direction :row
|
||||||
|
:padding-right 8}
|
||||||
|
(if outgoing
|
||||||
|
{:justify-content :flex-end}
|
||||||
|
{:justify-content :flex-start})
|
||||||
|
(if display-photo?
|
||||||
|
{:padding-left (+ 16 photos/default-size)}
|
||||||
|
{:padding-left 8})))
|
||||||
|
|
||||||
|
(defn reaction-button [active]
|
||||||
|
(merge {:width 40
|
||||||
|
:height 40
|
||||||
|
:border-radius 20
|
||||||
|
:justify-content :center
|
||||||
|
:align-items :center
|
||||||
|
:margin-horizontal 1
|
||||||
|
:border-width 1
|
||||||
|
:border-color :transparent}
|
||||||
|
(when active
|
||||||
|
{:background-color (:interactive-02 @colors/theme)
|
||||||
|
;; FIXME: Use broder color here
|
||||||
|
:border-color "rgba(67, 96, 223, 0.2)"})))
|
|
@ -152,57 +152,6 @@
|
||||||
:accessibility-label :delete-transaccent-button
|
:accessibility-label :delete-transaccent-button
|
||||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message chat-id message-id])}]]))
|
:on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message chat-id message-id])}]]))
|
||||||
|
|
||||||
(defn message-long-press [{:keys [content from outgoing] :as message}]
|
|
||||||
(fn []
|
|
||||||
(let [photo @(re-frame/subscribe [:chats/photo-path from])
|
|
||||||
contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
|
||||||
[react/view
|
|
||||||
(when-not outgoing
|
|
||||||
[quo/list-item
|
|
||||||
{:theme :accent
|
|
||||||
:icon [chat-icon/contact-icon-contacts-tab photo]
|
|
||||||
:title contact-name
|
|
||||||
:subtitle (i18n/label :t/view-profile)
|
|
||||||
:accessibility-label :view-chat-details-button
|
|
||||||
:chevron true
|
|
||||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/show-profile from])}])
|
|
||||||
[quo/list-item
|
|
||||||
{:theme :accent
|
|
||||||
:title (i18n/label :t/message-reply)
|
|
||||||
:icon :main-icons/reply
|
|
||||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/reply-to-message message])}]
|
|
||||||
[quo/list-item
|
|
||||||
{:theme :accent
|
|
||||||
:title (i18n/label :t/sharing-copy-to-clipboard)
|
|
||||||
:icon :main-icons/copy
|
|
||||||
:on-press (fn []
|
|
||||||
(re-frame/dispatch [:bottom-sheet/hide])
|
|
||||||
(react/copy-to-clipboard (:text content)))}]
|
|
||||||
[quo/list-item
|
|
||||||
{:theme :accent
|
|
||||||
:title (i18n/label :t/sharing-share)
|
|
||||||
:icon :main-icons/share
|
|
||||||
:on-press (fn []
|
|
||||||
(re-frame/dispatch [:bottom-sheet/hide])
|
|
||||||
;; https://github.com/facebook/react-native/pull/26839
|
|
||||||
(js/setTimeout
|
|
||||||
#(list-selection/open-share {:message (:text content)})
|
|
||||||
250))}]])))
|
|
||||||
|
|
||||||
(defn sticker-long-press [{:keys [from]}]
|
|
||||||
(fn []
|
|
||||||
(let [photo @(re-frame/subscribe [:chats/photo-path from])
|
|
||||||
contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
|
||||||
[react/view
|
|
||||||
[quo/list-item
|
|
||||||
{:theme :accent
|
|
||||||
:icon [chat-icon/contact-icon-contacts-tab photo]
|
|
||||||
:title contact-name
|
|
||||||
:subtitle (i18n/label :t/view-profile)
|
|
||||||
:accessibility-label :view-chat-details-button
|
|
||||||
:chevron true
|
|
||||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/show-profile from])}]])))
|
|
||||||
|
|
||||||
(defn image-long-press [{:keys [content identicon from outgoing] :as message} from-preview?]
|
(defn image-long-press [{:keys [content identicon from outgoing] :as message} from-preview?]
|
||||||
(fn []
|
(fn []
|
||||||
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
||||||
|
|
|
@ -10,11 +10,6 @@
|
||||||
[outgoing]
|
[outgoing]
|
||||||
{:color (if outgoing colors/white-persist colors/text)})
|
{:color (if outgoing colors/white-persist colors/text)})
|
||||||
|
|
||||||
(defn last-message-padding
|
|
||||||
[{:keys [first? typing]}]
|
|
||||||
(when (and first? (not typing))
|
|
||||||
{:padding-bottom 16}))
|
|
||||||
|
|
||||||
(defn system-message-body
|
(defn system-message-body
|
||||||
[_]
|
[_]
|
||||||
{:margin-top 4
|
{:margin-top 4
|
||||||
|
@ -54,12 +49,8 @@
|
||||||
:bottom 9 ; 6 Bubble bottom, 3 message baseline
|
:bottom 9 ; 6 Bubble bottom, 3 message baseline
|
||||||
(if rtl? :left :right) 12})))
|
(if rtl? :left :right) 12})))
|
||||||
|
|
||||||
(defn message-wrapper-base [message]
|
(defn message-wrapper [{:keys [outgoing]}]
|
||||||
(merge {:flex-direction :column}
|
(merge {:flex-direction :column}
|
||||||
(last-message-padding message)))
|
|
||||||
|
|
||||||
(defn message-wrapper [{:keys [outgoing] :as message}]
|
|
||||||
(merge (message-wrapper-base message)
|
|
||||||
(if outgoing
|
(if outgoing
|
||||||
{:margin-left 96}
|
{:margin-left 96}
|
||||||
{:margin-right 52})))
|
{:margin-right 52})))
|
||||||
|
@ -83,8 +74,8 @@
|
||||||
:padding-left 8}))
|
:padding-left 8}))
|
||||||
|
|
||||||
(def message-author-touchable
|
(def message-author-touchable
|
||||||
{:margin-left 12
|
{:margin-left 12
|
||||||
:padding-vertical 2})
|
:flex-direction :row})
|
||||||
|
|
||||||
(defn message-author-userpic [outgoing]
|
(defn message-author-userpic [outgoing]
|
||||||
(merge
|
(merge
|
||||||
|
@ -93,7 +84,7 @@
|
||||||
(if outgoing
|
(if outgoing
|
||||||
{:padding-left 8}
|
{:padding-left 8}
|
||||||
{:padding-horizontal 8
|
{:padding-horizontal 8
|
||||||
:padding-right 8})))
|
:padding-right 8})))
|
||||||
|
|
||||||
(def delivery-text
|
(def delivery-text
|
||||||
{:color colors/gray
|
{:color colors/gray
|
||||||
|
@ -164,13 +155,6 @@
|
||||||
:padding-left 12
|
:padding-left 12
|
||||||
:text-align-vertical :center})
|
:text-align-vertical :center})
|
||||||
|
|
||||||
(def message-author-name-container
|
|
||||||
{:padding-top 6
|
|
||||||
:padding-left 12
|
|
||||||
:padding-right 16
|
|
||||||
:margin-right 12
|
|
||||||
:text-align-vertical :center})
|
|
||||||
|
|
||||||
(defn quoted-message-container [outgoing]
|
(defn quoted-message-container [outgoing]
|
||||||
{:margin-bottom 6
|
{:margin-bottom 6
|
||||||
:padding-bottom 6
|
:padding-bottom 6
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
:border-radius (radius size)})
|
:border-radius (radius size)})
|
||||||
|
|
||||||
(defn photo [size]
|
(defn photo [size]
|
||||||
{:border-radius (radius size)
|
{:border-radius (radius size)
|
||||||
:width size
|
:width size
|
||||||
:height size})
|
:height size
|
||||||
|
:background-color colors/white})
|
||||||
|
|
|
@ -6,26 +6,40 @@
|
||||||
|
|
||||||
(def ^:private reply-symbol "↪ ")
|
(def ^:private reply-symbol "↪ ")
|
||||||
|
|
||||||
(defn format-author [contact-name style]
|
(defn format-author
|
||||||
(let [additional-styles (style false)]
|
([contact-name] (format-author contact-name false))
|
||||||
(if (or (= (aget contact-name 0) "@")
|
([contact-name modal]
|
||||||
;; in case of replies
|
(if (= (aget contact-name 0) "@")
|
||||||
(= (aget contact-name 1) "@"))
|
(let [trimmed-name (subs contact-name 0 81)]
|
||||||
(let [trimmed-name (subs contact-name 0 81)]
|
[react/text {:number-of-lines 2
|
||||||
[react/text {:number-of-lines 2
|
:style {:color (if modal colors/white-persist colors/blue)
|
||||||
:style (merge {:color colors/blue
|
:font-size 13
|
||||||
:font-size 13
|
:line-height 18
|
||||||
:line-height 18
|
:font-weight "500"}}
|
||||||
:font-weight "500"} additional-styles)}
|
(or (stateofus/username trimmed-name) trimmed-name)])
|
||||||
(or (stateofus/username trimmed-name) trimmed-name)])
|
[react/text {:style {:color (if modal colors/white-persist colors/gray)
|
||||||
[react/text {:style (merge {:color colors/gray
|
:font-size 12
|
||||||
:font-size 12
|
:line-height 18
|
||||||
:line-height 18
|
:font-weight "400"}}
|
||||||
:font-weight "400"} additional-styles)}
|
contact-name])))
|
||||||
contact-name])))
|
|
||||||
|
|
||||||
(defn format-reply-author [from username current-public-key style]
|
(defn format-reply-author [from username current-public-key style]
|
||||||
(or (and (= from current-public-key)
|
(let [contact-name (str reply-symbol username)]
|
||||||
[react/text {:style (style true)}
|
(or (and (= from current-public-key)
|
||||||
(str reply-symbol (i18n/label :t/You))])
|
[react/text {:style (style true)}
|
||||||
(format-author (str reply-symbol username) style)))
|
(str reply-symbol (i18n/label :t/You))])
|
||||||
|
(if (or (= (aget contact-name 0) "@")
|
||||||
|
;; in case of replies
|
||||||
|
(= (aget contact-name 1) "@"))
|
||||||
|
(let [trimmed-name (subs contact-name 0 81)]
|
||||||
|
[react/text {:number-of-lines 2
|
||||||
|
:style (merge {:color colors/blue
|
||||||
|
:font-size 13
|
||||||
|
:line-height 18
|
||||||
|
:font-weight "500"})}
|
||||||
|
(or (stateofus/username trimmed-name) trimmed-name)])
|
||||||
|
[react/text {:style (merge {:color colors/gray
|
||||||
|
:font-size 12
|
||||||
|
:line-height 18
|
||||||
|
:font-weight "400"})}
|
||||||
|
contact-name]))))
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[quo.animated :as animated]
|
[quo.animated :as animated]
|
||||||
[quo.react-native :as rn]
|
[quo.react-native :as rn]
|
||||||
[status-im.ui.screens.chat.audio-message.views :as audio-message]
|
[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]
|
[status-im.ui.screens.chat.message.message :as message]
|
||||||
[status-im.ui.screens.chat.stickers.views :as stickers]
|
[status-im.ui.screens.chat.stickers.views :as stickers]
|
||||||
[status-im.ui.screens.chat.styles.main :as style]
|
[status-im.ui.screens.chat.styles.main :as style]
|
||||||
|
@ -136,7 +137,7 @@
|
||||||
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
|
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
|
||||||
|
|
||||||
(defview messages-view
|
(defview messages-view
|
||||||
[{:keys [group-chat chat-id public?] :as chat} bottom-space pan-handler]
|
[{:keys [group-chat chat-id public?] :as chat} bottom-space pan-handler set-active-panel]
|
||||||
(letsubs [messages [:chats/current-chat-messages-stream]
|
(letsubs [messages [:chats/current-chat-messages-stream]
|
||||||
no-messages? [:chats/current-chat-no-messages?]
|
no-messages? [:chats/current-chat-no-messages?]
|
||||||
current-public-key [:multiaccount/public-key]]
|
current-public-key [:multiaccount/public-key]]
|
||||||
|
@ -161,11 +162,13 @@
|
||||||
:incoming-group (and group-chat (not outgoing))
|
:incoming-group (and group-chat (not outgoing))
|
||||||
:group-chat group-chat
|
:group-chat group-chat
|
||||||
:public? public?
|
:public? public?
|
||||||
:current-public-key current-public-key)])))
|
:current-public-key current-public-key)
|
||||||
|
set-active-panel])))
|
||||||
:on-viewable-items-changed on-viewable-items-changed
|
:on-viewable-items-changed on-viewable-items-changed
|
||||||
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
|
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
|
||||||
:on-scroll-to-index-failed #() ;;don't remove this
|
:on-scroll-to-index-failed #() ;;don't remove this
|
||||||
:content-container-style {:padding-top @bottom-space}
|
:content-container-style {:padding-top (+ @bottom-space 16)
|
||||||
|
:padding-bottom 16}
|
||||||
:scrollIndicatorInsets {:top @bottom-space}
|
:scrollIndicatorInsets {:top @bottom-space}
|
||||||
:keyboardDismissMode "interactive"
|
:keyboardDismissMode "interactive"
|
||||||
:keyboard-should-persist-taps :handled})]))
|
:keyboard-should-persist-taps :handled})]))
|
||||||
|
@ -187,12 +190,16 @@
|
||||||
active-panel (reagent/atom nil)
|
active-panel (reagent/atom nil)
|
||||||
position-y (animated/value 0)
|
position-y (animated/value 0)
|
||||||
pan-state (animated/value 0)
|
pan-state (animated/value 0)
|
||||||
|
text-input-ref (quo.react/create-ref)
|
||||||
on-update (partial reset! bottom-space)
|
on-update (partial reset! bottom-space)
|
||||||
pan-responder (accessory/create-pan-responder position-y pan-state)
|
pan-responder (accessory/create-pan-responder position-y pan-state)
|
||||||
set-active-panel (fn [panel]
|
set-active-panel (fn [panel]
|
||||||
(reset! active-panel panel)
|
|
||||||
(rn/configure-next
|
(rn/configure-next
|
||||||
(:ease-opacity-200 rn/custom-animations))
|
(:ease-opacity-200 rn/custom-animations))
|
||||||
|
(when (and (not panel)
|
||||||
|
(= :keep-space @active-panel))
|
||||||
|
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
|
||||||
|
(reset! active-panel panel)
|
||||||
(when panel
|
(when panel
|
||||||
(js/setTimeout #(react/dismiss-keyboard!) 100)))]
|
(js/setTimeout #(react/dismiss-keyboard!) 100)))]
|
||||||
(fn []
|
(fn []
|
||||||
|
@ -204,7 +211,7 @@
|
||||||
[react/view {:style {:flex 1}}
|
[react/view {:style {:flex 1}}
|
||||||
(when-not group-chat
|
(when-not group-chat
|
||||||
[add-contact-bar chat-id])
|
[add-contact-bar chat-id])
|
||||||
[messages-view current-chat bottom-space pan-responder]]]
|
[messages-view current-chat bottom-space pan-responder set-active-panel]]]
|
||||||
(when show-input?
|
(when show-input?
|
||||||
[accessory/view {:y position-y
|
[accessory/view {:y position-y
|
||||||
:pan-state pan-state
|
:pan-state pan-state
|
||||||
|
@ -212,5 +219,6 @@
|
||||||
:on-close #(set-active-panel nil)
|
:on-close #(set-active-panel nil)
|
||||||
:on-update-inset on-update}
|
:on-update-inset on-update}
|
||||||
[components/chat-toolbar {:active-panel @active-panel
|
[components/chat-toolbar {:active-panel @active-panel
|
||||||
:set-active-panel set-active-panel}]
|
:set-active-panel set-active-panel
|
||||||
|
:text-input-ref text-input-ref}]
|
||||||
[bottom-sheet @active-panel]])]))))
|
[bottom-sheet @active-panel]])]))))
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
[status-im.ui.screens.chat.utils :as chat.utils]
|
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||||
[status-im.ui.components.toolbar :as toolbar]
|
[status-im.ui.components.toolbar :as toolbar]
|
||||||
[status-im.ui.screens.chat.message.message :as message]
|
[status-im.ui.screens.chat.message.message :as message]
|
||||||
[status-im.ui.screens.chat.styles.message.message :as message.style]
|
|
||||||
[status-im.ui.screens.chat.photos :as photos]
|
[status-im.ui.screens.chat.photos :as photos]
|
||||||
[status-im.ui.screens.profile.components.views :as profile.components]
|
[status-im.ui.screens.profile.components.views :as profile.components]
|
||||||
[status-im.utils.debounce :as debounce]
|
[status-im.utils.debounce :as debounce]
|
||||||
|
@ -629,7 +628,7 @@
|
||||||
(views/defview my-name []
|
(views/defview my-name []
|
||||||
(views/letsubs [contact-name [:multiaccount/preferred-name]]
|
(views/letsubs [contact-name [:multiaccount/preferred-name]]
|
||||||
(when-not (string/blank? contact-name)
|
(when-not (string/blank? contact-name)
|
||||||
(chat.utils/format-author (str "@" contact-name) message.style/message-author-name-container))))
|
(chat.utils/format-author (str "@" contact-name)))))
|
||||||
|
|
||||||
(views/defview registered [names {:keys [preferred-name] :as account} _ registrations]
|
(views/defview registered [names {:keys [preferred-name] :as account} _ registrations]
|
||||||
[react/view {:style {:flex 1}}
|
[react/view {:style {:flex 1}}
|
||||||
|
@ -674,9 +673,9 @@
|
||||||
[react/view {:padding-left 72}
|
[react/view {:padding-left 72}
|
||||||
[my-name]]
|
[my-name]]
|
||||||
[react/view {:flex-direction :row}
|
[react/view {:flex-direction :row}
|
||||||
[react/view {:padding-left 16 :padding-right 8 :padding-top 4}
|
[react/view {:padding-left 16 :padding-top 4}
|
||||||
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]
|
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]
|
||||||
[message/text-message message]]])]])
|
[message/->message message {:on-long-press identity}]]])]])
|
||||||
|
|
||||||
(views/defview main []
|
(views/defview main []
|
||||||
(views/letsubs [{:keys [names multiaccount show? registrations]} [:ens.main/screen]]
|
(views/letsubs [{:keys [names multiaccount show? registrations]} [:ens.main/screen]]
|
||||||
|
|
|
@ -316,7 +316,7 @@
|
||||||
|
|
||||||
(defn error-item []
|
(defn error-item []
|
||||||
(fn [title show-error]
|
(fn [title show-error]
|
||||||
[gh/touchable-opacity {:on-press #(swap! show-error not)}
|
[gh/touchable-highlight {:on-press #(swap! show-error not)}
|
||||||
[react/view {:style {:align-items :center
|
[react/view {:style {:align-items :center
|
||||||
:flex-direction :row}}
|
:flex-direction :row}}
|
||||||
[react/text {:style {:color colors/red :margin-right 8}}
|
[react/text {:style {:color colors/red :margin-right 8}}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
(ns status-im.utils.profiler)
|
||||||
|
|
||||||
|
(defmacro with-measure
|
||||||
|
[name & body]
|
||||||
|
`(let [start# (js/performance.now)
|
||||||
|
res# (do ~@body)
|
||||||
|
end# (js/performance.now)
|
||||||
|
time# (.toFixed (- end# start#) 2)]
|
||||||
|
(taoensso.timbre/info "[perf|" ~name "] => " time#)
|
||||||
|
res#))
|
|
@ -0,0 +1,39 @@
|
||||||
|
(ns status-im.utils.profiler
|
||||||
|
"Performance profiling for react components."
|
||||||
|
(:require-macros [status-im.utils.profiler])
|
||||||
|
(:require [reagent.core :as reagent]
|
||||||
|
[taoensso.timbre :as log]
|
||||||
|
[oops.core :refer [oget ocall]]
|
||||||
|
[goog.functions :as f]))
|
||||||
|
|
||||||
|
(defonce memo (atom {}))
|
||||||
|
|
||||||
|
(def td (js/require "tdigest"))
|
||||||
|
(def tdigest (oget td "TDigest"))
|
||||||
|
|
||||||
|
(def react (js/require "react"))
|
||||||
|
(def profiler (reagent/adapt-react-class (oget react "Profiler")))
|
||||||
|
|
||||||
|
(defn on-render-factory
|
||||||
|
[label]
|
||||||
|
(let [buf (new tdigest)
|
||||||
|
log (f/debounce
|
||||||
|
(fn [phase buf]
|
||||||
|
(log/info "[profile:" label "(" phase ")]: \n"
|
||||||
|
(ocall buf "summary")))
|
||||||
|
300)]
|
||||||
|
(fn [_ phase adur _ _ _ _]
|
||||||
|
(.push buf adur)
|
||||||
|
(log phase buf))))
|
||||||
|
|
||||||
|
(defn perf [{:keys [label]}]
|
||||||
|
(let [this (reagent/current-component)
|
||||||
|
children (reagent/children this)
|
||||||
|
on-render (if-let [render-fn (get @memo label)]
|
||||||
|
render-fn
|
||||||
|
(do
|
||||||
|
(swap! memo assoc label (on-render-factory label))
|
||||||
|
(get @memo label)))]
|
||||||
|
(into [profiler {:id label
|
||||||
|
:on-render on-render}]
|
||||||
|
children)))
|
|
@ -2,7 +2,7 @@
|
||||||
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
||||||
"owner": "status-im",
|
"owner": "status-im",
|
||||||
"repo": "status-go",
|
"repo": "status-go",
|
||||||
"version": "v0.56.1",
|
"version": "v0.56.5",
|
||||||
"commit-sha1": "4574ab4c22ee6b662a7f87c39d0f714998c567dc",
|
"commit-sha1": "ab01a05cd63df0f749862d16d01a90fdd5f694e8",
|
||||||
"src-sha256": "0jd684lv7x93gxgvvhsv4ihxr5sw2rck2divsxfiql5g2c1v4alg"
|
"src-sha256": "004zjm66gykgz4mlrzsmv797x4pdqjm61dr2dcn1dnbdbdhnglz2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1060,6 +1060,7 @@
|
||||||
"view-etheremon": "View in Etheremon",
|
"view-etheremon": "View in Etheremon",
|
||||||
"view-gitcoin": "View in Gitcoin",
|
"view-gitcoin": "View in Gitcoin",
|
||||||
"view-profile": "View profile",
|
"view-profile": "View profile",
|
||||||
|
"view-details": "View Details",
|
||||||
"view-signing": "View signing phrase",
|
"view-signing": "View signing phrase",
|
||||||
"view-superrare": "View in SuperRare",
|
"view-superrare": "View in SuperRare",
|
||||||
"waiting-for-wifi": "No Wi-fi, message syncing disabled.",
|
"waiting-for-wifi": "No Wi-fi, message syncing disabled.",
|
||||||
|
|
12
yarn.lock
|
@ -2028,6 +2028,11 @@ bindings@^1.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
file-uri-to-path "1.0.0"
|
file-uri-to-path "1.0.0"
|
||||||
|
|
||||||
|
bintrees@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
|
||||||
|
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
|
||||||
|
|
||||||
binwrap@^0.2.2:
|
binwrap@^0.2.2:
|
||||||
version "0.2.2"
|
version "0.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/binwrap/-/binwrap-0.2.2.tgz#7d1ea74b28332f18dfdc75548aef993041ffafc9"
|
resolved "https://registry.yarnpkg.com/binwrap/-/binwrap-0.2.2.tgz#7d1ea74b28332f18dfdc75548aef993041ffafc9"
|
||||||
|
@ -7712,6 +7717,13 @@ tar@^4.4.10:
|
||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
yallist "^3.0.3"
|
yallist "^3.0.3"
|
||||||
|
|
||||||
|
tdigest@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021"
|
||||||
|
integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=
|
||||||
|
dependencies:
|
||||||
|
bintrees "1.0.1"
|
||||||
|
|
||||||
temp@0.8.3:
|
temp@0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
||||||
|
|