Support mutual contact requests

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Brian Sztamfater 2021-07-16 18:14:05 -03:00 committed by Andrea Maria Piana
parent 3f4a424169
commit 91f444ba80
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
32 changed files with 623 additions and 155 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -28,6 +28,7 @@
:inherit nil)
(case (or size :base)
:tiny typography/tiny
:x-small typography/x-small
:small typography/small
:base typography/base
:large typography/large

View File

@ -3,6 +3,9 @@
(def tiny {:font-size 10
:line-height 14})
(def x-small {:font-size 12
:line-height 16})
(def small {:font-size 13
:line-height 18})

View File

@ -143,6 +143,20 @@
:on-error #(log/error "failed to delete message message " %)
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})
(fx/defn show-contact-request-input
"Sets reference to previous chat message and focuses on input"
{:events [:chat.ui/send-contact-request]}
[{:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :sending-contact-request]
current-chat-id)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message]
nil)
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message] nil)
(update-in [:chat/inputs current-chat-id :metadata]
dissoc :sending-image))}))
(fx/defn cancel-message-reply
"Cancels stage message reply"
{:events [:chat.ui/cancel-message-reply]}
@ -179,6 +193,7 @@
(fx/defn clean-input [{:keys [db] :as cofx} current-chat-id]
(fx/merge cofx
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))}
@ -262,6 +277,34 @@
(mentions/clear-mentions)
(mentions/clear-cursor))))
(fx/defn send-contact-request
{:events [:contacts/send-contact-request]}
[{:keys [db] :as cofx} public-key message]
(fx/merge cofx
{::json-rpc/call [{:method "wakuext_sendContactRequest"
:js-response true
:params [{:id public-key :message message}]
:on-error #(log/warn "failed to send a contact request" %)
:on-success #(re-frame/dispatch [:transport/message-sent %])}]}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db))
(process-cooldown)))
(fx/defn cancel-contact-request
"Cancels contact request"
{:events [:chat.ui/cancel-contact-request]}
[{:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
(fx/merge cofx
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db))
(process-cooldown))))
(fx/defn chat-send-sticker
{:events [:chat/send-sticker]}
[{{:keys [current-chat-id] :as db} :db :as cofx} {:keys [hash packID] :as sticker}]

View File

@ -13,6 +13,13 @@
(def ^:const content-type-audio 8)
(def ^:const content-type-community 9)
(def ^:const content-type-gap 10)
(def ^:const content-type-contact-request 11) ;; TODO: temp, will be removed
(def ^:const contact-request-state-none 0)
(def ^:const contact-request-state-mutual 1)
(def ^:const contact-request-state-sent 2)
(def ^:const contact-request-state-received 3)
(def ^:const contact-request-state-dismissed 4)
(def ^:const emoji-reaction-love 1)
(def ^:const emoji-reaction-thumbs-up 2)
@ -28,6 +35,10 @@
(def ^:const timeline-chat-type 5)
(def ^:const community-chat-type 6)
(def ^:const contact-request-message-state-pending 1)
(def ^:const contact-request-message-state-accepted 2)
(def ^:const contact-request-message-state-declined 3)
(def request-to-join-pending-state 1)
(def reactions {emoji-reaction-love (:love resources/reactions)
@ -161,6 +172,8 @@
(def ^:const activity-center-notification-type-private-group-chat 2)
(def ^:const activity-center-notification-type-mention 3)
(def ^:const activity-center-notification-type-reply 4)
(def ^:const activity-center-notification-type-contact-request 5)
(def ^:const activity-center-notification-type-contact-request-retracted 6)
(def ^:const visibility-status-unknown 0)
(def ^:const visibility-status-automatic 1)
@ -173,4 +186,4 @@
(def ^:const sticker-pack-status-installed 1)
(def ^:const sticker-pack-status-pending 2)
(def ^:const sticker-pack-status-owned 3)
(def ^:const sticker-pack-status-owned 3)

View File

@ -5,6 +5,7 @@
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.navigation :as navigation]
[status-im.utils.fx :as fx]
[status-im.async-storage.core :as async-storage]
[taoensso.timbre :as log]
[status-im.constants :as constants]
[status-im.contact.block :as contact.block]))
@ -28,13 +29,6 @@
(= public-key (:public-key multiaccount))
(assoc :name (:name multiaccount))))
(defn- own-info
[db]
(let [{:keys [name preferred-name identicon address]} (:multiaccount db)]
{:name (or preferred-name name)
:profile-image identicon
:address address}))
(fx/defn ensure-contacts
[{:keys [db]} contacts chats]
(let [events
@ -66,6 +60,13 @@
(when (> (count events) 1)
{:dispatch-n events}))))
(defn- own-info
[db]
(let [{:keys [name preferred-name identicon address]} (:multiaccount db)]
{:name (or preferred-name name)
:profile-image identicon
:address address}))
(fx/defn send-contact-request
{:events [::send-contact-request]}
[{:keys [db] :as cofx} public-key]
@ -92,12 +93,33 @@
"Remove a contact from current account's contact list"
{:events [:contact.ui/remove-contact-pressed]}
[{:keys [db]} {:keys [public-key]}]
{:db (assoc-in db [:contacts/contacts public-key :added] false)
{:db (-> db
(assoc-in [:contacts/contacts public-key :added] false)
(assoc-in [:contacts/contacts public-key :contact-request-state] constants/contact-request-state-none))
::json-rpc/call [{:method "wakuext_removeContact"
:params [public-key]
:on-success #(log/debug "contact removed successfully")}
{:method "wakuext_retractContactRequest"
:params [{:contactId public-key}]
:on-success #(log/debug "contact removed successfully")}]
:dispatch [:offload-messages constants/timeline-chat-id]})
(fx/defn accept-contact-request
{:events [:contact-requests.ui/accept-request]}
[{:keys [db]} id]
{::json-rpc/call [{:method "wakuext_acceptContactRequest"
:params [{:id id}]
:js-response true
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})
(fx/defn decline-contact-request
{:events [:contact-requests.ui/decline-request]}
[{:keys [db]} id]
{::json-rpc/call [{:method "wakuext_dismissContactRequest"
:params [{:id id}]
:js-response true
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})
(fx/defn initialize-contacts [cofx]
(contacts-store/fetch-contacts-rpc cofx #(re-frame/dispatch [::contacts-loaded %])))
@ -119,3 +141,9 @@
nickname
#(re-frame/dispatch [:sanitize-messages-and-process-response %]))
(navigation/navigate-back)))
(fx/defn switch-mutual-contact-requests-enabled
{:events [:multiaccounts.ui/switch-mutual-contact-requests-enabled]}
[{:keys [db]} enabled?]
{::async-storage/set! {:mutual-contact-requests-enabled? enabled?}
:db (assoc db :mutual-contact-requests/enabled? enabled?)})

View File

@ -5,13 +5,18 @@
[taoensso.timbre :as log]))
(defn <-rpc [contact]
(clojure.set/rename-keys contact {:id :public-key
:ensVerifiedAt :ens-verified-at
:ensVerified :ens-verified
:ensVerificationRetries :ens-verification-retries
:lastENSClockValue :last-ens-clock-value
:lastUpdated :last-updated
:localNickname :nickname}))
(-> contact
(clojure.set/rename-keys {:id :public-key
:ensVerifiedAt :ens-verified-at
:ensVerified :ens-verified
:ensVerificationRetries :ens-verification-retries
:hasAddedUs :has-added-us
:contactRequestState :contact-request-state
:lastENSClockValue :last-ens-clock-value
:lastUpdated :last-updated
:localNickname :nickname})
(assoc :mutual? (and (:added contact)
(:hasAddedUs contact)))))
(fx/defn fetch-contacts-rpc
[_ on-success]

View File

@ -10,6 +10,7 @@
:lastUpdated 1}
expected-contact {:public-key "pk"
:address "address"
:mutual? nil
:name "name"
:identicon "identicon"
:last-updated 1}]

View File

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

View File

@ -61,4 +61,4 @@
::call
(fn [params]
(doseq [param params]
(call param))))
(call param))))

