Swipe gestures for Activity Center notifications with CTA (#15284)

Implements swipe actions for notifications with call to action (e.g. pending
contact requests, unverified identity verifications, etc).

Fixes https://github.com/status-im/status-mobile/issues/15118

According to the Design team, the goal is to deliver a consistent experience to
users, so whenever the user sees a notification with buttons, the same actions
can be taken via the swipe buttons.

Note: swipe buttons are using placeholder icons while the Design team works out
which ones to use

Additionally, a bunch of fixes:

- Fix: outgoing pending contact requests were not being removed from the UI when
  cancelled.
- Fix: Membership tab not showing unread indicator.
- Fix: dismissed membership notification not marked as read.
- Fix: dismissed membership notification was displaying decline/accept buttons.
  Regression came from changes in status-go related to soft deletion of
  notifications.
- Fix: incorrect check for the pending state of a contact request.
- Fixed lots of bugs for identity verification notifications, as it was
  completely broken. Unfortunately, somebody made lots of changes without
  actually testing the flows.
- Add basic error handling and log if accepting, declining or canceling contact
  requests fail.

The demo shows an identity verification with swipe actions to reply or decline.
[identity-verification-swipe-to-reply.webm](https://user-images.githubusercontent.com/46027/223565755-b2ca3f68-12e2-4e1e-9e52-edd52cfcc971.webm)

Out of scope: The old quo input is still in use in the identity verification
notification. This will eventually be solved by issue
https://github.com/status-im/status-mobile/issues/14364

### Steps to test

Notifications with one or more buttons (actions) are affected by this change,
because now the user can also swipe left/right to act on them.

- Membership notifications: private group chat. The following PR explains how to
  generate them https://github.com/status-im/status-mobile/pull/14785
- Contact requests, and community gated requests to join (Admin tab).
- Identity verifications. I believe the only way to test identity verification
  flows at the moment is to use the Desktop app, since initiating the challenge
  is not implemented in Mobile yet.
- Mentions and replies don't have new swipe buttons because they don't have call
  to action buttons throughout their lifecycle.

Steps to test identity verification flows:

#### Identity verification flow 1

- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
  decline or reply.
- `B` declines and the status `Declined` is shown instead of buttons.
- `B` can now either swipe to toggle read/unread or swipe delete the
  notification.
- `A` should not receive any notification after `A` declined.

#### Identity verification flow 2

- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
  decline or reply.
- `B` press `Reply` and a bottom sheet is displayed with a text input.
- `B` sends the reply/answer message and the status `Replied` is shown instead
  of buttons.
- `B` can now either swipe to toggle read/unread or swipe to delete the
  notification.
- `A` should receive a notification with the reply from `B`.
- `A` can either mark the answer as untrustworthy or accept it (trust it) via
  the normal buttons, as well as via the swipe left/right buttons.
- If `A` accepts the answer, then the status `Confirmed` is shown instead of
  buttons. On the other hand, if `A` marks as untrustworthy, then the status
  `Untrustworthy` is shown instead of buttons.
- `B` should receive no further notifications due to `A`s actions.
- `A` can now either swipe to toggle read/unread or swipe delete the
  notification.
This commit is contained in:
Icaro Motta 2023-03-14 12:34:13 -03:00 committed by GitHub
parent e98ce54830
commit 9473d3f40c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 672 additions and 390 deletions

View File

@ -37,7 +37,7 @@
(when on-update-reply (when on-update-reply
(on-update-reply %))) (on-update-reply %)))
:auto-capitalize :none :auto-capitalize :none
:auto-focus true :auto-focus false
:accessibility-label :identity-verification-reply-text-input :accessibility-label :identity-verification-reply-text-input
:placeholder (i18n/label :t/type-something) :placeholder (i18n/label :t/type-something)
:return-key-type :none :return-key-type :none
@ -129,7 +129,7 @@
(-> button (-> button
(assoc :size size) (assoc :size size)
(assoc :type subtype) (assoc :type subtype)
(assoc :disabled (and replying? (disable-when @reply-input))) (assoc :disabled (and replying? disable-when (disable-when @reply-input)))
(update :style merge common-style {:margin-right 8})) (update :style merge common-style {:margin-right 8}))
label])) label]))
@ -142,7 +142,7 @@
:blur? blur?}]) :blur? blur?}])
(defn- footer (defn- footer
[_ _] [_]
(let [reply-input (reagent/atom "")] (let [reply-input (reagent/atom "")]
(fn [{:keys [replying? items] :as props}] (fn [{:keys [replying? items] :as props}]
[:<> [:<>

View File

@ -55,6 +55,7 @@
[status-im.wallet.core :as wallet] [status-im.wallet.core :as wallet]
status-im.wallet.custom-tokens.core status-im.wallet.custom-tokens.core
status-im2.contexts.activity-center.events status-im2.contexts.activity-center.events
status-im2.contexts.activity-center.notification.contact-requests.events
status-im2.contexts.shell.events status-im2.contexts.shell.events
status-im.chat.models.gaps status-im.chat.models.gaps
[status-im2.navigation.events :as navigation])) [status-im2.navigation.events :as navigation]))

View File

@ -177,7 +177,7 @@
(reset! expanded? true)) (reset! expanded? true))
(and @keyboard-was-shown? (not keyboard-shown)) (and @keyboard-was-shown? (not keyboard-shown))
(reset! expanded? false)))) (reset! expanded? false))))
[@show-bottom-sheet? @keyboard-was-shown?]) [@show-bottom-sheet? @keyboard-was-shown? keyboard-shown])
(react/effect! #(do (react/effect! #(do
(when-not @gesture-running? (when-not @gesture-running?
(cond (cond

View File

@ -5,7 +5,6 @@
[status-im2.common.toasts.events :as toasts] [status-im2.common.toasts.events :as toasts]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im2.contexts.activity-center.notification-types :as types] [status-im2.contexts.activity-center.notification-types :as types]
status-im2.contexts.activity-center.notification.contact-requests.events
[status-im2.contexts.chat.events :as chat.events] [status-im2.contexts.chat.events :as chat.events]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.collection :as collection] [utils.collection :as collection]
@ -43,6 +42,12 @@
(log/warn (str "Failed to " action) (log/warn (str "Failed to " action)
{:notification-id notification-id :error error})) {:notification-id notification-id :error error}))
(defn get-notification
[db notification-id]
(->> (get-in db [:activity-center :notifications])
(filter #(= notification-id (:id %)))
first))
;;;; Notification reconciliation ;;;; Notification reconciliation
(defn- update-notifications (defn- update-notifications
@ -94,12 +99,6 @@
;;;; Status changes (read/dismissed/deleted) ;;;; Status changes (read/dismissed/deleted)
(defn- get-notification
[db notification-id]
(->> (get-in db [:activity-center :notifications])
(filter #(= notification-id (:id %)))
first))
(rf/defn mark-as-read (rf/defn mark-as-read
{:events [:activity-center.notifications/mark-as-read]} {:events [:activity-center.notifications/mark-as-read]}
[{:keys [db]} notification-id] [{:keys [db]} notification-id]
@ -235,7 +234,7 @@
{:events [:activity-center.notifications/dismiss-success]} {:events [:activity-center.notifications/dismiss-success]}
[{:keys [db] :as cofx} notification-id] [{:keys [db] :as cofx} notification-id]
(let [notification (get-notification db notification-id)] (let [notification (get-notification db notification-id)]
(notifications-reconcile cofx [(assoc notification :dismissed true)]))) (notifications-reconcile cofx [(assoc notification :read true :dismissed true)])))
(rf/defn delete-notification (rf/defn delete-notification
{:events [:activity-center.notifications/delete]} {:events [:activity-center.notifications/delete]}

View File

@ -137,15 +137,16 @@
:action :notification/accept}))) :action :notification/accept})))
(deftest notification-dismissal-test (deftest notification-dismissal-test
(testing "dismisses notification, but keep it in the app db" (testing "dismisses & mark notification as read, and keep it in the app db"
(h/run-test-sync (h/run-test-sync
(setup) (setup)
(let [notif-1 {:id "0x1" :type types/private-group-chat} (let [notif-1 {:id "0x1" :type types/private-group-chat}
notif-2 {:id "0x2" :type types/admin} notif-2 {:id "0x2" :type types/admin}
dismissed-notif-1 (assoc notif-1 :dismissed true)] dismissed-notif-1 (assoc notif-1 :dismissed true :read true)]
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2)) (h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
(rf/dispatch [:test/assoc-in [:activity-center :notifications] (rf/dispatch [:test/assoc-in [:activity-center]
[notif-2 notif-1]]) {:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}])
(rf/dispatch [:activity-center.notifications/dismiss (:id notif-1)]) (rf/dispatch [:activity-center.notifications/dismiss (:id notif-1)])
@ -520,7 +521,7 @@
:type types/mention}] :type types/mention}]
(get-in (h/db) [:activity-center :notifications])))))) (get-in (h/db) [:activity-center :notifications]))))))
(testing "resets loading flag after an error" (testing "resets loading state after error"
(h/run-test-sync (h/run-test-sync
(setup) (setup)
(let [spy-queue (atom [])] (let [spy-queue (atom [])]

View File

@ -2,79 +2,111 @@
(:require [quo2.core :as quo] (:require [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 common-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.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn swipeable (defn- swipe-button-accept
[{:keys [height active-swipeable notification]} child] [{:keys [style]} _]
(if (#{constants/activity-center-membership-status-accepted [common/swipe-button-container
{:style (common-style/swipe-success-container style)
:icon :i/placeholder
:text (i18n/label :t/accept)}])
(defn- swipe-button-decline
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/decline)}])
(defn- swipeable
[{:keys [active-swipeable notification extra-fn]} child]
(let [{:keys [community-id id membership-status]} notification]
(cond
(#{constants/activity-center-membership-status-accepted
constants/activity-center-membership-status-declined} constants/activity-center-membership-status-declined}
(:membership-status notification)) membership-status)
[common/swipeable [common/swipeable
{:left-button common/left-swipe-button {:left-button common/swipe-button-read-or-unread
:left-on-press common/left-swipe-on-press :left-on-press common/swipe-on-press-toggle-read
:right-button common/right-swipe-button :right-button common/swipe-button-delete
:right-on-press common/right-swipe-on-press :right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable :active-swipeable active-swipeable
:extra-fn (fn [] {:height @height :notification notification})} :extra-fn extra-fn}
child] child]
child))
(= membership-status constants/activity-center-membership-status-pending)
[common/swipeable
{:left-button swipe-button-accept
:left-on-press #(rf/dispatch [:communities.ui/accept-request-to-join-pressed community-id id])
:right-button swipe-button-decline
:right-on-press #(rf/dispatch [:communities.ui/decline-request-to-join-pressed community-id
id])
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
:else
child)))
(defn view (defn view
[{:keys [author community-id id membership-status read timestamp]} [{:keys [notification set-swipeable-height] :as props}]
set-swipeable-height] (let [{:keys [author community-id id membership-status
(let [community (rf/sub [:communities/community community-id]) read timestamp]} notification
community-name (:name community) community (rf/sub [:communities/community community-id])
community-image (get-in community [:images :thumbnail :uri])] community-name (:name community)
[quo/activity-log community-image (get-in community [:images :thumbnail :uri])]
{:title (i18n/label :t/join-request) [swipeable props
:icon :i/add-user [quo/activity-log
:timestamp (datetime/timestamp->relative timestamp) {:title (i18n/label :t/join-request)
:unread? (not read) :icon :i/add-user
:on-layout set-swipeable-height :timestamp (datetime/timestamp->relative timestamp)
:context [[common/user-avatar-tag author] :unread? (not read)
(i18n/label :t/wants-to-join) :on-layout set-swipeable-height
[quo/context-tag :context [[common/user-avatar-tag author]
{:size :small (i18n/label :t/wants-to-join)
:override-theme :dark [quo/context-tag
:color colors/primary-50 {:size :small
:style style/user-avatar-tag :override-theme :dark
:text-style style/user-avatar-tag-text} :color colors/primary-50
{:uri community-image} community-name]] :style common-style/user-avatar-tag
:items (case membership-status :text-style common-style/user-avatar-tag-text}
constants/activity-center-membership-status-accepted {:uri community-image} community-name]]
[{:type :status :items (case membership-status
:subtype :positive constants/activity-center-membership-status-accepted
:key :status-accepted [{:type :status
:blur? true :subtype :positive
:label (i18n/label :t/accepted)}] :key :status-accepted
:blur? true
:label (i18n/label :t/accepted)}]
constants/activity-center-membership-status-declined constants/activity-center-membership-status-declined
[{:type :status [{:type :status
:subtype :negative :subtype :negative
:key :status-declined :key :status-declined
:blur? true :blur? true
:label (i18n/label :t/declined)}] :label (i18n/label :t/declined)}]
constants/activity-center-membership-status-pending constants/activity-center-membership-status-pending
[{:type :button [{:type :button
:subtype :danger :subtype :danger
:key :button-decline :key :button-decline
:label (i18n/label :t/decline) :label (i18n/label :t/decline)
:accessibility-label :decline-join-request :accessibility-label :decline-join-request
:on-press (fn [] :on-press (fn []
(rf/dispatch [:communities.ui/decline-request-to-join-pressed (rf/dispatch
community-id id]))} [:communities.ui/decline-request-to-join-pressed
{:type :button community-id id]))}
:subtype :positive {:type :button
:key :button-accept :subtype :positive
:label (i18n/label :t/accept) :key :button-accept
:accessibility-label :accept-join-request :label (i18n/label :t/accept)
:on-press (fn [] :accessibility-label :accept-join-request
(rf/dispatch [:communities.ui/accept-request-to-join-pressed :on-press (fn []
community-id id]))}] (rf/dispatch [:communities.ui/accept-request-to-join-pressed
community-id id]))}]
nil)}])) nil)}]]))

View File

@ -30,23 +30,29 @@
:outputRange [0 swipe-action-width] :outputRange [0 swipe-action-width]
:extrapolate :clamp})) :extrapolate :clamp}))
(defn left-swipe-container (def swipe-base
[style-props] {:align-items :center
(merge {:background-color colors/primary-60 :justify-content :center
:align-items :center :border-radius swipe-button-border-radius
:justify-content :center :width swipe-action-width})
:border-radius swipe-button-border-radius
:width swipe-action-width}
style-props))
(defn right-swipe-container (defn swipe-success-container
[style-props] [style]
(merge {:background-color colors/danger-60 (merge swipe-base
:align-items :center {:background-color colors/success-60}
:justify-content :center style))
:border-radius swipe-button-border-radius
:width swipe-action-width} (defn swipe-danger-container
style-props)) [style]
(merge swipe-base
{:background-color colors/danger-60}
style))
(defn swipe-primary-container
[style]
(merge swipe-base
{:background-color colors/primary-60}
style))
(def swipe-text (def swipe-text
{:margin-top 5 {:margin-top 5

View File

@ -54,39 +54,42 @@
(.close ^js @active-swipeable)) (.close ^js @active-swipeable))
(reset! active-swipeable @swipeable))) (reset! active-swipeable @swipeable)))
(defn left-swipe-button (defn swipe-button-container
[{:keys [style]} {:keys [notification]}] [{:keys [style icon text]} _]
[rn/animated-view [rn/animated-view
{:accessibility-label :notification-left-swipe {:accessibility-label :notification-swipe
:style (style/left-swipe-container style)} :style style}
[rn/view {:style style/swipe-text-wrapper} [rn/view {:style style/swipe-text-wrapper}
[quo/icon [quo/icon icon
(if (:read notification)
:i/notifications
:i/check)
{:color colors/white}] {:color colors/white}]
[quo/text {:style style/swipe-text} [quo/text {:style style/swipe-text}
(if (:read notification) text]]])
(i18n/label :t/unread)
(i18n/label :t/read))]]])
(defn right-swipe-button (defn swipe-button-read-or-unread
[{:keys [style]} {:keys [notification]}]
[swipe-button-container
{:style (style/swipe-primary-container style)
:icon (if (:read notification)
:i/notifications
:i/check)
:text (if (:read notification)
(i18n/label :t/unread)
(i18n/label :t/read))}])
(defn swipe-button-delete
[{:keys [style]}] [{:keys [style]}]
[rn/animated-view [swipe-button-container
{:accessibility-label :notification-right-swipe {:style (style/swipe-danger-container style)
:style (style/right-swipe-container style)} :icon :i/delete
[rn/view {:style style/swipe-text-wrapper} :text (i18n/label :t/delete)}])
[quo/icon :i/delete {:color colors/white}]
[quo/text {:style style/swipe-text}
(i18n/label :t/delete)]]])
(defn left-swipe-on-press (defn swipe-on-press-toggle-read
[{:keys [notification]}] [{:keys [notification]}]
(if (:read notification) (if (:read notification)
(rf/dispatch [:activity-center.notifications/mark-as-unread (:id notification)]) (rf/dispatch [:activity-center.notifications/mark-as-unread (:id notification)])
(rf/dispatch [:activity-center.notifications/mark-as-read (:id notification)]))) (rf/dispatch [:activity-center.notifications/mark-as-read (:id notification)])))
(defn right-swipe-on-press (defn swipe-on-press-delete
[{:keys [notification]}] [{:keys [notification]}]
(rf/dispatch [:activity-center.notifications/delete (:id notification)])) (rf/dispatch [:activity-center.notifications/delete (:id notification)]))
@ -102,15 +105,15 @@
& children] & children]
(into (into
[gesture/swipeable [gesture/swipeable
{:ref #(reset! swipeable-ref %) (merge
:accessibility-label :notification-swipeable {:ref #(reset! swipeable-ref %)
:friction 2 :accessibility-label :notification-swipeable
:left-threshold style/swipe-action-width :friction 2
:right-threshold style/swipe-action-width :on-swipeable-will-open (close-active-swipeable active-swipeable swipeable-ref)}
:overshoot-left false (when left-button
:overshoot-right false {:overshoot-left false
:on-swipeable-will-open (close-active-swipeable active-swipeable swipeable-ref) :left-threshold style/swipe-action-width
:render-left-actions (render-swipe-action :render-left-actions (render-swipe-action
{:active-swipeable active-swipeable {:active-swipeable active-swipeable
:extra-fn extra-fn :extra-fn extra-fn
:interpolation-opacity style/left-swipe-opacity-interpolation-js :interpolation-opacity style/left-swipe-opacity-interpolation-js
@ -118,14 +121,17 @@
style/left-swipe-translate-x-interpolation-js style/left-swipe-translate-x-interpolation-js
:on-press left-on-press :on-press left-on-press
:swipe-button left-button :swipe-button left-button
:swipeable-ref swipeable-ref}) :swipeable-ref swipeable-ref})})
:render-right-actions (render-swipe-action (when right-button
{:active-swipeable active-swipeable {:overshoot-right false
:extra-fn extra-fn :right-threshold style/swipe-action-width
:interpolation-opacity style/right-swipe-opacity-interpolation-js :render-right-actions (render-swipe-action
:interpolation-translate-x {:active-swipeable active-swipeable
style/right-swipe-translate-x-interpolation-js :extra-fn extra-fn
:on-press right-on-press :interpolation-opacity style/right-swipe-opacity-interpolation-js
:swipe-button right-button :interpolation-translate-x
:swipeable-ref swipeable-ref})}] style/right-swipe-translate-x-interpolation-js
:on-press right-on-press
:swipe-button right-button
:swipeable-ref swipeable-ref})}))]
children)))) children))))

