feat: deleted for me message UI (#14168)

* feat: delete for me message UI

delete and sync deleted for me messages immediately after leaving chat
view

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix: system message width/height

Signed-off-by: yqrashawn <namy.19@gmail.com>

Signed-off-by: yqrashawn <namy.19@gmail.com>
This commit is contained in:
yqrashawn 2022-10-21 13:12:40 +08:00 committed by GitHub
parent 1b6eaec719
commit 0cbd3ec805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 151 deletions

View File

@ -1,14 +1,13 @@
(ns quo2.components.messages.system-message
(:require [status-im.i18n.i18n :as i18n]
[quo.react-native :as rn]
[status-im.utils.core :as utils]
(:require [quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
[quo2.reanimated :as ra]
[quo2.foundations.colors :as colors]
[quo2.components.avatars.icon-avatar :as icon-avatar]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.avatars.icon-avatar :as icon-avatar]))
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[quo2.reanimated :as ra]
[status-im.i18n.i18n :as i18n]
[status-im.utils.core :as utils]))
(def themes-landed {:pinned colors/primary-50-opa-5
:added colors/primary-50-opa-5
@ -55,7 +54,7 @@
(defmulti sm-render :type)
(defmethod sm-render :deleted [{:keys [state action timestamp-str]}]
(defmethod sm-render :deleted [{:keys [label timestamp-str]}]
[rn/view {:align-items :center
:justify-content :space-between
:flex 1
@ -64,15 +63,12 @@
:flex-direction :row}
[sm-icon {:icon :main-icons/delete16
:color :danger
:opacity (if (= state :landed) 0 5)}]
:opacity 5}]
[text/text {:size :paragraph-2
:style {:color (get-color :text)
:margin-right 5}}
(i18n/label (if action :message-deleted-for-you :message-deleted))]
(when (nil? action) [sm-timestamp timestamp-str])]
(when action [button/button {:size 24
:before :main-icons/timeout
:type :grey} (i18n/label :undo)])])
(i18n/label (or label :message-deleted))]
[sm-timestamp timestamp-str]]])
(defmethod sm-render :added [{:keys [state mentions timestamp-str]}]
[rn/view {:align-items :center
@ -140,23 +136,27 @@
:style {:color (get-color :time)}}
(utils/truncate-str (:info content) 24)])]]]])
(defn system-message [{:keys [type] :as message}]
(defn system-message
[{:keys [type style non-pressable? animate-landing?] :as message}]
[:f>
(fn []
(let [sv-color (ra/use-shared-value (get-color :bg :landed type))]
(ra/animate-shared-value-with-delay
sv-color (get-color :bg :default type) 0 :linear 1000)
(let [sv-color (ra/use-shared-value
(get-color :bg (if animate-landing? :landed :default) type))]
(when animate-landing?
(ra/animate-shared-value-with-delay
sv-color (get-color :bg :default type) 0 :linear 1000))
[ra/touchable-opacity
{:on-press #(ra/set-shared-value
sv-color (get-color :bg :pressed type))
{:on-press #(when-not non-pressable?
(ra/set-shared-value
sv-color (get-color :bg :pressed type)))
:style (ra/apply-animations-to-style
{:background-color sv-color}
{:flex-direction :row
:flex 1
:border-radius 16
:padding-vertical 9
:padding-horizontal 11
:width 359
:height 52
: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]]))])

View File

@ -1,5 +1,6 @@
(ns status-im.chat.models.delete-message-for-me
(:require [re-frame.core :as re-frame]
[status-im.chat.models.message-list :as message-list]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx]
@ -43,17 +44,22 @@
{:events [:chat.ui/delete-message-for-me]}
[{:keys [db]} {:keys [chat-id message-id]} undo-time-limit-ms]
(when (get-in db [:messages chat-id message-id])
{:db (update-db-delete-locally db chat-id message-id undo-time-limit-ms)
(assoc
(message-list/rebuild-message-list
{:db (update-db-delete-locally db chat-id message-id undo-time-limit-ms)}
chat-id)
:utils/dispatch-later [{:dispatch [:chat.ui/delete-message-for-me-and-sync
{:chat-id chat-id
:message-id message-id}]
:ms undo-time-limit-ms}]}))
:ms undo-time-limit-ms}])))
(fx/defn undo
{:events [:chat.ui/undo-delete-message-for-me]}
[{:keys [db]} {:keys [chat-id message-id]}]
(when (get-in db [:messages chat-id message-id])
{:db (update-db-undo-locally db chat-id message-id)}))
(message-list/rebuild-message-list
{:db (update-db-undo-locally db chat-id message-id)}
chat-id)))
(fx/defn delete-and-sync
{:events [:chat.ui/delete-message-for-me-and-sync]}
@ -66,3 +72,19 @@
:on-error #(log/error "failed to delete message for me " %)
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response
%])}]}))
(defn- chats-reducer
"traverse all messages find not yet synced deleted-for-me? messages, generate dispatch vector"
[acc chat-id messages]
(reduce-kv
(fn [inner-acc message-id {:keys [deleted-for-me? deleted-for-me-undoable-till]}]
(if (and deleted-for-me? deleted-for-me-undoable-till)
(conj inner-acc [:chat.ui/delete-message-for-me-and-sync chat-id message-id])
inner-acc))
acc
messages))
(fx/defn sync-all
"Get all deleted-for-me messages that not yet synced with status-go and sync them"
{:events [:chat.ui/sync-all-deleted-for-me-messages]}
[{:keys [db]}]
{:dispatch-n (reduce-kv chats-reducer [] (:messages db))})