View File

@ -94,6 +94,13 @@
:wallet-connect-enabled?
#(re-frame/dispatch [:multiaccounts.ui/switch-wallet-connect-enabled %]))))
(re-frame/reg-fx
::initialize-mutual-contact-requests
(fn []
(async-storage/get-item
:mutual-contact-requests-enabled?
#(re-frame/dispatch [:multiaccounts.ui/switch-mutual-contact-requests-enabled %]))))
(defn rpc->accounts [accounts]
(reduce (fn [acc {:keys [chat type wallet] :as account}]
(if chat
@ -359,6 +366,10 @@
[cofx]
{::initialize-wallet-connect nil})
(fx/defn initialize-mutual-contact-requests
[cofx]
{::initialize-mutual-contact-requests nil})
(fx/defn get-node-config-callback
{:events [::get-node-config-callback]}
[{:keys [db] :as cofx} node-config-json]
@ -393,6 +404,7 @@
(initialize-appearance)
(initialize-communities-enabled)
(initialize-wallet-connect)
(initialize-mutual-contact-requests)
(get-node-config)
(communities/fetch)
(logging/set-log-level (:log-level multiaccount))

View File

@ -1,23 +1,38 @@
(ns status-im.notifications-center.core
(:require [status-im.utils.fx :as fx]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.constants :as constants]
[taoensso.timbre :as log]
[re-frame.core :as re-frame]
[status-im.data-store.activities :as data-store.activities]))
(def non-dismissable-notifications
#{constants/activity-center-notification-type-contact-request
constants/activity-center-notification-type-contact-request-retracted})
(fx/defn handle-activities [{:keys [db]} activities]
(let [{:keys [unread-count notifications]}
(reduce (fn [acc {:keys [read dismissed accepted] :as notification}]
(as-> acc a
(if read
(update a :unread-count dec)
(update a :unread-count inc))
(let [index-existing (->> (map-indexed vector (:notifications acc))
(filter (fn [[idx {:keys [id]}]] (= id (:id notification))))
first
first)]
(as-> acc a
(if read
(update a :unread-count dec)
(update a :unread-count inc))
(if (or dismissed accepted)
(update a :notifications (fn [items] (remove #(= (:id notification) (:id %)) items)))
(update a :notifications conj notification))))
(if index-existing
(if (or dismissed accepted)
;; Remove at specific location
(assoc a :notifications
(into (subvec (:notifications a) 0 index-existing) (subvec (:notifications a) (inc index-existing))))
;; Replace element
(do
(assoc-in a [:notifications index-existing] notification)))
(update a :notifications conj notification)))))
{:unread-count (get db :activity.center/notifications-count 0)
:notifications (get-in db [:activity.center/notifications :notifications])}
:notifications (into [] (get-in db [:activity.center/notifications :notifications]))}
activities)]
(merge
{:db (-> db
@ -69,14 +84,21 @@
{:events [:accept-all-activity-center-notifications-from-chat]}
[{:keys [db]} chat-id]
(let [notifications (get-in db [:activity.center/notifications :notifications])
notifications-from-chat (filter #(= chat-id (:chat-id %)) notifications)
notifications-from-chat (filter #(and
(= chat-id (:chat-id %))
(not (contains? non-dismissable-notifications (:type %))))
notifications)
notifications-from-chat-not-read (filter #(and (= chat-id (:chat-id %))
(not (:read %))) notifications)
ids (map :id notifications-from-chat)]
(not (:read %)))
notifications)
ids (into #{} (map :id notifications-from-chat))]
(when (seq ids)
{:db (-> db
(update-in [:activity.center/notifications :notifications]
(fn [items] (filter #(not (= chat-id (:chat-id %))) items)))
(fn [items]
(filter
#(not (contains? ids (:id %)))
items)))
(update :activity.center/notifications-count - (min (db :activity.center/notifications-count) (count notifications-from-chat-not-read))))
::json-rpc/call [{:method "wakuext_acceptActivityCenterNotifications"
:params [ids]
@ -170,4 +192,3 @@
(update-in [:activity.center/notifications :notifications]
concat
(map data-store.activities/<-rpc notifications)))})

View File

@ -45,6 +45,7 @@
:notifications (js/require "../resources/images/ui/notifications.png")
:collectible (js/require "../resources/images/ui/collectible.png")
:collectible-dark (js/require "../resources/images/ui/collectible-dark.png")
:hand-wave (js/require "../resources/images/ui/hand-wave.png")
:graph (js/require "../resources/images/ui/graph.png")})
(defn get-theme-image [k]

View File

@ -264,6 +264,9 @@
(reg-root-key-sub :wallet-connect/sessions :wallet-connect/sessions)
(reg-root-key-sub :wallet-connect-legacy/sessions :wallet-connect-legacy/sessions)
(reg-root-key-sub :wallet-connect/session-managed :wallet-connect/session-managed)
(reg-root-key-sub :contact-requests/pending :contact-requests/pending)
(reg-root-key-sub :mutual-contact-requests/enabled? :mutual-contact-requests/enabled?)
(re-frame/reg-sub
:communities
@ -941,6 +944,13 @@
(fn [ui-props [_ prop]]
(get ui-props prop)))
(re-frame/reg-sub
:chats/current-chat-contact
:<- [:contacts/contacts]
:<- [:chats/current-chat-id]
(fn [[contacts current-chat-id]]
(get contacts current-chat-id)))
(re-frame/reg-sub
:chats/home-list-chats
:<- [::chats]
@ -1033,7 +1043,10 @@
:<- [:multiaccount/public-key]
:<- [:communities/current-community]
:<- [:contacts/blocked-set]
(fn [[{:keys [group-chat chat-id] :as current-chat} my-public-key community blocked-users-set]]
:<- [:contacts/contacts]
:<- [:chat/inputs]
:<- [:mutual-contact-requests/enabled?]
(fn [[{:keys [group-chat chat-id] :as current-chat} my-public-key community blocked-users-set contacts inputs mutual-contact-requests-enabled?]]
(when current-chat
(cond-> current-chat
(chat.models/public-chat? current-chat)
@ -1049,7 +1062,15 @@
(assoc :show-input? true)
(not group-chat)
(assoc :show-input? (not (contains? blocked-users-set chat-id)))))))
(assoc :show-input?
(and
(or
(not mutual-contact-requests-enabled?)
(get-in inputs [chat-id :metadata :sending-contact-request])
(and mutual-contact-requests-enabled?
(= constants/contact-request-state-mutual
(get-in contacts [chat-id :contact-request-state]))))
(not (contains? blocked-users-set chat-id))))))))
(re-frame/reg-sub
:chats/current-chat-chat-view
@ -1300,6 +1321,12 @@
(fn [{:keys [metadata]}]
(:editing-message metadata)))
(re-frame/reg-sub
:chats/sending-contact-request
:<- [:chats/current-chat-inputs]
(fn [{:keys [metadata]}]
(:sending-contact-request metadata)))
(re-frame/reg-sub
:chats/sending-image
:<- [:chats/current-chat-inputs]
@ -1321,22 +1348,27 @@
:<- [:current-chat/metadata]
:<- [:chats/reply-message]
:<- [:chats/edit-message]
(fn [[{:keys [processing]} sending-image mainnet? one-to-one-chat? {:keys [public?]} reply edit]]
:<- [:chats/sending-contact-request]
(fn [[{:keys [processing]} sending-image mainnet? one-to-one-chat? {:keys [public?]} reply edit sending-contact-request]]
(let [sending-image (seq sending-image)]
{:send (not processing)
:stickers (and (or config/stickers-test-enabled? mainnet?)
(not sending-image)
(not sending-contact-request)
(not reply))
:image (and (not reply)
(not edit)
(not sending-contact-request)
(not public?))
:extensions (and one-to-one-chat?
(or config/commands-enabled? mainnet?)
(not edit)
(not sending-contact-request)
(not reply))
:audio (and (not sending-image)
(not reply)
(not edit)
(not sending-contact-request)
(not public?))
:sending-image sending-image})))
@ -1896,6 +1928,8 @@
(filter (fn [{:keys [type last-message]}]
(or (and (= constants/activity-center-notification-type-one-to-one-chat type)
(not (nil? last-message)))
(= constants/activity-center-notification-type-contact-request type)
(= constants/activity-center-notification-type-contact-request-retracted type)
(= constants/activity-center-notification-type-private-group-chat type)
(= constants/activity-center-notification-type-reply type)
(= constants/activity-center-notification-type-mention type)))

View File

@ -14,8 +14,9 @@
wakuv2-flag
current-fleet
webview-debug
wallet-connect-enabled?
new-ui-enabled?]}]
new-ui-enabled?
mutual-contact-requests-enabled?
wallet-connect-enabled?]}]
(keep
identity
[{:size :small
@ -108,6 +109,15 @@
[:multiaccounts.ui/waku-bloom-filter-mode-switched (not waku-bloom-filter-mode)])
:accessory :switch
:active waku-bloom-filter-mode}
{:size :small
:title (i18n/label :t/mutual-contact-requests)
:accessibility-label :wallet-connect-settings-switch
:container-margin-bottom 8
:on-press
#(re-frame/dispatch
[:multiaccounts.ui/switch-mutual-contact-requests-enabled (not mutual-contact-requests-enabled?)])
:accessory :switch
:active mutual-contact-requests-enabled?}
{:size :small
:title (i18n/label :t/wallet-connect)
:accessibility-label :wallet-connect-settings-switch
@ -143,6 +153,7 @@
transactions-management-enabled? [:wallet/transactions-management-enabled?]
current-log-level [:log-level/current-log-level]
current-fleet [:fleets/current-fleet]
mutual-contact-requests-enabled? [:mutual-contact-requests/enabled?]
wallet-connect-enabled? [:wallet-connect/enabled?]]
[list/flat-list
{:data (flat-list-data
@ -155,7 +166,8 @@
:wakuv2-flag wakuv2-flag
:waku-bloom-filter-mode waku-bloom-filter-mode
:webview-debug webview-debug
:wallet-connect-enabled? wallet-connect-enabled?
:new-ui-enabled? @config/new-ui-enabled?})
:new-ui-enabled? @config/new-ui-enabled?
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
:wallet-connect-enabled? wallet-connect-enabled?})
:key-fn (fn [_ i] (str i))
:render-fn render-item}]))

