Implement identity verification flow (#14365)
This commit is contained in:
parent
509ffc2a01
commit
0f7ccce3df
|
@ -1,10 +1,50 @@
|
||||||
(ns quo2.components.notifications.activity-logs
|
(ns quo2.components.notifications.activity-logs
|
||||||
(:require [react-native.core :as rn]
|
(:require [quo.core :as quo]
|
||||||
[quo2.components.buttons.button :as button]
|
[quo2.components.buttons.button :as button]
|
||||||
[quo2.components.icon :as icon]
|
[quo2.components.icon :as icon]
|
||||||
[quo2.components.markdown.text :as text]
|
[quo2.components.markdown.text :as text]
|
||||||
[quo2.components.tags.status-tags :as status-tags]
|
[quo2.components.tags.status-tags :as status-tags]
|
||||||
[quo2.foundations.colors :as colors]))
|
[quo2.foundations.colors :as colors]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[status-im.i18n.i18n :as i18n]))
|
||||||
|
|
||||||
|
(def ^:private max-reply-length
|
||||||
|
280)
|
||||||
|
|
||||||
|
(defn- valid-reply?
|
||||||
|
[reply]
|
||||||
|
(<= (count reply) max-reply-length))
|
||||||
|
|
||||||
|
(defn- activity-reply-text-input
|
||||||
|
[reply-input on-update-reply]
|
||||||
|
[rn/view
|
||||||
|
[rn/view {:style {:margin-top 16
|
||||||
|
:margin-bottom 8
|
||||||
|
:flex-direction :row}}
|
||||||
|
[text/text {:weight :medium
|
||||||
|
:style {:flex-grow 1
|
||||||
|
:color colors/neutral-40}}
|
||||||
|
(i18n/label :t/your-answer)]
|
||||||
|
[text/text {:style {:flex-shrink 1
|
||||||
|
:color (if (valid-reply? @reply-input)
|
||||||
|
colors/neutral-40
|
||||||
|
colors/danger-60)}}
|
||||||
|
(str (count @reply-input) "/" max-reply-length)]]
|
||||||
|
[rn/view
|
||||||
|
;; TODO(@ilmotta): Replace with quo2 component when available.
|
||||||
|
;; https://github.com/status-im/status-mobile/issues/14364
|
||||||
|
[quo/text-input
|
||||||
|
{:on-change-text #(do (reset! reply-input %)
|
||||||
|
(when on-update-reply
|
||||||
|
(on-update-reply %)))
|
||||||
|
:auto-capitalize :none
|
||||||
|
:auto-focus true
|
||||||
|
:accessibility-label :identity-verification-reply-text-input
|
||||||
|
:placeholder (i18n/label :t/type-something)
|
||||||
|
:return-key-type :none
|
||||||
|
:multiline false
|
||||||
|
:auto-correct false}]]])
|
||||||
|
|
||||||
(defn- activity-icon
|
(defn- activity-icon
|
||||||
[icon]
|
[icon]
|
||||||
|
@ -14,7 +54,6 @@
|
||||||
:margin-top 10
|
:margin-top 10
|
||||||
:border-width 1
|
:border-width 1
|
||||||
:border-color colors/white-opa-5
|
:border-color colors/white-opa-5
|
||||||
:flex-direction :column
|
|
||||||
:align-items :center
|
:align-items :center
|
||||||
:justify-content :center}
|
:justify-content :center}
|
||||||
[icon/icon icon {:color colors/white}]])
|
[icon/icon icon {:color colors/white}]])
|
||||||
|
@ -29,17 +68,17 @@
|
||||||
:border-radius 4}])
|
:border-radius 4}])
|
||||||
|
|
||||||
(defn- activity-context
|
(defn- activity-context
|
||||||
[context]
|
[context replying?]
|
||||||
(let [margin-top 4]
|
(let [first-line-offset (if replying? 4 -2)
|
||||||
(into [rn/view {:flex 1
|
gap-between-lines 4]
|
||||||
:flex-direction :row
|
(into [rn/view {:flex-direction :row
|
||||||
:align-items :center
|
:align-items :center
|
||||||
:flex-wrap :wrap
|
:flex-wrap :wrap
|
||||||
:margin-top (+ 4 (- margin-top))}]
|
:margin-top first-line-offset}]
|
||||||
(map-indexed (fn [index detail]
|
(map-indexed (fn [index detail]
|
||||||
^{:key index}
|
^{:key index}
|
||||||
[rn/view {:margin-right 4
|
[rn/view {:margin-right 4
|
||||||
:margin-top margin-top}
|
:margin-top gap-between-lines}
|
||||||
(if (string? detail)
|
(if (string? detail)
|
||||||
[text/text {:size :paragraph-2}
|
[text/text {:size :paragraph-2}
|
||||||
detail]
|
detail]
|
||||||
|
@ -52,12 +91,11 @@
|
||||||
:margin-top 12
|
:margin-top 12
|
||||||
:padding-horizontal 12
|
:padding-horizontal 12
|
||||||
:padding-vertical 8
|
:padding-vertical 8
|
||||||
:background-color colors/white-opa-5
|
:background-color colors/white-opa-5}
|
||||||
:flex 1
|
|
||||||
:flex-direction :column}
|
|
||||||
(when title
|
(when title
|
||||||
[text/text {:size :paragraph-2
|
[text/text {:size :paragraph-2
|
||||||
:style {:color colors/white-opa-40}}
|
:style {:color colors/white-opa-40
|
||||||
|
:margin-bottom 2}}
|
||||||
title])
|
title])
|
||||||
(if (string? body)
|
(if (string? body)
|
||||||
[text/text {:style {:color colors/white}
|
[text/text {:style {:color colors/white}
|
||||||
|
@ -66,25 +104,24 @@
|
||||||
body)])
|
body)])
|
||||||
|
|
||||||
(defn- activity-buttons
|
(defn- activity-buttons
|
||||||
[button-1 button-2]
|
[button-1 button-2 replying? reply-input]
|
||||||
(let [size 24
|
(let [size (if replying? 40 24)
|
||||||
common-style {:padding-top 3
|
common-style (when replying?
|
||||||
:padding-right 8
|
{:padding-vertical 9
|
||||||
:padding-bottom 4
|
:flex-grow 1
|
||||||
:padding-left 8}]
|
:flex-basis 0})]
|
||||||
[rn/view {:margin-top 12
|
[rn/view {:margin-top 12
|
||||||
:flex 1
|
|
||||||
:flex-direction :row
|
:flex-direction :row
|
||||||
:align-items :flex-start}
|
:align-items :flex-start}
|
||||||
(when button-1
|
(when button-1
|
||||||
[button/button (-> button-1
|
[button/button (-> button-1
|
||||||
(assoc :size size)
|
(assoc :size size)
|
||||||
(assoc-in [:style :margin-right] 8)
|
(update :style merge common-style {:margin-right 8}))
|
||||||
(update :style merge common-style))
|
|
||||||
(:label button-1)])
|
(:label button-1)])
|
||||||
(when button-2
|
(when button-2
|
||||||
[button/button (-> button-2
|
[button/button (-> button-2
|
||||||
(assoc :size size)
|
(assoc :size size)
|
||||||
|
(assoc :disabled (and replying? (not (valid-reply? @reply-input))))
|
||||||
(update :style merge common-style))
|
(update :style merge common-style))
|
||||||
(:label button-2)])]))
|
(:label button-2)])]))
|
||||||
|
|
||||||
|
@ -98,10 +135,10 @@
|
||||||
:status status}]])
|
:status status}]])
|
||||||
|
|
||||||
(defn- activity-title
|
(defn- activity-title
|
||||||
[title]
|
[title replying?]
|
||||||
[text/text {:weight :semi-bold
|
[text/text {:weight :semi-bold
|
||||||
:style {:color colors/white}
|
:style {:color colors/white}
|
||||||
:size :paragraph-1}
|
:size (if replying? :heading-2 :paragraph-1)}
|
||||||
title])
|
title])
|
||||||
|
|
||||||
(defn- activity-timestamp
|
(defn- activity-timestamp
|
||||||
|
@ -112,46 +149,54 @@
|
||||||
:color colors/neutral-40}}
|
:color colors/neutral-40}}
|
||||||
timestamp]])
|
timestamp]])
|
||||||
|
|
||||||
|
(defn- footer
|
||||||
|
[_]
|
||||||
|
(let [reply-input (reagent/atom "")]
|
||||||
|
(fn [{:keys [replying? on-update-reply status button-1 button-2]}]
|
||||||
|
[:<>
|
||||||
|
(when replying?
|
||||||
|
[activity-reply-text-input reply-input on-update-reply])
|
||||||
|
(cond (some? status)
|
||||||
|
[activity-status status]
|
||||||
|
|
||||||
|
(or button-1 button-2)
|
||||||
|
[activity-buttons button-1 button-2 replying? reply-input])])))
|
||||||
|
|
||||||
(defn activity-log
|
(defn activity-log
|
||||||
[{:keys [button-1
|
[{:keys [icon
|
||||||
button-2
|
|
||||||
icon
|
|
||||||
message
|
message
|
||||||
status
|
|
||||||
context
|
context
|
||||||
timestamp
|
timestamp
|
||||||
title
|
title
|
||||||
unread?]}]
|
replying?
|
||||||
|
unread?]
|
||||||
|
:as props}]
|
||||||
[rn/view {:flex-direction :row
|
[rn/view {:flex-direction :row
|
||||||
:flex 1
|
:align-items :flex-start
|
||||||
:border-radius 16
|
:border-radius 16
|
||||||
:padding-top 8
|
:padding-top 8
|
||||||
:padding-horizontal 12
|
:padding-horizontal (if replying? 20 12)
|
||||||
:padding-bottom 12
|
:padding-bottom 12
|
||||||
:background-color (when unread?
|
:background-color (when (and unread? (not replying?))
|
||||||
colors/primary-50-opa-10)}
|
colors/primary-50-opa-10)}
|
||||||
[activity-icon icon]
|
(when-not replying?
|
||||||
[rn/view {:flex-direction :column
|
[activity-icon icon])
|
||||||
:padding-left 8
|
[rn/view {:padding-left (when-not replying? 8)
|
||||||
:flex 1}
|
:flex-grow 1}
|
||||||
[rn/view {:flex 1
|
[rn/view {:flex-grow 1
|
||||||
:align-items :center
|
:align-items :center
|
||||||
:flex-direction :row}
|
:flex-direction :row}
|
||||||
[rn/view {:flex 1
|
[rn/view {:flex 1
|
||||||
:align-items :center
|
:align-items :center
|
||||||
:flex-direction :row}
|
:flex-direction :row}
|
||||||
[rn/view {:flex-shrink 1}
|
[rn/view {:flex-shrink 1}
|
||||||
[activity-title title]]
|
[activity-title title replying?]]
|
||||||
[activity-timestamp timestamp]]
|
(when-not replying?
|
||||||
(when unread?
|
[activity-timestamp timestamp])]
|
||||||
|
(when (and unread? (not replying?))
|
||||||
[activity-unread-dot])]
|
[activity-unread-dot])]
|
||||||
(when context
|
(when context
|
||||||
[activity-context context])
|
[activity-context context replying?])
|
||||||
(when message
|
(when message
|
||||||
[activity-message message])
|
[activity-message message])
|
||||||
(cond
|
[footer props]]])
|
||||||
(some? status)
|
|
||||||
[activity-status status]
|
|
||||||
|
|
||||||
(or button-1 button-2)
|
|
||||||
[activity-buttons button-1 button-2])]])
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
quo2.components.notifications.info-count
|
quo2.components.notifications.info-count
|
||||||
quo2.components.notifications.notification-dot
|
quo2.components.notifications.notification-dot
|
||||||
quo2.components.tags.tags
|
quo2.components.tags.tags
|
||||||
|
quo2.components.tags.context-tags
|
||||||
quo2.components.tabs.tabs
|
quo2.components.tabs.tabs
|
||||||
quo2.components.tabs.account-selector
|
quo2.components.tabs.account-selector
|
||||||
quo2.components.navigation.top-nav
|
quo2.components.navigation.top-nav
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
(def system-message quo2.components.messages.system-message/system-message)
|
(def system-message quo2.components.messages.system-message/system-message)
|
||||||
(def reaction quo2.components.reactions.reaction/reaction)
|
(def reaction quo2.components.reactions.reaction/reaction)
|
||||||
(def tags quo2.components.tags.tags/tags)
|
(def tags quo2.components.tags.tags/tags)
|
||||||
|
(def user-avatar-tag quo2.components.tags.context-tags/user-avatar-tag)
|
||||||
(def tabs quo2.components.tabs.tabs/tabs)
|
(def tabs quo2.components.tabs.tabs/tabs)
|
||||||
(def scrollable-tabs quo2.components.tabs.tabs/scrollable-tabs)
|
(def scrollable-tabs quo2.components.tabs.tabs/scrollable-tabs)
|
||||||
(def account-selector quo2.components.tabs.account-selector/account-selector)
|
(def account-selector quo2.components.tabs.account-selector/account-selector)
|
||||||
|
@ -88,4 +90,4 @@
|
||||||
;;;; NOTIFICATIONS
|
;;;; NOTIFICATIONS
|
||||||
(def activity-log quo2.components.notifications.activity-logs/activity-log)
|
(def activity-log quo2.components.notifications.activity-logs/activity-log)
|
||||||
(def info-count quo2.components.notifications.info-count/info-count)
|
(def info-count quo2.components.notifications.info-count/info-count)
|
||||||
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
|
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
|
||||||
|
|
|
@ -6,6 +6,14 @@
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
[taoensso.timbre :as log]))
|
[taoensso.timbre :as log]))
|
||||||
|
|
||||||
|
;;;; Misc
|
||||||
|
|
||||||
|
(fx/defn process-notification-failure
|
||||||
|
{:events [:activity-center/process-notification-failure]}
|
||||||
|
[_ notification-id action error]
|
||||||
|
(log/warn (str "Failed to " action)
|
||||||
|
{:notification-id notification-id :error error}))
|
||||||
|
|
||||||
;;;; Notification reconciliation
|
;;;; Notification reconciliation
|
||||||
|
|
||||||
(defn- update-notifications
|
(defn- update-notifications
|
||||||
|
@ -45,38 +53,69 @@
|
||||||
{:db (update-in db [:activity-center :notifications]
|
{:db (update-in db [:activity-center :notifications]
|
||||||
update-notifications new-notifications)}))
|
update-notifications new-notifications)}))
|
||||||
|
|
||||||
;;;; Contact verification
|
(fx/defn notifications-reconcile-from-response
|
||||||
|
{:events [:activity-center/reconcile-notifications-from-response]}
|
||||||
(fx/defn contact-verification-decline
|
|
||||||
{:events [:activity-center.contact-verification/decline]}
|
|
||||||
[_ contact-verification-id]
|
|
||||||
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
|
|
||||||
:params [contact-verification-id]
|
|
||||||
:on-success #(rf/dispatch [:activity-center.contact-verification/decline-success %])
|
|
||||||
:on-error #(rf/dispatch [:activity-center.contact-verification/decline-error contact-verification-id %])}]})
|
|
||||||
|
|
||||||
(fx/defn contact-verification-decline-success
|
|
||||||
{:events [:activity-center.contact-verification/decline-success]}
|
|
||||||
[cofx response]
|
[cofx response]
|
||||||
(->> response
|
(->> response
|
||||||
:activityCenterNotifications
|
:activityCenterNotifications
|
||||||
(map data-store.activities/<-rpc)
|
(map data-store.activities/<-rpc)
|
||||||
(notifications-reconcile cofx)))
|
(notifications-reconcile cofx)))
|
||||||
|
|
||||||
(fx/defn contact-verification-decline-error
|
;;;; Contact verification
|
||||||
{:events [:activity-center.contact-verification/decline-error]}
|
|
||||||
[_ contact-verification-id error]
|
(fx/defn contact-verification-decline
|
||||||
(log/warn "Failed to decline contact verification"
|
{:events [:activity-center.contact-verification/decline]}
|
||||||
{:contact-verification-id contact-verification-id
|
[_ notification-id]
|
||||||
:error error})
|
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
|
||||||
nil)
|
:params [notification-id]
|
||||||
|
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:contact-verification/decline
|
||||||
|
%])}]})
|
||||||
|
|
||||||
|
(fx/defn contact-verification-reply
|
||||||
|
{:events [:activity-center.contact-verification/reply]}
|
||||||
|
[_ notification-id reply]
|
||||||
|
{::json-rpc/call [{:method "wakuext_acceptContactVerificationRequest"
|
||||||
|
:params [notification-id reply]
|
||||||
|
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:contact-verification/reply
|
||||||
|
%])}]})
|
||||||
|
|
||||||
|
(fx/defn contact-verification-mark-as-trusted
|
||||||
|
{:events [:activity-center.contact-verification/mark-as-trusted]}
|
||||||
|
[_ notification-id]
|
||||||
|
{::json-rpc/call [{:method "wakuext_verifiedTrusted"
|
||||||
|
:params [{:id notification-id}]
|
||||||
|
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:contact-verification/mark-as-trusted
|
||||||
|
%])}]})
|
||||||
|
|
||||||
|
(fx/defn contact-verification-mark-as-untrustworthy
|
||||||
|
{:events [:activity-center.contact-verification/mark-as-untrustworthy]}
|
||||||
|
[_ notification-id]
|
||||||
|
{::json-rpc/call [{:method "wakuext_verifiedUntrustworthy"
|
||||||
|
:params [{:id notification-id}]
|
||||||
|
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
|
||||||
|
:on-error #(rf/dispatch [:activity-center/process-notification-failure
|
||||||
|
notification-id
|
||||||
|
:contact-verification/mark-as-untrustworthy
|
||||||
|
%])}]})
|
||||||
|
|
||||||
;;;; Notifications fetching and pagination
|
;;;; Notifications fetching and pagination
|
||||||
|
|
||||||
(def defaults
|
(def defaults
|
||||||
{:filter-status :unread
|
{:filter-status :unread
|
||||||
:filter-type types/no-type
|
:filter-type types/no-type
|
||||||
:notifications-per-page 10})
|
;; Choose the maximum number of notifications that *usually/safely* fit on
|
||||||
|
;; most screens, so that the UI doesn't have to needlessly render
|
||||||
|
;; notifications.
|
||||||
|
:notifications-per-page 8})
|
||||||
|
|
||||||
(def start-or-end-cursor
|
(def start-or-end-cursor
|
||||||
"")
|
"")
|
||||||
|
@ -92,9 +131,9 @@
|
||||||
|
|
||||||
(defn status [filter-status]
|
(defn status [filter-status]
|
||||||
(case filter-status
|
(case filter-status
|
||||||
:read status-read
|
:read status-read
|
||||||
:unread status-unread
|
:unread status-unread
|
||||||
:all status-all
|
:all status-all
|
||||||
99))
|
99))
|
||||||
|
|
||||||
(fx/defn notifications-fetch
|
(fx/defn notifications-fetch
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
[day8.re-frame.test :as rf-test]
|
[day8.re-frame.test :as rf-test]
|
||||||
[re-frame.core :as rf]
|
[re-frame.core :as rf]
|
||||||
[status-im.activity-center.notification-types :as types]
|
[status-im.activity-center.notification-types :as types]
|
||||||
|
[status-im.constants :as constants]
|
||||||
[status-im.ethereum.json-rpc :as json-rpc]
|
[status-im.ethereum.json-rpc :as json-rpc]
|
||||||
status-im.events
|
status-im.events
|
||||||
[status-im.test-helpers :as h]
|
[status-im.test-helpers :as h]
|
||||||
|
@ -14,87 +15,140 @@
|
||||||
|
|
||||||
;;;; Contact verification
|
;;;; Contact verification
|
||||||
|
|
||||||
|
(def notification-id 24)
|
||||||
|
|
||||||
|
(def contact-verification-rpc-response
|
||||||
|
{:activityCenterNotifications
|
||||||
|
[{:accepted false
|
||||||
|
:author "0x04d03f"
|
||||||
|
:chatId "0x04d03f"
|
||||||
|
:contactVerificationStatus constants/contact-verification-status-pending
|
||||||
|
:dismissed false
|
||||||
|
:id notification-id
|
||||||
|
:message {}
|
||||||
|
:name "0x04d03f"
|
||||||
|
:read true
|
||||||
|
:timestamp 1666647286000
|
||||||
|
:type types/contact-verification}]})
|
||||||
|
|
||||||
|
(def contact-verification-expected-notification
|
||||||
|
{:accepted false
|
||||||
|
:author "0x04d03f"
|
||||||
|
:chat-id "0x04d03f"
|
||||||
|
:contact-verification-status constants/contact-verification-status-pending
|
||||||
|
:dismissed false
|
||||||
|
:id notification-id
|
||||||
|
:last-message nil
|
||||||
|
:message {:command-parameters nil
|
||||||
|
:content {:chat-id nil
|
||||||
|
:ens-name nil
|
||||||
|
:image nil
|
||||||
|
:line-count nil
|
||||||
|
:links nil
|
||||||
|
:parsed-text nil
|
||||||
|
:response-to nil
|
||||||
|
:rtl? nil
|
||||||
|
:sticker nil
|
||||||
|
:text nil}
|
||||||
|
:outgoing false
|
||||||
|
:outgoing-status nil
|
||||||
|
:quoted-message nil}
|
||||||
|
:name "0x04d03f"
|
||||||
|
:read true
|
||||||
|
:reply-message nil
|
||||||
|
:timestamp 1666647286000
|
||||||
|
:type types/contact-verification})
|
||||||
|
|
||||||
|
(defn test-log-on-failure
|
||||||
|
[{:keys [notification-id event action]}]
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(h/using-log-test-appender
|
||||||
|
(fn [logs]
|
||||||
|
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
|
||||||
|
|
||||||
|
(rf/dispatch event)
|
||||||
|
|
||||||
|
(is (= {:args [(str "Failed to " action)
|
||||||
|
{:notification-id notification-id
|
||||||
|
:error :fake-error}]
|
||||||
|
:level :warn}
|
||||||
|
(last @logs)))))))
|
||||||
|
|
||||||
|
(defn test-contact-verification-event
|
||||||
|
[{:keys [event expected-rpc-call]}]
|
||||||
|
(rf-test/run-test-sync
|
||||||
|
(setup)
|
||||||
|
(let [spy-queue (atom [])]
|
||||||
|
(h/stub-fx-with-callbacks ::json-rpc/call :on-success (constantly contact-verification-rpc-response))
|
||||||
|
(h/spy-fx spy-queue ::json-rpc/call)
|
||||||
|
(rf/dispatch event)
|
||||||
|
|
||||||
|
(is (= {types/no-type
|
||||||
|
{:read {:data [contact-verification-expected-notification]}
|
||||||
|
:unread {:data []}}
|
||||||
|
types/contact-verification
|
||||||
|
{:read {:data [contact-verification-expected-notification]}
|
||||||
|
:unread {:data []}}}
|
||||||
|
(get-in (h/db) [:activity-center :notifications])))
|
||||||
|
|
||||||
|
(is (= expected-rpc-call
|
||||||
|
(-> @spy-queue
|
||||||
|
(get-in [0 :args 0])
|
||||||
|
(select-keys [:method :params])))))))
|
||||||
|
|
||||||
(deftest contact-verification-decline-test
|
(deftest contact-verification-decline-test
|
||||||
(with-redefs [config/new-activity-center-enabled? true]
|
(with-redefs [config/new-activity-center-enabled? true]
|
||||||
(testing "successfully declines and reconciles returned notification"
|
(testing "declines notification and reconciles"
|
||||||
(rf-test/run-test-sync
|
(test-contact-verification-event
|
||||||
(setup)
|
{:event [:activity-center.contact-verification/decline notification-id]
|
||||||
(let [spy-queue (atom [])
|
:expected-rpc-call {:method "wakuext_declineContactVerificationRequest"
|
||||||
contact-verification-id 24
|
:params [notification-id]}}))
|
||||||
expected-notification {:accepted false
|
(testing "logs on failure"
|
||||||
:author "0x04d03f"
|
(test-log-on-failure
|
||||||
:chat-id "0x04d03f"
|
{:notification-id notification-id
|
||||||
:contact-verification-status 3
|
:event [:activity-center.contact-verification/decline notification-id]
|
||||||
:dismissed false
|
:action :contact-verification/decline}))))
|
||||||
:id 24
|
|
||||||
:last-message nil
|
|
||||||
:message {:command-parameters nil
|
|
||||||
:content {:chat-id nil
|
|
||||||
:ens-name nil
|
|
||||||
:image nil
|
|
||||||
:line-count nil
|
|
||||||
:links nil
|
|
||||||
:parsed-text nil
|
|
||||||
:response-to nil
|
|
||||||
:rtl? nil
|
|
||||||
:sticker nil
|
|
||||||
:text nil}
|
|
||||||
:outgoing false
|
|
||||||
:outgoing-status nil
|
|
||||||
:quoted-message nil}
|
|
||||||
:name "0x04d03f"
|
|
||||||
:read true
|
|
||||||
:reply-message nil
|
|
||||||
:timestamp 1666647286000
|
|
||||||
:type types/contact-verification}]
|
|
||||||
(h/stub-fx-with-callbacks
|
|
||||||
::json-rpc/call
|
|
||||||
:on-success (constantly {:activityCenterNotifications
|
|
||||||
[{:accepted false
|
|
||||||
:author "0x04d03f"
|
|
||||||
:chatId "0x04d03f"
|
|
||||||
:contactVerificationStatus 3
|
|
||||||
:dismissed false
|
|
||||||
:id contact-verification-id
|
|
||||||
:message {}
|
|
||||||
:name "0x04d03f"
|
|
||||||
:read true
|
|
||||||
:timestamp 1666647286000
|
|
||||||
:type types/contact-verification}]}))
|
|
||||||
|
|
||||||
(h/spy-fx spy-queue ::json-rpc/call)
|
(deftest contact-verification-reply-test
|
||||||
|
(with-redefs [config/new-activity-center-enabled? true]
|
||||||
|
(testing "sends reply and reconciles"
|
||||||
|
(let [reply "any answer"]
|
||||||
|
(test-contact-verification-event
|
||||||
|
{:event [:activity-center.contact-verification/reply notification-id reply]
|
||||||
|
:expected-rpc-call {:method "wakuext_acceptContactVerificationRequest"
|
||||||
|
:params [notification-id reply]}})))
|
||||||
|
(testing "logs on failure"
|
||||||
|
(test-log-on-failure
|
||||||
|
{:notification-id notification-id
|
||||||
|
:event [:activity-center.contact-verification/reply notification-id "any answer"]
|
||||||
|
:action :contact-verification/reply}))))
|
||||||
|
|
||||||
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
|
(deftest contact-verification-mark-as-trusted-test
|
||||||
|
(with-redefs [config/new-activity-center-enabled? true]
|
||||||
|
(testing "marks notification as trusted and reconciles"
|
||||||
|
(test-contact-verification-event
|
||||||
|
{:event [:activity-center.contact-verification/mark-as-trusted notification-id]
|
||||||
|
:expected-rpc-call {:method "wakuext_verifiedTrusted"
|
||||||
|
:params [{:id notification-id}]}}))
|
||||||
|
(testing "logs on failure"
|
||||||
|
(test-log-on-failure
|
||||||
|
{:notification-id notification-id
|
||||||
|
:event [:activity-center.contact-verification/mark-as-trusted notification-id]
|
||||||
|
:action :contact-verification/mark-as-trusted}))))
|
||||||
|
|
||||||
(is (= {:method "wakuext_declineContactVerificationRequest"
|
(deftest contact-verification-mark-as-untrustworthy-test
|
||||||
:params [contact-verification-id]}
|
(with-redefs [config/new-activity-center-enabled? true]
|
||||||
(-> @spy-queue
|
(testing "marks notification as untrustworthy and reconciles"
|
||||||
(get-in [0 :args 0])
|
(test-contact-verification-event
|
||||||
(select-keys [:method :params]))))
|
{:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
|
||||||
|
:expected-rpc-call {:method "wakuext_verifiedUntrustworthy"
|
||||||
(is (= {types/no-type
|
:params [{:id notification-id}]}}))
|
||||||
{:read {:data [expected-notification]}
|
(testing "logs on failure"
|
||||||
:unread {:data []}}
|
(test-log-on-failure
|
||||||
types/contact-verification
|
{:notification-id notification-id
|
||||||
{:read {:data [expected-notification]}
|
:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
|
||||||
:unread {:data []}}}
|
:action :contact-verification/mark-as-untrustworthy}))))
|
||||||
(get-in (h/db) [:activity-center :notifications]))))))
|
|
||||||
|
|
||||||
(testing "logs failure"
|
|
||||||
(rf-test/run-test-sync
|
|
||||||
(setup)
|
|
||||||
(let [contact-verification-id 666]
|
|
||||||
(h/using-log-test-appender
|
|
||||||
(fn [logs]
|
|
||||||
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
|
|
||||||
|
|
||||||
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
|
|
||||||
|
|
||||||
(is (= {:args ["Failed to decline contact verification"
|
|
||||||
{:contact-verification-id contact-verification-id
|
|
||||||
:error :fake-error}]
|
|
||||||
:level :warn}
|
|
||||||
(last @logs))))))))))
|
|
||||||
|
|
||||||
;;;; Notification reconciliation
|
;;;; Notification reconciliation
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
(def ^:const mention 3)
|
(def ^:const mention 3)
|
||||||
(def ^:const reply 4)
|
(def ^:const reply 4)
|
||||||
(def ^:const contact-request 5)
|
(def ^:const contact-request 5)
|
||||||
(def ^:const contact-verification 6)
|
(def ^:const contact-verification 10)
|
||||||
|
|
||||||
;; TODO: Remove this constant once the old Notification Center code is removed.
|
;; TODO: Remove this constant once the old Notification Center code is removed.
|
||||||
;; Its value clashes with the new constant `-contact-verification`
|
;; Its value clashes with the new constant `-contact-verification`
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
(def ^:const contact-request-state-received 3)
|
(def ^:const contact-request-state-received 3)
|
||||||
(def ^:const contact-request-state-dismissed 4)
|
(def ^:const contact-request-state-dismissed 4)
|
||||||
|
|
||||||
(def ^:const contact-verification-state-unknown 0)
|
(def ^:const contact-verification-status-unknown 0)
|
||||||
(def ^:const contact-verification-state-pending 1)
|
(def ^:const contact-verification-status-pending 1)
|
||||||
(def ^:const contact-verification-state-accepted 2)
|
(def ^:const contact-verification-status-accepted 2)
|
||||||
(def ^:const contact-verification-state-declined 3)
|
(def ^:const contact-verification-status-declined 3)
|
||||||
(def ^:const contact-verification-state-cancelled 4)
|
(def ^:const contact-verification-status-cancelled 4)
|
||||||
(def ^:const contact-verification-state-trusted 5)
|
(def ^:const contact-verification-status-trusted 5)
|
||||||
|
(def ^:const contact-verification-status-untrustworthy 6)
|
||||||
|
|
||||||
(def ^:const emoji-reaction-love 1)
|
(def ^:const emoji-reaction-love 1)
|
||||||
(def ^:const emoji-reaction-thumbs-up 2)
|
(def ^:const emoji-reaction-thumbs-up 2)
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
(def raw-notification
|
(def raw-notification
|
||||||
{:chatId chat-id
|
{:chatId chat-id
|
||||||
:contactVerificationStatus constants/contact-verification-state-pending
|
:contactVerificationStatus constants/contact-verification-status-pending
|
||||||
:lastMessage {}
|
:lastMessage {}
|
||||||
:name chat-name
|
:name chat-name
|
||||||
:replyMessage {}})
|
:replyMessage {}})
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
(testing "renames keys"
|
(testing "renames keys"
|
||||||
(is (= {:name chat-name
|
(is (= {:name chat-name
|
||||||
:chat-id chat-id
|
:chat-id chat-id
|
||||||
:contact-verification-status constants/contact-verification-state-pending}
|
:contact-verification-status constants/contact-verification-status-pending}
|
||||||
(-> raw-notification
|
(-> raw-notification
|
||||||
store/<-rpc
|
store/<-rpc
|
||||||
(dissoc :last-message :message :reply-message)))))
|
(dissoc :last-message :message :reply-message)))))
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.notification.contact-request.style
|
||||||
|
(:require [quo2.foundations.colors :as colors]))
|
||||||
|
|
||||||
|
(def context-tag-text
|
||||||
|
{:color colors/white})
|
||||||
|
|
||||||
|
(def user-avatar-tag-text
|
||||||
|
{:color colors/white})
|
||||||
|
|
||||||
|
(def user-avatar-tag
|
||||||
|
{:background-color colors/white-opa-10})
|
|
@ -0,0 +1,58 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.notification.contact-request.view
|
||||||
|
(:require [quo.components.animated.pressable :as animation]
|
||||||
|
[quo2.core :as quo2]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[status-im.i18n.i18n :as i18n]
|
||||||
|
[status-im.multiaccounts.core :as multiaccounts]
|
||||||
|
[status-im.ui.screens.activity-center.notification.contact-request.style :as style]
|
||||||
|
[status-im.ui.screens.activity-center.utils :as activity-center.utils]
|
||||||
|
[status-im.utils.datetime :as datetime]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[{:keys [id author message last-message] :as notification}]
|
||||||
|
(let [message (or message last-message)
|
||||||
|
contact (rf/sub [:contacts/contact-by-identity author])
|
||||||
|
pressable (case (:contact-request-state message)
|
||||||
|
constants/contact-request-message-state-accepted
|
||||||
|
;; NOTE(2022-09-21): We need to dispatch to
|
||||||
|
;; `:contact.ui/send-message-pressed` instead of
|
||||||
|
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen
|
||||||
|
;; looks completely broken if it has never been opened
|
||||||
|
;; before for the accepted contact.
|
||||||
|
[animation/pressable {:on-press (fn []
|
||||||
|
(rf/dispatch [:hide-popover])
|
||||||
|
(rf/dispatch [:contact.ui/send-message-pressed {:public-key author}]))}]
|
||||||
|
[:<>])]
|
||||||
|
(conj pressable
|
||||||
|
[quo2/activity-log
|
||||||
|
(merge {:title (i18n/label :t/contact-request)
|
||||||
|
:icon :main-icons2/add-user
|
||||||
|
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
||||||
|
:unread? (not (:read notification))
|
||||||
|
:context [[quo2/user-avatar-tag
|
||||||
|
{:color :purple
|
||||||
|
:override-theme :dark
|
||||||
|
:size :small
|
||||||
|
:style style/user-avatar-tag
|
||||||
|
:text-style style/user-avatar-tag-text}
|
||||||
|
(activity-center.utils/contact-name contact)
|
||||||
|
(multiaccounts/displayed-photo contact)]
|
||||||
|
[quo2/text {:style style/context-tag-text}
|
||||||
|
(i18n/label :t/contact-request-sent)]]
|
||||||
|
:message {:body (get-in message [:content :text])}
|
||||||
|
:status (case (:contact-request-state message)
|
||||||
|
constants/contact-request-message-state-accepted
|
||||||
|
{:type :positive :label (i18n/label :t/accepted)}
|
||||||
|
constants/contact-request-message-state-declined
|
||||||
|
{:type :negative :label (i18n/label :t/declined)}
|
||||||
|
nil)}
|
||||||
|
(case (:contact-request-state message)
|
||||||
|
constants/contact-request-state-mutual
|
||||||
|
{:button-1 {:label (i18n/label :t/decline)
|
||||||
|
:type :danger
|
||||||
|
:on-press #(rf/dispatch [:contact-requests.ui/decline-request id])}
|
||||||
|
:button-2 {:label (i18n/label :t/accept)
|
||||||
|
:type :positive
|
||||||
|
:on-press #(rf/dispatch [:contact-requests.ui/accept-request id])}}
|
||||||
|
nil))])))
|
|
@ -0,0 +1,11 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.notification.contact-verification.style
|
||||||
|
(:require [quo2.foundations.colors :as colors]))
|
||||||
|
|
||||||
|
(def context-tag-text
|
||||||
|
{:color colors/white})
|
||||||
|
|
||||||
|
(def user-avatar-tag-text
|
||||||
|
{:color colors/white})
|
||||||
|
|
||||||
|
(def user-avatar-tag
|
||||||
|
{:background-color colors/white-opa-10})
|
|
@ -0,0 +1,105 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.notification.contact-verification.view
|
||||||
|
(:require [clojure.string :as str]
|
||||||
|
[quo2.core :as quo2]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[status-im.i18n.i18n :as i18n]
|
||||||
|
[status-im.multiaccounts.core :as multiaccounts]
|
||||||
|
[status-im.ui.screens.activity-center.notification.contact-verification.style :as style]
|
||||||
|
[status-im.ui.screens.activity-center.utils :as activity-center.utils]
|
||||||
|
[status-im.utils.datetime :as datetime]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn- hide-bottom-sheet-and-dispatch
|
||||||
|
[event]
|
||||||
|
(rf/dispatch [:bottom-sheet/hide])
|
||||||
|
(rf/dispatch [:dismiss-keyboard])
|
||||||
|
(rf/dispatch event))
|
||||||
|
|
||||||
|
(defn- context-tags
|
||||||
|
[challenger? {:keys [author contact-verification-status]}]
|
||||||
|
(let [contact (rf/sub [:contacts/contact-by-identity author])]
|
||||||
|
[[quo2/user-avatar-tag
|
||||||
|
{:color :purple
|
||||||
|
:override-theme :dark
|
||||||
|
:size :small
|
||||||
|
:style style/user-avatar-tag
|
||||||
|
:text-style style/user-avatar-tag-text}
|
||||||
|
(activity-center.utils/contact-name contact)
|
||||||
|
(multiaccounts/displayed-photo contact)]
|
||||||
|
[quo2/text {:style style/context-tag-text}
|
||||||
|
(if challenger?
|
||||||
|
(cond (or (= contact-verification-status constants/contact-verification-status-accepted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-trusted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-untrustworthy))
|
||||||
|
(str (str/lower-case (i18n/label :t/replied)) ":"))
|
||||||
|
(cond (or (= contact-verification-status constants/contact-verification-status-accepted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-pending)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-declined))
|
||||||
|
(str (i18n/label :t/identity-verification-request-sent) ":")))]]))
|
||||||
|
|
||||||
|
(defn- activity-message
|
||||||
|
[challenger? {:keys [contact-verification-status message reply-message]}]
|
||||||
|
(if challenger?
|
||||||
|
(cond (or (= contact-verification-status constants/contact-verification-status-accepted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-trusted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-untrustworthy))
|
||||||
|
{:title (get-in message [:content :text])
|
||||||
|
:body (get-in reply-message [:content :text])})
|
||||||
|
(cond (or (= contact-verification-status constants/contact-verification-status-accepted)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-pending)
|
||||||
|
(= contact-verification-status constants/contact-verification-status-declined))
|
||||||
|
{: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)})))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[_ _]
|
||||||
|
(let [reply (atom "")]
|
||||||
|
(fn [{:keys [id message contact-verification-status] :as notification} {:keys [replying?]}]
|
||||||
|
(let [challenger? (:outgoing message)]
|
||||||
|
;; TODO(@ilmotta): Declined challenges should only be displayed for the
|
||||||
|
;; challengee, not the challenger.
|
||||||
|
;; https://github.com/status-im/status-mobile/issues/14354
|
||||||
|
(when-not (and challenger? (= contact-verification-status constants/contact-verification-status-declined))
|
||||||
|
[quo2/activity-log
|
||||||
|
(merge {:title (i18n/label :t/identity-verification-request)
|
||||||
|
:icon :i/friend
|
||||||
|
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
||||||
|
:unread? (not (:read notification))
|
||||||
|
:on-update-reply #(reset! reply %)
|
||||||
|
:replying? replying?
|
||||||
|
:context (context-tags challenger? notification)
|
||||||
|
:message (activity-message challenger? notification)
|
||||||
|
:status (activity-status challenger? contact-verification-status)}
|
||||||
|
(if challenger?
|
||||||
|
(cond (= contact-verification-status constants/contact-verification-status-accepted)
|
||||||
|
{:button-1 {:label (i18n/label :t/untrustworthy)
|
||||||
|
:type :danger
|
||||||
|
:on-press #(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy id])}
|
||||||
|
:button-2 {:label (i18n/label :t/accept)
|
||||||
|
:type :positive
|
||||||
|
:on-press #(rf/dispatch [:activity-center.contact-verification/mark-as-trusted id])}})
|
||||||
|
(cond (= contact-verification-status constants/contact-verification-status-pending)
|
||||||
|
{:button-1 {:label (i18n/label :t/decline)
|
||||||
|
:type :danger
|
||||||
|
:on-press #(hide-bottom-sheet-and-dispatch [:activity-center.contact-verification/decline id])}
|
||||||
|
:button-2 (if replying?
|
||||||
|
{:label (i18n/label :t/send-reply)
|
||||||
|
:type :primary
|
||||||
|
:on-press #(hide-bottom-sheet-and-dispatch [:activity-center.contact-verification/reply id @reply])}
|
||||||
|
{:label (i18n/label :t/message-reply)
|
||||||
|
:type :primary
|
||||||
|
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
|
||||||
|
:activity-center.contact-verification/reply
|
||||||
|
{:notification notification
|
||||||
|
:replying? true}])})})))])))))
|
|
@ -0,0 +1,9 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.sheet.contact-verification
|
||||||
|
(:require [status-im.ui.screens.activity-center.notification.contact-verification.view :as contact-verification]))
|
||||||
|
|
||||||
|
(defn- reply-view
|
||||||
|
[{:keys [notification replying?]}]
|
||||||
|
[contact-verification/view notification {:replying? replying?}])
|
||||||
|
|
||||||
|
(def reply
|
||||||
|
{:content reply-view})
|
|
@ -0,0 +1,31 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.style
|
||||||
|
(:require [quo2.foundations.colors :as colors]))
|
||||||
|
|
||||||
|
(def screen-padding 20)
|
||||||
|
|
||||||
|
(def header-button
|
||||||
|
{:margin-bottom 12
|
||||||
|
:margin-left screen-padding})
|
||||||
|
|
||||||
|
(def header-heading
|
||||||
|
{:padding-horizontal screen-padding
|
||||||
|
:padding-vertical 12
|
||||||
|
:color colors/white})
|
||||||
|
|
||||||
|
(defn screen-container
|
||||||
|
[window-width top bottom]
|
||||||
|
{:flex 1
|
||||||
|
:width window-width
|
||||||
|
:padding-top (if (pos? 0) (+ top 12) 12)
|
||||||
|
:padding-bottom bottom})
|
||||||
|
|
||||||
|
(def notifications-container
|
||||||
|
{:flex-grow 1})
|
||||||
|
|
||||||
|
(defn notification-container
|
||||||
|
[index]
|
||||||
|
{:margin-top (if (zero? index) 0 4)
|
||||||
|
:padding-horizontal screen-padding})
|
||||||
|
|
||||||
|
(def tabs
|
||||||
|
{:padding-left screen-padding})
|
|
@ -0,0 +1,6 @@
|
||||||
|
(ns status-im.ui.screens.activity-center.utils)
|
||||||
|
|
||||||
|
(defn contact-name
|
||||||
|
[contact]
|
||||||
|
(or (get-in contact [:names :nickname])
|
||||||
|
(get-in contact [:names :three-words-name])))
|
|
@ -1,154 +1,32 @@
|
||||||
(ns status-im.ui.screens.activity-center.views
|
(ns status-im.ui.screens.activity-center.views
|
||||||
(:require [quo.components.animated.pressable :as animation]
|
(:require [quo.components.safe-area :as safe-area]
|
||||||
[quo.react :as react]
|
[quo.react :as react]
|
||||||
[quo.react-native :as rn]
|
[quo.react-native :as rn]
|
||||||
[quo2.components.buttons.button :as button]
|
[quo2.core :as quo2]
|
||||||
[quo2.components.markdown.text :as text]
|
|
||||||
[quo2.components.notifications.activity-logs :as activity-logs]
|
|
||||||
[quo2.components.tabs.tabs :as tabs]
|
|
||||||
[quo2.components.tags.context-tags :as context-tags]
|
|
||||||
[quo2.foundations.colors :as colors]
|
[quo2.foundations.colors :as colors]
|
||||||
[status-im.constants :as constants]
|
|
||||||
[status-im.activity-center.notification-types :as types]
|
[status-im.activity-center.notification-types :as types]
|
||||||
[status-im.i18n.i18n :as i18n]
|
[status-im.i18n.i18n :as i18n]
|
||||||
[status-im.multiaccounts.core :as multiaccounts]
|
[status-im.ui.screens.activity-center.notification.contact-request.view :as contact-request]
|
||||||
[status-im.utils.datetime :as datetime]
|
[status-im.ui.screens.activity-center.notification.contact-verification.view :as contact-verification]
|
||||||
[goog.string :as gstring]
|
[status-im.ui.screens.activity-center.style :as style]
|
||||||
[status-im.utils.handlers :refer [<sub >evt]]
|
[utils.re-frame :as rf]))
|
||||||
[quo.components.safe-area :as safe-area]))
|
|
||||||
|
|
||||||
;;;; Misc
|
|
||||||
|
|
||||||
(defn sender-name
|
|
||||||
[contact]
|
|
||||||
(or (get-in contact [:names :nickname])
|
|
||||||
(get-in contact [:names :three-words-name])))
|
|
||||||
|
|
||||||
(defmulti notification-component :type)
|
|
||||||
|
|
||||||
;; TODO(rasom): should be removed as soon as all notifications types covered
|
|
||||||
(defmethod notification-component :default
|
|
||||||
[{:keys [type]}]
|
|
||||||
[rn/view {:style {:width 300, :height 100}}
|
|
||||||
[rn/text
|
|
||||||
(gstring/format
|
|
||||||
"I exist just to avoid crashing for no reason. I'm sorry. Type %d" type)]])
|
|
||||||
|
|
||||||
;;;; Contact request notifications
|
|
||||||
|
|
||||||
(defmethod notification-component types/contact-request
|
|
||||||
[{:keys [id] :as notification}]
|
|
||||||
(let [message (or (:message notification) (:last-message notification))
|
|
||||||
contact (<sub [:contacts/contact-by-identity (:author notification)])
|
|
||||||
pressable (case (:contact-request-state message)
|
|
||||||
constants/contact-request-message-state-accepted
|
|
||||||
;; NOTE [2022-09-21]: We need to dispatch to
|
|
||||||
;; `:contact.ui/send-message-pressed` instead of
|
|
||||||
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
|
|
||||||
;; broken if it has never been opened before for the accepted contact.
|
|
||||||
[animation/pressable {:on-press (fn []
|
|
||||||
(>evt [:hide-popover])
|
|
||||||
(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}]))}]
|
|
||||||
[:<>])]
|
|
||||||
(conj pressable
|
|
||||||
[activity-logs/activity-log
|
|
||||||
(merge {:title (i18n/label :t/contact-request)
|
|
||||||
:icon :i/add-user
|
|
||||||
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
|
||||||
:unread? (not (:read notification))
|
|
||||||
:context [[context-tags/user-avatar-tag
|
|
||||||
{:color :purple
|
|
||||||
:override-theme :dark
|
|
||||||
:size :small
|
|
||||||
:style {:background-color colors/white-opa-10}
|
|
||||||
:text-style {:color colors/white}}
|
|
||||||
(sender-name contact)
|
|
||||||
(multiaccounts/displayed-photo contact)]
|
|
||||||
[rn/text {:style {:color colors/white}}
|
|
||||||
(i18n/label :t/contact-request-sent)]]
|
|
||||||
:message {:body (get-in message [:content :text])}
|
|
||||||
:status (case (:contact-request-state message)
|
|
||||||
constants/contact-request-message-state-accepted
|
|
||||||
{:type :positive :label (i18n/label :t/accepted)}
|
|
||||||
constants/contact-request-message-state-declined
|
|
||||||
{:type :negative :label (i18n/label :t/declined)}
|
|
||||||
nil)}
|
|
||||||
(case (:contact-request-state message)
|
|
||||||
constants/contact-request-state-mutual
|
|
||||||
{:button-1 {:label (i18n/label :t/decline)
|
|
||||||
:type :danger
|
|
||||||
:on-press #(>evt [:contact-requests.ui/decline-request id])}
|
|
||||||
:button-2 {:label (i18n/label :t/message-reply)
|
|
||||||
:type :success
|
|
||||||
:override-background-color colors/success-60
|
|
||||||
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
|
|
||||||
nil))])))
|
|
||||||
|
|
||||||
;;;; Contact verification notifications
|
|
||||||
|
|
||||||
(defmethod notification-component types/contact-verification
|
|
||||||
[{:keys [id contact-verification-status] :as notification}]
|
|
||||||
(let [message (or (:message notification) (:last-notification notification))
|
|
||||||
contact (<sub [:contacts/contact-by-identity (:author notification)])]
|
|
||||||
[activity-logs/activity-log
|
|
||||||
(merge {:title (i18n/label :t/identity-verification-request)
|
|
||||||
:icon :i/friend
|
|
||||||
:timestamp (datetime/timestamp->relative (:timestamp notification))
|
|
||||||
:unread? (not (:read notification))
|
|
||||||
:context [[context-tags/user-avatar-tag
|
|
||||||
{:color :purple
|
|
||||||
:override-theme :dark
|
|
||||||
:size :small
|
|
||||||
:style {:background-color colors/white-opa-10}
|
|
||||||
:text-style {:color colors/white}}
|
|
||||||
(sender-name contact)
|
|
||||||
(multiaccounts/displayed-photo contact)]
|
|
||||||
[rn/text {:style {:color colors/white}}
|
|
||||||
(str (i18n/label :t/identity-verification-request-sent)
|
|
||||||
":")]]
|
|
||||||
:message (case contact-verification-status
|
|
||||||
(constants/contact-verification-state-pending
|
|
||||||
constants/contact-verification-state-declined)
|
|
||||||
{:body (get-in message [:content :text])}
|
|
||||||
nil)
|
|
||||||
:status (case contact-verification-status
|
|
||||||
constants/contact-verification-state-declined
|
|
||||||
{:type :negative :label (i18n/label :t/declined)}
|
|
||||||
nil)}
|
|
||||||
(case contact-verification-status
|
|
||||||
constants/contact-verification-state-pending
|
|
||||||
{:button-1 {:label (i18n/label :t/decline)
|
|
||||||
:type :danger
|
|
||||||
:on-press #(>evt [:activity-center.contact-verification/decline id])}
|
|
||||||
:button-2 {:label (i18n/label :t/accept)
|
|
||||||
:type :primary
|
|
||||||
;; TODO: The acceptance flow will be implemented in follow-up PRs.
|
|
||||||
:on-press identity}}
|
|
||||||
nil))]))
|
|
||||||
|
|
||||||
;;;; Type-independent components
|
|
||||||
|
|
||||||
(defn render-notification
|
|
||||||
[notification index]
|
|
||||||
[rn/view {:margin-top (if (= 0 index) 0 4)
|
|
||||||
:padding-horizontal 20}
|
|
||||||
[notification-component notification]])
|
|
||||||
|
|
||||||
(defn filter-selector-read-toggle
|
(defn filter-selector-read-toggle
|
||||||
[]
|
[]
|
||||||
(let [unread-filter-enabled? (<sub [:activity-center/filter-status-unread-enabled?])]
|
(let [unread-filter-enabled? (rf/sub [:activity-center/filter-status-unread-enabled?])]
|
||||||
;; TODO: Replace the button by a Filter Selector component once available for use.
|
;; TODO(@ilmotta): Replace the button by a Filter Selector.
|
||||||
[button/button {:icon true
|
;; https://github.com/status-im/status-mobile/issues/14355
|
||||||
:type (if unread-filter-enabled? :primary :blur-bg-outline)
|
[quo2/button {:icon true
|
||||||
:size 32
|
:type (if unread-filter-enabled? :primary :blur-bg-outline)
|
||||||
:override-theme :dark
|
:size 32
|
||||||
:on-press #(>evt [:activity-center.notifications/fetch-first-page
|
:override-theme :dark
|
||||||
{:filter-status (if unread-filter-enabled?
|
:on-press #(rf/dispatch [:activity-center.notifications/fetch-first-page
|
||||||
:all
|
{:filter-status (if unread-filter-enabled?
|
||||||
:unread)}])}
|
:all
|
||||||
|
:unread)}])}
|
||||||
:i/unread]))
|
:i/unread]))
|
||||||
|
|
||||||
;; TODO(2022-10-07): The empty state is still under design analysis, so we
|
;; TODO(@ilmotta,2022-10-07): The empty state is still under design analysis, so we
|
||||||
;; shouldn't even care about translations at this point. A placeholder box is
|
;; shouldn't even care about translations at this point. A placeholder box is
|
||||||
;; used instead of an image.
|
;; used instead of an image.
|
||||||
(defn empty-tab
|
(defn empty-tab
|
||||||
|
@ -161,24 +39,24 @@
|
||||||
:height 120
|
:height 120
|
||||||
:margin-bottom 20
|
:margin-bottom 20
|
||||||
:width 120}}]
|
:width 120}}]
|
||||||
[text/text {:size :paragraph-1
|
[quo2/text {:size :paragraph-1
|
||||||
:style {:padding-bottom 2}
|
:style {:padding-bottom 2}
|
||||||
:weight :semi-bold}
|
:weight :semi-bold}
|
||||||
"No notifications"]
|
"No notifications"]
|
||||||
[text/text {:size :paragraph-2}
|
[quo2/text {:size :paragraph-2}
|
||||||
"Your notifications will be here"]])
|
"Your notifications will be here"]])
|
||||||
|
|
||||||
(defn tabs
|
(defn tabs
|
||||||
[]
|
[]
|
||||||
(let [filter-type (<sub [:activity-center/filter-type])]
|
(let [filter-type (rf/sub [:activity-center/filter-type])]
|
||||||
[tabs/scrollable-tabs {:size 32
|
[quo2/scrollable-tabs {:size 32
|
||||||
:blur? true
|
:blur? true
|
||||||
:override-theme :dark
|
:override-theme :dark
|
||||||
:style {:padding-left 20}
|
:style style/tabs
|
||||||
:fade-end-percentage 0.79
|
:fade-end-percentage 0.79
|
||||||
:scroll-on-press? true
|
:scroll-on-press? true
|
||||||
:fade-end? true
|
:fade-end? true
|
||||||
:on-change #(>evt [:activity-center.notifications/fetch-first-page {:filter-type %}])
|
:on-change #(rf/dispatch [:activity-center.notifications/fetch-first-page {:filter-type %}])
|
||||||
:default-active filter-type
|
:default-active filter-type
|
||||||
:data [{:id types/no-type
|
:data [{:id types/no-type
|
||||||
:label (i18n/label :t/all)}
|
:label (i18n/label :t/all)}
|
||||||
|
@ -203,19 +81,16 @@
|
||||||
[]
|
[]
|
||||||
(let [screen-padding 20]
|
(let [screen-padding 20]
|
||||||
[rn/view
|
[rn/view
|
||||||
[button/button {:icon true
|
[quo2/button {:icon true
|
||||||
:type :blur-bg
|
:type :blur-bg
|
||||||
:size 32
|
:size 32
|
||||||
:override-theme :dark
|
:override-theme :dark
|
||||||
:style {:margin-bottom 12
|
:style style/header-button
|
||||||
:margin-left screen-padding}
|
:on-press #(rf/dispatch [:hide-popover])}
|
||||||
:on-press #(>evt [:hide-popover])}
|
|
||||||
:i/close]
|
:i/close]
|
||||||
[text/text {:size :heading-1
|
[quo2/text {:size :heading-1
|
||||||
:weight :semi-bold
|
:weight :semi-bold
|
||||||
:style {:padding-horizontal screen-padding
|
:style style/header-heading}
|
||||||
:padding-vertical 12
|
|
||||||
:color colors/white}}
|
|
||||||
(i18n/label :t/notifications)]
|
(i18n/label :t/notifications)]
|
||||||
[rn/view {:flex-direction :row
|
[rn/view {:flex-direction :row
|
||||||
:padding-vertical 12}
|
:padding-vertical 12}
|
||||||
|
@ -227,22 +102,31 @@
|
||||||
:padding-right screen-padding}
|
:padding-right screen-padding}
|
||||||
[filter-selector-read-toggle]]]]))
|
[filter-selector-read-toggle]]]]))
|
||||||
|
|
||||||
|
(defn render-notification
|
||||||
|
[notification index]
|
||||||
|
[rn/view {:style (style/notification-container index)}
|
||||||
|
(case (:type notification)
|
||||||
|
types/contact-verification
|
||||||
|
[contact-verification/view notification {}]
|
||||||
|
|
||||||
|
types/contact-request
|
||||||
|
[contact-request/view notification]
|
||||||
|
|
||||||
|
nil)])
|
||||||
|
|
||||||
(defn activity-center
|
(defn activity-center
|
||||||
[]
|
[]
|
||||||
[:f>
|
[:f>
|
||||||
(fn []
|
(fn []
|
||||||
(let [notifications (<sub [:activity-center/filtered-notifications])
|
(let [notifications (rf/sub [:activity-center/filtered-notifications])
|
||||||
window-width (<sub [:dimensions/window-width])
|
window-width (rf/sub [:dimensions/window-width])
|
||||||
{:keys [top bottom]} (safe-area/use-safe-area)]
|
{:keys [top bottom]} (safe-area/use-safe-area)]
|
||||||
(react/effect! #(>evt [:activity-center.notifications/fetch-first-page]))
|
(react/effect! #(rf/dispatch [:activity-center.notifications/fetch-first-page]))
|
||||||
[rn/view {:style {:flex 1
|
[rn/view {:style (style/screen-container window-width top bottom)}
|
||||||
:width window-width
|
|
||||||
:padding-top (if (pos? top) (+ top 12) 12)
|
|
||||||
:padding-bottom bottom}}
|
|
||||||
[header]
|
[header]
|
||||||
[rn/flat-list {:content-container-style {:flex-grow 1}
|
[rn/flat-list {:content-container-style style/notifications-container
|
||||||
:data notifications
|
:data notifications
|
||||||
:empty-component [empty-tab]
|
:empty-component [empty-tab]
|
||||||
:key-fn :id
|
:key-fn :id
|
||||||
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
:on-end-reached #(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||||
:render-fn render-notification}]]))])
|
:render-fn render-notification}]]))])
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
(ns status-im.ui.screens.bottom-sheets.views
|
(ns status-im.ui.screens.bottom-sheets.views
|
||||||
(:require [status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
|
(:require [quo.core :as quo]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
|
[status-im.ui.screens.about-app.views :as about-app]
|
||||||
|
[status-im.ui.screens.activity-center.sheet.contact-verification :as contact-verification.sheet]
|
||||||
[status-im.ui.screens.home.sheet.views :as home.sheet]
|
[status-im.ui.screens.home.sheet.views :as home.sheet]
|
||||||
[status-im.ui.screens.keycard.views :as keycard]
|
[status-im.ui.screens.keycard.views :as keycard]
|
||||||
|
[status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
|
||||||
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage]
|
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage]
|
||||||
[status-im.ui.screens.about-app.views :as about-app]
|
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]))
|
||||||
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
|
|
||||||
[quo.core :as quo]))
|
|
||||||
|
|
||||||
(defn bottom-sheet []
|
(defn bottom-sheet []
|
||||||
(let [{:keys [show? view options]} @(re-frame/subscribe [:bottom-sheet])
|
(let [{:keys [show? view options]} @(re-frame/subscribe [:bottom-sheet])
|
||||||
|
@ -27,6 +28,9 @@
|
||||||
(= view :add-new)
|
(= view :add-new)
|
||||||
(merge home.sheet/add-new)
|
(merge home.sheet/add-new)
|
||||||
|
|
||||||
|
(= view :activity-center.contact-verification/reply)
|
||||||
|
(merge contact-verification.sheet/reply)
|
||||||
|
|
||||||
(= view :keycard.login/more)
|
(= view :keycard.login/more)
|
||||||
(merge keycard/more-sheet)
|
(merge keycard/more-sheet)
|
||||||
|
|
||||||
|
@ -40,4 +44,4 @@
|
||||||
(merge key-storage/migrate-account-password))]
|
(merge key-storage/migrate-account-password))]
|
||||||
[quo/bottom-sheet opts
|
[quo/bottom-sheet opts
|
||||||
(when content
|
(when content
|
||||||
[content (when options options)])]))
|
[content (when options options)])]))
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
(def descriptor [{:label "Unread?"
|
(def descriptor [{:label "Unread?"
|
||||||
:key :unread?
|
:key :unread?
|
||||||
:type :boolean}
|
:type :boolean}
|
||||||
|
{:label "Replying?"
|
||||||
|
:key :replying?
|
||||||
|
:type :boolean}
|
||||||
{:label "Icon"
|
{:label "Icon"
|
||||||
:key :icon
|
:key :icon
|
||||||
:type :select
|
:type :select
|
||||||
|
@ -138,6 +141,10 @@
|
||||||
(= (:message @state) :with-mention)
|
(= (:message @state) :with-mention)
|
||||||
(assoc :message message-with-mention)
|
(assoc :message message-with-mention)
|
||||||
|
|
||||||
|
(some? (:status @state))
|
||||||
|
(update :status (fn [status]
|
||||||
|
{:label (name status) :type status}))
|
||||||
|
|
||||||
(= (:message @state) :with-title)
|
(= (:message @state) :with-title)
|
||||||
(assoc :message message-with-title)
|
(assoc :message message-with-title)
|
||||||
|
|
||||||
|
|
|
@ -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.115.0",
|
"version": "v0.115.1",
|
||||||
"commit-sha1": "2341dedfbabfb8e392aaa49ecb9bb7fab6a74b99",
|
"commit-sha1": "2572321063182c0a0376da8a3462544e598c0f9d",
|
||||||
"src-sha256": "1x8r3rln2vna82kkvl42q11wh7zn2acr3crzj63pzcawdsr6l990"
|
"src-sha256": "07d996c10z4acin5wk21qyh9j1k1sc75a7x5d5hffaxllha7m9rs"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1138,6 +1138,7 @@
|
||||||
"send-logs-to": "Report a bug to {{email}}",
|
"send-logs-to": "Report a bug to {{email}}",
|
||||||
"send-message": "Send message",
|
"send-message": "Send message",
|
||||||
"send-request": "Send request",
|
"send-request": "Send request",
|
||||||
|
"send-reply": "Send reply",
|
||||||
"send-request-amount": "Amount",
|
"send-request-amount": "Amount",
|
||||||
"send-request-amount-max-decimals": "Max number of decimals is {{asset-decimals}}",
|
"send-request-amount-max-decimals": "Max number of decimals is {{asset-decimals}}",
|
||||||
"send-request-unknown-token": "Unknown token - {{asset}}",
|
"send-request-unknown-token": "Unknown token - {{asset}}",
|
||||||
|
@ -1827,11 +1828,15 @@
|
||||||
"mentions": "Mentions",
|
"mentions": "Mentions",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"replies": "Replies",
|
"replies": "Replies",
|
||||||
|
"replied": "Replied",
|
||||||
"identity-verification": "Identity verification",
|
"identity-verification": "Identity verification",
|
||||||
"identity-verification-request": "Identity verification request",
|
"identity-verification-request": "Identity verification",
|
||||||
"identity-verification-request-sent": "asked you",
|
"identity-verification-request-sent": "asks",
|
||||||
|
"type-something": "Type something",
|
||||||
|
"your-answer": "Your answer",
|
||||||
"membership": "Membership",
|
"membership": "Membership",
|
||||||
"jump-to": "Jump to",
|
"jump-to": "Jump to",
|
||||||
|
"untrustworthy": "Untrustworthy",
|
||||||
"blank-messages-text": "Your messages will be here",
|
"blank-messages-text": "Your messages will be here",
|
||||||
"groups": "Groups",
|
"groups": "Groups",
|
||||||
"shell-placeholder-title": "Your apps will run here",
|
"shell-placeholder-title": "Your apps will run here",
|
||||||
|
|
Loading…
Reference in New Issue