From 7b46c445a7f5a6d884d19f61d9de7c01d4aaeed3 Mon Sep 17 00:00:00 2001 From: andrey Date: Mon, 9 Nov 2020 12:53:05 +0100 Subject: [PATCH] [#11383] Status Updates : Add reactions and copy to clipboard Signed-off-by: andrey --- src/status_im/chat/models/reactions.cljs | 50 ++++---- src/status_im/subs.cljs | 4 +- .../ui/screens/chat/message/reactions.cljs | 6 +- .../chat/message/reactions_picker.cljs | 15 ++- .../screens/chat/message/reactions_row.cljs | 4 +- .../ui/screens/chat/message/styles.cljs | 26 ++-- src/status_im/ui/screens/status/views.cljs | 121 ++++++++++++------ status-go-version.json | 6 +- 8 files changed, 145 insertions(+), 87 deletions(-) diff --git a/src/status_im/chat/models/reactions.cljs b/src/status_im/chat/models/reactions.cljs index 677126e415..eff4550b2f 100644 --- a/src/status_im/chat/models/reactions.cljs +++ b/src/status_im/chat/models/reactions.cljs @@ -4,22 +4,28 @@ [status-im.utils.fx :as fx] [taoensso.timbre :as log] [status-im.transport.message.protocol :as message.protocol] - [status-im.data-store.reactions :as data-store.reactions])) + [status-im.data-store.reactions :as data-store.reactions] + [status-im.chat.models :as chat])) -(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 update-reaction [acc retracted chat-id message-id emoji-id emoji-reaction-id reaction] + ;; NOTE(Ferossgp): For a better performance, better to not keep in db all retracted reactions + ;; 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))) + +(defn process-reactions [chats] + (fn [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}] + (cond-> (update-reaction acc retracted chat-id message-id emoji-id emoji-reaction-id reaction) + (get-in chats [chat-id :profile-public-key]) + (update-reaction retracted chat/timeline-chat-id message-id emoji-id emoji-reaction-id reaction))) + reactions + new-reactions))) (defn- earlier-than-deleted-at? [{:keys [db]} {:keys [chat-id clock-value]}] @@ -30,7 +36,7 @@ (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)})) + {:db (update db :reactions (process-reactions (:chats db)) reactions)})) (fx/defn load-more-reactions [{:keys [db] :as cofx} cursor] @@ -55,7 +61,7 @@ (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)}))) + {:db (update db :reactions (process-reactions (:chats db)) reactions-w-chat-id)}))) ;; Send reactions @@ -63,20 +69,20 @@ (fx/defn send-emoji-reaction {:events [::send-emoji-reaction]} - [{{:keys [current-chat-id] :as db} :db :as cofx} reaction] + [{{:keys [current-chat-id]} :db :as cofx} reaction] (message.protocol/send-reaction cofx - (assoc reaction :chat-id current-chat-id))) + (update reaction :chat-id #(or % 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] + [{{:keys [current-chat-id]} :db :as cofx} reaction] (message.protocol/send-retract-reaction cofx - (assoc reaction :chat-id current-chat-id))) + (update reaction :chat-id #(or % current-chat-id)))) (fx/defn receive-one {:events [::receive-one]} [{:keys [db]} reaction] - {:db (update db :reactions process-reactions [reaction])}) + {:db (update db :reactions (process-reactions (:chats db)) [reaction])}) (defn message-reactions [current-public-key reactions] (reduce diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 31965a98ac..4c91fe4192 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -715,10 +715,10 @@ :<- [:multiaccount/public-key] :<- [::reactions] :<- [:chats/current-chat-id] - (fn [[current-public-key reactions chat-id] [_ message-id]] + (fn [[current-public-key reactions current-chat-id] [_ message-id chat-id]] (models.reactions/message-reactions current-public-key - (get-in reactions [chat-id message-id])))) + (get-in reactions [(or chat-id current-chat-id) message-id])))) (re-frame/reg-sub :chats/messages-gaps diff --git a/src/status_im/ui/screens/chat/message/reactions.cljs b/src/status_im/ui/screens/chat/message/reactions.cljs index de208d28ee..6ea4176665 100644 --- a/src/status_im/ui/screens/chat/message/reactions.cljs +++ b/src/status_im/ui/screens/chat/message/reactions.cljs @@ -38,7 +38,8 @@ 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]}] + (fn [{:keys [message reactions outgoing outgoing-status render send-emoji retract-emoji picker-on-open + picker-on-close timeline]}] (let [own-reactions (reduce (fn [acc {:keys [emoji-id own]}] (if own (conj acc emoji-id) acc)) [] reactions) @@ -70,7 +71,7 @@ (and outgoing (= outgoing-status :sent))) (reset! actions act) (get-picker-position ref on-open)))}]] - [reaction-row/message-reactions message reactions]] + [reaction-row/message-reactions message reactions timeline]] [rn/modal {:visible @visible :on-request-close on-close :on-show (fn [] @@ -86,6 +87,7 @@ :on-close on-close :actions @actions :own-reactions own-reactions + :timeline timeline :send-emoji (fn [emoji] (on-close) (js/setTimeout #(on-emoji-press emoji) diff --git a/src/status_im/ui/screens/chat/message/reactions_picker.cljs b/src/status_im/ui/screens/chat/message/reactions_picker.cljs index 9fa27060e3..81d76f82b8 100644 --- a/src/status_im/ui/screens/chat/message/reactions_picker.cljs +++ b/src/status_im/ui/screens/chat/message/reactions_picker.cljs @@ -18,8 +18,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})} +(defn picker [{:keys [outgoing actions own-reactions on-close send-emoji timeline]}] + [rn/view {:style (styles/container-style {:outgoing outgoing :timeline timeline})} [rn/view {:style (styles/reactions-picker-row)} (doall (for [[id resource] constants/reactions @@ -55,7 +55,8 @@ actions :actions send-emoji :sendEmoji own-reactions :ownReactions - children :children} + children :children + timeline :timeline} (bean/bean props) {bottom-inset :bottom} (safe-area/use-safe-area) {window-height :height} (rn/use-window-dimensions) @@ -66,7 +67,7 @@ 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 + translation-x (if (and outgoing (not timeline)) translate-x (* -1 translate-x))] (reagent/as-element @@ -93,12 +94,14 @@ [animated/view {:on-layout on-picker-layout :pointer-events :box-none :style (merge (styles/picker-wrapper-style {:display-photo? display-photo - :outgoing outgoing}) + :timeline timeline + :outgoing (and outgoing (not timeline))}) {: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 + [picker {:outgoing (and outgoing (not timeline)) + :timeline timeline :actions actions :on-close on-close :own-reactions (into #{} (js->clj own-reactions)) diff --git a/src/status_im/ui/screens/chat/message/reactions_row.cljs b/src/status_im/ui/screens/chat/message/reactions_row.cljs index ee3a79371f..27f9297320 100644 --- a/src/status_im/ui/screens/chat/message/reactions_row.cljs +++ b/src/status_im/ui/screens/chat/message/reactions_row.cljs @@ -15,9 +15,9 @@ :style (styles/reaction-quantity-style {:own own})} quantity]]) -(defn message-reactions [message reactions] +(defn message-reactions [message reactions timeline] (when (seq reactions) - [rn/view {:style (styles/reactions-row message)} + [rn/view {:style (styles/reactions-row message timeline)} (for [emoji-reaction reactions] ^{:key (str emoji-reaction)} [reaction message emoji-reaction])])) diff --git a/src/status_im/ui/screens/chat/message/styles.cljs b/src/status_im/ui/screens/chat/message/styles.cljs index 74ea064ed7..48d7f70034 100644 --- a/src/status_im/ui/screens/chat/message/styles.cljs +++ b/src/status_im/ui/screens/chat/message/styles.cljs @@ -3,7 +3,7 @@ [status-im.ui.screens.chat.styles.photos :as photos] [status-im.ui.components.colors :as components.colors])) -(defn picker-wrapper-style [{:keys [display-photo? outgoing]}] +(defn picker-wrapper-style [{:keys [display-photo? outgoing timeline]}] (merge {:flex-direction :row :flex 1 :padding-top 4 @@ -11,19 +11,23 @@ (if outgoing {:justify-content :flex-end} {:justify-content :flex-start}) - (if display-photo? - {:padding-left (+ 16 photos/default-size)} - {:padding-left 8}))) + (when-not timeline + (if display-photo? + {:padding-left (+ 16 photos/default-size)} + {:padding-left 8})))) -(defn container-style [{:keys [outgoing]}] +(defn container-style [{:keys [outgoing timeline]}] (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}))) + (if timeline + {:border-top-left-radius 16 + :border-top-right-radius 4} + (if outgoing + {:border-top-right-radius 4} + {:border-top-left-radius 4})))) (defn reactions-picker-row [] {:flex-direction :row @@ -62,13 +66,13 @@ colors/white (:text-01 @colors/theme))}) -(defn reactions-row [{:keys [outgoing display-photo?]}] +(defn reactions-row [{:keys [outgoing display-photo?]} timeline] (merge {:flex-direction :row :padding-right 8} - (if outgoing + (if (and outgoing (not timeline)) {:justify-content :flex-end} {:justify-content :flex-start}) - (if display-photo? + (if (or display-photo? timeline) {:padding-left (+ 16 photos/default-size)} {:padding-left 8}))) diff --git a/src/status_im/ui/screens/status/views.cljs b/src/status_im/ui/screens/status/views.cljs index af29dcf21c..7c62c18e03 100644 --- a/src/status_im/ui/screens/status/views.cljs +++ b/src/status_im/ui/screens/status/views.cljs @@ -18,7 +18,11 @@ [status-im.ui.components.tabs :as tabs] [status-im.utils.contenthash :as contenthash] [status-im.multiaccounts.core :as multiaccounts] - [status-im.ui.screens.chat.message.link-preview :as link-preview])) + [status-im.ui.screens.chat.message.reactions :as reactions] + [status-im.chat.models.reactions :as models.reactions] + [status-im.ui.screens.chat.components.reply :as components.reply] + [status-im.ui.screens.chat.message.link-preview :as link-preview] + [status-im.chat.models :as chat])) (defonce messages-list-ref (atom nil)) (def image-max-dimension 260) @@ -54,9 +58,17 @@ :justify-content :center} [icons/icon :main-icons/close-circle {:color colors/white-persist}]]])]))) +(defn on-long-press-fn [on-long-press content image] + (on-long-press + (when-not image + [{:on-press #(react/copy-to-clipboard + (components.reply/get-quoted-text-with-mentions + (get content :parsed-text))) + :label (i18n/label :t/sharing-copy-to-clipboard)}]))) + (defn image-message [] (let [visible (reagent/atom false)] - (fn [{:keys [content] :as message}] + (fn [{:keys [content] :as message} on-long-press] [:<> [preview/preview-image {:message (assoc message :cant-be-replied true) :visible @visible @@ -65,49 +77,80 @@ (reagent/flush))}] [react/touchable-highlight {:on-press (fn [_] (reset! visible true) - (react/dismiss-keyboard!))} + (react/dismiss-keyboard!)) + :on-long-press #(on-long-press-fn on-long-press content true)} [message-content-image (:image content) false]]]))) -(defn message-item [{:keys [content-type content from last-in-group? timestamp identicon outgoing] :as message} timeline? account] - [react/view (when last-in-group? - {:padding-bottom 8 - :margin-bottom 8 - :border-bottom-width 1 - :border-bottom-color colors/gray-lighter}) - [react/view {:padding-vertical 8 - :flex-direction :row - :background-color (when (and timeline? outgoing) colors/blue-light) - :padding-horizontal 16} - [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from])} - [react/view {:padding-top 2 :padding-right 8} - (if outgoing - [photos/member-identicon (multiaccounts/displayed-photo account)] - [photos/member-identicon identicon])]] - [react/view {:flex 1} - [react/view {:flex-direction :row - :justify-content :space-between} - [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from])} +(defn message-item [timeline? account] + (fn [{:keys [content-type content from timestamp identicon outgoing] :as message} + {:keys [modal on-long-press close-modal]}] + [react/view (merge {:padding-vertical 8 + :flex-direction :row + :background-color (if (and timeline? outgoing) colors/blue-light colors/white) + :padding-horizontal 16} + (when modal + {:border-radius 16})) + [react/touchable-highlight + {:on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + [react/view {:padding-top 2 :padding-right 8} (if outgoing - [message/message-my-name {:profile? true :you? false}] - [message/message-author-name from {:profile? true}])] - [react/text {:style {:font-size 10 :color colors/gray}} - (datetime/time-ago (datetime/to-date timestamp))]] - (if (= content-type constants/content-type-image) - [image-message message] - [react/view - [message/render-parsed-text (assoc message :outgoing false) (:parsed-text content)] - [link-preview/link-preview-wrapper (:links content) outgoing]])]]]) + [photos/member-identicon (multiaccounts/displayed-photo account)] + [photos/member-identicon identicon])]] + [react/view {:flex 1} + [react/view {:flex-direction :row + :justify-content :space-between} + [react/touchable-highlight + {:on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + (if outgoing + [message/message-my-name {:profile? true :you? false}] + [message/message-author-name from {:profile? true}])] + [react/text {:style {:font-size 10 :color colors/gray}} + (datetime/time-ago (datetime/to-date timestamp))]] + [react/view + (if (= content-type constants/content-type-image) + [image-message message on-long-press] + [react/touchable-highlight (when-not modal + {:on-long-press #(on-long-press-fn on-long-press content false)}) + [message/render-parsed-text (assoc message :outgoing false) (:parsed-text content)]]) + [link-preview/link-preview-wrapper (:links content) outgoing]]]])) (defn render-message [timeline? account] (fn [{:keys [type] :as message} idx] - (if (= type :datemark) - nil - (if (= type :gap) - (if timeline? - nil - [gap/gap message idx messages-list-ref]) - ; message content - [message-item message timeline? account])))) + [(fn [] + (if (= type :datemark) + nil + (if (= type :gap) + (if timeline? + nil + [gap/gap message idx messages-list-ref]) + ; message content + (let [chat-id (chat/profile-chat-topic (:from message))] + [react/view (merge {:accessibility-label :chat-item} + (when (:last-in-group? message) + {:padding-bottom 8 + :margin-bottom 8 + :border-bottom-width 1 + :border-bottom-color colors/gray-lighter})) + [reactions/with-reaction-picker + {:message message + :timeline true + :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)]) + :picker-on-open (fn []) + :picker-on-close (fn []) + :send-emoji (fn [{:keys [emoji-id]}] + (re-frame/dispatch [::models.reactions/send-emoji-reaction + {:message-id (:message-id message) + :chat-id chat-id + :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) + :chat-id chat-id + :emoji-id emoji-id + :emoji-reaction-id emoji-reaction-id}])) + :render (message-item timeline? account)}]]))))])) (def state (reagent/atom {:tab :timeline})) diff --git a/status-go-version.json b/status-go-version.json index b3190283e8..2ce1bf24d9 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.63.7", - "commit-sha1": "4026841dc1516865f385e0a6c2b57fead2aad773", - "src-sha256": "1sr9aaf9by2lzfphakqqpcir101zfgxa59wag2grsb2077k0gxj5" + "version": "v0.63.8", + "commit-sha1": "e8dbc66227b98fcf91b0626c7747c58f3d8b31fb", + "src-sha256": "16952l1zyj8bqzgb979w274d55nsihr5l0papvwkdk5i2xyps9q7" }