View File

@ -227,13 +227,13 @@
[react/animated-view {:style (styles/rec-outer-circle outer-scale)}]
[react/animated-view {:style (styles/rec-inner-circle inner-scale inner-border-radius)}]]]))
(defn- cancel-button [disabled? on-press]
(defn- cancel-button [disabled? on-press contact-request]
[pressable/pressable {:type :scale
:disabled disabled?
:on-press on-press}
[react/view {:style (input.style/send-message-button)}
[icons/icon :main-icons/close
{:container-style (merge (input.style/send-message-container) {:background-color colors/gray})
{:container-style (merge (input.style/send-message-container contact-request) {:background-color colors/gray})
:accessibility-label :cancel-message-button
:color colors/white-persist}]]])
@ -272,13 +272,14 @@
(reset! on-background-cb nil))}
(let [base-params {:rec-button-anim-value rec-button-anim-value
:ctrl-buttons-anim-value ctrl-buttons-anim-value
:timer timer}]
:timer timer}
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[react/view {:style styles/container}
[react/text {:style styles/timer
:accessibility-label :audio-message-recorded-time} @timer]
[react/view {:style styles/buttons-container}
[react/animated-view {:style {:opacity ctrl-buttons-anim-value}}
[cancel-button (:cancel-disabled? @state) #(stop-recording base-params)]]
[cancel-button (:cancel-disabled? @state) #(stop-recording base-params) contact-request]]
[rec-button-view (merge base-params {:state state})]
[react/animated-view {:style {:opacity ctrl-buttons-anim-value}}
[input/send-button (fn [] (cond

View File

@ -0,0 +1,86 @@
(ns status-im.ui.screens.chat.components.contact-request
(:require [quo.core :as quo]
[quo.react :as quo.react]
[quo.react-native :as rn]
[quo.design-system.colors :as quo.colors]
[status-im.i18n.i18n :as i18n]
[status-im.ethereum.stateofus :as stateofus]
[status-im.ui.screens.chat.components.style :as styles]
[re-frame.core :as re-frame]
[clojure.string :as string])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(def ^:private contact-request-symbol "↪ ")
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(defn format-author [contact-name]
(let [author (if (or (= (aget contact-name 0) "@")
;; in case of replies
(= (aget contact-name 1) "@"))
(or (stateofus/username contact-name)
(subs contact-name 0 81))
contact-name)]
(i18n/label :contact-requesting-to {:author author})))
(defn format-contact-request-author [from username current-public-key]
(or (and (= from current-public-key)
(str contact-request-symbol (i18n/label :t/You)))
(str contact-request-symbol (format-author username))))
(defn get-quoted-text-with-mentions [parsed-text]
(string/join
(mapv (fn [{:keys [type literal children]}]
(cond
(= type "paragraph")
(get-quoted-text-with-mentions children)
(= type "mention")
@(re-frame/subscribe [:contacts/contact-name-by-identity literal])
(seq children)
(get-quoted-text-with-mentions children)
:else
literal))
parsed-text)))
(defn contact-request-message [their-public-key]
(let [{:keys [input-text]} @(re-frame/subscribe [:chats/current-chat-inputs])]
[rn/view {:style {:flex-direction :row}}
[rn/view {:style (styles/contact-request-content)}
[quo/button {:type :secondary
:weight :medium
:number-of-lines 1
:style {:line-height 18}
:on-press #(re-frame/dispatch [:chat.ui/cancel-contact-request])}
(i18n/label :t/cancel)]
[quo/button {:type :secondary
:disabled (string/blank? input-text)
:weight :medium
:after :main-icons/send
:on-press #(re-frame/dispatch [:contacts/send-contact-request their-public-key input-text])
:style {:line-height 18}}
(i18n/label :t/send-request)]]]))
(defn focus-input-on-contact-request [contact-request had-contact-request text-input-ref]
;;when we show contact-request we focus input
(when-not (= contact-request @had-contact-request)
(reset! had-contact-request contact-request)
(when contact-request
(js/setTimeout #(input-focus text-input-ref) 250))))
(defn contact-request-message-wrapper [contact-request]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
:border-top-color (:ui-01 @quo.colors/theme)
:padding-vertical 8}}
[contact-request-message contact-request]])
(defview contact-request-message-auto-focus-wrapper [text-input-ref]
(letsubs [had-reply (atom nil)
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
{:component-did-mount #(focus-input-on-contact-request contact-request had-reply text-input-ref)}
(when contact-request
[contact-request-message-wrapper contact-request])))

View File

@ -73,13 +73,14 @@
[icons/icon :main-icons/keyboard (styles/icon false)]
[icons/icon :main-icons/speech (styles/icon false)])]])
(defn send-button [on-send]
(defn send-button [on-send contact-request]
[rn/touchable-opacity {:on-press-in on-send}
[rn/view {:style (styles/send-message-button)}
[icons/icon :main-icons/arrow-up
{:container-style (styles/send-message-container)
:accessibility-label :send-message-button
:color (styles/send-icon-color)}]]])
(when-not contact-request
[icons/icon :main-icons/arrow-up
{:container-style (styles/send-message-container contact-request)
:accessibility-label :send-message-button
:color (styles/send-icon-color)}])]])
(defn on-selection-change [timeout-id last-text-change mentionable-users args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
@ -232,10 +233,11 @@
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)
mentions-enabled (get @mentions-enabled chat-id)]
mentions-enabled (get @mentions-enabled chat-id)
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[rn/text-input
{:style (styles/text-input)
{:style (styles/text-input contact-request)
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
@ -327,8 +329,8 @@
(when (seq sending-image)
[reply/send-image sending-image])))
(defn actions [extensions image show-send actions-ref active-panel set-active-panel]
[rn/view {:style (styles/actions-wrapper show-send)
(defn actions [extensions image show-send actions-ref active-panel set-active-panel contact-request]
[rn/view {:style (styles/actions-wrapper (and (not contact-request) show-send))
:ref actions-ref}
(when extensions
[touchable-icon {:panel :extensions
@ -354,12 +356,13 @@
:sticker-ref sticker-ref
:text-input-ref text-input-ref}
{:keys [send stickers image extensions audio sending-image]} @toolbar-options
show-send (or sending-image (seq (get @input-texts chat-id)))]
show-send (or sending-image (seq (get @input-texts chat-id)))
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[rn/view {:style (styles/toolbar)
:on-layout on-chat-toolbar-layout}
;;EXTENSIONS and IMAGE buttons
[actions extensions image show-send actions-ref active-panel set-active-panel]
[rn/view {:style (styles/input-container)}
[actions extensions image show-send actions-ref active-panel set-active-panel contact-request]
[rn/view {:style (styles/input-container contact-request)}
[send-image]
[rn/view {:style styles/input-row}
[text-input {:chat-id chat-id
@ -370,7 +373,8 @@
[rn/view {:ref send-ref :style (when-not show-send {:width 0 :right -100})}
(when send
[send-button #(do (clear-input chat-id refs)
(re-frame/dispatch [:chat.ui/send-current-message]))])]
(re-frame/dispatch [:chat.ui/send-current-message]))
contact-request])]
;;STICKERS and AUDIO buttons
(when-not @(re-frame/subscribe [:chats/edit-message])

View File

@ -11,14 +11,15 @@
:align-items :flex-end
:flex-direction :row})
(defn input-container []
(defn input-container [contact-request]
{:background-color (:ui-01 @colors/theme)
:flex 1
:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-right-radius 4
:border-bottom-left-radius 16
:margin-horizontal 8})
:height (when contact-request 44)
:border-top-left-radius (if contact-request 8 16)
:border-top-right-radius (if contact-request 8 16)
:border-bottom-right-radius (if contact-request 8 4)
:border-bottom-left-radius (if contact-request 8 16)
:margin-horizontal (when contact-request 8)})
(def input-row
{:flex-direction :row
@ -34,7 +35,7 @@
(when platform/ios?
{:padding-top 2})))
(defn text-input []
(defn text-input [contact-request]
(merge typography/font-regular
typography/base
{:flex 1
@ -46,8 +47,8 @@
:padding-horizontal 12}
(if platform/android?
{:padding-vertical 2}
{:padding-top 2
:padding-bottom 6})))
{:padding-top (if contact-request 10 2)
:padding-bottom (if contact-request 5 6)})))
(defn actions-wrapper [show-send]
(merge
@ -91,6 +92,11 @@
:padding-horizontal 10
:flex 1})
(defn contact-request-content []
{:flex 1
:flex-direction :row
:justify-content :space-between})
(defn close-button []
{:margin-top 3})
@ -98,11 +104,11 @@
{:margin-vertical 4
:margin-horizontal 5})
(defn send-message-container []
(defn send-message-container [contact-request]
{:background-color (:interactive-01 @colors/theme)
:width 26
:height 26
:border-radius 13
:height (if contact-request 44 26)
:border-radius (if contact-request 22 13)
:justify-content :center
:align-items :center})

View File

@ -621,6 +621,49 @@
[message.audio/message-content message] [message-status message]]]]]
reaction-picker])))
(defn contact-request-status-pending []
[react/view {:style {:flex-direction :row}}
[quo/text {:style {:margin-right 5.27}
:weight :medium
:color :secondary}
(i18n/label :t/contact-request-pending)]
[react/activity-indicator {:animating true
:size :small
:color colors/gray}]])
(defn contact-request-status-accepted []
[quo/text {:style {:color colors/green}
:weight :medium}
(i18n/label :t/contact-request-accepted)])
(defn contact-request-status-declined []
[quo/text {:style {:color colors/red}
:weight :medium}
(i18n/label :t/contact-request-declined)])
(defn contact-request-status-label [state]
[react/view {:style (style/contact-request-status-label state)}
(case state
constants/contact-request-message-state-pending [contact-request-status-pending]
constants/contact-request-message-state-accepted [contact-request-status-accepted]
constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request
[{:keys [outgoing] :as message} _]
[react/view {:style (style/content-type-contact-request outgoing)}
[react/image {:source (resources/get-image :hand-wave)
:style {:width 112
:height 97}}]
[quo/text {:style {:margin-top 6}
:weight :bold
:size :large}
(i18n/label :t/contact-request)]
[react/view {:style {:padding-horizontal 16}}
[quo/text {:style {:margin-top 2
:margin-bottom 14}}
(get-in message [:content :text])]]
[contact-request-status-label (:contact-request-state message)]])
(defmethod ->message :default [message]
[message-content-wrapper message
[unknown-content-type message]])

View File

@ -116,4 +116,4 @@
[connectivity/loading-indicator]
[pinned-messages-view {:chat chat
:pan-responder pan-responder
:space-keeper space-keeper}]]))))
:space-keeper space-keeper}]]))))