View File

@ -8,65 +8,47 @@
(defonce cid "chat-id")
(deftest delete-for-me
(let [db {:messages {cid {mid {:id mid}}}}
message {:message-id mid :chat-id cid}]
(testing "delete for me"
(let [expected {:db {:messages {"chat-id" {"message-id"
{:id "message-id"
:deleted-for-me? true}}}}
:utils/dispatch-later
[{:dispatch [:chat.ui/delete-message-for-me-and-sync
{:chat-id "chat-id" :message-id "message-id"}]
:ms 1000}]}
result (delete-message-for-me/delete {:db db} message 1000)
timestamp (+ (datetime/timestamp) 1000)]
(is (= (update-in result [:db :messages "chat-id" "message-id"] dissoc :deleted-for-me-undoable-till)
expected))
(is (-> (get-in result [:db :messages "chat-id" "message-id" :deleted-for-me-undoable-till])
(- timestamp)
js/Math.abs
(< 10)))))
(testing "should return nil if message in db"
(is (= (delete-message-for-me/delete {:db {:messages []}} message 1000)
nil)))))
(with-redefs [datetime/timestamp (constantly 1)]
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "delete for me"
(let [result-message (get-in (delete-message-for-me/delete {:db db} message 1000)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (true? (:deleted-for-me? result-message)))
(is (= (:deleted-for-me-undoable-till result-message) 1001))))
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/delete {:db {:messages []}} message 1000)
nil))))))
(deftest undo-delete-for-me
(let [db {:messages {cid {mid {:id mid}}}}
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "undo delete for me in time"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till
(+ (datetime/timestamp) 1000))
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till
(+ (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me? result-message)))
(is (nil? (:deleted-for-me-undoable-till result-message)))))
expected {:db {:messages {"chat-id" {"message-id"
{:id "message-id"}}}}}]
(is (= (delete-message-for-me/undo {:db db} message) expected))))
(testing "remain deleted for me when undo delete for me late"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till (- (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message) [:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me-undoable-till result-message)))
(is (true? (:deleted-for-me? result-message)))))
expected {:db {:messages {"chat-id" {"message-id"
{:id "message-id"
:deleted-for-me? true}}}}}]
(is (= (delete-message-for-me/undo {:db db} message) expected))))
(testing "remain deleted for me when undo delete for me late"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till (- (datetime/timestamp) 1000))
expected {:db {:messages {"chat-id" {"message-id"
{:id "message-id"
:deleted-for-me? true}}}}}]
(is (= (delete-message-for-me/undo {:db db} message) expected))))
(testing "should return nil if message in db"
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/undo {:db {:messages []}} message)
nil)))))
@ -74,7 +56,7 @@
(let [db {:messages {cid {mid {:id mid}}}}
message {:message-id mid :chat-id cid}]
(testing "delete for me and sync"
(let [expected-db {:messages {"chat-id" {"message-id" {:id "message-id"}}}}
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync {:db db} message)
result-db (:db effects)
rpc-calls (:status-im.ethereum.json-rpc/call effects)]
@ -100,7 +82,7 @@
second)
mid))))
(testing "delete for me and sync, should clean undo timer"
(let [expected-db {:messages {"chat-id" {"message-id" {:id "message-id"}}}}
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync
{:db (update-in db
[:messages cid mid
@ -109,7 +91,7 @@
message)
result-db (:db effects)]
(is (= result-db expected-db))))
(testing "should return nil if message in db"
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}}
message)
nil)))))

View File

