diff --git a/src/quo2/components/messages/system_message.cljs b/src/quo2/components/messages/system_message.cljs index c8c05eb52e..9087b48b5e 100644 --- a/src/quo2/components/messages/system_message.cljs +++ b/src/quo2/components/messages/system_message.cljs @@ -173,7 +173,7 @@ (utils/truncate-str (:info content) 24)])]]]]) (defn system-message - [{:keys [type style non-pressable? animate-landing? labels] :as message}] + [{:keys [type style non-pressable? animate-landing? labels on-long-press] :as message}] [:f> (fn [] (let [sv-color (reanimated/use-shared-value @@ -186,16 +186,17 @@ :linear 1000)) [reanimated/touchable-opacity - {:on-press #(when-not non-pressable? - (reanimated/set-shared-value sv-color (get-color :bg :pressed type))) - :style (reanimated/apply-animations-to-style - {:background-color sv-color} - (merge - {:flex-direction :row - :flex 1 - :border-radius 16 - :padding-vertical 9 - :padding-horizontal 11 - :background-color sv-color} - style))} + {:on-press #(when-not non-pressable? + (reanimated/set-shared-value sv-color (get-color :bg :pressed type))) + :on-long-press on-long-press + :style (reanimated/apply-animations-to-style + {:background-color sv-color} + (merge + {:flex-direction :row + :flex 1 + :border-radius 16 + :padding-vertical 9 + :padding-horizontal 11 + :background-color sv-color} + style))} [sm-render message labels]]))]) diff --git a/src/status_im/ui2/screens/chat/components/reply/view.cljs b/src/status_im/ui2/screens/chat/components/reply/view.cljs index 23dab5d171..606c8014ee 100644 --- a/src/status_im/ui2/screens/chat/components/reply/view.cljs +++ b/src/status_im/ui2/screens/chat/components/reply/view.cljs @@ -45,7 +45,7 @@ [from username current-public-key] (or (and (= from current-public-key) (i18n/label :t/You)) - (format-author username))) + (when username (format-author username)))) (defn reply-deleted-message [] diff --git a/src/status_im/ui2/screens/chat/messages/message.cljs b/src/status_im/ui2/screens/chat/messages/message.cljs index 43b9e63df4..88e9e1f0b8 100644 --- a/src/status_im/ui2/screens/chat/messages/message.cljs +++ b/src/status_im/ui2/screens/chat/messages/message.cljs @@ -4,15 +4,13 @@ [quo.react-native :as rn] [quo2.components.icon :as icons] [quo2.components.markdown.text :as text] + [quo2.core :as quo] [quo2.foundations.colors :as colors] [quo2.foundations.typography :as typography] [re-frame.core :as re-frame] [reagent.core :as reagent] - [status-im2.constants :as constants] - [utils.i18n :as i18n] [status-im.react-native.resources :as resources] [status-im.ui.components.fast-image :as fast-image] - [status-im.ui.components.react :as react] [status-im.ui.screens.chat.image.preview.views :as preview] [status-im.ui.screens.chat.message.audio :as message.audio] [status-im.ui.screens.chat.message.gap :as message.gap] @@ -21,14 +19,14 @@ [status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.communities.icon :as communities.icon] [status-im.ui2.screens.chat.components.reply.view :as components.reply] - [status-im2.config :as config] - [utils.datetime :as datetime] [status-im.utils.utils :as utils] + [status-im2.constants :as constants] [status-im2.contexts.chat.home.chat-list-item.view :as home.chat-list-item] [status-im2.contexts.chat.messages.delete-message-for-me.events] [status-im2.contexts.chat.messages.delete-message.events] - [utils.re-frame :as rf] - [quo2.core :as quo]) + [utils.datetime :as datetime] + [utils.i18n :as i18n] + [utils.re-frame :as rf]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn system-text? @@ -230,58 +228,6 @@ (re-frame/dispatch [:pin-message/show-pin-limit-modal chat-id])) (re-frame/dispatch [:pin-message/send-pin-message (assoc message :pinned (not pinned))])))) -(defn on-long-press-fn - [on-long-press - {:keys [pinned message-pin-enabled outgoing edit-enabled show-input? community? - can-delete-message-for-everyone?] - :as message} content] - (on-long-press - (concat - (when (and outgoing edit-enabled) - [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/edit-message message]) - :label (i18n/label :t/edit-message) - :icon :i/edit - :id :edit}]) - (when show-input? - [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :label (i18n/label :t/message-reply) - :icon :i/reply - :id :reply}]) - [{:type :main - :on-press #(react/copy-to-clipboard - (components.reply/get-quoted-text-with-mentions - (get content :parsed-text))) - :label (i18n/label :t/copy-text) - :icon :i/copy - :id :copy}] - (when message-pin-enabled - [{:type :main - :on-press #(pin-message message) - :label (i18n/label (if pinned - (if community? :t/unpin-from-channel :t/unpin-from-chat) - (if community? :t/pin-to-channel :t/pin-to-chat))) - :icon :i/pin - :id (if pinned :unpin :pin)}]) - (when-not pinned - [{:type :danger - :on-press (fn [] - (re-frame/dispatch - [:chat.ui/delete-message-for-me message - config/delete-message-for-me-undo-time-limit-ms])) - :label (i18n/label :t/delete-for-me) - :icon :i/delete - :id :delete-for-me}]) - (when (and (or outgoing can-delete-message-for-everyone?) config/delete-message-enabled?) - [{:type :danger - :on-press #(re-frame/dispatch [:chat.ui/delete-message - message - config/delete-message-undo-time-limit-ms]) - :label (i18n/label :t/delete-for-everyone) - :icon :i/delete - :id :delete-for-all}])))) - ;; STATUS ? whats that ? (defmethod ->message constants/content-type-status [{:keys [content content-type]}] diff --git a/src/status_im2/config.cljs b/src/status_im2/config.cljs index 9b88d5b305..301e9c50ee 100644 --- a/src/status_im2/config.cljs +++ b/src/status_im2/config.cljs @@ -34,7 +34,6 @@ (def communities-enabled? (enabled? (get-config :COMMUNITIES_ENABLED "0"))) (def database-management-enabled? (enabled? (get-config :DATABASE_MANAGEMENT_ENABLED "0"))) (def debug-webview? (enabled? (get-config :DEBUG_WEBVIEW "0"))) -(def delete-message-enabled? (enabled? (get-config :DELETE_MESSAGE_ENABLED "0"))) (def collectibles-enabled? (enabled? (get-config :COLLECTIBLES_ENABLED "1"))) (def test-stateofus? (enabled? (get-config :TEST_STATEOFUS "0"))) (def two-minutes-syncing? (enabled? (get-config :TWO_MINUTES_SYNCING "0"))) diff --git a/src/status_im2/contexts/chat/messages/content/deleted/view.cljs b/src/status_im2/contexts/chat/messages/content/deleted/view.cljs index deb95733d3..0a00847075 100644 --- a/src/status_im2/contexts/chat/messages/content/deleted/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/deleted/view.cljs @@ -1,6 +1,7 @@ (ns status-im2.contexts.chat.messages.content.deleted.view (:require [quo2.core :as quo] [react-native.core :as rn] + [status-im2.contexts.chat.messages.drawers.view :as drawers] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -20,8 +21,20 @@ [quo/text {:style {:margin-left 4} :size :paragraph-2} (i18n/label :t/deleted-this-message)]]) +(defn- compute-on-long-press-fn + [{:keys [deleted? pinned] :as message} + {:keys [message-pin-enabled] :as context}] + ;; only show drawer for user who has the permission to unpin messages + (when (and pinned deleted? message-pin-enabled) + (fn [] + (rf/dispatch [:dismiss-keyboard]) + (rf/dispatch [:bottom-sheet/show-sheet + {:content (drawers/reactions-and-actions message + context)}])))) + (defn deleted-by-message - [{:keys [deleted-by deleted-undoable-till timestamp-str deleted-for-me-undoable-till from]}] + [{:keys [deleted-by deleted-undoable-till timestamp-str deleted-for-me-undoable-till from]} + on-long-press-fn] (let [;; deleted message with nil deleted-by is deleted by (:from message) display-name (first (rf/sub [:contacts/contact-two-names-by-identity (or deleted-by from)])) contact (rf/sub [:contacts/contact-by-address (or deleted-by from)]) @@ -32,21 +45,25 @@ :timestamp-str timestamp-str :child [user-xxx-deleted-this-message {:display-name display-name :profile-picture photo-path}] - :non-pressable? true + :on-long-press on-long-press-fn + :non-pressable? (if on-long-press-fn false true) :animate-landing? (or deleted-undoable-till deleted-for-me-undoable-till)}])) (defn deleted-message [{:keys [deleted? deleted-by deleted-undoable-till timestamp-str deleted-for-me-undoable-till from] - :as message}] - (let [pub-key (rf/sub [:multiaccount/public-key]) - deleted-by-me? (= (or deleted-by from) pub-key)] - (if (not deleted-by-me?) - [deleted-by-message message] + :as message} + context] + (let [pub-key (rf/sub [:multiaccount/public-key]) + deleted-by-me? (= (or deleted-by from) pub-key) + on-long-press-fn (compute-on-long-press-fn message context)] + (if-not deleted-by-me? + [deleted-by-message message on-long-press-fn] [quo/system-message {:type :deleted :label (if deleted? :message-deleted :message-deleted-for-you) :labels {:message-deleted (i18n/label :t/message-deleted-for-everyone) :message-deleted-for-you (i18n/label :t/message-deleted-for-you)} + :on-long-press on-long-press-fn :timestamp-str timestamp-str - :non-pressable? true + :non-pressable? (if on-long-press-fn false true) :animate-landing? (or deleted-undoable-till deleted-for-me-undoable-till)}]))) diff --git a/src/status_im2/contexts/chat/messages/delete_message/events.cljs b/src/status_im2/contexts/chat/messages/delete_message/events.cljs index 8c8b216589..25a68b08ab 100644 --- a/src/status_im2/contexts/chat/messages/delete_message/events.cljs +++ b/src/status_im2/contexts/chat/messages/delete_message/events.cljs @@ -1,10 +1,10 @@ (ns status-im2.contexts.chat.messages.delete-message.events (:require - [utils.i18n :as i18n] [quo2.foundations.colors :as colors] [status-im2.contexts.chat.messages.list.events :as message-list] [taoensso.timbre :as log] [utils.datetime :as datetime] + [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn- update-db-clear-undo-timer @@ -47,6 +47,26 @@ (when (get-in db [:messages chat-id message-id]) (update-in db [:messages chat-id message-id] assoc :deleted? true :deleted-by deleted-by))) +(defn- should-and-able-to-unpin-to-be-deleted-message + "check + 1. if the to-be deleted message is pinned + 2. if user has the permission to unpin message in current chat" + [db {:keys [chat-id message-id]}] + (let [{:keys [group-chat chat-id public? community-id admins]} (get-in db [:chats chat-id]) + community (get-in db [:communities community-id]) + community-admin? (get community :admin false) + pub-key (get-in db [:multiaccount :public-key]) + group-admin? (get admins pub-key) + message-pin-enabled (and (not public?) + (or (not group-chat) + (and group-chat + (or group-admin? + community-admin?)))) + pinned (get-in db + [:pin-messages chat-id + message-id])] + (and pinned message-pin-enabled))) + (rf/defn delete "Delete message now locally and broadcast after undo time limit timeout" {:events [:chat.ui/delete-message]} @@ -56,14 +76,22 @@ ;; new delete operation will reset prev pending deletes' undo timelimit ;; undo will undo all pending deletes ;; all pending deletes are stored in toast - (let [pub-key (get-in db [:multiaccount :public-key]) + (let [unpin? (should-and-able-to-unpin-to-be-deleted-message + db + {:chat-id chat-id :message-id message-id}) + + pub-key (get-in db [:multiaccount :public-key]) + + ;; compute deleted by message-from (get message :from) deleted-by (when (not= pub-key message-from) pub-key) + existing-undo-toast (get-in db [:toasts :toasts :delete-message-for-everyone]) toast-count (inc (get existing-undo-toast :message-deleted-for-everyone-count 0)) - existing-undos (-> existing-undo-toast - (get :message-deleted-for-everyone-undos []) - (conj {:message-id message-id :chat-id chat-id}))] + existing-undos + (-> existing-undo-toast + (get :message-deleted-for-everyone-undos []) + (conj {:message-id message-id :chat-id chat-id :should-pin-back? unpin?}))] (assoc (message-list/rebuild-message-list {:db (reduce @@ -89,7 +117,10 @@ :undo-duration (/ undo-time-limit-ms 1000) :undo-on-press #(do (rf/dispatch [:chat.ui/undo-all-delete-message]) (rf/dispatch [:toasts/close - :delete-message-for-everyone]))}]] + :delete-message-for-everyone]))}] + (when unpin? + [:pin-message/send-pin-message-locally + {:chat-id chat-id :message-id message-id :pinned false}])] :utils/dispatch-later (mapv (fn [{:keys [chat-id message-id]}] {:dispatch [:chat.ui/delete-message-and-send {:chat-id chat-id :message-id message-id}] @@ -98,11 +129,16 @@ (rf/defn undo {:events [:chat.ui/undo-delete-message]} - [{:keys [db]} {:keys [chat-id message-id]}] + [{:keys [db]} {:keys [chat-id message-id should-pin-back?]}] (when (get-in db [:messages chat-id message-id]) - (message-list/rebuild-message-list - {:db (update-db-undo-locally db chat-id message-id)} - chat-id))) + (let [effects (message-list/rebuild-message-list + {:db (update-db-undo-locally db chat-id message-id)} + chat-id) + message (get-in effects [:db :messages chat-id message-id])] + (cond-> effects + should-pin-back? + (assoc :dispatch + [:pin-message/send-pin-message-locally (assoc message :pinned true)]))))) (rf/defn undo-all {:events [:chat.ui/undo-all-delete-message]} @@ -126,19 +162,32 @@ [{:keys [db]} {:keys [message-id chat-id]} force?] (when-let [message (get-in db [:messages chat-id message-id])] (when (or force? (check-before-delete-and-send db chat-id message-id)) - (cond-> {:db (update-db-clear-undo-timer db chat-id message-id) - :json-rpc/call [{:method "wakuext_deleteMessageAndSend" - :params [message-id] - :js-response true - :on-error #(log/error "failed to delete message " - {:message-id message-id :error %}) - :on-success #(rf/dispatch - [:sanitize-messages-and-process-response - %])}]} - (get-in db [:pin-messages chat-id message-id]) - (assoc :dispatch - [:pin-message/send-pin-message - {:chat-id chat-id :message-id message-id :pinned false}]))))) + (let [unpin-locally? + ;; this only check against local client data + ;; generally msg is already unpinned at delete locally phase when user + ;; has unpin permission + ;; + ;; will be true only if + ;; 1. admin delete an unpinned msg + ;; 2. another admin pin the msg within the undo time limit + ;; 3. msg finally deleted + (should-and-able-to-unpin-to-be-deleted-message db + {:chat-id chat-id + :message-id message-id})] + {:db (update-db-clear-undo-timer db chat-id message-id) + :json-rpc/call [{:method "wakuext_deleteMessageAndSend" + :params [message-id] + :js-response true + :on-error #(log/error "failed to delete message " + {:message-id message-id :error %}) + :on-success #(rf/dispatch + [:sanitize-messages-and-process-response + %])}] + :dispatch [:pin-message/send-pin-message + {:chat-id chat-id + :message-id message-id + :pinned false + :remote-only? (not unpin-locally?)}]})))) (defn- filter-pending-send-messages "traverse all messages find not yet synced deleted? messages" diff --git a/src/status_im2/contexts/chat/messages/drawers/view.cljs b/src/status_im2/contexts/chat/messages/drawers/view.cljs index 778a528d9f..52a2631c4d 100644 --- a/src/status_im2/contexts/chat/messages/drawers/view.cljs +++ b/src/status_im2/contexts/chat/messages/drawers/view.cljs @@ -1,13 +1,12 @@ (ns status-im2.contexts.chat.messages.drawers.view - (:require [react-native.core :as rn] - [status-im2.constants :as constants] - [utils.re-frame :as rf] - [quo2.core :as quo] - [utils.i18n :as i18n] - [status-im2.config :as config] + (:require [quo2.core :as quo] + [react-native.core :as rn] [status-im.ui.components.react :as react] [status-im.ui2.screens.chat.components.reply.view :as components.reply] - [status-im2.common.not-implemented :as not-implemented])) + [status-im2.common.not-implemented :as not-implemented] + [status-im2.constants :as constants] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (defn pin-message [{:keys [chat-id pinned pinned-by] :as message-data}] @@ -21,37 +20,39 @@ (assoc message-data :pinned message-not-pinned?)])))) (defn get-actions - [{:keys [outgoing content pinned-by outgoing-status] :as message-data} - {:keys [edit-enabled show-input? can-delete-message-for-everyone? community? message-pin-enabled]}] + [{:keys [outgoing content pinned outgoing-status deleted? deleted-for-me?] :as message-data} + {:keys [edit-enabled show-input? community? can-delete-message-for-everyone? + message-pin-enabled group-chat group-admin?]}] (concat - (when (and outgoing edit-enabled) + (when (and outgoing edit-enabled (not (or deleted? deleted-for-me?))) [{:type :main :on-press #(rf/dispatch [:chat.ui/edit-message message-data]) :label (i18n/label :t/edit-message) :icon :i/edit :id :edit}]) - (when (and show-input? (not= outgoing-status :sending)) + (when (and show-input? (not= outgoing-status :sending) (not (or deleted? deleted-for-me?))) [{:type :main :on-press #(rf/dispatch [:chat.ui/reply-to-message message-data]) :label (i18n/label :t/message-reply) :icon :i/reply :id :reply}]) - [{:type :main - :on-press #(react/copy-to-clipboard - (components.reply/get-quoted-text-with-mentions - (get content :parsed-text))) - :label (i18n/label :t/copy-text) - :icon :i/copy - :id :copy}] + (when-not (or deleted? deleted-for-me?) + [{:type :main + :on-press #(react/copy-to-clipboard + (components.reply/get-quoted-text-with-mentions + (get content :parsed-text))) + :label (i18n/label :t/copy-text) + :icon :i/copy + :id :copy}]) (when message-pin-enabled [{:type :main :on-press #(pin-message message-data) - :label (i18n/label (if pinned-by + :label (i18n/label (if pinned (if community? :t/unpin-from-channel :t/unpin-from-chat) (if community? :t/pin-to-channel :t/pin-to-chat))) :icon :i/pin - :id (if pinned-by :unpin :pin)}]) - (when-not pinned-by + :id (if pinned :unpin :pin)}]) + (when-not (or pinned deleted? deleted-for-me?) [{:type :danger :on-press (fn [] (rf/dispatch @@ -62,7 +63,13 @@ :label (i18n/label :t/delete-for-me) :icon :i/delete :id :delete-for-me}]) - (when (and (or outgoing can-delete-message-for-everyone?) config/delete-message-enabled?) + (when (cond + deleted? false + deleted-for-me? false + outgoing true + community? can-delete-message-for-everyone? + group-chat group-admin? + :else false) [{:type :danger :on-press (fn [] (rf/dispatch [:bottom-sheet/hide]) @@ -119,7 +126,8 @@ icon]])))])) (defn reactions-and-actions - [{:keys [message-id outgoing-status] :as message-data} {:keys [chat-id] :as context}] + [{:keys [message-id outgoing-status deleted? deleted-for-me?] :as message-data} + {:keys [chat-id] :as context}] (fn [] (let [actions (get-actions message-data context) main-actions (filter #(= (:type %) :main) actions) @@ -127,7 +135,7 @@ admin-actions (filter #(= (:type %) :admin) actions)] [:<> ;; REACTIONS - (when (not= outgoing-status :sending) + (when (and (not= outgoing-status :sending) (not (or deleted? deleted-for-me?))) [reactions {:chat-id chat-id :message-id message-id}]) ;; MAIN ACTIONS diff --git a/src/status_im2/contexts/chat/messages/list/view.cljs b/src/status_im2/contexts/chat/messages/list/view.cljs index 50cac734f0..fa2eba1dad 100644 --- a/src/status_im2/contexts/chat/messages/list/view.cljs +++ b/src/status_im2/contexts/chat/messages/list/view.cljs @@ -1,19 +1,19 @@ (ns status-im2.contexts.chat.messages.list.view - (:require [utils.i18n :as i18n] - [oops.core :as oops] + (:require [oops.core :as oops] [quo2.core :as quo] [react-native.background-timer :as background-timer] [react-native.core :as rn] [react-native.platform :as platform] [reagent.core :as reagent] - [utils.re-frame :as rf] - [status-im2.contexts.chat.messages.content.view :as message] + [status-im.ui.screens.chat.group :as chat.group] + [status-im.ui.screens.chat.message.gap :as message.gap] + [status-im2.common.not-implemented :as not-implemented] [status-im2.constants :as constants] [status-im2.contexts.chat.messages.content.deleted.view :as content.deleted] - [status-im2.common.not-implemented :as not-implemented] - [status-im.ui.screens.chat.group :as chat.group] + [status-im2.contexts.chat.messages.content.view :as message] [status-im2.contexts.chat.messages.list.state :as state] - [status-im.ui.screens.chat.message.gap :as message.gap])) + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (defonce messages-list-ref (atom nil)) @@ -69,6 +69,7 @@ (if platform/low-device? 700 200)))) (defn get-render-data + "compute data used to render message list, including pinned message list and message list in chats" [{:keys [group-chat chat-id public? community-id admins space-keeper show-input? edit-enabled in-pinned-view?]}] (let [current-public-key (rf/sub [:multiaccount/public-key]) @@ -82,6 +83,7 @@ (or group-admin? community-admin?))))] {:group-chat group-chat + :group-admin? group-admin? :public? public? :community? (not (nil? community-id)) :current-public-key current-public-key @@ -123,7 +125,7 @@ [message.gap/gap message-data]] [rn/view {:padding-horizontal 8} (if (or deleted? deleted-for-me?) - [content.deleted/deleted-message message-data] + [content.deleted/deleted-message message-data context] [message/message-with-reactions message-data context])]))]) (defn messages-list diff --git a/src/status_im2/contexts/chat/messages/pin/banner/view.cljs b/src/status_im2/contexts/chat/messages/pin/banner/view.cljs index 970935c15c..458500ed05 100644 --- a/src/status_im2/contexts/chat/messages/pin/banner/view.cljs +++ b/src/status_im2/contexts/chat/messages/pin/banner/view.cljs @@ -1,15 +1,21 @@ (ns status-im2.contexts.chat.messages.pin.banner.view (:require [quo2.core :as quo] + [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn banner [chat-id] - (let [pinned-messages (rf/sub [:chats/pinned-sorted-list chat-id]) - latest-pin-text (->> pinned-messages - last - :content - :text) - pins-count (count pinned-messages)] + (let [pinned-messages (rf/sub [:chats/pinned-sorted-list + chat-id]) + latest-pinned-message (last pinned-messages) + latest-pin-text (get-in latest-pinned-message [:content :text]) + {:keys [deleted? deleted-for-me?]} latest-pinned-message + pins-count (count pinned-messages) + + latest-pin-text + (cond deleted? (i18n/label :t/message-deleted-for-everyone) + deleted-for-me? (i18n/label :t/message-deleted-for-you) + :else latest-pin-text)] [quo/banner {:latest-pin-text latest-pin-text :pins-count pins-count diff --git a/src/status_im2/contexts/chat/messages/pin/events.cljs b/src/status_im2/contexts/chat/messages/pin/events.cljs index 0c910dae4b..0bc4cbabeb 100644 --- a/src/status_im2/contexts/chat/messages/pin/events.cljs +++ b/src/status_im2/contexts/chat/messages/pin/events.cljs @@ -1,14 +1,14 @@ (ns status-im2.contexts.chat.messages.pin.events - (:require [re-frame.core :as re-frame] - [utils.i18n :as i18n] - [quo2.foundations.colors :as colors] - [status-im2.contexts.chat.messages.list.events :as message-list] - [status-im2.common.toasts.events :as toasts] - [status-im2.constants :as constants] + (:require [quo2.foundations.colors :as colors] + [re-frame.core :as re-frame] [status-im.data-store.pin-messages :as data-store.pin-messages] [status-im.transport.message.protocol :as protocol] - [utils.re-frame :as rf] - [taoensso.timbre :as log])) + [status-im2.common.toasts.events :as toasts] + [status-im2.constants :as constants] + [status-im2.contexts.chat.messages.list.events :as message-list] + [taoensso.timbre :as log] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (rf/defn handle-failed-loading-pin-messages {:events [:pin-message/failed-loading-pin-messages]} @@ -59,23 +59,34 @@ (assoc-in [:pin-message-lists chat-id] (message-list/add-many nil (vals all-messages))))})))) -(rf/defn send-pin-message - "Pin message, rebuild pinned messages list" - {:events [:pin-message/send-pin-message]} + +(rf/defn send-pin-message-locally + "Pin message, rebuild pinned messages list locally" + {:events [:pin-message/send-pin-message-locally]} [{:keys [db] :as cofx} {:keys [chat-id message-id pinned] :as pin-message}] - (let [current-public-key (get-in db [:multiaccount :public-key]) - message (merge pin-message {:pinned-by current-public-key}) - preferred-name (get-in db [:multiaccount :preferred-name])] + (let [current-public-key (get-in db [:multiaccount :public-key]) + message (merge pin-message {:pinned-by current-public-key}) + pin-message-lists-exist? (some? (get-in db [:pin-message-lists chat-id]))] (rf/merge cofx {:db (cond-> db pinned (-> (update-in [:pin-message-lists chat-id] message-list/add message) (assoc-in [:pin-messages chat-id message-id] message)) - (not pinned) + (and (not pinned) pin-message-lists-exist?) (-> (update-in [:pin-message-lists chat-id] message-list/remove-message pin-message) - (update-in [:pin-messages chat-id] dissoc message-id)))} + (update-in [:pin-messages chat-id] dissoc message-id)))}))) + +(rf/defn send-pin-message + "Pin message, rebuild pinned messages list" + {:events [:pin-message/send-pin-message]} + [{:keys [db] :as cofx} {:keys [chat-id message-id pinned remote-only?] :as pin-message}] + (let [current-public-key (get-in db [:multiaccount :public-key]) + message (merge pin-message {:pinned-by current-public-key}) + preferred-name (get-in db [:multiaccount :preferred-name])] + (rf/merge cofx + (when-not remote-only? (send-pin-message-locally pin-message)) (data-store.pin-messages/send-pin-message {:chat-id (pin-message :chat-id) :message_id (pin-message :message-id) :pinned (pin-message :pinned)}) diff --git a/src/status_im2/contexts/chat/messages/pin/list/view.cljs b/src/status_im2/contexts/chat/messages/pin/list/view.cljs index 1f2d8a07b2..11528b3aeb 100644 --- a/src/status_im2/contexts/chat/messages/pin/list/view.cljs +++ b/src/status_im2/contexts/chat/messages/pin/list/view.cljs @@ -1,37 +1,33 @@ (ns status-im2.contexts.chat.messages.pin.list.view - (:require [utils.i18n :as i18n] - [quo2.core :as quo] + (:require [quo2.core :as quo] [quo2.foundations.colors :as colors] [react-native.core :as rn] + [status-im2.contexts.chat.messages.content.deleted.view :as content.deleted] [status-im2.contexts.chat.messages.content.view :as message] - [utils.re-frame :as rf] - [utils.datetime :as datetime])) + [status-im2.contexts.chat.messages.list.view :as list.view] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (def list-key-fn #(or (:message-id %) (:value %))) (defn message-render-fn - [{:keys [whisper-timestamp] :as message} - _ - {:keys [group-chat public? community? current-public-key show-input? edit-enabled]}] + [{:keys [deleted? deleted-for-me?] :as message} _ _ context] ;; TODO (flexsurfer) probably we don't want reactions here - [message/message-with-reactions - message - {:group-chat group-chat - :public? public? - :community? community? - :current-public-key current-public-key - :show-input? show-input? - :message-pin-enabled true - :in-pinned-view? true - :pinned true - :timestamp-str (datetime/timestamp->time whisper-timestamp) - :edit-enabled edit-enabled}]) + (if (or deleted? deleted-for-me?) + [content.deleted/deleted-message message context] + [message/message-with-reactions message context])) (defn pinned-messages-list [chat-id] - (let [pinned-messages (vec (vals (rf/sub [:chats/pinned chat-id]))) - current-chat (rf/sub [:chat-by-id chat-id]) - community (rf/sub [:communities/community (:community-id current-chat)])] + (let [pinned-messages (rf/sub [:chats/pinned-sorted-list + chat-id]) + current-chat (rf/sub [:chat-by-id chat-id]) + + {:keys [group-chat chat-id public? community-id admins]} + current-chat + + community (rf/sub [:communities/community + community-id])] [rn/view {:accessibility-label :pinned-messages-list} ;; TODO (flexsurfer) this should be a component in quo2 ;; https://github.com/status-im/status-mobile/issues/14529 @@ -62,10 +58,18 @@ (str "# " (:chat-name current-chat))]])] (if (> (count pinned-messages) 0) [rn/flat-list - {:data pinned-messages - :render-fn message-render-fn - :key-fn list-key-fn - :separator quo/separator}] + {:data pinned-messages + :render-data (list.view/get-render-data {:group-chat group-chat + :chat-id chat-id + :public? public? + :community-id community-id + :show-input? false + :admins admins + :edit-enabled true + :in-pinned-view? true}) + :render-fn message-render-fn + :key-fn list-key-fn + :separator quo/separator}] [rn/view {:style {:justify-content :center :align-items :center diff --git a/src/status_im2/subs/chat/messages.cljs b/src/status_im2/subs/chat/messages.cljs index 5f7641ee75..24b9ba6684 100644 --- a/src/status_im2/subs/chat/messages.cljs +++ b/src/status_im2/subs/chat/messages.cljs @@ -1,9 +1,9 @@ (ns status-im2.subs.chat.messages (:require [re-frame.core :as re-frame] - [status-im2.contexts.chat.messages.list.events :as models.message-list] [status-im.chat.models.reactions :as models.reactions] - [utils.datetime :as datetime] - [status-im2.constants :as constants])) + [status-im2.constants :as constants] + [status-im2.contexts.chat.messages.list.events :as models.message-list] + [utils.datetime :as datetime])) (defn intersperse-datemark "Reduce step which expects the input list of messages to be sorted by clock value. @@ -24,7 +24,7 @@ :acc (conj acc msg)} (and (not= last-datemark datemark) ; not the same day - (< whisper-timestamp last-timestamp)) ; not out-of-order + (< whisper-timestamp last-timestamp)) ; not out-of-order {:last-timestamp whisper-timestamp :last-datemark datemark :acc (conj acc @@ -32,7 +32,7 @@ :type :datemark} msg)} :else - {:last-timestamp (min whisper-timestamp last-timestamp) ; use last datemark + {:last-timestamp (min whisper-timestamp last-timestamp) ; use last datemark :last-datemark last-datemark :acc (conj acc (assoc msg :datemark last-datemark))})) @@ -158,14 +158,18 @@ [(re-frame/subscribe [:messages/pin-messages]) (re-frame/subscribe [:chats/chat-messages chat-id])]) (fn [[pin-messages messages] [_ chat-id]] - (let [pin-messages (get pin-messages chat-id {})] - (reduce-kv (fn [acc message-id message] - (let [{:keys [deleted? deleted-for-me?]} (get messages message-id)] - (if (or deleted? deleted-for-me?) - acc - (assoc acc message-id message)))) - {} - pin-messages)))) + (let [pinned-messages (get pin-messages chat-id {})] + (reduce-kv + (fn [pinned-messages pinned-message-id pinned-message] + (let [{:keys [deleted? deleted-for-me? deleted-by]} (get messages pinned-message-id) + pinned-message (assoc pinned-message + :deleted? deleted? + :deleted-for-me? deleted-for-me? + :deleted-by deleted-by + :pinned true)] + (assoc pinned-messages pinned-message-id pinned-message))) + pinned-messages + pinned-messages)))) ;; local messages will not have a :pinned-at key until user navigates away and to ;; chat screen. For this reason we want to retain order of local messages with :pinned-at nil diff --git a/src/status_im2/subs/chat/messages_test.cljs b/src/status_im2/subs/chat/messages_test.cljs index 94b8c8b87b..f1f392fb79 100644 --- a/src/status_im2/subs/chat/messages_test.cljs +++ b/src/status_im2/subs/chat/messages_test.cljs @@ -1,10 +1,10 @@ (ns status-im2.subs.chat.messages-test (:require [cljs.test :refer [deftest is testing]] + [re-frame.db :as rf-db] + [status-im2.constants :as constants] [status-im2.subs.chat.messages :as messages] [test-helpers.unit :as h] - [utils.re-frame :as rf] - [re-frame.db :as rf-db] - [status-im2.constants :as constants])) + [utils.re-frame :as rf])) (def messages-state [{:message-id "0x111" :album-id "abc" :albumize? true} @@ -109,51 +109,91 @@ [sub-name] (testing "It sorts three messages with pinned-at property" (swap! rf-db/app-db assoc :pin-messages pinned-messages-state) - (is (= [{:chat-id :0xChat - :message-id :0x1 - :pinned-at 1000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x2 - :pinned-at 2000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x3 - :pinned-at 3000 - :pinned-by :test-user}] + (is (= [{:chat-id :0xChat + :message-id :0x1 + :pinned-at 1000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x2 + :pinned-at 2000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x3 + :pinned-at 3000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil}] (rf/sub [sub-name :0xChat])))) (testing "It sorts messages from backend with pinned-at property and 1 new local pinned message" (swap! rf-db/app-db assoc :pin-messages pinned-messages-state-with-1-new-local-message) - (is (= [{:chat-id :0xChat - :message-id :0x1 - :pinned-at 2000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x2 - :pinned-at 3000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x3 - :pinned-at nil - :pinned-by :test-user}] + (is (= [{:chat-id :0xChat + :message-id :0x1 + :pinned-at 2000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x2 + :pinned-at 3000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x3 + :pinned-at nil + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil}] (rf/sub [sub-name :0xChat])))) (testing "It sorts messages from backend with pinned-at property and 2 new local pinned messages" (swap! rf-db/app-db assoc :pin-messages pinned-messages-state-with-2-new-local-messages) - (is (= [{:chat-id :0xChat - :message-id :0x1 - :pinned-at 2000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x2 - :pinned-at 3000 - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x3 - :pinned-at nil - :pinned-by :test-user} - {:chat-id :0xChat - :message-id :0x4 - :pinned-at nil - :pinned-by :test-user}] - (rf/sub [sub-name :0xChat]))))) - + (is + (= [{:chat-id :0xChat + :message-id :0x1 + :pinned-at 2000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x2 + :pinned-at 3000 + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x3 + :pinned-at nil + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil} + {:chat-id :0xChat + :message-id :0x4 + :pinned-at nil + :pinned-by :test-user + :pinned true + :deleted? nil + :deleted-for-me? nil + :deleted-by nil}] + (rf/sub [sub-name :0xChat])))))