Display contact verification requests and allow users to decline them (#14223)

This commit is contained in:
Icaro Motta 2022-10-26 07:48:07 -03:00 committed by GitHub
parent 2ae57f7b21
commit 910bafc5f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 514 additions and 196 deletions

View File

@ -1,6 +1,6 @@
(ns status-im.activity-center.core
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
(:require [re-frame.core :as rf]
[status-im.constants :as const]
[status-im.data-store.activities :as data-store.activities]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]
@ -18,21 +18,23 @@
~excessively~ big, this implementation will probably need to be revisited."
[db-notifications new-notifications]
(reduce (fn [acc {:keys [id type read] :as notification}]
(let [filter-status (if read :read :unread)]
(cond-> (-> acc
(update-in [type :read :data]
(fn [data]
(remove #(= id (:id %)) data)))
(update-in [type :unread :data]
(fn [data]
(remove #(= id (:id %)) data))))
(not (or (:dismissed notification) (:accepted notification)))
(update-in [type filter-status :data]
(fn [data]
(->> notification
(conj data)
(sort-by (juxt :timestamp :id))
reverse))))))
(let [filter-status (if read :read :unread)
remove-notification (fn [data]
(remove #(= id (:id %)) data))
insert-and-sort (fn [data]
(->> notification
(conj data)
(sort-by (juxt :timestamp :id))
reverse))]
(as-> acc $
(update-in $ [type :read :data] remove-notification)
(update-in $ [type :unread :data] remove-notification)
(update-in $ [const/activity-center-notification-type-no-type :read :data] remove-notification)
(update-in $ [const/activity-center-notification-type-no-type :unread :data] remove-notification)
(if (or (:dismissed notification) (:accepted notification))
$
(-> $ (update-in [type filter-status :data] insert-and-sort)
(update-in [const/activity-center-notification-type-no-type filter-status :data] insert-and-sort))))))
db-notifications
new-notifications))
@ -43,11 +45,37 @@
{:db (update-in db [:activity-center :notifications]
update-notifications new-notifications)}))
;;;; Contact verification
(fx/defn contact-verification-decline
{:events [:activity-center.contact-verification/decline]}
[_ contact-verification-id]
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
:params [contact-verification-id]
:on-success #(rf/dispatch [:activity-center.contact-verification/decline-success %])
:on-error #(rf/dispatch [:activity-center.contact-verification/decline-error contact-verification-id %])}]})
(fx/defn contact-verification-decline-success
{:events [:activity-center.contact-verification/decline-success]}
[cofx response]
(->> response
:activityCenterNotifications
(map data-store.activities/<-rpc)
(notifications-reconcile cofx)))
(fx/defn contact-verification-decline-error
{:events [:activity-center.contact-verification/decline-error]}
[_ contact-verification-id error]
(log/warn "Failed to decline contact verification"
{:contact-verification-id contact-verification-id
:error error})
nil)
;;;; Notifications fetching and pagination
(def defaults
{:filter-status :unread
:filter-type constants/activity-center-notification-type-no-type
:filter-type const/activity-center-notification-type-no-type
:notifications-per-page 10})
(def start-or-end-cursor
@ -70,8 +98,8 @@
{:db (assoc-in db [:activity-center :notifications filter-type filter-status :loading?] true)
::json-rpc/call [{:method (filter-status->rpc-method filter-status)
:params [cursor (defaults :notifications-per-page) filter-type]
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
(fx/defn notifications-fetch-first-page
{:events [:activity-center.notifications/fetch-first-page]}

View File

@ -2,7 +2,7 @@
(:require [cljs.test :refer [deftest is testing]]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.constants :as c]
[status-im.constants :as const]
[status-im.ethereum.json-rpc :as json-rpc]
status-im.events
[status-im.test-helpers :as h]))
@ -12,8 +12,8 @@
(rf/dispatch [:init/app-started]))
(defn remove-color-key
"Remove `:color` key from notifications because they have random values that we
can't assert against."
"Remove `:color` key from notifications because they have random values that are
inconvenient to assert against."
[grouped-notifications {:keys [type status]}]
(update-in grouped-notifications
[type status :data]
@ -21,27 +21,117 @@
(map #(dissoc % :color) old))
nil))
;;;; Contact verification
(deftest contact-verification-decline-test
(testing "successfully declines and reconciles returned notification"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])
contact-verification-id 24
expected-notification {:accepted false
:author "0x04d03f"
:chat-id "0x04d03f"
:contact-verification-status 3
:dismissed false
:id 24
:last-message nil
:message {:command-parameters nil
:content {:chat-id nil
:ens-name nil
:image nil
:line-count nil
:links nil
:parsed-text nil
:response-to nil
:rtl? nil
:sticker nil
:text nil}
:outgoing false
:outgoing-status nil
:quoted-message nil}
:name "0x04d03f"
:read true
:reply-message nil
:timestamp 1666647286000
:type const/activity-center-notification-type-contact-verification}]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:activityCenterNotifications
[{:accepted false
:author "0x04d03f"
:chatId "0x04d03f"
:contactVerificationStatus 3
:dismissed false
:id contact-verification-id
:message {}
:name "0x04d03f"
:read true
:timestamp 1666647286000
:type const/activity-center-notification-type-contact-verification}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
(is (= {:method "wakuext_declineContactVerificationRequest"
:params [contact-verification-id]}
(-> @spy-queue
(get-in [0 :args 0])
(select-keys [:method :params]))))
(is (= {const/activity-center-notification-type-no-type
{:read {:data [expected-notification]}
:unread {:data []}}
const/activity-center-notification-type-contact-verification
{:read {:data [expected-notification]}
:unread {:data []}}}
(-> (h/db)
(get-in [:activity-center :notifications])
(remove-color-key {:type const/activity-center-notification-type-no-type
:status :read})
(remove-color-key {:type const/activity-center-notification-type-contact-verification
:status :read})))))))
(testing "logs failure"
(rf-test/run-test-sync
(setup)
(let [contact-verification-id 666]
(h/using-log-test-appender
(fn [logs]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
(is (= {:args ["Failed to decline contact verification"
{:contact-verification-id contact-verification-id
:error :fake-error}]
:level :warn}
(last @logs)))))))))
;;;; Notification reconciliation
(deftest notifications-reconcile-test
(testing "does nothing when there are no new notifications"
(rf-test/run-test-sync
(setup)
(let [notifications {c/activity-center-notification-type-one-to-one-chat
(let [notifications {const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}
:type const/activity-center-notification-type-one-to-one-chat}
{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}]
:type const/activity-center-notification-type-private-group-chat}]}}}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(rf/dispatch [:activity-center.notifications/reconcile nil])
@ -52,96 +142,127 @@
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat}]}
:data [{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat}
{:id "0x2" :read true :type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat}]}}
2 {:unread {:cursor ""
:data [{:id "0x4" :read false :type 2}
{:id "0x6" :read false :type 2}]}}}])
:data [{:id "0x3" :read false :type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4" :read false :type const/activity-center-notification-type-private-group-chat}
{:id "0x6" :read false :type const/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:dismissed true}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:accepted true}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-private-group-chat
:dismissed true}
{:id "0x5"
:read false
:type c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-private-group-chat
:accepted true}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
(is (= {const/activity-center-notification-type-no-type
{:read {:data []}
:unread {:data []}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data []}}
c/activity-center-notification-type-private-group-chat
const/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}
:type const/activity-center-notification-type-private-group-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "replaces old notifications with newly arrived ones"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{const/activity-center-notification-type-no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}
:type const/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}])
:type const/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]])
:type const/activity-center-notification-type-private-group-chat}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
(is (= {const/activity-center-notification-type-no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:data []}}
c/activity-center-notification-type-private-group-chat
const/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}
:type const/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}}
(get-in (h/db) [:activity-center :notifications])))))
@ -149,23 +270,29 @@
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}}])
:type const/activity-center-notification-type-one-to-one-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]])
:type const/activity-center-notification-type-one-to-one-chat}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
(is (= {const/activity-center-notification-type-no-type
{:read {:data []}
:unread {:data [{:id "0x1"
:read false
:type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data []}
:unread {:data [{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}}
:type const/activity-center-notification-type-one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
;; Sorting by timestamp and ID is compatible with what the backend does when
@ -174,46 +301,59 @@
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
:data [{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
:data [{:id "0x3" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
[{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
(is (= {const/activity-center-notification-type-no-type
{:read {:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:data [{:id "0x4"
:read false
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100
:last-message {}}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1}
{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x5"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100}
{:id "0x4"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100
:last-message {}}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 50}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
;;;; Notifications fetching and pagination
(deftest notifications-fetch-test
(testing "fetches first page"
(rf-test/run-test-sync
@ -223,22 +363,22 @@
::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:id "0x1"
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type c/activity-center-notification-type-one-to-one-chat}])
{:filter-type const/activity-center-notification-type-one-to-one-chat}])
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with empty cursor when fetching first page")
(is (= {c/activity-center-notification-type-one-to-one-chat
(is (= {const/activity-center-notification-type-one-to-one-chat
{:unread {:cursor "10"
:data [{:chat-id "0x9"
:chat-name nil
:chat-type c/activity-center-notification-type-one-to-one-chat
:chat-type const/activity-center-notification-type-one-to-one-chat
:group-chat false
:id "0x1"
:public? false
@ -246,10 +386,10 @@
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-one-to-one-chat}]}}}
:type const/activity-center-notification-type-one-to-one-chat}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-one-to-one-chat}))))))
:type const/activity-center-notification-type-one-to-one-chat}))))))
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
@ -259,8 +399,8 @@
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@ -278,8 +418,8 @@
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
nil])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@ -294,15 +434,15 @@
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type c/activity-center-notification-type-mention
:type const/activity-center-notification-type-mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-mention :unread :cursor]
const/activity-center-notification-type-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-mention :unread :cursor]
"10"])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@ -310,7 +450,7 @@
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with current cursor")
(is (= {c/activity-center-notification-type-mention
(is (= {const/activity-center-notification-type-mention
{:unread {:cursor ""
:data [{:chat-id "0x9"
:chat-name nil
@ -320,10 +460,10 @@
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-mention}]}}}
:type const/activity-center-notification-type-mention}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-mention}))))))
:type const/activity-center-notification-type-mention}))))))
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
@ -333,10 +473,10 @@
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:read])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :cursor]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :read :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :loading?]
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :read :loading?]
true])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@ -353,15 +493,15 @@
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (nil? (get-in (h/db) [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
c/activity-center-notification-type-one-to-one-chat
const/activity-center-notification-type-one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue))))))))