@ -1,8 +1,8 @@
(ns status-im.chat.models.message-list
(:require [status-im.constants :as constants]
[status-im.utils.fx :as fx]
(:require ["functional-red-black-tree" :as rb-tree]
[status-im.constants :as constants]
[status-im.utils.datetime :as time]
["functional-red-black-tree" :as rb-tree]))
[status-im.utils.fx :as fx]))
(defn- add-datemark [{:keys [whisper-timestamp] :as msg}]
;;TODO this is slow
@ -16,16 +16,20 @@
message-type
from
outgoing
whisper-timestamp]}]
whisper-timestamp
deleted-for-me?]}]
(-> {:whisper-timestamp whisper-timestamp
:from from
:one-to-one? (= constants/message-type-one-to-one message-type)
:system-message? (= constants/message-type-private-group-system-message
message-type)
:clock-value clock-value
:type :message
:message-id message-id
:outgoing (boolean outgoing)}
:from from
:one-to-one? (= constants/message-type-one-to-one message-type)
:system-message? (boolean
(or
(= constants/message-type-private-group-system-message
message-type)
deleted-for-me?))
:clock-value clock-value
:type :message
:message-id message-id
:outgoing (boolean outgoing)}
add-datemark
add-timestamp))

View File

@ -1,21 +1,22 @@
(ns status-im.navigation.core
(:require
["react-native" :as rn]
[clojure.set :as clojure.set]
["react-native-gesture-handler" :refer (gestureHandlerRootHOC)]
["react-native-navigation" :refer (Navigation)]
[clojure.set :as clojure.set]
[quo.components.text-input :as quo.text-input]
[quo.design-system.colors :as quo.colors]
[re-frame.core :as re-frame]
[status-im.multiaccounts.login.core :as login-core]
[status-im.navigation.roots :as roots]
[status-im.navigation.state :as state]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.views :as views]
[status-im.utils.fx :as fx]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]
[status-im.multiaccounts.login.core :as login-core]
[status-im.navigation.state :as state]))
[taoensso.encore :as enc]
[taoensso.timbre :as log]))
(def debug? ^boolean js/goog.DEBUG)
@ -438,4 +439,6 @@
:community
:else
:home))}))
:home))
:dispatch-n (enc/conj-when []
(and (= view-id :chat) [:chat.ui/sync-all-deleted-for-me-messages]))}))

View File

@ -1,6 +1,10 @@
(ns status-im.ui2.screens.chat.messages.message
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]
[quo2.components.messages.system-message :as system-message]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.typography :as typography]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models.delete-message-for-me]
@ -11,11 +15,12 @@
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui2.screens.chat.components.reply :as components.reply]
[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.command :as message.command]
@ -26,17 +31,13 @@
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im.utils.config :as config]
[status-im.utils.security :as security]
[quo2.foundations.typography :as typography]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui.components.list.views :as list]
[quo.react-native :as rn]
[status-im.ui.components.chat-icon.screen :as chat-icon])
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.utils.security :as security])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
@ -263,7 +264,7 @@
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]
communities-enabled? [:communities/enabled?]]
communities-enabled? [:communities/enabled?]]
(when (and communities-enabled? community)
[rn/view {:style (assoc (style/message-wrapper message)
:margin-vertical 10
@ -294,51 +295,51 @@
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [last-in-group?
identicon
from in-popover? timestamp-str
deleted-for-me? pinned]
[{:keys [last-in-group? identicon from in-popover? timestamp-str
deleted-for-me? deleted-for-me-undoable-till pinned]
:as message} content {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[rn/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message)])
[rn/view {:style (style/message-body)
:pointer-events :box-none}
[rn/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[rn/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
(if deleted-for-me?
[system-message/system-message
{:type :deleted
:label :message-deleted-for-you
:timestamp-str timestamp-str
:non-pressable? true
:animate-landing? (if deleted-for-me-undoable-till true false)}]
[rn/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message)])
[rn/view {:style (style/message-body)
:pointer-events :box-none}
[rn/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[rn/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
[rn/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[rn/view {:style {:flex-direction :row :align-items :center}}
[rn/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[rn/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]])
;; MESSAGE CONTENT
;; TODO(yqrashawn): wait for system message component to display deleted for me UI
(if deleted-for-me?
[rn/view {:style {:border-width 2
:border-color :red}}
content]
content)
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
; delivery status
[rn/view (style/delivery-status)
[message-delivery-status message]]]))
[rn/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[rn/view {:style {:flex-direction :row :align-items :center}}
[rn/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[rn/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]])
;; MESSAGE CONTENT
content
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
;; delivery status
[rn/view (style/delivery-status)
[message-delivery-status message]]])))
(def image-max-width 260)
(def image-max-height 192)