View File

@ -1,26 +1,69 @@
(ns status-im2.contexts.activity-center.notification.contact-requests.events (ns status-im2.contexts.activity-center.notification.contact-requests.events
(:require [utils.re-frame :as rf])) (:require [status-im2.contexts.activity-center.events :as ac-events]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(rf/defn accept-contact-request (rf/defn accept-contact-request
{:events [:activity-center.contact-requests/accept-request]} {:events [:activity-center.contact-requests/accept]}
[{:keys [db]} id] [_ contact-id]
{:json-rpc/call [{:method "wakuext_acceptContactRequest" {:json-rpc/call
:params [{:id id}] [{:method "wakuext_acceptContactRequest"
:js-response true :params [{:id contact-id}]
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])}]}) :js-response true
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])
:on-error #(rf/dispatch [:activity-center.contact-requests/accept-error contact-id %])}]})
(rf/defn accept-contact-request-error
{:events [:activity-center.contact-requests/accept-error]}
[_ contact-id error]
(log/error "Failed to accept contact-request"
{:error error
:event :activity-center.contact-requests/accept
:contact-id contact-id})
nil)
(rf/defn decline-contact-request (rf/defn decline-contact-request
{:events [:activity-center.contact-requests/decline-request]} {:events [:activity-center.contact-requests/decline]}
[{:keys [db]} id] [_ contact-id]
{:json-rpc/call [{:method "wakuext_declineContactRequest" {:json-rpc/call
:params [{:id id}] [{:method "wakuext_declineContactRequest"
:js-response true :params [{:id contact-id}]
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])}]}) :js-response true
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])
:on-error #(rf/dispatch [:activity-center.contact-requests/decline-error contact-id %])}]})
(rf/defn decline-contact-request-error
{:events [:activity-center.contact-requests/decline-error]}
[_ contact-id error]
(log/error "Failed to decline contact-request"
{:error error
:event :activity-center.contact-requests/decline
:contact-id contact-id})
nil)
(rf/defn cancel-outgoing-contact-request (rf/defn cancel-outgoing-contact-request
{:events [:activity-center.contact-requests/cancel-outgoing-request]} {:events [:activity-center.contact-requests/cancel-outgoing]}
[{:keys [db]} id] [{:keys [db]} {:keys [contact-id notification-id]}]
{:json-rpc/call [{:method "wakuext_cancelOutgoingContactRequest" (when-let [notification (ac-events/get-notification db notification-id)]
:params [{:id id}] {:json-rpc/call
:js-response true [{:method "wakuext_cancelOutgoingContactRequest"
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])}]}) :params [{:id contact-id}]
:on-success #(rf/dispatch [:activity-center.contact-requests/cancel-outgoing-success
notification])
:on-error #(rf/dispatch [:activity-center.contact-requests/cancel-outgoing-error contact-id
%])}]}))
(rf/defn cancel-outgoing-contact-request-success
{:events [:activity-center.contact-requests/cancel-outgoing-success]}
[_ notification]
{:dispatch [:activity-center.notifications/reconcile
[(assoc notification :deleted true)]]})
(rf/defn cancel-outgoing-contact-request-error
{:events [:activity-center.contact-requests/cancel-outgoing-error]}
[_ contact-id error]
(log/error "Failed to cancel outgoing contact-request"
{:error error
:event :activity-center.contact-requests/cancel-outgoing
:contact-id contact-id})
nil)