View File

@ -53,8 +53,7 @@
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center
:height 324}
:align-items :center}
{:flex 1
:flex-direction :column
:justify-content :center
@ -148,3 +147,10 @@
{:font-size 13
:line-height 18
:text-align :center})
(def contact-request
{:width "100%"
:justify-content :center
:align-items :center
:border-top-width 1
:border-color colors/gray-transparent-10})

View File

@ -435,3 +435,32 @@
:border-bottom-left-radius 10
:border-bottom-right-radius 10
:border-color colors/gray-lighter})
(defn contact-request-status-label [state]
{:width 136
:border-radius 8
:flex 1
:justify-content :center
:align-items :center
:background-color (when (= :retry state)
colors/blue-light)
:border-width 1
:border-color (case state
constants/contact-request-message-state-accepted colors/green-transparent-10
constants/contact-request-message-state-declined colors/red-light
constants/contact-request-message-state-pending colors/gray-lighter)
:padding-vertical 10
:padding-horizontal 16})
(defn content-type-contact-request [outgoing]
{:width 168
:min-height 224.71
:border-radius 8
:border-width 1
:border-color colors/gray-lighter
:align-items :center
:padding-bottom 10
:margin-vertical 4
:align-self (if outgoing :flex-end :flex-start)
:margin-right (if outgoing 8 0)
:margin-left (if outgoing 0 8)})