View File

@ -22,6 +22,13 @@
(def ^:const contact-request-state-received 3)
(def ^:const contact-request-state-dismissed 4)
(def ^:const contact-verification-state-unknown 0)
(def ^:const contact-verification-state-pending 1)
(def ^:const contact-verification-state-accepted 2)
(def ^:const contact-verification-state-declined 3)
(def ^:const contact-verification-state-cancelled 4)
(def ^:const contact-verification-state-trusted 5)
(def ^:const emoji-reaction-love 1)
(def ^:const emoji-reaction-thumbs-up 2)
(def ^:const emoji-reaction-thumbs-down 3)
@ -183,11 +190,15 @@
(def ^:const activity-center-notification-type-mention 3)
(def ^:const activity-center-notification-type-reply 4)
(def ^:const activity-center-notification-type-contact-request 5)
(def ^:const activity-center-notification-type-contact-verification 6)
;; TODO: Remove this constant once the old Notification Center code is removed.
;; Its value clashes with the new constant `activity-center-notification-type-contact-verification`
;; used in status-go.
(def ^:const activity-center-notification-type-contact-request-retracted 6)
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const activity-center-notification-type-admin 66610)
(def ^:const activity-center-notification-type-identity-verification 66611)
(def ^:const activity-center-notification-type-tx 66612)
(def ^:const activity-center-notification-type-membership 66613)
(def ^:const activity-center-notification-type-system 66614)