View File

@ -2,31 +2,81 @@
(:require [quo2.core :as quo] (:require [quo2.core :as quo]
[react-native.gesture :as gesture] [react-native.gesture :as gesture]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im2.contexts.activity-center.notification.common.style :as common-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.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn swipeable (defn- swipe-button-accept
[{:keys [height active-swipeable notification]} child] [{:keys [style]} _]
(let [message (or (:message notification) (:last-message notification))] [common/swipe-button-container
(if (#{constants/contact-request-message-state-accepted {:style (common-style/swipe-success-container style)
constants/contact-request-message-state-declined} :icon :i/placeholder
(:contact-request-state message)) :text (i18n/label :t/accept)}])
(defn- swipe-button-decline
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/decline)}])
(defn- swipe-button-cancel-pending
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/cancel)}])
(defn- swipeable
[{:keys [active-swipeable extra-fn notification]} child]
(let [{:keys [id author message last-message]} notification
{:keys [contact-request-state]} (or (:message notification)
(:last-message notification))
{:keys [public-key]} (rf/sub [:multiaccount/contact])
message (or message last-message)]
(cond
(#{constants/contact-request-message-state-accepted
constants/contact-request-message-state-declined}
contact-request-state)
[common/swipeable [common/swipeable
{:left-button common/left-swipe-button {:left-button common/swipe-button-read-or-unread
:left-on-press common/left-swipe-on-press :left-on-press common/swipe-on-press-toggle-read
:right-button common/right-swipe-button :right-button common/swipe-button-delete
:right-on-press common/right-swipe-on-press :right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable :active-swipeable active-swipeable
:extra-fn (fn [] {:height @height :notification notification})} :extra-fn extra-fn}
child] child]
(= contact-request-state constants/contact-request-message-state-pending)
(if (= public-key author)
[common/swipeable
{:right-button swipe-button-cancel-pending
:right-on-press (fn []
(rf/dispatch
[:activity-center.contact-requests/cancel-outgoing
{:contact-id (:from message)
:notification-id id}]))
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
[common/swipeable
{:left-button swipe-button-accept
:left-on-press #(rf/dispatch [:activity-center.contact-requests/accept id])
:right-button swipe-button-decline
:right-on-press #(rf/dispatch [:activity-center.contact-requests/decline id])
:active-swipeable active-swipeable
:extra-fn extra-fn}
child])
:else
child))) child)))
(defn outgoing-contact-request-view (defn- outgoing-contact-request-view
[{:keys [id chat-id message last-message] :as notification} [{:keys [notification set-swipeable-height]}]
set-swipeable-height] (let [{:keys [id chat-id message last-message]} notification
(let [{:keys [contact-request-state] :as message} (or message last-message)] {:keys [contact-request-state] :as message} (or message last-message)]
(if (= contact-request-state constants/contact-request-message-state-accepted) (if (= contact-request-state constants/contact-request-message-state-accepted)
[quo/activity-log [quo/activity-log
{:title (i18n/label :t/contact-request-was-accepted) {:title (i18n/label :t/contact-request-was-accepted)
@ -48,7 +98,7 @@
[common/user-avatar-tag chat-id]] [common/user-avatar-tag chat-id]]
:message {:body (get-in message [:content :text])} :message {:body (get-in message [:content :text])}
:items (case contact-request-state :items (case contact-request-state
constants/contact-request-state-mutual constants/contact-request-message-state-pending
[{:type :button [{:type :button
:subtype :danger :subtype :danger
:key :button-cancel :key :button-cancel
@ -56,10 +106,9 @@
:accessibility-label :cancel-contact-request :accessibility-label :cancel-contact-request
:on-press (fn [] :on-press (fn []
(rf/dispatch (rf/dispatch
[:activity-center.contact-requests/cancel-outgoing-request [:activity-center.contact-requests/cancel-outgoing
(:from message)]) {:contact-id (:from message)
(rf/dispatch [:activity-center.notifications/mark-as-read :notification-id id}]))}
id]))}
{:type :status {:type :status
:subtype :pending :subtype :pending
:key :status-pending :key :status-pending
@ -75,10 +124,10 @@
nil)}]))) nil)}])))
(defn incoming-contact-request-view (defn- incoming-contact-request-view
[{:keys [id author message last-message] :as notification} [{:keys [notification set-swipeable-height]}]
set-swipeable-height] (let [{:keys [id author message last-message]} notification
(let [message (or message last-message)] message (or message last-message)]
[quo/activity-log [quo/activity-log
{:title (i18n/label :t/contact-request) {:title (i18n/label :t/contact-request)
:on-layout set-swipeable-height :on-layout set-swipeable-height
@ -110,36 +159,31 @@
:key :button-decline :key :button-decline
:label (i18n/label :t/decline) :label (i18n/label :t/decline)
:accessibility-label :decline-contact-request :accessibility-label :decline-contact-request
:on-press (fn [] :on-press #(rf/dispatch [:activity-center.contact-requests/decline id])}
(rf/dispatch [:activity-center.contact-requests/decline-request id])
(rf/dispatch [:activity-center.notifications/mark-as-read
id]))}
{:type :button {:type :button
:subtype :positive :subtype :positive
:key :button-accept :key :button-accept
:label (i18n/label :t/accept) :label (i18n/label :t/accept)
:accessibility-label :accept-contact-request :accessibility-label :accept-contact-request
:on-press (fn [] :on-press #(rf/dispatch [:activity-center.contact-requests/accept id])}]
(rf/dispatch [:activity-center.contact-requests/accept-request id])
(rf/dispatch [:activity-center.notifications/mark-as-read
id]))}]
nil)}])) nil)}]))
(defn view (defn view
[{:keys [author message last-message] :as notification} [{:keys [notification] :as props}]
set-swipeable-height] (let [{:keys [author message last-message]} notification
(let [{:keys [public-key]} (rf/sub [:multiaccount/contact]) {:keys [public-key]} (rf/sub [:multiaccount/contact])
{:keys [contact-request-state]} (or message last-message)] {:keys [contact-request-state]} (or message last-message)]
(cond [swipeable props
(= public-key author) (cond
[outgoing-contact-request-view notification set-swipeable-height] (= public-key author)
[outgoing-contact-request-view props]
(= contact-request-state constants/contact-request-message-state-accepted) (= contact-request-state constants/contact-request-message-state-accepted)
[gesture/touchable-without-feedback [gesture/touchable-without-feedback
{:on-press (fn [] {:on-press (fn []
(rf/dispatch [:hide-popover]) (rf/dispatch [:hide-popover])
(rf/dispatch [:chat.ui/start-chat author]))} (rf/dispatch [:chat.ui/start-chat author]))}
[incoming-contact-request-view notification set-swipeable-height]] [incoming-contact-request-view props]]
:else :else
[incoming-contact-request-view notification set-swipeable-height]))) [incoming-contact-request-view props])]))

