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-touch-id": "^4.4.1",
|
||||
"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"
|
||||
},
|
||||
"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)
|
||||
: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]
|
||||
(ocall anim "setValue" val))
|
||||
|
||||
|
|
|
@ -8,12 +8,6 @@
|
|||
[quo.components.controls.styles :as styles]
|
||||
[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]
|
||||
(fn [props]
|
||||
(let [{:keys [value onChange disabled]}
|
||||
|
@ -29,7 +23,7 @@
|
|||
[])
|
||||
transition (react/use-memo
|
||||
(fn []
|
||||
(animated/with-spring-transition state spring-config))
|
||||
(animated/with-spring-transition state (:lazy animated/springs)))
|
||||
[])
|
||||
press-end (fn []
|
||||
(when (and (not disabled) onChange)
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
(not on-long-press))
|
||||
rn/view
|
||||
animated animated/pressable
|
||||
:else gh/touchable-hightlight)]
|
||||
:else gh/touchable-highlight)]
|
||||
[rn/view {:background-color (if (and (= accessory :radio) active)
|
||||
active-background
|
||||
passive-background)}
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
:on-press #(reset! visible true)}
|
||||
|
||||
:else after)
|
||||
secure (and secure-text-entry
|
||||
secure (and (true? secure-text-entry)
|
||||
(or platform/android? (not @visible)))
|
||||
on-cancel (fn []
|
||||
(when on-cancel
|
||||
|
|
|
@ -32,10 +32,10 @@
|
|||
(def touchable-without-feedback
|
||||
(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]
|
||||
(into [touchable-hightlight-class (merge {:underlay-color (:interactive-02 @colors/theme)}
|
||||
(defn touchable-highlight [props & children]
|
||||
(into [touchable-highlight-class (merge {:underlay-color (:interactive-02 @colors/theme)}
|
||||
props)]
|
||||
children))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
[status-im.transport.filters.core :as filters]
|
||||
[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]))
|
||||
|
@ -133,6 +134,7 @@
|
|||
#(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)))))))
|
||||
|
||||
(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
|
||||
(:require [status-im.ethereum.core :as ethereum]
|
||||
[status-im.react-native.resources :as resources]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.config :as config]))
|
||||
|
||||
|
@ -16,6 +17,20 @@
|
|||
(def content-type-image 7)
|
||||
(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-public-group 2)
|
||||
(def message-type-private-group 3)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
(.addListener ^js react/keyboard
|
||||
(if platform/ios?
|
||||
"keyboardWillHide"
|
||||
"keyboardWDidHide")
|
||||
"keyboardDidHide")
|
||||
(fn [_]
|
||||
(re-frame/dispatch-sync [:set :keyboard-height 0])))
|
||||
(.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_blockContact" {}
|
||||
"shhext_updateMailservers" {}
|
||||
"shhext_sendEmojiReaction" {}
|
||||
"shhext_sendEmojiReactionRetraction" {}
|
||||
"shhext_emojiReactionsByChatID" {}
|
||||
;;TODO not used anywhere?
|
||||
"shhext_deleteChat" {}
|
||||
"shhext_saveContact" {}
|
||||
|
@ -125,6 +128,9 @@
|
|||
"wakuext_prepareContent" {}
|
||||
"wakuext_blockContact" {}
|
||||
"wakuext_updateMailservers" {}
|
||||
"wakuext_sendEmojiReaction" {}
|
||||
"wakuext_sendEmojiReactionRetraction" {}
|
||||
"wakuext_emojiReactionsByChatID" {}
|
||||
;;TODO not used anywhere?
|
||||
"wakuext_deleteChat" {}
|
||||
"wakuext_saveContact" {}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
[status-im.chat.models.input :as chat.input]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[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.contact.block :as contact.block]
|
||||
[status-im.contact.core :as contact]
|
||||
[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.ethereum.subscriptions :as ethereum.subscriptions]
|
||||
[status-im.fleet.core :as fleet]
|
||||
[status-im.group-chats.core :as group-chats]
|
||||
|
@ -794,12 +796,14 @@
|
|||
(fn [_ [_ 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)
|
||||
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)
|
||||
chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)]
|
||||
(apply fx/merge cofx (concat chat-fxs message-fxs))))
|
||||
(apply fx/merge cofx (concat chat-fxs message-fxs emoji-react-fxs))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:transport/message-sent
|
||||
|
@ -811,6 +815,16 @@
|
|||
(conj set-hash-fxs
|
||||
(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
|
||||
:transport.callback/node-info-fetched
|
||||
(fn [cofx [_ node-info]]
|
||||
|
|
|
@ -51,3 +51,11 @@
|
|||
(get @loaded-images k)
|
||||
(get (swap! loaded-images assoc 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.model :as multiaccounts.model]
|
||||
[status-im.multiaccounts.recover.core :as recover]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[status-im.pairing.core :as pairing]
|
||||
[status-im.signing.gas :as signing.gas]
|
||||
#_[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 ::messages :messages)
|
||||
(reg-root-key-sub ::reactions :reactions)
|
||||
(reg-root-key-sub ::message-lists :message-lists)
|
||||
(reg-root-key-sub ::pagination-info :pagination-info)
|
||||
|
||||
|
@ -637,6 +639,16 @@
|
|||
(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 chat-id] [_ message-id]]
|
||||
(models.reactions/message-reactions
|
||||
current-public-key
|
||||
(get-in reactions [chat-id message-id]))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/messages-gaps
|
||||
:<- [:mailserver/gaps]
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
status-im.transport.message.core
|
||||
(:require [status-im.chat.models.message :as models.message]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[status-im.contact.core :as models.contact]
|
||||
[status-im.pairing.core :as models.pairing]
|
||||
[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.utils.handlers :as handlers]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.utils.types :as types]))
|
||||
|
||||
|
@ -20,11 +21,17 @@
|
|||
(fx/defn handle-message [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)
|
||||
^js contacts (.-contacts response-js)
|
||||
^js installations (.-installations response-js)
|
||||
^js messages (.-messages response-js)]
|
||||
^js messages (.-messages response-js)
|
||||
^js emoji-reactions (.-emojiReactions response-js)]
|
||||
(cond
|
||||
(seq installations)
|
||||
(let [installations-clj (types/js->clj installations)]
|
||||
|
@ -54,12 +61,14 @@
|
|||
(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))))))))
|
||||
(handle-message (-> message (types/js->clj) (data-store.messages/<-rpc)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::process
|
||||
(fn [cofx [_ response-js]]
|
||||
(process-response cofx response-js)))
|
||||
(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)))))))
|
||||
|
||||
(fx/defn remove-hash
|
||||
[{:keys [db] :as cofx} envelope-hash]
|
||||
|
|
|
@ -31,3 +31,21 @@
|
|||
:on-success
|
||||
#(re-frame/dispatch [:transport/message-sent % 1])
|
||||
: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}])]]]])
|
||||
|
||||
(defn chat-toolbar []
|
||||
(let [text-input-ref (react/create-ref)
|
||||
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)
|
||||
(let [previous-layout (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?])
|
||||
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
||||
mainnet? @(re-frame/subscribe [:mainnet?])
|
||||
|
@ -151,6 +146,10 @@
|
|||
public? @(re-frame/subscribe [:current-chat/public?])
|
||||
reply @(re-frame/subscribe [:chats/reply-message])
|
||||
sending-image @(re-frame/subscribe [:chats/sending-image])
|
||||
input-focus (fn []
|
||||
(some-> ^js (react/current-ref text-input-ref) .focus))
|
||||
clear-input (fn []
|
||||
(some-> ^js (react/current-ref text-input-ref) .clear))
|
||||
empty-text (string/blank? (string/trim (or input-text "")))
|
||||
show-send (and (or (not empty-text)
|
||||
sending-image)
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.slider :as slider]))
|
||||
|
||||
(defn message-press-handlers [_]
|
||||
;;TBI save audio file?
|
||||
)
|
||||
|
||||
(defonce player-ref (atom nil))
|
||||
(defonce current-player-message-id (atom nil))
|
||||
(defonce current-active-state-ref-ref (atom nil))
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[quo.platform :as platform]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.react :as react]
|
||||
[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.photos :as photos]
|
||||
[status-im.ui.screens.chat.sheets :as sheets]
|
||||
|
@ -13,6 +15,7 @@
|
|||
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||
[status-im.utils.contenthash :as contenthash]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.ui.screens.chat.message.reactions :as reactions]
|
||||
[reagent.core :as reagent])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
|
@ -29,13 +32,6 @@
|
|||
outgoing
|
||||
(:rtl? content))} timestamp-str]))
|
||||
|
||||
(defn message-bubble-wrapper
|
||||
[message message-content appender]
|
||||
[react/view
|
||||
(style/message-view message)
|
||||
message-content
|
||||
appender])
|
||||
|
||||
(defview quoted-message
|
||||
[_ {:keys [from text image]} outgoing current-public-key public?]
|
||||
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
||||
|
@ -106,14 +102,6 @@
|
|||
|
||||
(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
|
||||
{:keys [type ^js literal children]}]
|
||||
(case type
|
||||
|
@ -153,51 +141,15 @@
|
|||
;; Append timestamp to new block
|
||||
(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
|
||||
[{:keys [outgoing content-type content] :as message}]
|
||||
[message-bubble-wrapper message
|
||||
[react/view (style/message-view message)
|
||||
[react/text
|
||||
{:style {:color (if outgoing colors/white-persist colors/black)}}
|
||||
(if (seq (:text content))
|
||||
(:text content)
|
||||
(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
|
||||
[chat-id message-id]
|
||||
[react/touchable-highlight
|
||||
|
@ -220,43 +172,39 @@
|
|||
(= outgoing-status :not-sent))
|
||||
[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]]
|
||||
(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
|
||||
"Author, userpic and delivery wrapper"
|
||||
[{:keys [alias first-in-group? display-photo? identicon display-username?
|
||||
from outgoing]
|
||||
:as message} content]
|
||||
:as message} content {:keys [modal close-modal]}]
|
||||
[react/view {:style (style/message-wrapper message)
|
||||
:pointer-events :box-none
|
||||
:accessibility-label :chat-item}
|
||||
[react/view (style/message-body message)
|
||||
[react/view {:style (style/message-body message)
|
||||
:pointer-events :box-none}
|
||||
(when display-photo?
|
||||
; userpic
|
||||
[react/view (style/message-author-userpic outgoing)
|
||||
(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]])])
|
||||
; username
|
||||
[react/view (style/message-author-wrapper outgoing display-photo?)
|
||||
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
||||
(when display-username?
|
||||
[react/touchable-opacity {:style style/message-author-touchable
|
||||
:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
|
||||
[message-author-name from alias]])
|
||||
:on-press #(do (when modal (close-modal))
|
||||
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||
[message-author-name from alias modal]])
|
||||
;;MESSAGE CONTENT
|
||||
content]]
|
||||
[react/view
|
||||
content]]]
|
||||
; delivery status
|
||||
[react/view (style/delivery-status outgoing)
|
||||
[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]}]
|
||||
(let [dimensions (reagent/atom [260 260])
|
||||
uri (:image content)]
|
||||
|
@ -271,57 +219,146 @@
|
|||
:resize-mode :contain
|
||||
:source {:uri uri}}]])))
|
||||
|
||||
(defn image-message-press-handlers [{:keys [content] :as message}]
|
||||
{: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}])})
|
||||
(defmulti ->message :content-type)
|
||||
|
||||
(defn sticker-message-press-handlers [{:keys [content] :as message}]
|
||||
(defmethod ->message constants/content-type-command
|
||||
[message]
|
||||
[message.command/command-content message-content-wrapper message])
|
||||
|
||||
(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}]
|
||||
[react/view {:accessibility-label :chat-item}
|
||||
[react/view (style/system-message-body message)
|
||||
[react/view (style/message-view message)
|
||||
[react/view
|
||||
[render-parsed-text message (:parsed-text content)]]]]])
|
||||
|
||||
(defmethod ->message constants/content-type-text
|
||||
[{:keys [content outgoing current-public-key public?] :as message} {:keys [on-long-press modal]
|
||||
:as reaction-picker}]
|
||||
[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)
|
||||
(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]]]
|
||||
reaction-picker])
|
||||
|
||||
(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 #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (sheets/sticker-long-press message)
|
||||
:height 64}])}))
|
||||
|
||||
(defn message-content-audio
|
||||
[message]
|
||||
[react/touchable-highlight (message.audio/message-press-handlers message)
|
||||
[message-bubble-wrapper message
|
||||
[message.audio/message-content message [message-timestamp message false]]]])
|
||||
|
||||
(defn chat-message [{:keys [public? content content-type] :as message}]
|
||||
(if (= content-type constants/content-type-command)
|
||||
[message.command/command-content message-content-wrapper message]
|
||||
(if (= content-type constants/content-type-system-text)
|
||||
[system-message-content-wrapper message [system-text-message message]]
|
||||
[message-content-wrapper
|
||||
message
|
||||
(if (= content-type constants/content-type-text)
|
||||
;; text message
|
||||
[text-message message]
|
||||
(if (= content-type constants/content-type-status)
|
||||
[message-content-status message]
|
||||
(if (= content-type constants/content-type-emoji)
|
||||
[emoji-message message]
|
||||
(if (= content-type constants/content-type-sticker)
|
||||
[react/touchable-highlight (sticker-message-press-handlers message)
|
||||
: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))}}]]
|
||||
(if (and (= content-type constants/content-type-image)
|
||||
;; Disabling images for public-chats
|
||||
(not public?))
|
||||
[react/touchable-highlight (image-message-press-handlers message)
|
||||
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]]
|
||||
(if (and (= content-type constants/content-type-audio)
|
||||
;; Disabling audio for public-chats
|
||||
(not public?))
|
||||
[message-content-audio message]
|
||||
[unknown-content-type 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
|
||||
: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?]
|
||||
(fn []
|
||||
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
[outgoing]
|
||||
{: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
|
||||
[_]
|
||||
{:margin-top 4
|
||||
|
@ -54,12 +49,8 @@
|
|||
:bottom 9 ; 6 Bubble bottom, 3 message baseline
|
||||
(if rtl? :left :right) 12})))
|
||||
|
||||
(defn message-wrapper-base [message]
|
||||
(defn message-wrapper [{:keys [outgoing]}]
|
||||
(merge {:flex-direction :column}
|
||||
(last-message-padding message)))
|
||||
|
||||
(defn message-wrapper [{:keys [outgoing] :as message}]
|
||||
(merge (message-wrapper-base message)
|
||||
(if outgoing
|
||||
{:margin-left 96}
|
||||
{:margin-right 52})))
|
||||
|
@ -84,7 +75,7 @@
|
|||
|
||||
(def message-author-touchable
|
||||
{:margin-left 12
|
||||
:padding-vertical 2})
|
||||
:flex-direction :row})
|
||||
|
||||
(defn message-author-userpic [outgoing]
|
||||
(merge
|
||||
|
@ -164,13 +155,6 @@
|
|||
:padding-left 12
|
||||
: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]
|
||||
{:margin-bottom 6
|
||||
:padding-bottom 6
|
||||
|
|
|
@ -20,4 +20,5 @@
|
|||
(defn photo [size]
|
||||
{:border-radius (radius size)
|
||||
:width size
|
||||
:height size})
|
||||
:height size
|
||||
:background-color colors/white})
|
||||
|
|
|
@ -6,8 +6,28 @@
|
|||
|
||||
(def ^:private reply-symbol "↪ ")
|
||||
|
||||
(defn format-author [contact-name style]
|
||||
(let [additional-styles (style false)]
|
||||
(defn format-author
|
||||
([contact-name] (format-author contact-name false))
|
||||
([contact-name modal]
|
||||
(if (= (aget contact-name 0) "@")
|
||||
(let [trimmed-name (subs contact-name 0 81)]
|
||||
[react/text {:number-of-lines 2
|
||||
:style {:color (if modal colors/white-persist colors/blue)
|
||||
:font-size 13
|
||||
:line-height 18
|
||||
:font-weight "500"}}
|
||||
(or (stateofus/username trimmed-name) trimmed-name)])
|
||||
[react/text {:style {:color (if modal colors/white-persist colors/gray)
|
||||
:font-size 12
|
||||
:line-height 18
|
||||
:font-weight "400"}}
|
||||
contact-name])))
|
||||
|
||||
(defn format-reply-author [from username current-public-key style]
|
||||
(let [contact-name (str reply-symbol username)]
|
||||
(or (and (= from current-public-key)
|
||||
[react/text {:style (style true)}
|
||||
(str reply-symbol (i18n/label :t/You))])
|
||||
(if (or (= (aget contact-name 0) "@")
|
||||
;; in case of replies
|
||||
(= (aget contact-name 1) "@"))
|
||||
|
@ -16,16 +36,10 @@
|
|||
:style (merge {:color colors/blue
|
||||
:font-size 13
|
||||
:line-height 18
|
||||
:font-weight "500"} additional-styles)}
|
||||
: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"} additional-styles)}
|
||||
contact-name])))
|
||||
|
||||
(defn format-reply-author [from username current-public-key style]
|
||||
(or (and (= from current-public-key)
|
||||
[react/text {:style (style true)}
|
||||
(str reply-symbol (i18n/label :t/You))])
|
||||
(format-author (str reply-symbol username) style)))
|
||||
:font-weight "400"})}
|
||||
contact-name]))))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[quo.animated :as animated]
|
||||
[quo.react-native :as rn]
|
||||
[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.stickers.views :as stickers]
|
||||
[status-im.ui.screens.chat.styles.main :as style]
|
||||
|
@ -136,7 +137,7 @@
|
|||
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
|
||||
|
||||
(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]
|
||||
no-messages? [:chats/current-chat-no-messages?]
|
||||
current-public-key [:multiaccount/public-key]]
|
||||
|
@ -161,11 +162,13 @@
|
|||
:incoming-group (and group-chat (not outgoing))
|
||||
:group-chat group-chat
|
||||
: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-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}
|
||||
:content-container-style {:padding-top (+ @bottom-space 16)
|
||||
:padding-bottom 16}
|
||||
:scrollIndicatorInsets {:top @bottom-space}
|
||||
:keyboardDismissMode "interactive"
|
||||
:keyboard-should-persist-taps :handled})]))
|
||||
|
@ -187,12 +190,16 @@
|
|||
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! bottom-space)
|
||||
pan-responder (accessory/create-pan-responder position-y pan-state)
|
||||
set-active-panel (fn [panel]
|
||||
(reset! active-panel panel)
|
||||
(rn/configure-next
|
||||
(: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
|
||||
(js/setTimeout #(react/dismiss-keyboard!) 100)))]
|
||||
(fn []
|
||||
|
@ -204,7 +211,7 @@
|
|||
[react/view {:style {:flex 1}}
|
||||
(when-not group-chat
|
||||
[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?
|
||||
[accessory/view {:y position-y
|
||||
:pan-state pan-state
|
||||
|
@ -212,5 +219,6 @@
|
|||
:on-close #(set-active-panel nil)
|
||||
:on-update-inset on-update}
|
||||
[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]])]))))
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[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.profile.components.views :as profile.components]
|
||||
[status-im.utils.debounce :as debounce]
|
||||
|
@ -629,7 +628,7 @@
|
|||
(views/defview my-name []
|
||||
(views/letsubs [contact-name [:multiaccount/preferred-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]
|
||||
[react/view {:style {:flex 1}}
|
||||
|
@ -674,9 +673,9 @@
|
|||
[react/view {:padding-left 72}
|
||||
[my-name]]
|
||||
[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}]]
|
||||
[message/text-message message]]])]])
|
||||
[message/->message message {:on-long-press identity}]]])]])
|
||||
|
||||
(views/defview main []
|
||||
(views/letsubs [{:keys [names multiaccount show? registrations]} [:ens.main/screen]]
|
||||
|
|
|
@ -316,7 +316,7 @@
|
|||
|
||||
(defn error-item []
|
||||
(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
|
||||
:flex-direction :row}}
|
||||
[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",
|
||||
"owner": "status-im",
|
||||
"repo": "status-go",
|
||||
"version": "v0.56.1",
|
||||
"commit-sha1": "4574ab4c22ee6b662a7f87c39d0f714998c567dc",
|
||||
"src-sha256": "0jd684lv7x93gxgvvhsv4ihxr5sw2rck2divsxfiql5g2c1v4alg"
|
||||
"version": "v0.56.5",
|
||||
"commit-sha1": "ab01a05cd63df0f749862d16d01a90fdd5f694e8",
|
||||
"src-sha256": "004zjm66gykgz4mlrzsmv797x4pdqjm61dr2dcn1dnbdbdhnglz2"
|
||||
}
|
||||
|
|
|
@ -1060,6 +1060,7 @@
|
|||
"view-etheremon": "View in Etheremon",
|
||||
"view-gitcoin": "View in Gitcoin",
|
||||
"view-profile": "View profile",
|
||||
"view-details": "View Details",
|
||||
"view-signing": "View signing phrase",
|
||||
"view-superrare": "View in SuperRare",
|
||||
"waiting-for-wifi": "No Wi-fi, message syncing disabled.",
|
||||
|
|
12
yarn.lock
|
@ -2028,6 +2028,11 @@ bindings@^1.5.0:
|
|||
dependencies:
|
||||
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:
|
||||
version "0.2.2"
|
||||
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"
|
||||
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:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
||||
|
|