View File

@ -10,6 +10,7 @@
[status-im.ui.components.list.views :as list]
[status-im.ui.screens.chat.components.reply :as reply]
[status-im.ui.screens.chat.components.edit :as edit]
[status-im.ui.screens.chat.components.contact-request :as contact-request]
[status-im.ui.components.react :as react]
[quo.animated :as animated]
[quo.react-native :as rn]
@ -35,7 +36,8 @@
[status-im.utils.utils :as utils]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.utils.debounce :as debounce]
[status-im.navigation.state :as navigation.state]))
[status-im.navigation.state :as navigation.state]
[status-im.react-native.resources :as resources]))
(defn invitation-requests [chat-id admins]
(let [current-pk @(re-frame/subscribe [:multiaccount/public-key])
@ -62,20 +64,45 @@
{:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]]))
(defn contact-request []
(let [contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[react/view {:style style/contact-request}
[react/image {:source (resources/get-image :hand-wave)
:style {:width 112
:height 96.71
:margin-top 17}}]
[quo/text {:style {:margin-top 14}
:weight :bold
:size :large}
(i18n/label :t/say-hi)]
[quo/text {:style {:margin-top 2
:margin-bottom 14}}
(i18n/label :t/send-contact-request-message)]
(when-not contact-request
[react/view {:style {:padding-horizontal 16
:padding-bottom 8}}
[quo/button
{:style {:width "100%"}
:accessibility-label :contact-request--button
:on-press #(re-frame/dispatch [:chat.ui/send-contact-request])}
(i18n/label :t/contact-request)]])]))
(defn chat-intro [{:keys [chat-id
chat-name
chat-type
group-chat
invitation-admin
mutual-contact-requests-enabled?
contact-name
color
loading-messages?
no-messages?
contact-request-state
emoji]}]
[react/view {:style (style/intro-header-container loading-messages? no-messages?)
:accessibility-label :history-chat}
;; Icon section
[react/view {:style {:margin-top 42
[react/view {:style {:margin-top 52
:margin-bottom 24}}
[chat-icon.screen/emoji-chat-intro-icon-view
chat-name chat-id group-chat emoji
@ -100,12 +127,28 @@
(str
(i18n/label :t/empty-chat-description-one-to-one)
contact-name)])])
contact-name)])
(when
(and
mutual-contact-requests-enabled?
(= chat-type constants/one-to-one-chat-type)
(or
(= contact-request-state constants/contact-request-state-none)
(= contact-request-state constants/contact-request-state-received)
(= contact-request-state constants/contact-request-state-dismissed)))
[contact-request])])
(defn chat-intro-one-to-one [{:keys [chat-id] :as opts}]
(let [contact-names @(re-frame/subscribe
(let [contact @(re-frame/subscribe
[:contacts/contact-by-identity chat-id])
mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?])
contact-names @(re-frame/subscribe
[:contacts/contact-two-names-by-identity chat-id])]
[chat-intro (assoc opts :contact-name (first contact-names))]))
[chat-intro (assoc opts
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
:contact-name (first contact-names)
:contact-request-state (or (:contact-request-state contact)
constants/contact-request-state-none))]))
(defn chat-intro-header-container
[{:keys [group-chat invitation-admin
@ -299,9 +342,23 @@
:edit-enabled edit-enabled
:in-pinned-view? in-pinned-view?}))
(defn messages-view [{:keys [chat bottom-space pan-responder space-keeper show-input?]}]
(let [{:keys [group-chat chat-id public? community-id admins]} chat
messages @(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id])]
(defn messages-view [{:keys [chat
bottom-space
pan-responder
mutual-contact-requests-enabled?
space-keeper
show-input?]}]
(let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat
messages @(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id])
one-to-one? (= chat-type constants/one-to-one-chat-type)
contact-added? (when one-to-one? @(re-frame/subscribe [:contacts/contact-added? chat-id]))
should-send-contact-request?
(and
mutual-contact-requests-enabled?
one-to-one?
(not contact-added?))]
;;do not use anonymous functions for handlers
[list/flat-list
(merge
@ -310,7 +367,8 @@
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data messages
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
@ -385,6 +443,7 @@
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as chat}
;;we want to react only on these fields, do not use full chat map here
@(re-frame/subscribe [:chats/current-chat-chat-view])
mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?])
max-bottom-space (max @bottom-space @panel-space)]
[:<>
[topbar]
@ -392,11 +451,12 @@
(when chat-id
(if group-chat
[invitation-requests chat-id admins]
[add-contact-bar chat-id]))
(when-not mutual-contact-requests-enabled? [add-contact-bar chat-id])))
;;MESSAGES LIST
[messages-view {:chat chat
:bottom-space max-bottom-space
:pan-responder pan-responder
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
:space-keeper space-keeper
:show-input? show-input?}]
(when (and group-chat invitation-admin)
@ -421,7 +481,8 @@
{:chat-id chat-id
:active-panel @active-panel
:set-active-panel set-active-panel
:text-input-ref text-input-ref}]]
:text-input-ref text-input-ref}]
[contact-request/contact-request-message-auto-focus-wrapper text-input-ref]]
[bottom-sheet @active-panel]])]))))
(defn chat []

View File

@ -82,6 +82,7 @@
[preview-label :t/no-messages]
(and (or (= constants/content-type-text content-type)
(= constants/content-type-contact-request content-type)
(= constants/content-type-emoji content-type)
(= constants/content-type-command content-type))
(not (string/blank? (:text content))))

View File

@ -62,9 +62,9 @@
(def notification-content-container
{:flex 1})
(def photo-container
(defn photo-container [has-header?]
{:position :absolute
:top 12
:top (if has-header? 37 12)
:left 16})
(defn title-text [title-text-width]

View File

@ -33,7 +33,11 @@
{:on-press (fn []
(if @selecting
(on-change)
(re-frame/dispatch [:accept-activity-center-notification-and-open-chat id])))
;; We don't dispatch on contact requests unless
;; accepted
(when (or (not= type constants/activity-center-notification-type-contact-request)
(= constants/contact-request-message-state-accepted (get-in home-item [:message :contact-request-state])))
(re-frame/dispatch [:accept-activity-center-notification-and-open-chat id]))))
:on-long-press #(do (reset! selecting true)
(when-not (= type constants/activity-center-notification-type-mention) (swap! selected-items conj id)))}]]])))
(defn filter-item []

View File

@ -2,6 +2,8 @@
(:require [status-im.ui.components.react :as react]
[re-frame.core :as re-frame]
[quo.core :as quo]
[quo.components.animated.pressable :as animation]
[status-im.i18n.i18n :as i18n]
[status-im.ui.screens.notifications-center.styles :as styles]
[status-im.utils.handlers :refer [<sub]]
[status-im.ui.screens.chat.photos :as photos]
@ -13,81 +15,119 @@
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]))
(defn contact-request-actions [request-id]
[react/view {:flex-direction :row}
[animation/pressable {:on-press #(re-frame/dispatch [:contact-requests.ui/accept-request request-id])}
[icons/icon :main-icons/checkmark-circle {:width 35
:height 35
:color colors/green}]]
[animation/pressable {:on-press #(re-frame/dispatch [:contact-requests.ui/decline-request request-id])}
[icons/icon :main-icons/cancel {:width 35
:height 35
:container-style {:margin-left 16}
:color colors/red}]]])
(defn activity-text-item [home-item opts]
(let [{:keys [chat-id chat-name message last-message reply-message muted read group-chat timestamp type color]} home-item
message (or message last-message)
{:keys [community-id]} (<sub [:chat-by-id chat-id])
{:keys [name]} @(re-frame/subscribe [:communities/community community-id])
contact (when message @(re-frame/subscribe [:contacts/contact-by-identity (message :from)]))
sender (when message (first @(re-frame/subscribe [:contacts/contact-two-names-by-identity (message :from)])))
title-text-width (* @(re-frame/subscribe [:dimensions/window-width]) 0.62)]
[react/touchable-opacity (merge {:style (styles/notification-container read)} opts)
[react/view {:style styles/notification-content-container}
(if (or
(= type constants/activity-center-notification-type-mention)
(= type constants/activity-center-notification-type-reply))
[react/view {:style styles/photo-container}
[photos/photo
(multiaccounts/displayed-photo contact)
{:size 40
:accessibility-label :current-account-photo}]]
[chat-icon.screen/chat-icon-view chat-id group-chat chat-name
{:container styles/photo-container
:size 40
:chat-icon chat-icon.styles/chat-icon-chat-list
:default-chat-icon (chat-icon.styles/default-chat-icon-chat-list color)
:default-chat-icon-text (chat-icon.styles/default-chat-icon-text 40)
:accessibility-label :current-account-photo}])
[quo/text {:weight :medium
:color (when muted :secondary)
:accessibility-label :chat-name-or-sender-text
:ellipsize-mode :tail
:number-of-lines 1
:style (styles/title-text title-text-width)}
title-text-width (* @(re-frame/subscribe [:dimensions/window-width]) 0.62)
sender (when message (first @(re-frame/subscribe [:contacts/contact-two-names-by-identity (message :from)])))]
[react/view
[react/touchable-opacity (merge {:style (styles/notification-container read)} opts)
[react/view {:style {:flex 1}}
(when (or
(= type constants/activity-center-notification-type-contact-request)
(= type constants/activity-center-notification-type-contact-request-retracted))
[react/view {:style {:padding-horizontal 20}}
[quo/text {:weight :bold}
(if
(= type constants/activity-center-notification-type-contact-request)
(i18n/label :t/contact-request)
(i18n/label :t/removed-from-contacts))]])
(if (or
(= type constants/activity-center-notification-type-mention)
(= type constants/activity-center-notification-type-contact-request)
(= type constants/activity-center-notification-type-reply))
sender
[home-item/chat-item-title chat-id muted group-chat chat-name])]
[react/text {:style styles/datetime-text
:number-of-lines 1
:accessibility-label :notification-time-text}
;;TODO (perf) move to event
(home-item/memo-timestamp timestamp)]
[react/view {:style styles/notification-message-container}
[home-item/message-content-text (select-keys message [:content :content-type :community-id]) false]
(cond (= type constants/activity-center-notification-type-mention)
[react/view {:style styles/group-info-container
:accessibility-label :chat-name-container}
[icons/icon
(if community-id :main-icons/tiny-community :main-icons/tiny-group)
{:color colors/gray
:width 16
:height 16
:container-style styles/group-icon}]
(when community-id
[react/view {:style styles/community-info-container}
[quo/text {:color :secondary
:weight :medium
:size :small}
name]
[icons/icon
:main-icons/chevron-right
{:color colors/gray
:width 16
:height 22}]])
[quo/text {:color :secondary
:weight :medium
:size :small}
(str (when community-id "#") chat-name)]]
[react/view {:style (styles/photo-container (= type constants/activity-center-notification-type-contact-request))}
(= type constants/activity-center-notification-type-reply)
[react/view {:style styles/reply-message-container
:accessibility-label :reply-message-container}
[icons/icon
:main-icons/tiny-reply
{:color colors/gray
:width 18
:height 18
:container-style styles/reply-icon}]
[home-item/message-content-text (select-keys reply-message [:content :content-type :community-id]) false]])]]]))
[photos/photo
(multiaccounts/displayed-photo contact)
{:size 40
:accessibility-label :current-account-photo}]]
[chat-icon.screen/chat-icon-view chat-id group-chat chat-name
{:container (styles/photo-container false)
:size 40
:chat-icon chat-icon.styles/chat-icon-chat-list
:default-chat-icon (chat-icon.styles/default-chat-icon-chat-list color)
:default-chat-icon-text (chat-icon.styles/default-chat-icon-text 40)
:accessibility-label :current-account-photo}])
[quo/text {:weight :medium
:color (when muted :secondary)
:accessibility-label :chat-name-or-sender-text
:ellipsize-mode :tail
:number-of-lines 1
:style (styles/title-text title-text-width)}
(if (or
(= type constants/activity-center-notification-type-mention)
(= type constants/activity-center-notification-type-contact-request)
(= type constants/activity-center-notification-type-contact-request-retracted)
(= type constants/activity-center-notification-type-reply))
sender
[home-item/chat-item-title chat-id muted group-chat chat-name])]
[react/text {:style styles/datetime-text
:number-of-lines 1
:accessibility-label :notification-time-text}
;;TODO (perf) move to event
(home-item/memo-timestamp timestamp)]
[react/view {:style styles/notification-message-container}
(when-not (= type constants/activity-center-notification-type-contact-request-retracted)
[home-item/message-content-text (select-keys message [:content :content-type :community-id]) false])
(cond (= type constants/activity-center-notification-type-mention)
[react/view {:style styles/group-info-container
:accessibility-label :chat-name-container}
[icons/icon
(if community-id :main-icons/tiny-community :main-icons/tiny-group)
{:color colors/gray
:width 16
:height 16
:container-style styles/group-icon}]
(when community-id
[react/view {:style styles/community-info-container}
[quo/text {:color :secondary
:weight :medium
:size :small}
name]
[icons/icon
:main-icons/chevron-right
{:color colors/gray
:width 16
:height 22}]])
[quo/text {:color :secondary
:weight :medium
:size :small}
(str (when community-id "#") chat-name)]]
(= type constants/activity-center-notification-type-reply)
[react/view {:style styles/reply-message-container
:accessibility-label :reply-message-container}
[icons/icon
:main-icons/tiny-reply
{:color colors/gray
:width 18
:height 18
:container-style styles/reply-icon}]
[home-item/message-content-text (select-keys reply-message [:content :content-type :community-id]) false]])]]]
(when (= type constants/activity-center-notification-type-contact-request)
[react/view {:style {:margin-right 20
:margin-top 10
:align-self :flex-end}}
(case (:contact-request-state message)
constants/contact-request-message-state-accepted
[quo/text {:style {:color colors/green}} (i18n/label :t/accepted)]
constants/contact-request-message-state-declined
[quo/text {:style {:color colors/red}} (i18n/label :t/declined)]
constants/contact-request-message-state-pending
[contact-request-actions (:message-id message)])])]))

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "a244d776571cc73ea34d7a700cc5db36166ce832",
"commit-sha1": "a244d776571cc73ea34d7a700cc5db36166ce832",
"src-sha256": "1v057jy565y9178ja8j9j6sg04z83y7a0bnry6ziy9zf2zda7nkg"
"version": "v0.100.0",
"commit-sha1": "1bfde4c4cc69292875ae9fdc41a8b4bd29e95a7a",
"src-sha256": "1nkk7dsqz1x31n305p33jdwpqr9bm7ajkqs3khq5l4ag3njagbrh"
}

View File

@ -1738,5 +1738,17 @@
"wallet-manage-app-connections": "Manage app connections",
"connection-request": "Connection Request",
"disconnect": "Disconnect",
"new-ui": "New UI"
"new-ui": "New UI",
"send-contact-request-message": "To start a chat you need to become contacts",
"contact-request": "Contact request",
"say-hi": "Say hi",
"accepted": "Accepted",
"declined": "Declined",
"contact-request": "Contact request",
"contact-request-header": "👋 Contact requests",
"contact-request-declined": "Declined ⓧ",
"contact-request-accepted": "Accepted ✓",
"contact-request-pending": "Pending...",
"removed-from-contacts": "Removed from contacts",
"mutual-contact-requests": "Mutual contact requests"
}