Show "Added to group chat" notifications (#14785)
Partially implements https://github.com/status-im/status-mobile/issues/14712
Fixes #14744
### Summary
This PR implements the first, among what will probably be many different kinds of membership notifications. For this PR, I started with implementing a particular flow for private group chats because it's already supported by `status-go` (albeit I had to make some changes, see [PR in status-go](https://github.com/status-im/status-go/pull/3088).
1. `A` and `B` are mutual contacts.
2. `A` creates a private group chat with `B` as member.
3. `B` sees the group chat in the app, but doesn't interact with it.
4. `B` reinstalls the app (remember to back up the seed phrase).
5. `A` mentions `B` in the group chat.
6. `B` should see a group chat notification, which can be accepted/declined.
- [x] Also fixes #14744
### Demo
In the video I'm simulating the steps outlined in the *Summary*, but using the approach described in *Steps to test*, because it's way easier to iterate during development.
[demo.webm](https://user-images.githubusercontent.com/46027/212470798-c135d229-948d-4ba5-98db-ee73cc5495cd.webm)
### Review Notes
Some changes had to be made in `status-go` ([PR](https://github.com/status-im/status-go/pull/3088)), namely:
- According to [Figma](https://www.figma.com/file/eDfxTa9IoaCMUy5cLTp0ys/Shell-for-Mobile?node-id=3806%3A586901&t=xLTAjLXjG1UtorpI-0), users should be able to see `accepted` group chat notifications. Until now, `status-go` hardcoded that `accepted` notifications would *not* be returned in query results, and so it would be impossible to show them to users. This was changed and now the RPC endpoint accepts an additional filter. The implementation on the backend is backwards compatible so as to not break Status desktop.
- The `Membership` tab needs to display various types of notifications (group chat, community, etc), but the membership type doesn't exist on the backend. To overcome this constraint, this PR makes the membership type a logical/virtual type, i.e. a Clojure set of types. `status-go` was changed to support querying for multiple notification types (also backwards compatible).
#### Platforms
- Android
- iOS
### Steps to test
Please, follow the steps described in the Summary and you should be able to test.
But during development, I followed these steps (recommended by @cammellos). I documented them here for reference.
1. Checkout `feature/e2e` in status-go. Apply the diff below.
2. `cd cmd/e2e && ./e2e`
3. This will create a temporary account automatically, let's call it `A`.
4. On another device, create account `B`.
5. Follow the steps documented in bdc406ea2e/cmd/e2e/README.md (L2)
in order for user `A` to create a group chat with `B` as member. Don't make `A` and `B` mutual contacts.
6. On `B`'s device, a notification should appear, and `B` should be able to accept or decline the "invitation" (actually *invitation* is another concept and related to another feature).
```diff
modified cmd/e2e/main.go
@@ -283,6 +283,11 @@ func defaultNodeConfig(installationID string) (*params.NodeConfig, error) {
nodeConfig.NetworkID = 1
nodeConfig.LogLevel = "ERROR"
nodeConfig.DataDir = "/ethereum/mainnet_rpc"
+ nodeConfig.HTTPEnabled = true
+ nodeConfig.HTTPPort = 8545
+ nodeConfig.HTTPHost = "localhost"
+ nodeConfig.HTTPVirtualHosts = []string{"localhost"}
+
nodeConfig.APIModules = "wakuext,ext,waku"
nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
modified protocol/messenger_group_chat.go
@@ -26,17 +26,17 @@ func (m *Messenger) validateAddedGroupMembers(members []string) error {
}
contact, _ := m.allContacts.Load(contactID)
- if contact == nil || !(contact.Added && contact.HasAddedUs) {
- return ErrGroupChatAddedContacts
- }
+ if contact == nil {
+ contact, err = buildContactFromPkString(contactID)
+ if err != nil {
+ return err
+ }
+ }
}
return nil
}
func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, members []string) (*MessengerResponse, error) {
- if err := m.validateAddedGroupMembers(members); err != nil {
- return nil, err
- }
var response MessengerResponse
logger := m.logger.With(zap.String("site", "CreateGroupChatWithMembers"))
```
This commit is contained in:
parent
b3a03119d1
commit
206730a777
|
@ -4,7 +4,6 @@
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[status-im.data-store.chats :as data-store.chats]
|
[status-im.data-store.chats :as data-store.chats]
|
||||||
[status-im.data-store.messages :as data-store.messages]
|
[status-im.data-store.messages :as data-store.messages]
|
||||||
[status-im2.contexts.activity-center.events :as activity-center]
|
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
@ -69,15 +68,15 @@
|
||||||
|
|
||||||
(rf/defn handle-mark-all-read-successful
|
(rf/defn handle-mark-all-read-successful
|
||||||
{:events [::mark-all-read-successful]}
|
{:events [::mark-all-read-successful]}
|
||||||
[cofx]
|
[_]
|
||||||
(activity-center/notifications-fetch-unread-count cofx))
|
{:dispatch [:activity-center.notifications/fetch-unread-count]})
|
||||||
|
|
||||||
(rf/defn handle-mark-all-read-in-community-successful
|
(rf/defn handle-mark-all-read-in-community-successful
|
||||||
{:events [::mark-all-read-in-community-successful]}
|
{:events [::mark-all-read-in-community-successful]}
|
||||||
[{:keys [db] :as cofx} chat-ids]
|
[{:keys [db] :as cofx} chat-ids]
|
||||||
(rf/merge cofx
|
(rf/merge cofx
|
||||||
{:db (reduce mark-chat-all-read db chat-ids)}
|
{:db (reduce mark-chat-all-read db chat-ids)
|
||||||
(activity-center/notifications-fetch-unread-count)))
|
:dispatch [:activity-center.notifications/fetch-unread-count]}))
|
||||||
|
|
||||||
(rf/defn handle-mark-all-read
|
(rf/defn handle-mark-all-read
|
||||||
{:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]}
|
{:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
(ns status-im2.contexts.activity-center.events
|
(ns status-im2.contexts.activity-center.events
|
||||||
(:require [status-im.data-store.activities :as data-store.activities]
|
(:require [status-im.data-store.activities :as data-store.activities]
|
||||||
|
[status-im.data-store.chats :as data-store.chats]
|
||||||
[status-im2.contexts.activity-center.notification-types :as types]
|
[status-im2.contexts.activity-center.notification-types :as types]
|
||||||
|
[status-im2.contexts.chat.events :as chat.events]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
@ -41,6 +43,12 @@
|
||||||
|
|
||||||
;;;; Notification reconciliation
|
;;;; Notification reconciliation
|
||||||
|
|
||||||
|
(defn- notification-type->filter-type
|
||||||
|
[type]
|
||||||
|
(if (some types/membership [type])
|
||||||
|
types/membership
|
||||||
|
type))
|
||||||
|
|
||||||
(defn- update-notifications
|
(defn- update-notifications
|
||||||
"Insert `new-notifications` in `db-notifications`.
|
"Insert `new-notifications` in `db-notifications`.
|
||||||
|
|
||||||
|
@ -50,8 +58,9 @@
|
||||||
If the number of existing notifications cached in the app db becomes
|
If the number of existing notifications cached in the app db becomes
|
||||||
~excessively~ big, this implementation will probably need to be revisited."
|
~excessively~ big, this implementation will probably need to be revisited."
|
||||||
[db-notifications new-notifications]
|
[db-notifications new-notifications]
|
||||||
(reduce (fn [acc {:keys [id type read] :as notification}]
|
(reduce (fn [acc {:keys [id read] :as notification}]
|
||||||
(let [remove-notification (fn [data]
|
(let [filter-type (notification-type->filter-type (:type notification))
|
||||||
|
remove-notification (fn [data]
|
||||||
(remove #(= id (:id %)) data))
|
(remove #(= id (:id %)) data))
|
||||||
insert-and-sort (fn [data]
|
insert-and-sort (fn [data]
|
||||||
(->> notification
|
(->> notification
|
||||||
|
@ -59,16 +68,16 @@
|
||||||
(sort-by (juxt :timestamp :id))
|
(sort-by (juxt :timestamp :id))
|
||||||
reverse))]
|
reverse))]
|
||||||
(as-> acc $
|
(as-> acc $
|
||||||
(update-in $ [type :all :data] remove-notification)
|
(update-in $ [filter-type :all :data] remove-notification)
|
||||||
(update-in $ [types/no-type :all :data] remove-notification)
|
(update-in $ [types/no-type :all :data] remove-notification)
|
||||||
(update-in $ [type :unread :data] remove-notification)
|
(update-in $ [filter-type :unread :data] remove-notification)
|
||||||
(update-in $ [types/no-type :unread :data] remove-notification)
|
(update-in $ [types/no-type :unread :data] remove-notification)
|
||||||
(if (or (:dismissed notification) (:accepted notification))
|
(if (:dismissed notification)
|
||||||
$
|
$
|
||||||
(cond-> (-> $
|
(cond-> (-> $
|
||||||
(update-in [type :all :data] insert-and-sort)
|
(update-in [filter-type :all :data] insert-and-sort)
|
||||||
(update-in [types/no-type :all :data] insert-and-sort))
|
(update-in [types/no-type :all :data] insert-and-sort))
|
||||||
(not read) (update-in [type :unread :data] insert-and-sort)
|
(not read) (update-in [filter-type :unread :data] insert-and-sort)
|
||||||
(not read) (update-in [types/no-type :unread :data] insert-and-sort))))))
|
(not read) (update-in [types/no-type :unread :data] insert-and-sort))))))
|
||||||
db-notifications
|
db-notifications
|
||||||
new-notifications))
|
new-notifications))
|
||||||
|
@ -77,10 +86,11 @@
|
||||||
{:events [:activity-center.notifications/reconcile]}
|
{:events [:activity-center.notifications/reconcile]}
|
||||||
[{:keys [db]} new-notifications]
|
[{:keys [db]} new-notifications]
|
||||||
(when (seq new-notifications)
|
(when (seq new-notifications)
|
||||||
{:db (update-in db
|
{:db (update-in db
|
||||||
[:activity-center :notifications]
|
[:activity-center :notifications]
|
||||||
update-notifications
|
update-notifications
|
||||||
new-notifications)}))
|
new-notifications)
|
||||||
|
:dispatch [:activity-center.notifications/fetch-unread-count]}))
|
||||||
|
|
||||||
(rf/defn notifications-reconcile-from-response
|
(rf/defn notifications-reconcile-from-response
|
||||||
{:events [:activity-center/reconcile-notifications-from-response]}
|
{:events [:activity-center/reconcile-notifications-from-response]}
|
||||||
|
@ -143,6 +153,46 @@
|
||||||
[cofx notification]
|
[cofx notification]
|
||||||
(notifications-reconcile cofx [(assoc notification :read true)]))
|
(notifications-reconcile cofx [(assoc notification :read true)]))
|
||||||
|
|
||||||
|
;;;; Acceptance/dismissal
|
||||||
|
|
||||||
|
(rf/defn accept-notification
|
||||||
|
{:events [:activity-center.notifications/accept]}
|
||||||
|
[{:keys [db]} notification-id]
|
||||||
|
{:json-rpc/call [{:method "wakuext_acceptActivityCenterNotifications"
|
||||||
|
:params [[notification-id]]
|
||||||
|
:on-success #(rf/dispatch [:activity-center.notifications/accept-success
|
||||||
|
notification-id %])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:notification/accept
|
||||||
|
%])}]})
|
||||||
|
|
||||||
|
(rf/defn accept-notification-success
|
||||||
|
{:events [:activity-center.notifications/accept-success]}
|
||||||
|
[{:keys [db] :as cofx} notification-id {:keys [chats]}]
|
||||||
|
(let [notification (get-notification db notification-id)]
|
||||||
|
(rf/merge cofx
|
||||||
|
(chat.events/ensure-chats (map data-store.chats/<-rpc chats))
|
||||||
|
(notifications-reconcile [(assoc notification :read true :accepted true)]))))
|
||||||
|
|
||||||
|
(rf/defn dismiss-notification
|
||||||
|
{:events [:activity-center.notifications/dismiss]}
|
||||||
|
[{:keys [db]} notification-id]
|
||||||
|
{:json-rpc/call [{:method "wakuext_dismissActivityCenterNotifications"
|
||||||
|
:params [[notification-id]]
|
||||||
|
:on-success #(rf/dispatch [:activity-center.notifications/dismiss-success
|
||||||
|
notification-id])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:notification/dismiss
|
||||||
|
%])}]})
|
||||||
|
|
||||||
|
(rf/defn dismiss-notification-success
|
||||||
|
{:events [:activity-center.notifications/dismiss-success]}
|
||||||
|
[{:keys [db] :as cofx} notification-id]
|
||||||
|
(let [notification (get-notification db notification-id)]
|
||||||
|
(notifications-reconcile cofx [(assoc notification :dismissed true)])))
|
||||||
|
|
||||||
;;;; Contact verification
|
;;;; Contact verification
|
||||||
|
|
||||||
(rf/defn contact-verification-decline
|
(rf/defn contact-verification-decline
|
||||||
|
@ -213,15 +263,35 @@
|
||||||
:all status-all
|
:all status-all
|
||||||
99))
|
99))
|
||||||
|
|
||||||
|
(defn filter-type->rpc-param
|
||||||
|
[filter-type]
|
||||||
|
(cond
|
||||||
|
(coll? filter-type)
|
||||||
|
filter-type
|
||||||
|
|
||||||
|
;; A "no-type" notification shouldn't be sent to the backend. If, for
|
||||||
|
;; instance, the mobile client needs notifications of any type (as in the
|
||||||
|
;; `All` tab), then just don't filter by type at all.
|
||||||
|
(= types/no-type filter-type)
|
||||||
|
nil
|
||||||
|
|
||||||
|
:else
|
||||||
|
[filter-type]))
|
||||||
|
|
||||||
(rf/defn notifications-fetch
|
(rf/defn notifications-fetch
|
||||||
[{:keys [db]} {:keys [cursor per-page filter-type filter-status reset-data?]}]
|
[{:keys [db]} {:keys [cursor per-page filter-type filter-status reset-data?]}]
|
||||||
(when-not (get-in db [:activity-center :notifications filter-type filter-status :loading?])
|
(when-not (get-in db [:activity-center :notifications filter-type filter-status :loading?])
|
||||||
(let [per-page (or per-page (defaults :notifications-per-page))]
|
(let [per-page (or per-page (defaults :notifications-per-page))
|
||||||
|
accepted? true]
|
||||||
{:db (assoc-in db
|
{:db (assoc-in db
|
||||||
[:activity-center :notifications filter-type filter-status :loading?]
|
[:activity-center :notifications filter-type filter-status :loading?]
|
||||||
true)
|
true)
|
||||||
:json-rpc/call [{:method "wakuext_activityCenterNotificationsBy"
|
:json-rpc/call [{:method "wakuext_activityCenterNotificationsBy"
|
||||||
:params [cursor per-page filter-type (status filter-status)]
|
:params [cursor
|
||||||
|
per-page
|
||||||
|
(filter-type->rpc-param filter-type)
|
||||||
|
(status filter-status)
|
||||||
|
accepted?]
|
||||||
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success
|
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success
|
||||||
filter-type filter-status reset-data? %])
|
filter-type filter-status reset-data? %])
|
||||||
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error
|
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error
|
||||||
|
@ -295,7 +365,7 @@
|
||||||
(rf/defn notifications-fetch-unread-count
|
(rf/defn notifications-fetch-unread-count
|
||||||
{:events [:activity-center.notifications/fetch-unread-count]}
|
{:events [:activity-center.notifications/fetch-unread-count]}
|
||||||
[_]
|
[_]
|
||||||
{:json-rpc/call [{:method "wakuext_unreadActivityCenterNotificationsCount"
|
{:json-rpc/call [{:method "wakuext_unreadAndAcceptedActivityCenterNotificationsCount"
|
||||||
:params []
|
:params []
|
||||||
:on-success #(rf/dispatch [:activity-center.notifications/fetch-unread-count-success
|
:on-success #(rf/dispatch [:activity-center.notifications/fetch-unread-count-success
|
||||||
%])
|
%])
|
||||||
|
|
|
@ -141,6 +141,74 @@
|
||||||
:filter {:type types/one-to-one-chat
|
:filter {:type types/one-to-one-chat
|
||||||
:status :all}}]))})))
|
:status :all}}]))})))
|
||||||
|
|
||||||
|
;;;; Acceptance/dismissal
|
||||||
|
|
||||||
|
(deftest notification-acceptance-test
|
||||||
|
(testing "marks notification as accepted and read, then reconciles"
|
||||||
|
(h/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [notif-1 {:id "0x1" :type types/private-group-chat}
|
||||||
|
notif-2 {:id "0x2" :type types/private-group-chat}
|
||||||
|
notif-3 {:id "0x3" :type types/admin}
|
||||||
|
notif-2-new (assoc notif-2 :accepted true :read true)]
|
||||||
|
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||||
|
{:notifications {types/membership
|
||||||
|
{:unread {:cursor "" :data [notif-2 notif-1]}}
|
||||||
|
|
||||||
|
types/admin
|
||||||
|
{:all {:cursor "" :data [notif-3]}}}
|
||||||
|
:filter {:type types/membership
|
||||||
|
:status :unread}}])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/accept (:id notif-2)])
|
||||||
|
|
||||||
|
(is (= {types/no-type {:all {:data [notif-2-new]}
|
||||||
|
:unread {:data []}}
|
||||||
|
types/membership {:all {:data [notif-2-new]}
|
||||||
|
:unread {:cursor "" :data [notif-1]}}
|
||||||
|
types/admin {:all {:cursor "" :data [notif-3]}}}
|
||||||
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
|
(testing "logs on failure"
|
||||||
|
(test-log-on-failure
|
||||||
|
{:notification-id notification-id
|
||||||
|
:event [:activity-center.notifications/accept notification-id]
|
||||||
|
:action :notification/accept})))
|
||||||
|
|
||||||
|
(deftest notification-dismissal-test
|
||||||
|
(testing "dismisses notification and removes from app db"
|
||||||
|
(h/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [notif-1 {:id "0x1" :type types/private-group-chat}
|
||||||
|
notif-2 {:id "0x2" :type types/admin}]
|
||||||
|
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
|
||||||
|
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||||
|
{:notifications {types/no-type
|
||||||
|
{:all {:cursor "" :data [notif-2 notif-1]}}
|
||||||
|
|
||||||
|
types/membership
|
||||||
|
{:unread {:cursor "" :data [notif-1]}}}
|
||||||
|
:filter {:type types/membership
|
||||||
|
:status :unread}}])
|
||||||
|
|
||||||
|
(rf/dispatch [:activity-center.notifications/dismiss (:id notif-1)])
|
||||||
|
|
||||||
|
(is (= {types/no-type
|
||||||
|
{:all {:cursor "" :data [notif-2]}
|
||||||
|
:unread {:data []}}
|
||||||
|
|
||||||
|
types/membership
|
||||||
|
{:all {:data []}
|
||||||
|
:unread {:cursor "" :data []}}}
|
||||||
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
|
(testing "logs on failure"
|
||||||
|
(test-log-on-failure
|
||||||
|
{:notification-id notification-id
|
||||||
|
:event [:activity-center.notifications/dismiss notification-id]
|
||||||
|
:action :notification/dismiss})))
|
||||||
|
|
||||||
;;;; Contact verification
|
;;;; Contact verification
|
||||||
|
|
||||||
(def contact-verification-rpc-response
|
(def contact-verification-rpc-response
|
||||||
|
@ -287,45 +355,44 @@
|
||||||
|
|
||||||
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
|
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
(testing "removes dismissed or accepted notifications"
|
(testing "removes dismissed notifications"
|
||||||
(h/run-test-sync
|
(h/run-test-sync
|
||||||
(setup)
|
(setup)
|
||||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
||||||
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
|
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
|
||||||
notif-3 {:id "0x3" :read false :type types/one-to-one-chat}
|
notif-3 {:id "0x3" :read false :type types/system}
|
||||||
notif-4 {:id "0x4" :read false :type types/private-group-chat}
|
notif-4 {:id "0x4" :read true :type types/system}
|
||||||
notif-5 {:id "0x5" :read true :type types/private-group-chat}
|
notif-5 {:id "0x5" :read false :type types/system :accepted true}]
|
||||||
notif-6 {:id "0x6" :read false :type types/private-group-chat}]
|
|
||||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||||
{types/one-to-one-chat
|
{types/one-to-one-chat
|
||||||
{:all {:cursor "" :data [notif-1 notif-2]}
|
{:all {:cursor "" :data [notif-1 notif-2]}
|
||||||
:unread {:cursor "" :data [notif-3]}}
|
:unread {:cursor "" :data [notif-2]}}
|
||||||
types/private-group-chat
|
types/system
|
||||||
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
|
{:all {:cursor "" :data [notif-4]}
|
||||||
|
:unread {:cursor "" :data [notif-3 notif-5]}}}])
|
||||||
|
|
||||||
(rf/dispatch [:activity-center.notifications/reconcile
|
(rf/dispatch [:activity-center.notifications/reconcile
|
||||||
[(assoc notif-1 :dismissed true)
|
[(assoc notif-1 :dismissed true)
|
||||||
(assoc notif-3 :accepted true)
|
|
||||||
(assoc notif-4 :dismissed true)
|
(assoc notif-4 :dismissed true)
|
||||||
notif-5]])
|
notif-5]])
|
||||||
|
|
||||||
(is (= {types/no-type
|
(is (= {types/no-type
|
||||||
{:all {:data [notif-5]}
|
{:all {:data [notif-5]}
|
||||||
:unread {:data []}}
|
:unread {:data [notif-5]}}
|
||||||
types/one-to-one-chat
|
types/one-to-one-chat
|
||||||
{:all {:cursor "" :data [notif-2]}
|
{:all {:cursor "" :data [notif-2]}
|
||||||
:unread {:cursor "" :data []}}
|
:unread {:cursor "" :data [notif-2]}}
|
||||||
types/private-group-chat
|
types/system
|
||||||
{:all {:data [notif-5]}
|
{:all {:cursor "" :data [notif-5]}
|
||||||
:unread {:cursor "" :data [notif-6]}}}
|
:unread {:cursor "" :data [notif-5 notif-3]}}}
|
||||||
(get-in (h/db) [:activity-center :notifications]))))))
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
(testing "replaces old notifications with newly arrived ones"
|
(testing "replaces old notifications with newly arrived ones"
|
||||||
(h/run-test-sync
|
(h/run-test-sync
|
||||||
(setup)
|
(setup)
|
||||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
||||||
notif-4 {:id "0x4" :read false :type types/private-group-chat}
|
notif-4 {:id "0x4" :read false :type types/system}
|
||||||
notif-6 {:id "0x6" :read false :type types/private-group-chat}
|
notif-6 {:id "0x6" :read false :type types/system}
|
||||||
new-notif-1 (assoc notif-1 :last-message {})
|
new-notif-1 (assoc notif-1 :last-message {})
|
||||||
new-notif-4 (assoc notif-4 :author "0xabc")]
|
new-notif-4 (assoc notif-4 :author "0xabc")]
|
||||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||||
|
@ -334,7 +401,7 @@
|
||||||
:unread {:cursor "" :data [notif-4 notif-6]}}
|
:unread {:cursor "" :data [notif-4 notif-6]}}
|
||||||
types/one-to-one-chat
|
types/one-to-one-chat
|
||||||
{:all {:cursor "" :data [notif-1]}}
|
{:all {:cursor "" :data [notif-1]}}
|
||||||
types/private-group-chat
|
types/system
|
||||||
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
|
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
|
||||||
|
|
||||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4 notif-6]])
|
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4 notif-6]])
|
||||||
|
@ -345,7 +412,7 @@
|
||||||
types/one-to-one-chat
|
types/one-to-one-chat
|
||||||
{:all {:cursor "" :data [new-notif-1]}
|
{:all {:cursor "" :data [new-notif-1]}
|
||||||
:unread {:data []}}
|
:unread {:data []}}
|
||||||
types/private-group-chat
|
types/system
|
||||||
{:all {:data [notif-6 new-notif-4]}
|
{:all {:data [notif-6 new-notif-4]}
|
||||||
:unread {:cursor "" :data [notif-6 new-notif-4]}}}
|
:unread {:cursor "" :data [notif-6 new-notif-4]}}}
|
||||||
(get-in (h/db) [:activity-center :notifications]))))))
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
@ -370,6 +437,26 @@
|
||||||
:unread {:data [new-notif-1]}}}
|
:unread {:data [new-notif-1]}}}
|
||||||
(get-in (h/db) [:activity-center :notifications]))))))
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
|
(testing "membership notifications"
|
||||||
|
(h/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [notif {:read false
|
||||||
|
:dismissed false
|
||||||
|
:accepted false
|
||||||
|
:type types/private-group-chat
|
||||||
|
:id "0x7"
|
||||||
|
:timestamp 1673445663000}]
|
||||||
|
(rf/dispatch [:activity-center.notifications/reconcile [notif]])
|
||||||
|
|
||||||
|
(is (= {types/no-type
|
||||||
|
{:all {:data [notif]}
|
||||||
|
:unread {:data [notif]}}
|
||||||
|
|
||||||
|
types/membership
|
||||||
|
{:all {:data [notif]}
|
||||||
|
:unread {:data [notif]}}}
|
||||||
|
(get-in (h/db) [:activity-center :notifications]))))))
|
||||||
|
|
||||||
;; Sorting by timestamp and ID is compatible with what the backend does when
|
;; Sorting by timestamp and ID is compatible with what the backend does when
|
||||||
;; returning paginated results.
|
;; returning paginated results.
|
||||||
(testing "sorts notifications by timestamp and id in descending order"
|
(testing "sorts notifications by timestamp and id in descending order"
|
||||||
|
@ -595,7 +682,7 @@
|
||||||
(:db actual)))
|
(:db actual)))
|
||||||
|
|
||||||
(is (= {:method "wakuext_activityCenterNotificationsBy"
|
(is (= {:method "wakuext_activityCenterNotificationsBy"
|
||||||
:params ["" per-page types/contact-request activity-center/status-unread]}
|
:params ["" per-page [types/contact-request] activity-center/status-unread true]}
|
||||||
(-> actual
|
(-> actual
|
||||||
:json-rpc/call
|
:json-rpc/call
|
||||||
first
|
first
|
||||||
|
@ -611,6 +698,6 @@
|
||||||
|
|
||||||
(rf/dispatch [:activity-center.notifications/fetch-unread-count])
|
(rf/dispatch [:activity-center.notifications/fetch-unread-count])
|
||||||
|
|
||||||
(is (= "wakuext_unreadActivityCenterNotificationsCount"
|
(is (= "wakuext_unreadAndAcceptedActivityCenterNotificationsCount"
|
||||||
(get-in @spy-queue [0 :args 0 :method])))
|
(get-in @spy-queue [0 :args 0 :method])))
|
||||||
(is (= 9 (get-in (h/db) [:activity-center :unread-count])))))))
|
(is (= 9 (get-in (h/db) [:activity-center :unread-count])))))))
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
(ns status-im2.contexts.activity-center.notification.admin.view
|
(ns status-im2.contexts.activity-center.notification.admin.view
|
||||||
(:require [utils.i18n :as i18n]
|
(:require [quo2.core :as quo]
|
||||||
[quo2.core :as quo]
|
|
||||||
[quo2.foundations.colors :as colors]
|
[quo2.foundations.colors :as colors]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[status-im2.contexts.activity-center.notification.common.style :as style]
|
[status-im2.contexts.activity-center.notification.common.style :as style]
|
||||||
[status-im2.contexts.activity-center.notification.common.view :as common]
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
[utils.datetime :as datetime]
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn view
|
(defn view
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
(ns status-im2.contexts.activity-center.notification.contact-request.view
|
(ns status-im2.contexts.activity-center.notification.contact-request.view
|
||||||
(:require [utils.i18n :as i18n]
|
(:require [quo2.core :as quo]
|
||||||
[quo2.core :as quo]
|
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[utils.datetime :as datetime]
|
|
||||||
[status-im2.contexts.activity-center.notification.common.view :as common]
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn view
|
(defn view
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
(ns status-im2.contexts.activity-center.notification.contact-verification.view
|
(ns status-im2.contexts.activity-center.notification.contact-verification.view
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[utils.i18n :as i18n]
|
|
||||||
[quo2.core :as quo]
|
[quo2.core :as quo]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[utils.datetime :as datetime]
|
|
||||||
[status-im2.contexts.activity-center.notification.common.view :as common]
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn- hide-bottom-sheet-and-dispatch
|
(defn- hide-bottom-sheet-and-dispatch
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
(ns status-im2.contexts.activity-center.notification.membership.view
|
||||||
|
(:require [quo2.core :as quo]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn pressable
|
||||||
|
[{:keys [accepted chat-id]} & children]
|
||||||
|
(if accepted
|
||||||
|
(into [rn/touchable-opacity
|
||||||
|
{:on-press (fn []
|
||||||
|
(rf/dispatch [:hide-popover])
|
||||||
|
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]))}]
|
||||||
|
children)
|
||||||
|
(into [:<>] children)))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[{:keys [id accepted author read timestamp chat-name chat-id]}]
|
||||||
|
[pressable {:accepted accepted :chat-id chat-id}
|
||||||
|
[quo/activity-log
|
||||||
|
(merge
|
||||||
|
{:title (i18n/label :t/added-to-group-chat)
|
||||||
|
:icon :i/add-user
|
||||||
|
:timestamp (datetime/timestamp->relative timestamp)
|
||||||
|
:unread? (not read)
|
||||||
|
:context [[common/user-avatar-tag author]
|
||||||
|
(i18n/label :t/added-you-to)
|
||||||
|
[quo/group-avatar-tag chat-name
|
||||||
|
{:size :small
|
||||||
|
:color :purple}]]}
|
||||||
|
(when-not accepted
|
||||||
|
{:button-2 {:label (i18n/label :t/accept)
|
||||||
|
:accessibility-label :accept-group-chat-invitation
|
||||||
|
:type :positive
|
||||||
|
:on-press #(rf/dispatch [:activity-center.notifications/accept id])}
|
||||||
|
:button-1 {:label (i18n/label :t/decline)
|
||||||
|
:accessibility-label :decline-group-chat-invitation
|
||||||
|
:type :danger
|
||||||
|
:on-press #(rf/dispatch [:activity-center.notifications/dismiss id])}}))]])
|
|
@ -1,12 +1,12 @@
|
||||||
(ns status-im2.contexts.activity-center.notification.mentions.view
|
(ns status-im2.contexts.activity-center.notification.mentions.view
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[utils.i18n :as i18n]
|
|
||||||
[quo2.core :as quo]
|
[quo2.core :as quo]
|
||||||
[quo2.foundations.colors :as colors]
|
[quo2.foundations.colors :as colors]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[utils.datetime :as datetime]
|
|
||||||
[status-im2.contexts.activity-center.notification.common.view :as common]
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
[status-im2.contexts.activity-center.notification.mentions.style :as style]
|
[status-im2.contexts.activity-center.notification.mentions.style :as style]
|
||||||
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(def tag-params
|
(def tag-params
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
(ns status-im2.contexts.activity-center.notification.reply.view
|
(ns status-im2.contexts.activity-center.notification.reply.view
|
||||||
(:require [utils.i18n :as i18n]
|
(:require [quo2.core :as quo]
|
||||||
[quo2.core :as quo]
|
|
||||||
[quo2.foundations.colors :as colors]
|
[quo2.foundations.colors :as colors]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[status-im.ui2.screens.chat.messages.message :as old-message]
|
[status-im.ui2.screens.chat.messages.message :as old-message]
|
||||||
|
@ -8,6 +7,7 @@
|
||||||
[status-im2.contexts.activity-center.notification.common.view :as common]
|
[status-im2.contexts.activity-center.notification.common.view :as common]
|
||||||
[status-im2.contexts.activity-center.notification.reply.style :as style]
|
[status-im2.contexts.activity-center.notification.reply.style :as style]
|
||||||
[utils.datetime :as datetime]
|
[utils.datetime :as datetime]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(def tag-params
|
(def tag-params
|
||||||
|
|
|
@ -11,5 +11,10 @@
|
||||||
|
|
||||||
;; TODO: Replace with correct enum values once status-go implements them.
|
;; TODO: Replace with correct enum values once status-go implements them.
|
||||||
(def ^:const tx 66612)
|
(def ^:const tx 66612)
|
||||||
(def ^:const membership 66613)
|
|
||||||
(def ^:const system 66614)
|
(def ^:const system 66614)
|
||||||
|
|
||||||
|
(def ^:const membership
|
||||||
|
"Membership is like a logical group of notifications with different types, i.e.
|
||||||
|
it doesn't have a corresponding type in the backend. Think of the collection
|
||||||
|
as a composite key of actual types."
|
||||||
|
#{private-group-chat})
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
[status-im2.contexts.activity-center.notification.contact-request.view :as contact-request]
|
[status-im2.contexts.activity-center.notification.contact-request.view :as contact-request]
|
||||||
[status-im2.contexts.activity-center.notification.contact-verification.view :as
|
[status-im2.contexts.activity-center.notification.contact-verification.view :as
|
||||||
contact-verification]
|
contact-verification]
|
||||||
|
[status-im2.contexts.activity-center.notification.membership.view :as membership]
|
||||||
[status-im2.contexts.activity-center.notification.mentions.view :as mentions]
|
[status-im2.contexts.activity-center.notification.mentions.view :as mentions]
|
||||||
[status-im2.contexts.activity-center.notification.reply.view :as reply]
|
[status-im2.contexts.activity-center.notification.reply.view :as reply]
|
||||||
[status-im2.contexts.activity-center.style :as style]
|
[status-im2.contexts.activity-center.style :as style]
|
||||||
|
@ -98,24 +99,28 @@
|
||||||
[filter-selector-read-toggle]]]])
|
[filter-selector-read-toggle]]]])
|
||||||
|
|
||||||
(defn render-notification
|
(defn render-notification
|
||||||
[notification index]
|
[{:keys [type] :as notification} index]
|
||||||
[rn/view {:style (style/notification-container index)}
|
[rn/view {:style (style/notification-container index)}
|
||||||
(case (:type notification)
|
(cond
|
||||||
types/contact-verification
|
(= type types/contact-verification)
|
||||||
[contact-verification/view notification {}]
|
[contact-verification/view notification {}]
|
||||||
|
|
||||||
types/contact-request
|
(= type types/contact-request)
|
||||||
[contact-request/view notification]
|
[contact-request/view notification]
|
||||||
|
|
||||||
types/mention
|
(= type types/mention)
|
||||||
[mentions/view notification]
|
[mentions/view notification]
|
||||||
|
|
||||||
types/reply
|
(= type types/reply)
|
||||||
[reply/view notification]
|
[reply/view notification]
|
||||||
|
|
||||||
types/admin
|
(= type types/admin)
|
||||||
[admin/view notification]
|
[admin/view notification]
|
||||||
|
|
||||||
|
(some types/membership [type])
|
||||||
|
[membership/view notification]
|
||||||
|
|
||||||
|
:else
|
||||||
nil)])
|
nil)])
|
||||||
|
|
||||||
(defn view
|
(defn view
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
|
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
|
||||||
"owner": "status-im",
|
"owner": "status-im",
|
||||||
"repo": "status-go",
|
"repo": "status-go",
|
||||||
"version": "v0.122.1",
|
"version": "v0.125.0",
|
||||||
"commit-sha1": "d60c1d00ed93d4340ab06a32ce062dd7e67e67c0",
|
"commit-sha1": "e40cbfc28f6195b31eb0f59cbd58fd1d77a12821",
|
||||||
"src-sha256": "1452a1n5fijcpq56mxaaacjx4091mpxkq62qi1b2xiy91ll6ik5f"
|
"src-sha256": "0nmys3mf46pilmqrr937b0bc1qi9h51aamyswngy4qfb96nvky85"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
"accept-community-rules": "I agree with the community rules",
|
"accept-community-rules": "I agree with the community rules",
|
||||||
"account-added": "Account added",
|
"account-added": "Account added",
|
||||||
"account-color": "Account color",
|
"account-color": "Account color",
|
||||||
|
"added-to-group-chat": "Added to group chat",
|
||||||
|
"added-you-to": "added you to",
|
||||||
"anyone": "Anyone",
|
"anyone": "Anyone",
|
||||||
"messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group",
|
"messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group",
|
||||||
"messages-gap-warning": "Some messages might be missing",
|
"messages-gap-warning": "Some messages might be missing",
|
||||||
|
|
Loading…
Reference in New Issue