Notification reconciliation is now implemented, which means new notifications are "merged" with existing app db notifications. The original implementation was not compatible with the way notifications are fetched by read/unread status. Unit tests were written to cover event handlers in the new status-im.activity-center.core namespace. New test utilities were added for two main reasons: 1) reduce test clutter to arrange data, and 2) to spy on effects to make sure they are dispatched correctly.
This commit is contained in:
parent
ba1ff91cdd
commit
ca144fbe1b
|
@ -94,6 +94,7 @@
|
||||||
:align-items :flex-start
|
:align-items :flex-start
|
||||||
:flex 1}
|
:flex 1}
|
||||||
[status-tags/status-tag {:size :small
|
[status-tags/status-tag {:size :small
|
||||||
|
:label (:label status)
|
||||||
:status status}]])
|
:status status}]])
|
||||||
|
|
||||||
(defn- activity-title
|
(defn- activity-title
|
||||||
|
|
|
@ -5,6 +5,34 @@
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
[taoensso.timbre :as log]))
|
[taoensso.timbre :as log]))
|
||||||
|
|
||||||
|
;;;; Notification reconciliation
|
||||||
|
|
||||||
|
(defn- update-notifications
|
||||||
|
[old new]
|
||||||
|
(let [ids-to-be-removed (->> new
|
||||||
|
(filter #(or (:dismissed %) (:accepted %)))
|
||||||
|
(map :id))
|
||||||
|
grouped-new (apply dissoc (group-by :id new) ids-to-be-removed)
|
||||||
|
grouped-old (apply dissoc (group-by :id old) ids-to-be-removed)]
|
||||||
|
(->> (merge grouped-old grouped-new)
|
||||||
|
vals
|
||||||
|
(map first))))
|
||||||
|
|
||||||
|
(fx/defn notifications-reconcile
|
||||||
|
{:events [:activity-center.notifications/reconcile]}
|
||||||
|
[{:keys [db]} new-notifications]
|
||||||
|
(let [{read-new true
|
||||||
|
unread-new false} (group-by :read new-notifications)
|
||||||
|
read-old (get-in db [:activity-center :notifications-read :data])
|
||||||
|
unread-old (get-in db [:activity-center :notifications-unread :data])]
|
||||||
|
{:db (-> db
|
||||||
|
(assoc-in [:activity-center :notifications-read :data]
|
||||||
|
(update-notifications read-old read-new))
|
||||||
|
(assoc-in [:activity-center :notifications-unread :data]
|
||||||
|
(update-notifications unread-old unread-new)))}))
|
||||||
|
|
||||||
|
;;;; Notifications fetching and pagination
|
||||||
|
|
||||||
(def notifications-per-page
|
(def notifications-per-page
|
||||||
20)
|
20)
|
||||||
|
|
||||||
|
@ -29,11 +57,11 @@
|
||||||
{:db (assoc-in db [:activity-center notifications-group :loading?] true)
|
{:db (assoc-in db [:activity-center notifications-group :loading?] true)
|
||||||
::json-rpc/call [{:method (notifications-group->rpc-method notifications-group)
|
::json-rpc/call [{:method (notifications-group->rpc-method notifications-group)
|
||||||
:params [cursor notifications-per-page]
|
:params [cursor notifications-per-page]
|
||||||
:on-success #(re-frame/dispatch [:activity-center/notifications-fetch-success notifications-group %])
|
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success notifications-group %])
|
||||||
:on-error #(re-frame/dispatch [:activity-center/notifications-fetch-error notifications-group %])}]}))
|
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error notifications-group %])}]}))
|
||||||
|
|
||||||
(fx/defn notifications-fetch-first-page
|
(fx/defn notifications-fetch-first-page
|
||||||
{:events [:activity-center/notifications-fetch-first-page]}
|
{:events [:activity-center.notifications/fetch-first-page]}
|
||||||
[{:keys [db] :as cofx} {:keys [status-filter] :or {status-filter :unread}}]
|
[{:keys [db] :as cofx} {:keys [status-filter] :or {status-filter :unread}}]
|
||||||
(let [notifications-group (notifications-read-status->group status-filter)]
|
(let [notifications-group (notifications-read-status->group status-filter)]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
|
@ -44,7 +72,7 @@
|
||||||
(notifications-fetch start-or-end-cursor notifications-group))))
|
(notifications-fetch start-or-end-cursor notifications-group))))
|
||||||
|
|
||||||
(fx/defn notifications-fetch-next-page
|
(fx/defn notifications-fetch-next-page
|
||||||
{:events [:activity-center/notifications-fetch-next-page]}
|
{:events [:activity-center.notifications/fetch-next-page]}
|
||||||
[{:keys [db] :as cofx}]
|
[{:keys [db] :as cofx}]
|
||||||
(let [status-filter (get-in db [:activity-center :current-status-filter])
|
(let [status-filter (get-in db [:activity-center :current-status-filter])
|
||||||
notifications-group (notifications-read-status->group status-filter)
|
notifications-group (notifications-read-status->group status-filter)
|
||||||
|
@ -53,7 +81,7 @@
|
||||||
(notifications-fetch cofx cursor notifications-group))))
|
(notifications-fetch cofx cursor notifications-group))))
|
||||||
|
|
||||||
(fx/defn notifications-fetch-success
|
(fx/defn notifications-fetch-success
|
||||||
{:events [:activity-center/notifications-fetch-success]}
|
{:events [:activity-center.notifications/fetch-success]}
|
||||||
[{:keys [db]} notifications-group {:keys [cursor notifications]}]
|
[{:keys [db]} notifications-group {:keys [cursor notifications]}]
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(update-in [:activity-center notifications-group] dissoc :loading?)
|
(update-in [:activity-center notifications-group] dissoc :loading?)
|
||||||
|
@ -63,7 +91,7 @@
|
||||||
(map data-store.activities/<-rpc notifications)))})
|
(map data-store.activities/<-rpc notifications)))})
|
||||||
|
|
||||||
(fx/defn notifications-fetch-error
|
(fx/defn notifications-fetch-error
|
||||||
{:events [:activity-center/notifications-fetch-error]}
|
{:events [:activity-center.notifications/fetch-error]}
|
||||||
[{:keys [db]} notifications-group error]
|
[{:keys [db]} notifications-group error]
|
||||||
(log/warn "Failed to load Activity Center notifications" error)
|
(log/warn "Failed to load Activity Center notifications" error)
|
||||||
{:db (update-in db [:activity-center notifications-group] dissoc :loading?)})
|
{:db (update-in db [:activity-center notifications-group] dissoc :loading?)})
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
(ns status-im.activity-center.core-test
|
||||||
|
(:require [cljs.test :refer [deftest is testing]]
|
||||||
|
[day8.re-frame.test :as rf-test]
|
||||||
|
[re-frame.core :as rf]
|
||||||
|
[status-im.ethereum.json-rpc :as json-rpc]
|
||||||
|
[status-im.test-helpers :as h]
|
||||||
|
status-im.events))
|
||||||
|
|
||||||
|
(defn setup []
|
||||||
|
(h/register-helper-events)
|
||||||
|
(rf/dispatch [:init/app-started]))
|
||||||
|
|
||||||
|
(deftest notifications-reconcile-test
|
||||||
|
(testing "does nothing when there are no new notifications"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [read [{:id "0x1" :read true}]
|
||||||
|
unread [{:id "0x4" :read false}]]
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data] read])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data] unread])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/reconcile nil])
|
||||||
|
|
||||||
|
(is (= read (get-in (h/db) [:activity-center :notifications-read :data])))
|
||||||
|
(is (= unread (get-in (h/db) [:activity-center :notifications-unread :data]))))))
|
||||||
|
|
||||||
|
(testing "removes dismissed or accepted notifications"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
|
||||||
|
[{:id "0x1" :read true}
|
||||||
|
{:id "0x2" :read true}
|
||||||
|
{:id "0x3" :read true}]])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
|
||||||
|
[{:id "0x4" :read false}
|
||||||
|
{:id "0x5" :read false}
|
||||||
|
{:id "0x6" :read false}]])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/reconcile
|
||||||
|
[{:id "0x1" :read true :dismissed true}
|
||||||
|
{:id "0x3" :read true :accepted true}
|
||||||
|
{:id "0x4" :read false :dismissed true}
|
||||||
|
{:id "0x5" :read false :accepted true}]])
|
||||||
|
|
||||||
|
(is (= [{:id "0x2" :read true}]
|
||||||
|
(get-in (h/db) [:activity-center :notifications-read :data])))
|
||||||
|
(is (= [{:id "0x6" :read false}]
|
||||||
|
(get-in (h/db) [:activity-center :notifications-unread :data])))))
|
||||||
|
|
||||||
|
(testing "replaces old notifications with newly arrived ones"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
|
||||||
|
[{:id "0x1" :read true}
|
||||||
|
{:id "0x2" :read true}]])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
|
||||||
|
[{:id "0x3" :read false}
|
||||||
|
{:id "0x4" :read false}]])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/reconcile
|
||||||
|
[{:id "0x1" :read true :name "ABC"}
|
||||||
|
{:id "0x3" :read false :name "XYZ"}]])
|
||||||
|
|
||||||
|
(is (= [{:id "0x1" :read true :name "ABC"}
|
||||||
|
{:id "0x2" :read true}]
|
||||||
|
(get-in (h/db) [:activity-center :notifications-read :data])))
|
||||||
|
(is (= [{:id "0x3" :read false :name "XYZ"}
|
||||||
|
{:id "0x4" :read false}]
|
||||||
|
(get-in (h/db) [:activity-center :notifications-unread :data]))))))
|
||||||
|
|
||||||
|
(deftest notifications-fetch-test
|
||||||
|
(testing "fetches first page"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/stub-fx-with-callbacks ::json-rpc/call
|
||||||
|
:on-success (constantly {:cursor "10"
|
||||||
|
:notifications [{:chatId "0x1"}]}))
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/fetch-first-page])
|
||||||
|
|
||||||
|
(is (= :unread (get-in (h/db) [:activity-center :current-status-filter])))
|
||||||
|
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
|
||||||
|
(is (= "10" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
|
||||||
|
(is (= "" (get-in @spy-queue [0 :args 0 :params 0])))
|
||||||
|
(is (= [{:chat-id "0x1"}]
|
||||||
|
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
|
||||||
|
(map #(select-keys % [:chat-id]))))))))
|
||||||
|
|
||||||
|
(testing "does not fetch next page when pagination cursor reached the end"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||||
|
|
||||||
|
(is (= [] @spy-queue)))))
|
||||||
|
|
||||||
|
(testing "fetches next page when pagination cursor is not empty"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/stub-fx-with-callbacks ::json-rpc/call
|
||||||
|
:on-success (constantly {:cursor ""
|
||||||
|
:notifications [{:chatId "0x1"}]}))
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] "10"])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||||
|
|
||||||
|
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
|
||||||
|
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0])))
|
||||||
|
(is (= "" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
|
||||||
|
(is (= [{:chat-id "0x1"}]
|
||||||
|
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
|
||||||
|
(map #(select-keys % [:chat-id]))))))))
|
||||||
|
|
||||||
|
(testing "does not fetch next page while it is still loading"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :read])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :cursor] "10"])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :loading?] true])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||||
|
|
||||||
|
(is (= [] @spy-queue)))))
|
||||||
|
|
||||||
|
(testing "resets loading flag after an error"
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
|
||||||
|
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/fetch-first-page])
|
||||||
|
|
||||||
|
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
|
||||||
|
(is (= [:activity-center.notifications/fetch-error
|
||||||
|
:notifications-unread
|
||||||
|
:fake-error]
|
||||||
|
(:args (last @spy-queue))))))))
|
|
@ -0,0 +1,87 @@
|
||||||
|
(ns status-im.test-helpers
|
||||||
|
"Utilities for simplifying the process of writing tests and improving test
|
||||||
|
readability.
|
||||||
|
|
||||||
|
Avoid coupling this namespace with particularities of the Status' domain, thus
|
||||||
|
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.events :as rf-events]
|
||||||
|
[re-frame.db :as rf-db]))
|
||||||
|
|
||||||
|
(defn db
|
||||||
|
"A simple wrapper to get the latest value from the app db."
|
||||||
|
[]
|
||||||
|
@rf-db/app-db)
|
||||||
|
|
||||||
|
(defn register-helper-events
|
||||||
|
"Register utility events for testing.
|
||||||
|
|
||||||
|
Note that re-frame-test removes such events if they're declared in the scope
|
||||||
|
of the macro `day8.re-frame.test/run-test-sync` (or the async variant)."
|
||||||
|
[]
|
||||||
|
(rf/reg-event-db
|
||||||
|
:test/assoc-in
|
||||||
|
(fn [db [_ keys value]]
|
||||||
|
(assoc-in db keys value))))
|
||||||
|
|
||||||
|
(defn spy-event-fx
|
||||||
|
"Re-register event effect using id `id`, but conj to `state` the event
|
||||||
|
arguments before calling the original handler.
|
||||||
|
|
||||||
|
Callers of this function can later on deref `state` to make assertions.
|
||||||
|
|
||||||
|
It's recommended to run this function in the scope of the macro
|
||||||
|
`day8.re-frame.test/run-test-sync` (or the async variant) as they
|
||||||
|
automatically clean up effects."
|
||||||
|
[state id]
|
||||||
|
(let [interceptors (get-in @rf-registrar/kind->id->handler [:event id])]
|
||||||
|
(rf-events/register
|
||||||
|
id
|
||||||
|
(concat (butlast interceptors)
|
||||||
|
(list {:id :test/spy-event-fx
|
||||||
|
:before (fn [context]
|
||||||
|
(swap! state conj {:id id :args (get-in context [:coeffects :event])})
|
||||||
|
context)}
|
||||||
|
(last interceptors))))))
|
||||||
|
|
||||||
|
(defn spy-fx
|
||||||
|
"Re-register effect using id `id`, but conj to `state` the effect arguments
|
||||||
|
before calling the original effect handler.
|
||||||
|
|
||||||
|
Callers of this function can later on inspect `state` to make assertions.
|
||||||
|
|
||||||
|
It's recommended to run this function in the scope of the macro
|
||||||
|
`day8.re-frame.test/run-test-sync` (or the async variant) as they
|
||||||
|
automatically clean up effects."
|
||||||
|
[state id]
|
||||||
|
(let [original-fn (get-in @rf-registrar/kind->id->handler [:fx id])]
|
||||||
|
(rf/reg-fx
|
||||||
|
id
|
||||||
|
(fn [fx-args]
|
||||||
|
(swap! state conj {:id id :args fx-args})
|
||||||
|
(original-fn fx-args)))))
|
||||||
|
|
||||||
|
(defn stub-fx-with-callbacks
|
||||||
|
"Re-register effect using id `id` with a no-op version.
|
||||||
|
|
||||||
|
This function is useful to redefine effects that expect callbacks, usually to
|
||||||
|
pass downstream dummy data to successful/failure events. In re-frame parlance,
|
||||||
|
such effects accept on-success and on-error keywords.
|
||||||
|
|
||||||
|
The original effect handler for `id` is expected to receive a single map as
|
||||||
|
argument with either :on-success or :on-error keywords.
|
||||||
|
|
||||||
|
This function expects to receive either `on-success` or `on-error`, but not
|
||||||
|
both. If both are passed, `on-error` will be preferred."
|
||||||
|
[id & {:keys [on-success on-error]}]
|
||||||
|
(rf/reg-fx
|
||||||
|
id
|
||||||
|
(fn [[fx-map]]
|
||||||
|
(let [original-on-error (:on-error fx-map)
|
||||||
|
original-on-success (:on-success fx-map)]
|
||||||
|
(cond (and original-on-error on-error)
|
||||||
|
(original-on-error (on-error fx-map))
|
||||||
|
(and original-on-success on-success)
|
||||||
|
(original-on-success (on-success fx-map)))))))
|
|
@ -1,9 +1,11 @@
|
||||||
(ns ^{:doc "Definition of the StatusMessage protocol"}
|
(ns ^{:doc "Definition of the StatusMessage protocol"}
|
||||||
status-im.transport.message.core
|
status-im.transport.message.core
|
||||||
(:require [status-im.chat.models.message :as models.message]
|
(:require [status-im.activity-center.core :as activity-center]
|
||||||
|
[status-im.chat.models.message :as models.message]
|
||||||
[status-im.chat.models.pin-message :as models.pin-message]
|
[status-im.chat.models.pin-message :as models.pin-message]
|
||||||
[status-im.chat.models :as models.chat]
|
[status-im.chat.models :as models.chat]
|
||||||
[status-im.chat.models.reactions :as models.reactions]
|
[status-im.chat.models.reactions :as models.reactions]
|
||||||
|
[status-im.utils.config :as config]
|
||||||
[status-im.contact.core :as models.contact]
|
[status-im.contact.core :as models.contact]
|
||||||
[status-im.communities.core :as models.communities]
|
[status-im.communities.core :as models.communities]
|
||||||
[status-im.pairing.core :as models.pairing]
|
[status-im.pairing.core :as models.pairing]
|
||||||
|
@ -70,8 +72,13 @@
|
||||||
(do
|
(do
|
||||||
(js-delete response-js "activityCenterNotifications")
|
(js-delete response-js "activityCenterNotifications")
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(notifications-center/handle-activities (map data-store.activities/<-rpc
|
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
|
||||||
(types/js->clj activity-notifications)))
|
(->> activity-notifications
|
||||||
|
types/js->clj
|
||||||
|
(map data-store.activities/<-rpc)
|
||||||
|
activity-center/notifications-reconcile)
|
||||||
|
(notifications-center/handle-activities (map data-store.activities/<-rpc
|
||||||
|
(types/js->clj activity-notifications))))
|
||||||
(process-next response-js sync-handler)))
|
(process-next response-js sync-handler)))
|
||||||
|
|
||||||
(seq installations)
|
(seq installations)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
(ns status-im.ui.components.topnav
|
(ns status-im.ui.components.topnav
|
||||||
(:require
|
(:require [quo2.components.buttons.button :as quo2.button]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[quo2.components.buttons.button :as quo2.button]
|
[status-im.i18n.i18n :as i18n]
|
||||||
[status-im.qr-scanner.core :as qr-scanner]
|
[status-im.qr-scanner.core :as qr-scanner]
|
||||||
[status-im.utils.handlers :refer [<sub]]
|
[status-im.ui.components.react :as react]
|
||||||
[status-im.i18n.i18n :as i18n]
|
[status-im.ui.screens.home.styles :as styles]
|
||||||
[status-im.ui.screens.home.styles :as styles]
|
[status-im.utils.config :as config]
|
||||||
[status-im.ui.components.react :as react]))
|
[status-im.utils.handlers :refer [<sub]]))
|
||||||
|
|
||||||
(defn qr-scanner []
|
(defn qr-scanner []
|
||||||
[quo2.button/button
|
[quo2.button/button
|
||||||
|
@ -39,10 +39,12 @@
|
||||||
:accessibility-label :notifications-button
|
:accessibility-label :notifications-button
|
||||||
:on-press #(do
|
:on-press #(do
|
||||||
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
|
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
|
||||||
(re-frame/dispatch [:navigate-to :notifications-center]))}
|
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
|
||||||
|
(re-frame/dispatch [:navigate-to :activity-center])
|
||||||
|
(re-frame/dispatch [:navigate-to :notifications-center])))}
|
||||||
:main-icons2/notifications]
|
:main-icons2/notifications]
|
||||||
(when (pos? notif-count)
|
(when (pos? notif-count)
|
||||||
[react/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
|
[react/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
|
||||||
:pointer-events :none}
|
:pointer-events :none}
|
||||||
[react/view {:style styles/counter-public
|
[react/view {:style styles/counter-public
|
||||||
:accessibility-label :notifications-unread-badge}]])]))
|
:accessibility-label :notifications-unread-badge}]])]))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
(ns status-im.ui.screens.activity-center.views
|
(ns status-im.ui.screens.activity-center.views
|
||||||
(:require [quo.react-native :as rn]
|
(:require [quo.components.animated.pressable :as animation]
|
||||||
|
[quo.react-native :as rn]
|
||||||
[quo2.components.buttons.button :as button]
|
[quo2.components.buttons.button :as button]
|
||||||
[quo2.components.notifications.activity-logs :as activity-logs]
|
[quo2.components.notifications.activity-logs :as activity-logs]
|
||||||
[quo2.components.tags.context-tags :as context-tags]
|
[quo2.components.tags.context-tags :as context-tags]
|
||||||
|
@ -64,29 +65,44 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defn activity-buttons
|
(defn activity-buttons
|
||||||
[{:keys [type]}]
|
[{:keys [id type]}]
|
||||||
(case type
|
(case type
|
||||||
constants/activity-center-notification-type-contact-request
|
constants/activity-center-notification-type-contact-request
|
||||||
{:button-1 {:label (i18n/label :t/decline)
|
{:button-1 {:label (i18n/label :t/decline)
|
||||||
:type :danger}
|
:type :danger
|
||||||
:button-2 {:label (i18n/label :t/accept)
|
:on-press #(>evt [:contact-requests.ui/decline-request id])}
|
||||||
:type :success}}
|
:button-2 {:label (i18n/label :t/accept)
|
||||||
|
:type :success
|
||||||
|
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
(defn activity-pressable
|
||||||
|
[notification & children]
|
||||||
|
(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 #(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}])}
|
||||||
|
children]
|
||||||
|
[:<> children]))
|
||||||
|
|
||||||
(defn render-notification
|
(defn render-notification
|
||||||
[notification index]
|
[notification index]
|
||||||
[rn/view {:flex 1
|
[rn/view {:flex 1
|
||||||
:flex-direction :column
|
:flex-direction :column
|
||||||
:margin-top (if (= 0 index) 0 4)}
|
:margin-top (if (= 0 index) 0 4)}
|
||||||
[activity-logs/activity-log
|
[activity-pressable notification
|
||||||
(merge {:context (activity-context notification)
|
[activity-logs/activity-log
|
||||||
:icon (activity-icon notification)
|
(merge {:context (activity-context notification)
|
||||||
:message (activity-message notification)
|
:icon (activity-icon notification)
|
||||||
:status (activity-status notification)
|
:message (activity-message notification)
|
||||||
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
:status (activity-status notification)
|
||||||
:title (activity-title notification)
|
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
||||||
:unread? (not (:read notification))}
|
:title (activity-title notification)
|
||||||
(activity-buttons notification))]])
|
:unread? (not (:read notification))}
|
||||||
|
(activity-buttons notification))]]])
|
||||||
|
|
||||||
(defn notifications-list
|
(defn notifications-list
|
||||||
[]
|
[]
|
||||||
|
@ -94,12 +110,12 @@
|
||||||
[rn/flat-list {:style {:padding-horizontal 8}
|
[rn/flat-list {:style {:padding-horizontal 8}
|
||||||
:data notifications
|
:data notifications
|
||||||
:key-fn :id
|
:key-fn :id
|
||||||
:on-end-reached #(>evt [:activity-center/notifications-fetch-next-page])
|
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
||||||
:render-fn render-notification}]))
|
:render-fn render-notification}]))
|
||||||
|
|
||||||
(defn activity-center []
|
(defn activity-center []
|
||||||
(reagent/create-class
|
(reagent/create-class
|
||||||
{:component-did-mount #(>evt [:activity-center/notifications-fetch-first-page {:status-filter :unread}])
|
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
|
||||||
:reagent-render
|
:reagent-render
|
||||||
(fn []
|
(fn []
|
||||||
[:<>
|
[:<>
|
||||||
|
@ -107,8 +123,8 @@
|
||||||
:title (i18n/label :t/activity)}]
|
:title (i18n/label :t/activity)}]
|
||||||
;; TODO(ilmotta): Temporary solution to switch between read/unread
|
;; TODO(ilmotta): Temporary solution to switch between read/unread
|
||||||
;; notifications while the Design team works on the mockups.
|
;; notifications while the Design team works on the mockups.
|
||||||
[button/button {:on-press #(>evt [:activity-center/notifications-fetch-first-page {:status-filter :unread}])}
|
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])}
|
||||||
"Unread"]
|
"Unread"]
|
||||||
[button/button {:on-press #(>evt [:activity-center/notifications-fetch-first-page {:status-filter :read}])}
|
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :read}])}
|
||||||
"Read"]
|
"Read"]
|
||||||
[notifications-list]])}))
|
[notifications-list]])}))
|
||||||
|
|
|
@ -274,7 +274,9 @@
|
||||||
:accessibility-label :notifications-button
|
:accessibility-label :notifications-button
|
||||||
:on-press #(do
|
:on-press #(do
|
||||||
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
|
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
|
||||||
(re-frame/dispatch [:navigate-to :notifications-center]))}
|
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
|
||||||
|
(re-frame/dispatch [:navigate-to :activity-center])
|
||||||
|
(re-frame/dispatch [:navigate-to :notifications-center])))}
|
||||||
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/black quo2.colors/white)}]]
|
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/black quo2.colors/white)}]]
|
||||||
(when (pos? notif-count)
|
(when (pos? notif-count)
|
||||||
[react/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
|
[react/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
|
||||||
|
|
|
@ -177,3 +177,8 @@
|
||||||
(def wallet-connect-project-id "87815d72a81d739d2a7ce15c2cfdefb3")
|
(def wallet-connect-project-id "87815d72a81d739d2a7ce15c2cfdefb3")
|
||||||
|
|
||||||
(def new-ui-enabled? (atom false))
|
(def new-ui-enabled? (atom false))
|
||||||
|
|
||||||
|
;; TODO: Remove this (highly) temporary flag once the new Activity Center is
|
||||||
|
;; usable enough to replace the old one **in the new UI**.
|
||||||
|
(def new-activity-center-enabled?
|
||||||
|
(atom false))
|
||||||
|
|
Loading…
Reference in New Issue