View File

@ -1,44 +1,44 @@
(ns status-im.data-store.activities
(:require [status-im.data-store.messages :as messages]
[status-im.constants :as constants]
(:require [clojure.set :as set]
[quo.design-system.colors :as colors]
clojure.set))
[status-im.constants :as constants]
[status-im.data-store.messages :as messages]))
(defn rpc->type [{:keys [type name] :as chat}]
(cond
(= constants/activity-center-notification-type-reply type)
(defn- rpc->type [{:keys [type name] :as chat}]
(case type
constants/activity-center-notification-type-reply
(assoc chat
:chat-name name
:chat-type constants/private-group-chat-type)
(= constants/activity-center-notification-type-mention type)
constants/activity-center-notification-type-mention
(assoc chat
:chat-type constants/private-group-chat-type
:chat-name name)
(= constants/activity-center-notification-type-private-group-chat type)
constants/activity-center-notification-type-private-group-chat
(assoc chat
:chat-type constants/private-group-chat-type
:chat-name name
:public? false
:group-chat true)
(= constants/activity-center-notification-type-one-to-one-chat type)
constants/activity-center-notification-type-one-to-one-chat
(assoc chat
:chat-type constants/one-to-one-chat-type
:chat-name name
:public? false
:group-chat false)
:else
chat))
(defn <-rpc [item]
(-> item
rpc->type
(clojure.set/rename-keys {:lastMessage :last-message
:replyMessage :reply-message
:chatId :chat-id})
(set/rename-keys {:lastMessage :last-message
:replyMessage :reply-message
:chatId :chat-id
:contactVerificationStatus :contact-verification-status})
(assoc :color (rand-nth colors/chat-colors))
(update :last-message #(when % (messages/<-rpc %)))
(update :message #(when % (messages/<-rpc %)))

View File

@ -0,0 +1,101 @@
(ns status-im.data-store.activities-test
(:require [cljs.test :refer [deftest is testing]]
[quo.design-system.colors :as colors]
[status-im.constants :as constants]
[status-im.data-store.activities :as store]))
(def chat-id
"0x04c66155")
(def chat-name
"0x04c661")
(def raw-notification
{:chatId chat-id
:contactVerificationStatus constants/contact-verification-state-pending
:lastMessage {}
:name chat-name
:replyMessage {}})
(deftest <-rpc-test
(testing "assocs random chat color"
(is (contains? (set colors/chat-colors) (:color (store/<-rpc raw-notification)))))
(testing "renames keys"
(is (= {:name chat-name
:chat-id chat-id
:contact-verification-status constants/contact-verification-state-pending}
(-> raw-notification
store/<-rpc
(dissoc :color :last-message :message :reply-message)))))
(testing "transforms messages from RPC response"
(is (= {:last-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}
:message nil
:reply-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}}
(-> raw-notification
store/<-rpc
(select-keys [:last-message :message :reply-message])))))
(testing "augments notification based on its type"
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:name chat-name}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-reply)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:name chat-name}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-mention)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:group-chat true
:name chat-name
:public? false}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-private-group-chat)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/one-to-one-chat-type
:group-chat false
:name chat-name
:public? false}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-one-to-one-chat)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))))

