mirror of
https://github.com/status-im/status-react.git
synced 2025-01-11 03:26:31 +00:00
feat: undo delete with toast (#14618)
This commit is contained in:
parent
6f10ff4d3e
commit
02a1c3597f
@ -206,58 +206,60 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
||||
|
||||
(def react-native-reanimated
|
||||
#js
|
||||
{:default #js
|
||||
{:createAnimatedComponent identity
|
||||
:eq nil
|
||||
:greaterOrEq nil
|
||||
:greaterThan nil
|
||||
:lessThan nil
|
||||
:lessOrEq nil
|
||||
:add nil
|
||||
:diff nil
|
||||
:divide nil
|
||||
:sub nil
|
||||
:multiply nil
|
||||
:abs nil
|
||||
:min nil
|
||||
:max nil
|
||||
:neq nil
|
||||
:and nil
|
||||
:or nil
|
||||
:not nil
|
||||
:set nil
|
||||
:startClock nil
|
||||
:stopClock nil
|
||||
:Value nil
|
||||
:Clock nil
|
||||
:debug nil
|
||||
:log nil
|
||||
:event nil
|
||||
:cond nil
|
||||
:block nil
|
||||
:interpolateNode nil
|
||||
:call nil
|
||||
:timing nil
|
||||
:onChange nil
|
||||
:View #js {}
|
||||
:Image #js {}
|
||||
:ScrollView #js {}
|
||||
:Text #js {}
|
||||
:Extrapolate #js {:CLAMP nil}
|
||||
:Code #js {}}
|
||||
:EasingNode #js
|
||||
{:bezier identity
|
||||
:linear identity}
|
||||
:clockRunning nil
|
||||
:useSharedValue (fn [])
|
||||
:useAnimatedStyle (fn [])
|
||||
:withTiming (fn [])
|
||||
:withDelay (fn [])
|
||||
:Easing #js {:bezier identity}
|
||||
:Keyframe (fn [])
|
||||
:SlideOutUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__
|
||||
:SlideInUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__
|
||||
:LinearTransition js/__STATUS_MOBILE_JS_IDENTITY_PROXY__})
|
||||
{:default #js
|
||||
{:createAnimatedComponent identity
|
||||
:eq nil
|
||||
:greaterOrEq nil
|
||||
:greaterThan nil
|
||||
:lessThan nil
|
||||
:lessOrEq nil
|
||||
:add nil
|
||||
:diff nil
|
||||
:divide nil
|
||||
:sub nil
|
||||
:multiply nil
|
||||
:abs nil
|
||||
:min nil
|
||||
:max nil
|
||||
:neq nil
|
||||
:and nil
|
||||
:or nil
|
||||
:not nil
|
||||
:set nil
|
||||
:startClock nil
|
||||
:stopClock nil
|
||||
:Value nil
|
||||
:Clock nil
|
||||
:debug nil
|
||||
:log nil
|
||||
:event nil
|
||||
:cond nil
|
||||
:block nil
|
||||
:interpolateNode nil
|
||||
:call nil
|
||||
:timing nil
|
||||
:onChange nil
|
||||
:View #js {}
|
||||
:Image #js {}
|
||||
:ScrollView #js {}
|
||||
:Text #js {}
|
||||
:Extrapolate #js {:CLAMP nil}
|
||||
:Code #js {}}
|
||||
:EasingNode #js
|
||||
{:bezier identity
|
||||
:linear identity}
|
||||
:clockRunning nil
|
||||
:useSharedValue (fn [])
|
||||
:useAnimatedStyle (fn [])
|
||||
:withTiming (fn [])
|
||||
:withDelay (fn [])
|
||||
:Easing #js {:bezier identity}
|
||||
:Keyframe (fn [])
|
||||
:enableLayoutAnimations (fn [])
|
||||
:SlideOutUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__
|
||||
:SlideInUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__
|
||||
:LinearTransition js/__STATUS_MOBILE_JS_IDENTITY_PROXY__})
|
||||
|
||||
(def react-native-gesture-handler
|
||||
#js
|
||||
{:default #js {}
|
||||
|
@ -8,14 +8,14 @@
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def ^:private themes
|
||||
{:container {:light {:background-color colors/white-opa-70}
|
||||
:dark {:background-color colors/neutral-80-opa-70}}
|
||||
:text {:light {:color colors/neutral-100}
|
||||
:dark {:color colors/white}}
|
||||
:icon {:light {:color colors/neutral-100}
|
||||
:dark {:color colors/white}}
|
||||
:action-container {:light {:background-color :colors/neutral-80-opa-5}
|
||||
:dark {:background-color :colors/white-opa-5}}})
|
||||
{:container {:dark {:background-color colors/white-opa-70}
|
||||
:light {:background-color colors/neutral-80-opa-70}}
|
||||
:text {:dark {:color colors/neutral-100}
|
||||
:light {:color colors/white}}
|
||||
:icon {:dark {:color colors/neutral-100}
|
||||
:light {:color colors/white}}
|
||||
:action-container {:dark {:background-color :colors/neutral-80-opa-5}
|
||||
:light {:background-color :colors/white-opa-5}}})
|
||||
|
||||
(defn- merge-theme-style
|
||||
[component-key styles]
|
||||
@ -23,7 +23,9 @@
|
||||
|
||||
(defn toast-action-container
|
||||
[{:keys [on-press style]} & children]
|
||||
[rn/touchable-highlight {:on-press on-press}
|
||||
[rn/touchable-highlight
|
||||
{:on-press on-press
|
||||
:underlay-color :transparent}
|
||||
[into
|
||||
[rn/view
|
||||
{:style (merge
|
||||
@ -40,7 +42,8 @@
|
||||
|
||||
(defn toast-undo-action
|
||||
[duration on-press]
|
||||
[toast-action-container {:on-press on-press}
|
||||
[toast-action-container
|
||||
{:on-press on-press :accessibility-label :toast-undo-action}
|
||||
[rn/view {:style {:margin-right 5}}
|
||||
[count-down-circle/circle-timer {:duration duration}]]
|
||||
[text/text
|
||||
@ -63,7 +66,10 @@
|
||||
[rn/view {:style {:padding 2}} left]
|
||||
[rn/view {:style {:padding 4 :flex 1}}
|
||||
[text/text
|
||||
{:size :paragraph-2 :weight :medium :style (merge-theme-style :text {})}
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (merge-theme-style :text {})
|
||||
:accessibility-label :toast-content}
|
||||
middle]]
|
||||
(when right right)]])
|
||||
|
||||
|
@ -101,4 +101,4 @@
|
||||
[f]
|
||||
(let [fn-ref (use-ref f)]
|
||||
(oops/oset! fn-ref "current" f)
|
||||
(use-effect-once (fn [] #((oops/oget fn-ref "current"))))))
|
||||
(use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current"))))))
|
||||
|
@ -12,10 +12,13 @@
|
||||
cancelAnimation
|
||||
SlideInUp
|
||||
SlideOutUp
|
||||
LinearTransition)]
|
||||
LinearTransition
|
||||
enableLayoutAnimations)]
|
||||
[clojure.string :as string]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(enableLayoutAnimations true)
|
||||
|
||||
;; Animations
|
||||
(def slide-in-up-animation SlideInUp)
|
||||
(def slide-out-up-animation SlideOutUp)
|
||||
|
@ -1,8 +1,8 @@
|
||||
(ns status-im.chat.models.message-list
|
||||
(:require ["functional-red-black-tree" :as rb-tree]
|
||||
[status-im.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.datetime :as datetime]))
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- add-datemark
|
||||
[{:keys [whisper-timestamp] :as msg}]
|
||||
@ -20,6 +20,7 @@
|
||||
from
|
||||
outgoing
|
||||
whisper-timestamp
|
||||
deleted?
|
||||
deleted-for-me?
|
||||
albumize?]}]
|
||||
(-> {:whisper-timestamp whisper-timestamp
|
||||
@ -29,6 +30,7 @@
|
||||
(or
|
||||
(= constants/message-type-private-group-system-message
|
||||
message-type)
|
||||
deleted?
|
||||
deleted-for-me?))
|
||||
:clock-value clock-value
|
||||
:type :message
|
||||
|
@ -26,6 +26,11 @@
|
||||
(.damping 20)
|
||||
(.stiffness 300)))
|
||||
|
||||
(defn toast
|
||||
[id]
|
||||
(let [toast-opts (rf/sub [:toasts/toast id])]
|
||||
[quo/toast toast-opts]))
|
||||
|
||||
(defn container
|
||||
[id]
|
||||
(let [dismissed-locally? (reagent/atom false)
|
||||
@ -35,13 +40,11 @@
|
||||
(fn []
|
||||
[:f>
|
||||
(fn []
|
||||
(let [toast-opts (rf/sub [:toasts/toast id])
|
||||
duration (get toast-opts :duration 3000)
|
||||
on-dismissed #((get toast-opts :on-dismissed identity) id)
|
||||
translate-y (reanimated/use-shared-value 0)
|
||||
(let [duration (or (rf/sub [:toasts/toast-cursor id :duration]) 3000)
|
||||
on-dismissed #((or (rf/sub [:toasts/toast-cursor id :on-dismissed]) identity) id)
|
||||
create-timer (fn []
|
||||
(reset! timer (utils.utils/set-timeout #(do (close!) (on-dismissed))
|
||||
duration)))
|
||||
(reset! timer (utils.utils/set-timeout close! duration)))
|
||||
translate-y (reanimated/use-shared-value 0)
|
||||
pan
|
||||
(->
|
||||
(gesture/gesture-pan)
|
||||
@ -84,7 +87,7 @@
|
||||
:style (reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}]}
|
||||
style/each-toast-container)}
|
||||
[quo/toast toast-opts]]]))])))
|
||||
[toast id]]]))])))
|
||||
|
||||
(defn toasts
|
||||
[]
|
||||
@ -92,6 +95,4 @@
|
||||
[into
|
||||
[rn/view
|
||||
{:style style/outmost-transparent-container}]
|
||||
(->> toasts-ordered
|
||||
reverse
|
||||
(map (fn [id] ^{:key id} [container id])))]))
|
||||
(map (fn [id] ^{:key id} [container id]) toasts-ordered)]))
|
||||
|
@ -1,6 +1,6 @@
|
||||
(ns status-im2.contexts.chat.messages.content.deleted.view
|
||||
(:require [quo2.core :as quo]
|
||||
[i18n.i18n :as i18n]))
|
||||
(:require [i18n.i18n :as i18n]
|
||||
[quo2.core :as quo]))
|
||||
|
||||
(defn deleted-message
|
||||
[{:keys [deleted? deleted-undoable-till timestamp-str deleted-for-me-undoable-till]}]
|
||||
|
@ -1,8 +1,11 @@
|
||||
(ns status-im2.contexts.chat.messages.delete-message.events
|
||||
(:require [status-im.chat.models.message-list :as message-list]
|
||||
[utils.datetime :as datetime]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
(:require
|
||||
[i18n.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- update-db-clear-undo-timer
|
||||
[db chat-id message-id]
|
||||
@ -20,8 +23,7 @@
|
||||
[:messages chat-id message-id]
|
||||
assoc
|
||||
:deleted? true
|
||||
:deleted-undoable-till (+ (datetime/timestamp)
|
||||
undo-time-limit-ms))))
|
||||
:deleted-undoable-till (+ (datetime/timestamp) undo-time-limit-ms))))
|
||||
|
||||
(defn- update-db-undo-locally
|
||||
"Restore deleted message if called within timelimit"
|
||||
@ -48,15 +50,43 @@
|
||||
{:events [:chat.ui/delete-message]}
|
||||
[{:keys [db]} {:keys [chat-id message-id]} undo-time-limit-ms]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
(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-and-send
|
||||
{:chat-id chat-id
|
||||
:message-id message-id}]
|
||||
:ms undo-time-limit-ms}])))
|
||||
;; all delete message toast are the same toast with id :delete-message-for-everyone
|
||||
;; new delete operation will reset prev pending deletes' undo timelimit
|
||||
;; undo will undo all pending deletes
|
||||
;; all pending deletes are stored in toast
|
||||
(let [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}))]
|
||||
(assoc
|
||||
(message-list/rebuild-message-list
|
||||
{:db (reduce
|
||||
;; sync all pending deletes' undo timelimit, extend to the latest one
|
||||
(fn [db-acc {:keys [chat-id message-id]}]
|
||||
(update-db-delete-locally db-acc chat-id message-id undo-time-limit-ms))
|
||||
db
|
||||
existing-undos)}
|
||||
chat-id)
|
||||
|
||||
:dispatch-n
|
||||
[[:toasts/close :delete-message-for-everyone]
|
||||
[:toasts/upsert :delete-message-for-everyone
|
||||
{:icon :info
|
||||
:icon-color colors/danger-50-opa-40
|
||||
:message-deleted-for-everyone-count toast-count
|
||||
:message-deleted-for-everyone-undos existing-undos
|
||||
:text (i18n/label-pluralize
|
||||
toast-count
|
||||
:t/message-deleted-for-everyone-count)
|
||||
:duration undo-time-limit-ms
|
||||
: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]))}]]
|
||||
:utils/dispatch-later [{:dispatch [:chat.ui/delete-message-and-send
|
||||
{:chat-id chat-id :message-id message-id}]
|
||||
:ms undo-time-limit-ms}]))))
|
||||
|
||||
(rf/defn undo
|
||||
{:events [:chat.ui/undo-delete-message]}
|
||||
@ -66,22 +96,41 @@
|
||||
{:db (update-db-undo-locally db chat-id message-id)}
|
||||
chat-id)))
|
||||
|
||||
(rf/defn undo-all
|
||||
{:events [:chat.ui/undo-all-delete-message]}
|
||||
[{:keys [db]}]
|
||||
(when-let [pending-undos (get-in db
|
||||
[:toasts :toasts :delete-message-for-everyone
|
||||
:message-deleted-for-everyone-undos])]
|
||||
{:dispatch-n (mapv #(vector :chat.ui/undo-delete-message %) pending-undos)}))
|
||||
|
||||
(defn- check-before-delete-and-send
|
||||
"make sure message alredy deleted? locally and undo timelimit has passed"
|
||||
[db chat-id message-id]
|
||||
(let [message (get-in db [:messages chat-id message-id])
|
||||
{:keys [deleted? deleted-undoable-till]} message]
|
||||
(and deleted?
|
||||
deleted-undoable-till
|
||||
(>= (datetime/timestamp) deleted-undoable-till))))
|
||||
|
||||
(rf/defn delete-and-send
|
||||
{:events [:chat.ui/delete-message-and-send]}
|
||||
[{:keys [db]} {:keys [message-id chat-id]}]
|
||||
[{:keys [db]} {:keys [message-id chat-id]} force?]
|
||||
(when-let [message (get-in db [:messages 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}]))))
|
||||
(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}])))))
|
||||
|
||||
(defn- filter-pending-send-messages
|
||||
"traverse all messages find not yet synced deleted? messages"
|
||||
@ -96,7 +145,7 @@
|
||||
{:events [:chat.ui/send-all-deleted-messages]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [pending-send-messages (reduce-kv filter-pending-send-messages [] (:messages db))]
|
||||
(apply rf/merge cofx (map delete-and-send pending-send-messages))))
|
||||
(apply rf/merge cofx (map #(delete-and-send % true) pending-send-messages))))
|
||||
|
||||
(rf/defn delete-messages-localy
|
||||
"Mark messages :deleted? localy in client"
|
||||
|
@ -15,11 +15,46 @@
|
||||
(let [result-message (get-in (delete-message/delete {:db db} message 1000)
|
||||
[:db :messages cid mid])]
|
||||
(is (= (:id result-message) mid))
|
||||
(is (true? (:deleted? result-message)))
|
||||
(is (= (:deleted-undoable-till result-message) 1001))))
|
||||
(is (true? (:deleted? result-message)) "mark message :deleted?")
|
||||
(is (= (:deleted-undoable-till result-message) 1001) "set message undo timelimit")))
|
||||
(testing "delete with pending deletes"
|
||||
(let [db (-> db
|
||||
(update-in [:messages cid "pending-delete-message"]
|
||||
assoc
|
||||
:deleted? true
|
||||
:deleted-undoable-till 0
|
||||
:whisper-timestamp 0)
|
||||
(update-in [:toasts :toasts :delete-message-for-everyone]
|
||||
assoc
|
||||
:message-deleted-for-everyone-count 1
|
||||
:message-deleted-for-everyone-undos [{:message-id
|
||||
"pending-delete-message"
|
||||
:chat-id cid}]))
|
||||
effects (delete-message/delete {:db db} message 1000)]
|
||||
(is (= (get-in effects [:db :messages cid mid :deleted-undoable-till])
|
||||
(get-in effects [:db :messages cid "pending-delete-message" :deleted-undoable-till])
|
||||
1001)
|
||||
"sync all pending delete undo timelimit")
|
||||
(let [upsert-toast (-> effects :dispatch-n second)]
|
||||
(is (= (-> upsert-toast last :message-deleted-for-everyone-count) 2)
|
||||
"+1 pending deletes")
|
||||
(is
|
||||
(and
|
||||
(-> upsert-toast
|
||||
last
|
||||
:message-deleted-for-everyone-undos
|
||||
first
|
||||
:message-id
|
||||
(= "pending-delete-message"))
|
||||
(-> upsert-toast
|
||||
last
|
||||
:message-deleted-for-everyone-undos
|
||||
second
|
||||
:message-id
|
||||
(= mid)))
|
||||
"pending deletes are in order"))))
|
||||
(testing "return nil if message not in db"
|
||||
(is (= (delete-message/delete {:db {:messages []}} message 1000)
|
||||
nil)))))))
|
||||
(is (= (delete-message/delete {:db {:messages []}} message 1000) nil)))))))
|
||||
|
||||
(deftest undo-delete
|
||||
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
|
||||
@ -30,14 +65,11 @@
|
||||
[:messages cid mid]
|
||||
assoc
|
||||
:deleted? true
|
||||
:deleted-undoable-till
|
||||
(+ (datetime/timestamp) 1000))
|
||||
result-message (get-in (delete-message/undo {:db db} message)
|
||||
[:db :messages cid mid])]
|
||||
:deleted-undoable-till (+ (datetime/timestamp) 1000))
|
||||
result-message (get-in (delete-message/undo {:db db} message) [:db :messages cid mid])]
|
||||
(is (= (:id result-message) mid))
|
||||
(is (nil? (:deleted? result-message)))
|
||||
(is (nil? (:deleted-undoable-till result-message)))))
|
||||
|
||||
(testing "remain deleted when undo after timelimit"
|
||||
(let [db (update-in db
|
||||
[:messages cid mid]
|
||||
@ -48,47 +80,46 @@
|
||||
(is (= (:id result-message) mid))
|
||||
(is (nil? (:deleted-undoable-till result-message)))
|
||||
(is (true? (:deleted? result-message)))))
|
||||
|
||||
(testing "return nil if message not in db"
|
||||
(is (= (delete-message/undo {:db {:messages []}} message)
|
||||
nil))))))
|
||||
(is (= (delete-message/undo {:db {:messages []}} message) nil))))))
|
||||
|
||||
(deftest delete-and-send
|
||||
(let [db {:messages {cid {mid {:id mid}}}}
|
||||
(let [db {:messages {cid {mid {:id mid :deleted? true :deleted-undoable-till 0}}}}
|
||||
message {:message-id mid :chat-id cid}]
|
||||
(testing "delete and send"
|
||||
(testing "dispatch right rpc call fx"
|
||||
(let [expected-db {:messages {cid {mid {:id mid}}}}
|
||||
effects (delete-message/delete-and-send {:db db} message)
|
||||
(let [expected-db {:messages {cid {mid {:id mid :deleted? true}}}}
|
||||
effects (delete-message/delete-and-send {:db db} message false)
|
||||
result-db (:db effects)
|
||||
rpc-calls (:json-rpc/call effects)]
|
||||
(is (= result-db expected-db))
|
||||
(is (= (count rpc-calls) 1))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:method)
|
||||
"wakuext_deleteMessageAndSend"))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:params
|
||||
count)
|
||||
1))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:params
|
||||
first)
|
||||
mid))))
|
||||
(is (= (-> rpc-calls first :method) "wakuext_deleteMessageAndSend"))
|
||||
(is (= (-> rpc-calls first :params count) 1))
|
||||
(is (= (-> rpc-calls first :params first) mid))))
|
||||
(testing "clean undo timer"
|
||||
(let [expected-db {:messages {cid {mid {:id mid}}}}
|
||||
(let [expected-db {:messages {cid {mid {:id mid :deleted? true}}}}
|
||||
effects (delete-message/delete-and-send
|
||||
{:db (update-in db
|
||||
[:messages cid mid
|
||||
:deleted-undoable-till]
|
||||
[:messages cid mid :deleted-undoable-till]
|
||||
(constantly (datetime/timestamp)))}
|
||||
message)
|
||||
message
|
||||
false)
|
||||
result-db (:db effects)]
|
||||
(is (= result-db expected-db))))
|
||||
(testing "before deleted locally"
|
||||
(let [effects (delete-message/delete-and-send
|
||||
{:db (update-in db [:messages cid mid] dissoc :deleted?)}
|
||||
message
|
||||
false)]
|
||||
(is (-> effects :db nil?) "not delete and send")))
|
||||
(testing "before undo timelimit"
|
||||
(with-redefs [datetime/timestamp (constantly 1)]
|
||||
(let [effects (delete-message/delete-and-send
|
||||
{:db (update-in db [:messages cid mid] assoc :deleted-undoable-till 2)}
|
||||
message
|
||||
false)]
|
||||
(is (-> effects :db nil?)))))
|
||||
(testing "return nil if message not in db"
|
||||
(is (= (delete-message/delete-and-send {:db {:messages []}}
|
||||
message)
|
||||
(is (= (delete-message/delete-and-send {:db {:messages []}} message false)
|
||||
nil))))))
|
||||
|
@ -1,8 +1,11 @@
|
||||
(ns status-im2.contexts.chat.messages.delete-message-for-me.events
|
||||
(:require [status-im.chat.models.message-list :as message-list]
|
||||
[utils.datetime :as datetime]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
(:require
|
||||
[i18n.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- update-db-clear-undo-timer
|
||||
[db chat-id message-id]
|
||||
@ -42,15 +45,37 @@
|
||||
{: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])
|
||||
(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}])))
|
||||
(let [existing-undo-toast (get-in db [:toasts :toasts :delete-message-for-me])
|
||||
toast-count (inc (get existing-undo-toast :message-deleted-for-me-count 0))
|
||||
existing-undos (-> existing-undo-toast
|
||||
(get :message-deleted-for-me-undos [])
|
||||
(conj {:message-id message-id :chat-id chat-id}))]
|
||||
(assoc
|
||||
(message-list/rebuild-message-list
|
||||
{:db (reduce
|
||||
;; sync all pending deletes' undo timelimit, extend to the latest one
|
||||
(fn [db-acc {:keys [message-id chat-id]}]
|
||||
(update-db-delete-locally db-acc chat-id message-id undo-time-limit-ms))
|
||||
db
|
||||
existing-undos)}
|
||||
chat-id)
|
||||
:dispatch-n [[:toasts/close :delete-message-for-me]
|
||||
[:toasts/upsert :delete-message-for-me
|
||||
{:icon :info
|
||||
:icon-color colors/danger-50-opa-40
|
||||
:message-deleted-for-me-count toast-count
|
||||
:message-deleted-for-me-undos existing-undos
|
||||
:text (i18n/label-pluralize toast-count
|
||||
:t/message-deleted-for-you-count)
|
||||
:duration undo-time-limit-ms
|
||||
:undo-duration (/ undo-time-limit-ms 1000)
|
||||
:undo-on-press #(do (rf/dispatch
|
||||
[:chat.ui/undo-all-delete-message-for-me])
|
||||
(rf/dispatch [:toasts/close
|
||||
:delete-message-for-me]))}]]
|
||||
: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}]))))
|
||||
|
||||
(rf/defn undo
|
||||
{:events [:chat.ui/undo-delete-message-for-me]}
|
||||
@ -60,17 +85,37 @@
|
||||
{:db (update-db-undo-locally db chat-id message-id)}
|
||||
chat-id)))
|
||||
|
||||
(rf/defn undo-all
|
||||
{:events [:chat.ui/undo-all-delete-message-for-me]}
|
||||
[{:keys [db]}]
|
||||
(when-let [pending-undos (get-in db
|
||||
[:toasts :toasts :delete-message-for-me
|
||||
:message-deleted-for-me-undos])]
|
||||
{:dispatch-n (mapv #(vector :chat.ui/undo-delete-message-for-me %) pending-undos)}))
|
||||
|
||||
(defn- check-before-delete-and-sync
|
||||
"Make sure message alredy deleted-for-me? locally and undo timelimit has passed"
|
||||
[db chat-id message-id]
|
||||
(let [message (get-in db [:messages chat-id message-id])
|
||||
{:keys [deleted-for-me? deleted-for-me-undoable-till]} message]
|
||||
(and deleted-for-me?
|
||||
deleted-for-me-undoable-till
|
||||
(>= (datetime/timestamp) deleted-for-me-undoable-till))))
|
||||
|
||||
(rf/defn delete-and-sync
|
||||
{:events [:chat.ui/delete-message-for-me-and-sync]}
|
||||
[{:keys [db]} {:keys [message-id chat-id]}]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
{:db (update-db-clear-undo-timer db chat-id message-id)
|
||||
:json-rpc/call [{:method "wakuext_deleteMessageForMeAndSync"
|
||||
:params [chat-id message-id]
|
||||
:js-response true
|
||||
:on-error #(log/error "failed to delete message for me, message id: "
|
||||
{:message-id message-id :error %})
|
||||
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])}]}))
|
||||
[{: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-sync db chat-id message-id))
|
||||
{:db (update-db-clear-undo-timer db chat-id message-id)
|
||||
:json-rpc/call [{:method "wakuext_deleteMessageForMeAndSync"
|
||||
:params [chat-id message-id]
|
||||
:js-response true
|
||||
:on-error #(log/error
|
||||
"failed to delete message for me, message id: "
|
||||
{:message-id message-id :error %})
|
||||
:on-success #(rf/dispatch [:sanitize-messages-and-process-response
|
||||
%])}]})))
|
||||
|
||||
(defn- filter-pending-sync-messages
|
||||
"traverse all messages find not yet synced deleted-for-me? messages"
|
||||
@ -86,4 +131,4 @@
|
||||
{:events [:chat.ui/sync-all-deleted-for-me-messages]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [pending-sync-messages (reduce-kv filter-pending-sync-messages [] (:messages db))]
|
||||
(apply rf/merge cofx (map delete-and-sync pending-sync-messages))))
|
||||
(apply rf/merge cofx (map #(delete-and-sync % true) pending-sync-messages))))
|
||||
|
@ -18,6 +18,43 @@
|
||||
(is (= (:id result-message) mid))
|
||||
(is (true? (:deleted-for-me? result-message)))
|
||||
(is (= (:deleted-for-me-undoable-till result-message) 1001))))
|
||||
(testing "delete with pending deletes"
|
||||
(let [db (-> db
|
||||
(update-in [:messages cid "pending-delete-message"]
|
||||
assoc
|
||||
:deleted-for-me? true
|
||||
:deleted-for-me-undoable-till 0
|
||||
:whisper-timestamp 0)
|
||||
(update-in [:toasts :toasts :delete-message-for-me]
|
||||
assoc
|
||||
:message-deleted-for-me-count 1
|
||||
:message-deleted-for-me-undos [{:message-id
|
||||
"pending-delete-message"
|
||||
:chat-id cid}]))
|
||||
effects (delete-message-for-me/delete {:db db} message 1000)]
|
||||
(is (= (get-in effects [:db :messages cid mid :deleted-for-me-undoable-till])
|
||||
(get-in effects
|
||||
[:db :messages cid "pending-delete-message" :deleted-for-me-undoable-till])
|
||||
1001)
|
||||
"sync all pending delete undo timelimit")
|
||||
(let [upsert-toast (-> effects :dispatch-n second)]
|
||||
(is (= (-> upsert-toast last :message-deleted-for-me-count) 2)
|
||||
"+1 pending deletes")
|
||||
(is
|
||||
(and
|
||||
(-> upsert-toast
|
||||
last
|
||||
:message-deleted-for-me-undos
|
||||
first
|
||||
:message-id
|
||||
(= "pending-delete-message"))
|
||||
(-> upsert-toast
|
||||
last
|
||||
:message-deleted-for-me-undos
|
||||
second
|
||||
:message-id
|
||||
(= mid)))
|
||||
"pending deletes are in order"))))
|
||||
(testing "return nil if message not in db"
|
||||
(is (= (delete-message-for-me/delete {:db {:messages []}} message 1000)
|
||||
nil)))))))
|
||||
@ -56,46 +93,42 @@
|
||||
nil))))))
|
||||
|
||||
(deftest delete-for-me-and-sync
|
||||
(let [db {:messages {cid {mid {:id mid}}}}
|
||||
(let [db {:messages {cid {mid {:id mid :deleted-for-me? true :deleted-for-me-undoable-till 0}}}}
|
||||
message {:message-id mid :chat-id cid}]
|
||||
(testing "delete for me and sync"
|
||||
(testing "dispatch right rpc call"
|
||||
(let [expected-db {:messages {cid {mid {:id mid}}}}
|
||||
effects (delete-message-for-me/delete-and-sync {:db db} message)
|
||||
(let [expected-db {:messages {cid {mid {:id mid :deleted-for-me? true}}}}
|
||||
effects (delete-message-for-me/delete-and-sync {:db db} message false)
|
||||
result-db (:db effects)
|
||||
rpc-calls (:json-rpc/call effects)]
|
||||
(is (= result-db expected-db))
|
||||
(is (= (count rpc-calls) 1))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:method)
|
||||
"wakuext_deleteMessageForMeAndSync"))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:params
|
||||
count)
|
||||
2))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:params
|
||||
first)
|
||||
cid))
|
||||
(is (= (-> rpc-calls
|
||||
first
|
||||
:params
|
||||
second)
|
||||
mid))))
|
||||
(is (= (-> rpc-calls first :method) "wakuext_deleteMessageForMeAndSync"))
|
||||
(is (= (-> rpc-calls first :params count) 2))
|
||||
(is (= (-> rpc-calls first :params first) cid))
|
||||
(is (= (-> rpc-calls first :params second) mid))))
|
||||
(testing "clean undo timer"
|
||||
(let [expected-db {:messages {cid {mid {:id mid}}}}
|
||||
(let [expected-db {:messages {cid {mid {:id mid :deleted-for-me? true}}}}
|
||||
effects (delete-message-for-me/delete-and-sync
|
||||
{:db (update-in db
|
||||
[:messages cid mid
|
||||
:deleted-for-me-undoable-till]
|
||||
[:messages cid mid :deleted-for-me-undoable-till]
|
||||
(constantly (datetime/timestamp)))}
|
||||
message)
|
||||
message
|
||||
false)
|
||||
result-db (:db effects)]
|
||||
(is (= result-db expected-db))))
|
||||
(testing "before deleted locally"
|
||||
(let [effects (delete-message-for-me/delete-and-sync
|
||||
{:db (update-in db [:messages cid mid] dissoc :deleted-for-me?)}
|
||||
message
|
||||
false)]
|
||||
(is (-> effects :db nil?) "not delete and send")))
|
||||
(testing "before undo timelimit"
|
||||
(with-redefs [datetime/timestamp (constantly 1)]
|
||||
(let [effects (delete-message-for-me/delete-and-sync
|
||||
{:db (update-in db [:messages cid mid] assoc :deleted-for-me-undoable-till 2)}
|
||||
message
|
||||
false)]
|
||||
(is (-> effects :db nil?)))))
|
||||
(testing "return nil if message not in db"
|
||||
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}}
|
||||
message)
|
||||
nil))))))
|
||||
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}} message false) nil))))))
|
||||
|
@ -34,6 +34,7 @@
|
||||
:chat/spam-messages-frequency 0
|
||||
:chats-home-list #{}
|
||||
:home-items-show-number 20
|
||||
:toasts {:ordered '() :toasts {}}
|
||||
:tooltips {}
|
||||
:dimensions/window (rn/get-window)
|
||||
:registry {}
|
||||
|
@ -6,3 +6,9 @@
|
||||
:<- [:toasts]
|
||||
(fn [toasts [_ toast-id]]
|
||||
(get-in toasts [:toasts toast-id])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:toasts/toast-cursor
|
||||
:<- [:toasts]
|
||||
(fn [toasts [_ toast-id & cursor]]
|
||||
(get-in toasts (into [:toasts toast-id] cursor))))
|
||||
|
@ -878,7 +878,15 @@
|
||||
"message": "Message",
|
||||
"message-deleted": "Message deleted",
|
||||
"message-deleted-for-everyone": "Message deleted for everyone",
|
||||
"message-deleted-for-everyone-count": {
|
||||
"one": "1 message deleted for everyone",
|
||||
"other": "{{count}} messages deleted for everyone"
|
||||
},
|
||||
"message-deleted-for-you": "Message deleted for you",
|
||||
"message-deleted-for-you-count": {
|
||||
"one": "1 message deleted for you",
|
||||
"other": "{{count}} messages deleted for you"
|
||||
},
|
||||
"message-not-sent": "Message not sent",
|
||||
"message-options-cancel": "Cancel",
|
||||
"message-reply": "Reply",
|
||||
|
Loading…
x
Reference in New Issue
Block a user