View File

@ -1,16 +1,40 @@
(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.style :as common-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.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- hide-bottom-sheet-and-dispatch (defn- swipe-button-decline
[event] [{:keys [style]} _]
(rf/dispatch [:bottom-sheet/hide]) [common/swipe-button-container
(rf/dispatch event)) {:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/decline)}])
(defn- swipe-button-reply
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-primary-container style)
:icon :i/placeholder
:text (i18n/label :t/message-reply)}])
(defn- swipe-button-untrustworthy
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/untrustworthy)}])
(defn- swipe-button-trust
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-success-container style)
:icon :i/placeholder
:text (i18n/label :t/accept)}])
(defn- context-tags (defn- context-tags
[challenger? {:keys [author contact-verification-status]}] [challenger? {:keys [author contact-verification-status]}]
@ -38,18 +62,6 @@
(= contact-verification-status constants/contact-verification-status-declined)) (= contact-verification-status constants/contact-verification-status-declined))
{:body (get-in message [:content :text])}))) {:body (get-in message [:content :text])})))
(defn- activity-status
[challenger? contact-verification-status]
(if challenger?
(cond (= contact-verification-status constants/contact-verification-status-trusted)
{:type :positive :label (i18n/label :t/status-confirmed)}
(= contact-verification-status constants/contact-verification-status-untrustworthy)
{:type :negative :label (i18n/label :t/untrustworthy)})
(cond (= contact-verification-status constants/contact-verification-status-accepted)
{:type :positive :label (i18n/label :t/replied)}
(= contact-verification-status constants/contact-verification-status-declined)
{:type :negative :label (i18n/label :t/declined)})))
(def ^:private max-reply-length (def ^:private max-reply-length
280) 280)
@ -57,84 +69,181 @@
[reply] [reply]
(<= (count reply) max-reply-length)) (<= (count reply) max-reply-length))
(def ^:private invalid-reply?
(comp not valid-reply?))
(declare view)
(defn- decline-challenge
[id]
(rf/dispatch [:bottom-sheet/hide])
(rf/dispatch [:activity-center.contact-verification/decline id])
(rf/dispatch [:activity-center.notifications/mark-as-read id]))
(defn- prepare-challenge-reply
[props]
(rf/dispatch [:bottom-sheet/show-sheet
{:content view
:override-theme :dark}
(assoc props :replying? true)]))
(defn- send-challenge-reply
[id reply]
(rf/dispatch [:bottom-sheet/hide])
(rf/dispatch [:activity-center.contact-verification/reply id reply])
(rf/dispatch [:activity-center.notifications/mark-as-read id]))
(defn- mark-challenge-untrustworthy
[id]
(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy id])
(rf/dispatch [:activity-center.notifications/mark-as-read id]))
(defn- mark-challenge-trusted
[id]
(rf/dispatch [:activity-center.contact-verification/mark-as-trusted id])
(rf/dispatch [:activity-center.notifications/mark-as-read id]))
(defn- swipeable
[{:keys [active-swipeable extra-fn notification replying?] :as props} child]
(let [{:keys [id message
contact-verification-status]} notification
challenger? (:outgoing message)]
(cond
replying?
child
(and (not challenger?)
(= contact-verification-status constants/contact-verification-status-pending))
[common/swipeable
{:left-button swipe-button-reply
:left-on-press #(prepare-challenge-reply props)
:right-button swipe-button-decline
:right-on-press #(decline-challenge id)
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
(and challenger?
(= contact-verification-status constants/contact-verification-status-accepted))
[common/swipeable
{:left-button swipe-button-trust
:left-on-press #(mark-challenge-trusted id)
:right-button swipe-button-untrustworthy
:right-on-press #(mark-challenge-untrustworthy id)
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
(#{constants/contact-verification-status-accepted
constants/contact-verification-status-declined
constants/contact-verification-status-trusted}
contact-verification-status)
[common/swipeable
{:left-button common/swipe-button-read-or-unread
:left-on-press common/swipe-on-press-toggle-read
:right-button common/swipe-button-delete
:right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
:else
child)))
(defn view (defn view
[_ _] [_]
(let [reply (atom "")] (let [reply (atom "")]
(fn [{:keys [id message contact-verification-status] :as notification} {:keys [replying?]}] (fn [{:keys [notification set-swipeable-height replying?] :as props}]
(let [challenger? (:outgoing message)] (let [{:keys [id message
contact-verification-status]} notification
challenger? (:outgoing message)]
;; TODO(@ilmotta): Declined challenges should only be displayed for the ;; TODO(@ilmotta): Declined challenges should only be displayed for the
;; challengee, not the challenger. ;; challengee, not the challenger.
;; https://github.com/status-im/status-mobile/issues/14354 ;; https://github.com/status-im/status-mobile/issues/14354
(when-not (and challenger? (when-not
(= contact-verification-status constants/contact-verification-status-declined)) (and challenger?
[quo/activity-log (= contact-verification-status constants/contact-verification-status-declined))
(merge [swipeable props
{:title (i18n/label :t/identity-verification-request) [quo/activity-log
:icon :i/friend (merge
:timestamp (datetime/timestamp->relative (:timestamp notification)) (when-not replying?
:unread? (not (:read notification)) {:on-layout set-swipeable-height})
:on-update-reply #(reset! reply %) {:title (i18n/label :t/identity-verification-request)
:replying? replying? :icon :i/friend
:max-reply-length max-reply-length :timestamp (datetime/timestamp->relative (:timestamp notification))
:valid-reply? valid-reply? :unread? (not (:read notification))
:context (context-tags challenger? notification) :on-update-reply #(reset! reply %)
:message (activity-message challenger? notification) :replying? replying?
:status (activity-status challenger? contact-verification-status) :max-reply-length max-reply-length
:items :valid-reply? valid-reply?
(if challenger? :context (context-tags challenger? notification)
(when (= contact-verification-status constants/contact-verification-status-accepted) :message (activity-message challenger? notification)
:items
(cond-> []
(and challenger?
(= contact-verification-status constants/contact-verification-status-accepted))
(concat
[{:type :button [{:type :button
:subtype :danger :subtype :danger
:key :button-mark-as-untrustworthy :key :button-mark-as-untrustworthy
:label (i18n/label :t/untrustworthy) :label (i18n/label :t/untrustworthy)
:accessibility-label :mark-contact-verification-as-untrustworthy :accessibility-label :mark-contact-verification-as-untrustworthy
:on-press (fn [] :on-press #(mark-challenge-untrustworthy id)}
(rf/dispatch
[:activity-center.contact-verification/mark-as-untrustworthy
id])
(rf/dispatch [:activity-center.notifications/mark-as-read
id]))}
{:type :button {:type :button
:subtype :positive :subtype :positive
:key :button-accept :key :button-accept
:label (i18n/label :t/accept) :label (i18n/label :t/accept)
:accessibility-label :mark-contact-verification-as-trusted :accessibility-label :mark-contact-verification-as-trusted
:on-press (fn [] :on-press #(mark-challenge-trusted id)}])
(rf/dispatch
[:activity-center.contact-verification/mark-as-trusted id]) (and challenger?
(rf/dispatch [:activity-center.notifications/mark-as-read (= contact-verification-status constants/contact-verification-status-trusted))
id]))}]) (concat [{:type :status
(when (= contact-verification-status constants/contact-verification-status-pending) :subtype :positive
:key :status-trusted
:label (i18n/label :t/status-confirmed)}])
(and challenger?
(= contact-verification-status constants/contact-verification-status-untrustworthy))
(concat [{:type :status
:subtype :negative
:key :status-untrustworthy
:label (i18n/label :t/untrustworthy)}])
(and (not challenger?)
(= contact-verification-status constants/contact-verification-status-accepted))
(concat [{:type :status
:subtype :positive
:key :status-accepted
:label (i18n/label :t/replied)}])
(and (not challenger?)
(= contact-verification-status constants/contact-verification-status-declined))
(concat [{:type :status
:subtype :negative
:key :status-declined
:label (i18n/label :t/declined)}])
(and (not challenger?)
(= contact-verification-status constants/contact-verification-status-pending))
(concat
[{:type :button [{:type :button
:subtype :danger :subtype :danger
:key :button-decline :key :button-decline
:label (i18n/label :t/decline) :label (i18n/label :t/decline)
:accessibility-label :decline-contact-verification :accessibility-label :decline-contact-verification
:on-press (fn [] :on-press #(decline-challenge id)}
(hide-bottom-sheet-and-dispatch
[:activity-center.contact-verification/decline id])
(rf/dispatch
[:activity-center.notifications/mark-as-read id]))}
(if replying? (if replying?
{:type :button {:type :button
:subtype :primary :subtype :primary
:key :button-reply :key :button-reply
:label (i18n/label :t/send-reply) :label (i18n/label :t/send-reply)
:accessibility-label :reply-to-contact-verification :accessibility-label :reply-to-contact-verification
:disable-when #(not (valid-reply? %)) :disable-when invalid-reply?
:on-press (fn [] :on-press #(send-challenge-reply id @reply)}
(hide-bottom-sheet-and-dispatch
[:activity-center.contact-verification/reply id
@reply])
(rf/dispatch
[:activity-center.notifications/mark-as-read id]))}
{:type :button {:type :button
:subtype :primary :subtype :primary
:key :button-send-reply :key :button-send-reply
:label (i18n/label :t/message-reply) :label (i18n/label :t/message-reply)
:accessibility-label :send-reply-to-contact-verification :accessibility-label :send-reply-to-contact-verification
:on-press (fn [] :on-press #(prepare-challenge-reply props)})]))})]])))))
(rf/dispatch [:bottom-sheet/show-sheet
{:content view}
{:notification notification
:replying? true}]))})]))})])))))

View File

@ -1,45 +1,84 @@
(ns status-im2.contexts.activity-center.notification.membership.view (ns status-im2.contexts.activity-center.notification.membership.view
(:require [quo2.core :as quo] (:require [quo2.core :as quo]
[react-native.core :as rn] [react-native.gesture :as gesture]
[status-im2.contexts.activity-center.notification.common.style :as common-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.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn pressable (defn- pressable
[{:keys [accepted chat-id]} & children] [{:keys [accepted chat-id]} child]
(if accepted (if accepted
(into [rn/touchable-opacity [gesture/touchable-without-feedback
{:on-press (fn [] {:on-press (fn []
(rf/dispatch [:hide-popover]) (rf/dispatch [:hide-popover])
(rf/dispatch [:chat/navigate-to-chat chat-id]))}] (rf/dispatch [:chat/navigate-to-chat chat-id]))}
children) child]
(into [:<>] children))) child))
(defn- swipe-button-accept
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-success-container style)
:icon :i/placeholder
:text (i18n/label :t/accept)}])
(defn- swipe-button-decline
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/decline)}])
(defn- swipeable
[{:keys [active-swipeable notification extra-fn]} child]
(let [{:keys [accepted dismissed id]} notification]
(if (or accepted dismissed)
[common/swipeable
{:left-button common/swipe-button-read-or-unread
:left-on-press common/swipe-on-press-toggle-read
:right-button common/swipe-button-delete
:right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable
:extra-fn extra-fn}
child]
[common/swipeable
{:left-button swipe-button-accept
:left-on-press #(rf/dispatch [:activity-center.notifications/accept id])
:right-button swipe-button-decline
:right-on-press #(rf/dispatch [:activity-center.notifications/dismiss id])
:active-swipeable active-swipeable
:extra-fn extra-fn}
child])))
(defn view (defn view
[{:keys [id accepted author read timestamp chat-name chat-id]}] [{:keys [notification set-swipeable-height] :as props}]
[pressable {:accepted accepted :chat-id chat-id} (let [{:keys [id accepted dismissed author read timestamp chat-name chat-id]} notification]
[quo/activity-log [swipeable props
{:title (i18n/label :t/added-to-group-chat) [pressable {:accepted accepted :chat-id chat-id}
:icon :i/add-user [quo/activity-log
:timestamp (datetime/timestamp->relative timestamp) {:title (i18n/label :t/added-to-group-chat)
:unread? (not read) :on-layout set-swipeable-height
:context [[common/user-avatar-tag author] :icon :i/add-user
(i18n/label :t/added-you-to) :timestamp (datetime/timestamp->relative timestamp)
[quo/group-avatar-tag chat-name :unread? (not read)
{:size :small :context [[common/user-avatar-tag author]
:color :purple}]] (i18n/label :t/added-you-to)
:items (when-not accepted [quo/group-avatar-tag chat-name
[{:type :button {:size :small
:subtype :positive :color :purple}]]
:key :button-accept :items (when-not (or accepted dismissed)
:label (i18n/label :t/accept) [{:type :button
:accessibility-label :accept-group-chat-invitation :subtype :positive
:on-press #(rf/dispatch [:activity-center.notifications/accept id])} :key :button-accept
{:type :button :label (i18n/label :t/accept)
:subtype :danger :accessibility-label :accept-group-chat-invitation
:key :button-decline :on-press #(rf/dispatch [:activity-center.notifications/accept id])}
:label (i18n/label :t/decline) {:type :button
:accessibility-label :decline-group-chat-invitation :subtype :danger
:on-press #(rf/dispatch [:activity-center.notifications/dismiss :key :button-decline
id])}])}]]) :label (i18n/label :t/decline)
:accessibility-label :decline-group-chat-invitation
:on-press #(rf/dispatch [:activity-center.notifications/dismiss
id])}])}]]]))

View File

@ -9,14 +9,14 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(def tag-params (def ^:private tag-params
{:size :small {:size :small
:override-theme :dark :override-theme :dark
:color colors/primary-50 :color colors/primary-50
:style style/tag :style style/tag
:text-style style/tag-text}) :text-style style/tag-text})
(defn message-body (defn- message-body
[message] [message]
(let [parsed-text (get-in message [:content :parsed-text]) (let [parsed-text (get-in message [:content :parsed-text])
parsed-text-children (:children (first parsed-text))] parsed-text-children (:children (first parsed-text))]
@ -35,37 +35,39 @@
literal)) literal))
parsed-text-children)))) parsed-text-children))))
(defn swipeable (defn- swipeable
[{:keys [height active-swipeable notification]} child] [{:keys [active-swipeable extra-fn]} child]
[common/swipeable [common/swipeable
{:left-button common/left-swipe-button {:left-button common/swipe-button-read-or-unread
:left-on-press common/left-swipe-on-press :left-on-press common/swipe-on-press-toggle-read
:right-button common/right-swipe-button :right-button common/swipe-button-delete
:right-on-press common/right-swipe-on-press :right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable :active-swipeable active-swipeable
:extra-fn (fn [] {:height @height :notification notification})} :extra-fn extra-fn}
child]) child])
(defn view (defn view
[{:keys [author chat-name community-id chat-id message read timestamp]} [{:keys [notification set-swipeable-height] :as props}]
set-swipeable-height] (let [{:keys [author chat-name community-id chat-id
(let [community-chat? (not (string/blank? community-id)) message read timestamp]} notification
community (rf/sub [:communities/community community-id]) community-chat? (not (string/blank? community-id))
community-name (:name community) community (rf/sub [:communities/community community-id])
community-image (get-in community [:images :thumbnail :uri])] community-name (:name community)
[gesture/touchable-without-feedback community-image (get-in community [:images :thumbnail :uri])]
{:on-press (fn [] [swipeable props
(rf/dispatch [:hide-popover]) [gesture/touchable-without-feedback
(rf/dispatch [:chat/navigate-to-chat chat-id]))} {:on-press (fn []
[quo/activity-log (rf/dispatch [:hide-popover])
{:title (i18n/label :t/mention) (rf/dispatch [:chat/navigate-to-chat chat-id]))}
:on-layout set-swipeable-height [quo/activity-log
:icon :i/mention {:title (i18n/label :t/mention)
:timestamp (datetime/timestamp->relative timestamp) :on-layout set-swipeable-height
:unread? (not read) :icon :i/mention
:context [[common/user-avatar-tag author] :timestamp (datetime/timestamp->relative timestamp)
[quo/text {:style style/tag-text} (string/lower-case (i18n/label :t/on))] :unread? (not read)
(if community-chat? :context [[common/user-avatar-tag author]
[quo/context-tag tag-params {:uri community-image} community-name chat-name] [quo/text {:style style/tag-text} (string/lower-case (i18n/label :t/on))]
[quo/group-avatar-tag chat-name tag-params])] (if community-chat?
:message {:body (message-body message)}}]])) [quo/context-tag tag-params {:uri community-image} community-name chat-name]
[quo/group-avatar-tag chat-name tag-params])]
:message {:body (message-body message)}}]]]))

View File

@ -12,7 +12,7 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(def tag-params (def ^:private tag-params
{:size :small {:size :small
:override-theme :dark :override-theme :dark
:color colors/primary-50 :color colors/primary-50
@ -20,7 +20,7 @@
:text-style style/tag-text}) :text-style style/tag-text})
;; NOTE: Replies support text, image and stickers only. ;; NOTE: Replies support text, image and stickers only.
(defn get-message-content (defn- get-message-content
[{:keys [content-type] :as message}] [{:keys [content-type] :as message}]
(case content-type (case content-type
constants/content-type-text (get-in message [:content :text]) constants/content-type-text (get-in message [:content :text])
@ -37,38 +37,40 @@
nil)) nil))
(defn swipeable (defn- swipeable
[{:keys [height active-swipeable notification]} child] [{:keys [active-swipeable extra-fn]} child]
[common/swipeable [common/swipeable
{:left-button common/left-swipe-button {:left-button common/swipe-button-read-or-unread
:left-on-press common/left-swipe-on-press :left-on-press common/swipe-on-press-toggle-read
:right-button common/right-swipe-button :right-button common/swipe-button-delete
:right-on-press common/right-swipe-on-press :right-on-press common/swipe-on-press-delete
:active-swipeable active-swipeable :active-swipeable active-swipeable
:extra-fn (fn [] {:height @height :notification notification})} :extra-fn extra-fn}
child]) child])
(defn view (defn view
[{:keys [author chat-name community-id chat-id message read timestamp]} [{:keys [notification set-swipeable-height] :as props}]
set-swipeable-height] (let [{:keys [author chat-name community-id chat-id
(let [community-chat? (not (string/blank? community-id)) message read timestamp]} notification
community (rf/sub [:communities/community community-id]) community-chat? (not (string/blank? community-id))
community-name (:name community) community (rf/sub [:communities/community community-id])
community-image (get-in community [:images :thumbnail :uri])] community-name (:name community)
[gesture/touchable-without-feedback community-image (get-in community [:images :thumbnail :uri])]
{:on-press (fn [] [swipeable props
(rf/dispatch [:hide-popover]) [gesture/touchable-without-feedback
(rf/dispatch [:chat/navigate-to-chat chat-id]))} {:on-press (fn []
[quo/activity-log (rf/dispatch [:hide-popover])
{:title (i18n/label :t/message-reply) (rf/dispatch [:chat/navigate-to-chat chat-id]))}
:on-layout set-swipeable-height [quo/activity-log
:icon :i/reply {:title (i18n/label :t/message-reply)
:timestamp (datetime/timestamp->relative timestamp) :on-layout set-swipeable-height
:unread? (not read) :icon :i/reply
:context [[common/user-avatar-tag author] :timestamp (datetime/timestamp->relative timestamp)
[quo/text {:style style/lowercase-text} (i18n/label :t/on)] :unread? (not read)
(if community-chat? :context [[common/user-avatar-tag author]
[quo/context-tag tag-params {:uri community-image} community-name chat-name] [quo/text {:style style/lowercase-text} (i18n/label :t/on)]
[quo/group-avatar-tag chat-name tag-params])] (if community-chat?
:message {:body-number-of-lines 1 [quo/context-tag tag-params {:uri community-image} community-name chat-name]
:body (get-message-content message)}}]])) [quo/group-avatar-tag chat-name tag-params])]
:message {:body-number-of-lines 1
:body (get-message-content message)}}]]]))

View File

@ -1,5 +1,6 @@
(ns status-im2.contexts.activity-center.view (ns status-im2.contexts.activity-center.view
(:require [oops.core :as oops] (:require [clojure.set :as set]
[oops.core :as oops]
[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]
@ -127,7 +128,7 @@
:label (i18n/label :t/membership) :label (i18n/label :t/membership)
:accessibility-label :tab-membership :accessibility-label :tab-membership
:notification-dot? (when-not is-mark-all-as-read-undoable? :notification-dot? (when-not is-mark-all-as-read-undoable?
(contains? types-with-unread types/membership))} (set/subset? types/membership types-with-unread))}
{:id types/system {:id types/system
:label (i18n/label :t/system) :label (i18n/label :t/system)
:accessibility-label :tab-system :accessibility-label :tab-system
@ -172,32 +173,30 @@
(let [height (atom 0) (let [height (atom 0)
set-swipeable-height #(reset! height (oops/oget % "nativeEvent.layout.height"))] set-swipeable-height #(reset! height (oops/oget % "nativeEvent.layout.height"))]
(fn [{:keys [type] :as notification} index _ active-swipeable] (fn [{:keys [type] :as notification} index _ active-swipeable]
(let [swipeable-args {:height height (let [props {:height height
:active-swipeable active-swipeable :active-swipeable active-swipeable
:notification notification}] :set-swipeable-height set-swipeable-height
:notification notification
:extra-fn (fn [] {:height @height :notification notification})}]
[rn/view {:style (style/notification-container index)} [rn/view {:style (style/notification-container index)}
(cond (cond
(= type types/contact-verification) (= type types/contact-verification)
[contact-verification/view notification {}] [contact-verification/view props]
(= type types/contact-request) (= type types/contact-request)
[contact-requests/swipeable swipeable-args [contact-requests/view props]
[contact-requests/view notification set-swipeable-height]]
(= type types/mention) (= type types/mention)
[mentions/swipeable swipeable-args [mentions/view props]
[mentions/view notification set-swipeable-height]]
(= type types/reply) (= type types/reply)
[reply/swipeable swipeable-args [reply/view props]
[reply/view notification set-swipeable-height]]
(= type types/admin) (= type types/admin)
[admin/swipeable swipeable-args [admin/view props]
[admin/view notification set-swipeable-height]]
(some types/membership [type]) (some types/membership [type])
[membership/view notification] [membership/view props]
:else :else
nil)])))) nil)]))))

View File

@ -286,7 +286,7 @@ class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase):
self.home_1.just_fyi("Mark it as read and check filter") self.home_1.just_fyi("Mark it as read and check filter")
reply_element.swipe_right_on_element() reply_element.swipe_right_on_element()
self.home_1.activity_left_swipe_button.click() self.home_1.activity_notification_swipe_button.click()
if reply_element.is_element_displayed(2): if reply_element.is_element_displayed(2):
self.errors.append("Message is not marked as read!") self.errors.append("Message is not marked as read!")
self.home_1.activity_unread_filter_button.click() self.home_1.activity_unread_filter_button.click()
@ -295,7 +295,7 @@ class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase):
self.home_1.just_fyi("Mark it as unread and check filter via right swipe") self.home_1.just_fyi("Mark it as unread and check filter via right swipe")
reply_element.swipe_right_on_element() reply_element.swipe_right_on_element()
self.home_1.activity_left_swipe_button.click() self.home_1.activity_notification_swipe_button.click()
if not reply_element.unread_indicator.is_element_displayed(): if not reply_element.unread_indicator.is_element_displayed():
self.errors.append("No unread dot is shown on activity center element after marking it as unread!") self.errors.append("No unread dot is shown on activity center element after marking it as unread!")
@ -310,7 +310,7 @@ class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase):
self.home_1.just_fyi("Delete it from unread via left swipe") self.home_1.just_fyi("Delete it from unread via left swipe")
self.home_1.open_activity_center_button.click() self.home_1.open_activity_center_button.click()
reply_element.swipe_left_on_element() reply_element.swipe_left_on_element()
self.home_1.activity_right_swipe_button.click() self.home_1.activity_notification_swipe_button.click()
if reply_element.is_element_displayed(): if reply_element.is_element_displayed():
self.errors.append("Reply is still shown after removing from activity centre!") self.errors.append("Reply is still shown after removing from activity centre!")

View File

@ -254,8 +254,7 @@ class HomeView(BaseView):
# Activity centre # Activity centre
self.mention_activity_tab_button = ActivityTabButton(self.driver, accessibility_id="tab-mention") self.mention_activity_tab_button = ActivityTabButton(self.driver, accessibility_id="tab-mention")
self.reply_activity_tab_button = ActivityTabButton(self.driver, accessibility_id="tab-reply") self.reply_activity_tab_button = ActivityTabButton(self.driver, accessibility_id="tab-reply")
self.activity_right_swipe_button = Button(self.driver, accessibility_id="notification-right-swipe") self.activity_notification_swipe_button = Button(self.driver, accessibility_id="notification-swipe")
self.activity_left_swipe_button = Button(self.driver, accessibility_id="notification-left-swipe")
self.activity_unread_filter_button = Button(self.driver, accessibility_id="selector-filter") self.activity_unread_filter_button = Button(self.driver, accessibility_id="selector-filter")