View File

@ -20,6 +20,7 @@
(clojure.set/rename-keys {:id :message-id
:whisperTimestamp :whisper-timestamp
:editedAt :edited-at
:contactVerificationState :contact-verification-state
:contactRequestState :contact-request-state
:commandParameters :command-parameters
:gapParameters :gap-parameters

View File

@ -20,6 +20,8 @@
:response-to "a"
:links nil}
:whisper-timestamp 1
:contact-verification-state 1
:contact-request-state 2
:outgoing-status :sending
:command-parameters nil
:outgoing true
@ -35,6 +37,8 @@
:whisperTimestamp 1
:parsedText "parsed-text"
:ensName "ens-name"
:contactVerificationState 1
:contactRequestState 2
:localChatId chat-id
:from from
:text "hta"

View File

@ -6,9 +6,10 @@
prefer to use it for more general purpose concepts, such as the re-frame event
layer."
(:require [re-frame.core :as rf]
[re-frame.registrar :as rf-registrar]
[re-frame.db :as rf-db]
[re-frame.events :as rf-events]
[re-frame.db :as rf-db]))
[re-frame.registrar :as rf-registrar]
[taoensso.timbre :as log]))
(defn db
"A simple wrapper to get the latest value from the app db."
@ -85,3 +86,17 @@
(original-on-error (on-error fx-map))
(and original-on-success on-success)
(original-on-success (on-success fx-map)))))))
(defn using-log-test-appender
"Rebinds `taoensso.timbre/*config*` to use a custom test appender that persists
all `taoensso.timbre/log` call arguments. `f` is called with the atom
reference so that tests can de-reference it and verify log messages and their
respective levels."
[f]
(let [logs (atom [])]
(binding [log/*config* (assoc-in log/*config*
[:appenders :test]
{:enabled? true
:fn (fn [{:keys [vargs level]}]
(swap! logs conj {:args vargs :level level}))})]
(f logs))))

View File

@ -15,98 +15,114 @@
[status-im.utils.handlers :refer [<sub >evt]]
[quo.components.safe-area :as safe-area]))
(defn activity-title
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
(i18n/label :t/contact-request)
;;;; Misc
constants/activity-center-notification-type-one-to-one-chat
"Dummy 1:1 chat title"
(defn sender-name
[contact]
(or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name])))
"Dummy default title"))
(defmulti notification-component :type)
(defn activity-icon
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
:main-icons2/add-user
:main-icons2/placeholder))
;;;; Contact request notifications
(defn activity-context
[{:keys [message last-message type]}]
(case type
constants/activity-center-notification-type-contact-request
(let [message (or message last-message)
contact (<sub [:contacts/contact-by-identity (:from message)])
sender-name (or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name]))]
[[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
sender-name
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(i18n/label :t/contact-request-sent)]])
nil))
(defmethod notification-component constants/activity-center-notification-type-contact-request
[{:keys [id] :as notification}]
(let [message (or (:message notification) (:last-message notification))
contact (<sub [:contacts/contact-by-identity (:author notification)])
pressable (case (:contact-request-state message)
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
;; `:contact.ui/send-message-pressed` instead of
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
;; broken if it has never been opened before for the accepted contact.
[animation/pressable {:on-press (fn []
(>evt [:hide-popover])
(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}]))}]
[:<>])]
(conj pressable
[activity-logs/activity-log
(merge {:title (i18n/label :t/contact-request)
:icon :main-icons2/add-user
:timestamp (datetime/timestamp->relative (:timestamp notification))
:unread? (not (:read notification))
:context [[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
(sender-name contact)
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(i18n/label :t/contact-request-sent)]]
:message {:body (get-in message [:content :text])}
:status (case (:contact-request-state message)
constants/contact-request-message-state-accepted
{:type :positive :label (i18n/label :t/accepted)}
constants/contact-request-message-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil)}
(case (:contact-request-state message)
constants/contact-request-state-mutual
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:contact-requests.ui/decline-request id])}
:button-2 {:label (i18n/label :t/message-reply)
:type :success
:override-background-color colors/success-60
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
nil))])))
(defn activity-message
[{:keys [message last-message]}]
{:body (get-in (or message last-message) [:content :text])})
;;;; Contact verification notifications
(defn activity-status
[notification]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
{:type :positive :label (i18n/label :t/accepted)}
constants/contact-request-message-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil))
(defmethod notification-component constants/activity-center-notification-type-contact-verification
[{:keys [id contact-verification-status] :as notification}]
(let [message (or (:message notification) (:last-notification notification))
contact (<sub [:contacts/contact-by-identity (:author notification)])]
[activity-logs/activity-log
(merge {:title (i18n/label :t/identity-verification-request)
:icon :main-icons2/friend
:timestamp (datetime/timestamp->relative (:timestamp notification))
:unread? (not (:read notification))
:context [[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
(sender-name contact)
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(str (i18n/label :t/identity-verification-request-sent)
":")]]
:message (case contact-verification-status
(constants/contact-verification-state-pending
constants/contact-verification-state-declined)
{:body (get-in message [:content :text])}
nil)
:status (case contact-verification-status
constants/contact-verification-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil)}
(case contact-verification-status
constants/contact-verification-state-pending
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:activity-center.contact-verification/decline id])}
:button-2 {:label (i18n/label :t/accept)
:type :primary
;; TODO: The acceptance flow will be implemented in follow-up PRs.
:on-press identity}}
nil))]))
(defn activity-buttons
[{:keys [id type]}]
(case type
constants/activity-center-notification-type-contact-request
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:contact-requests.ui/decline-request id])}
:button-2 {:label (i18n/label :t/accept)
:type :success
:override-background-color colors/success-60
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
nil))
(defn activity-pressable
[notification activity]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
;; `:contact.ui/send-message-pressed` instead of
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
;; broken if it has never been opened before for the accepted contact.
[animation/pressable {:on-press (fn []
(>evt [:hide-popover])
(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}]))}
activity]
activity))
;;;; Type-independent components
(defn render-notification
[notification index]
[rn/view {:margin-top (if (= 0 index) 0 4)
:padding-horizontal 20}
[activity-pressable notification
[activity-logs/activity-log
(merge {:context (activity-context notification)
:icon (activity-icon notification)
:message (activity-message notification)
:status (activity-status notification)
:timestamp (datetime/timestamp->relative (:timestamp notification))
:title (activity-title notification)
:unread? (not (:read notification))}
(activity-buttons notification))]]])
[notification-component notification]])
(defn filter-selector-read-toggle
[]
@ -164,7 +180,7 @@
:label (i18n/label :t/replies)}
{:id constants/activity-center-notification-type-contact-request
:label (i18n/label :t/contact-requests)}
{:id constants/activity-center-notification-type-identity-verification
{:id constants/activity-center-notification-type-contact-verification
:label (i18n/label :t/identity-verification)}
{:id constants/activity-center-notification-type-tx
:label (i18n/label :t/transactions)}

View File

@ -1819,6 +1819,8 @@
"admin": "Admin",
"replies": "Replies",
"identity-verification": "Identity verification",
"identity-verification-request": "Identity verification request",
"identity-verification-request-sent": "asked you",
"membership": "Membership",
"jump-to": "Jump to",
"blank-messages-text": "Your messages will be here",