perf: Fix app freeze after login (#20729)
We do a few things to reduce the initial load and make the app more responsive after login. The scenario we are covering is a user who joined communities with a large number of members and/or which contain token-gated channels with many members. - Related to https://github.com/status-im/status-mobile/issues/20283 - Related to https://github.com/status-im/status-mobile/issues/20285 - Optimize how we convert a community from JS to CLJS. Community members and chat members are no longer transformed to CLJS, they are kept as JS. Read more details below. - Delay processing lower-priority events by creating a third login phase. The goal is to not put on the same queue we process communities less important events, like fetching the count of unread notifications. Around 15 events could be delayed without causing trouble (and this further prevent a big chain of more events to be dispatched right after login). - Tried to use re-frame's flush-dom metadata, but removed due to uncertainty, check out the discussion: https://github.com/status-im/status-mobile/pull/20729#discussion_r1683047969 Use re-frame’s support for the flush-dom metadata whenever a signal arrives. According to the official documentation, this should tell re-frame to only process the event after the UI has been updated. It’s hard to say if this makes any difference, but the theory is sound. - Reduce the amount of data returned to the subscription that renders a list of communities. We were returning too much, like all members, chats, token permissions, etc. Other things I fixed or improved along the way: - Because members are now stored as JS, I took the opportunity to fix how members are sorted when they are listed. - Removed a few unused subs. - Configured oops to not throw during development (in production the behavior is to never throw). This means oops is now safe to be used instead of interop that can mysteriously fail in advanced compilation. - Show compressed key instead of public key in member list for the account currently logged in. Technical details The number one reason affecting the freeze after login was coming from converting thousands of members inside communities and also because we were doing it in an inefficient way using clojure.walk/stringify-keys. We shouldn't also transform that much data on the client as the parent issue created by flexsurfer correctly recommends. Ever since PR https://github.com/status-im/status-mobile/pull/20414 was merged, status-go doesn't return members in open channels, which greatly helps, for example, to load the Status community. The problem still exists for communities with token-gated channels with many members. The current code in develop does something quite inefficient: it fetches the communities, then transforms them recursively with js->clj and keywordizes keys, then transforms again all the potentially thousands of member IDs back to strings. This PR changes this. We now shallowly convert a community and ignore members because they can grow too fast. From artificial benchmarks simulating many members in token-gated channels, or communities with thousands of members, the improvement is noticeable. You will only really notice improvements if you have spectated or joined a community with 1000+ members and/or a community with many token-gated channels, each containing perhaps hundreds of members. What's the ideal solution? We should consider removing community members and channel members from the community entity returned by status-go entirely. The members should be a separate resource and paginated so that the client doesn't need to worry about the number of members, for the most part.
This commit is contained in:
parent
0fed8113d1
commit
c1d2d44da4
|
@ -1,8 +1,8 @@
|
|||
(ns legacy.status-im.data-store.communities
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[clojure.walk :as walk]
|
||||
[status-im.constants :as constants]))
|
||||
[status-im.constants :as constants]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn <-revealed-accounts-rpc
|
||||
[accounts]
|
||||
|
@ -22,19 +22,26 @@
|
|||
(reduce #(assoc %1 (key-fn %2) (<-request-to-join-community-rpc %2)) {} requests))
|
||||
|
||||
(defn <-chats-rpc
|
||||
[chats]
|
||||
(reduce-kv (fn [acc k v]
|
||||
(assoc acc
|
||||
(name k)
|
||||
(-> v
|
||||
(assoc :token-gated? (:tokenGated v)
|
||||
:can-post? (:canPost v)
|
||||
:can-view? (:canView v)
|
||||
:hide-if-permissions-not-met? (:hideIfPermissionsNotMet v))
|
||||
(dissoc :canPost :tokenGated :canView :hideIfPermissionsNotMet)
|
||||
(update :members walk/stringify-keys))))
|
||||
{}
|
||||
chats))
|
||||
"This transformation from RPC is optimized differently because there can be
|
||||
thousands of members in all chats and we don't want to transform them from JS
|
||||
to CLJS because they will only be used to list community members or community
|
||||
chat members."
|
||||
[chats-js]
|
||||
(let [chat-key-fn (fn [k]
|
||||
(case k
|
||||
"tokenGated" :token-gated?
|
||||
"canPost" :can-post?
|
||||
"can-view" :can-view?
|
||||
"hideIfPermissionsNotMet" :hide-if-permissions-not-met?
|
||||
(keyword k)))
|
||||
chat-val-fn (fn [k v]
|
||||
(if (= "members" k)
|
||||
v
|
||||
(transforms/js->clj v)))]
|
||||
(transforms/<-js-map
|
||||
chats-js
|
||||
{:val-fn (fn [_ v]
|
||||
(transforms/<-js-map v {:key-fn chat-key-fn :val-fn chat-val-fn}))})))
|
||||
|
||||
(defn <-categories-rpc
|
||||
[categ]
|
||||
|
@ -48,49 +55,59 @@
|
|||
[token-permission]
|
||||
(= (:type token-permission) constants/community-token-permission-become-member))
|
||||
|
||||
(defn- rename-community-key
|
||||
[k]
|
||||
(case k
|
||||
"canRequestAccess" :can-request-access?
|
||||
"canManageUsers" :can-manage-users?
|
||||
"canDeleteMessageForEveryone" :can-delete-message-for-everyone?
|
||||
;; This flag is misleading based on its name alone
|
||||
;; because it should not be used to decide if the user
|
||||
;; is *allowed* to join. Allowance is based on token
|
||||
;; permissions. Still, the flag can be used to know
|
||||
;; whether or not the user will have to wait until an
|
||||
;; admin approves a join request.
|
||||
"canJoin" :can-join?
|
||||
"requestedToJoinAt" :requested-to-join-at
|
||||
"isMember" :is-member?
|
||||
"outroMessage" :outro-message
|
||||
"adminSettings" :admin-settings
|
||||
"tokenPermissions" :token-permissions
|
||||
"communityTokensMetadata" :tokens-metadata
|
||||
"introMessage" :intro-message
|
||||
"muteTill" :muted-till
|
||||
"lastOpenedAt" :last-opened-at
|
||||
"joinedAt" :joined-at
|
||||
(keyword k)))
|
||||
|
||||
(defn <-rpc
|
||||
[c]
|
||||
(-> c
|
||||
(set/rename-keys
|
||||
{:canRequestAccess :can-request-access?
|
||||
:canManageUsers :can-manage-users?
|
||||
:canDeleteMessageForEveryone :can-delete-message-for-everyone?
|
||||
;; This flag is misleading based on its name alone
|
||||
;; because it should not be used to decide if the user
|
||||
;; is *allowed* to join. Allowance is based on token
|
||||
;; permissions. Still, the flag can be used to know
|
||||
;; whether or not the user will have to wait until an
|
||||
;; admin approves a join request.
|
||||
:canJoin :can-join?
|
||||
:requestedToJoinAt :requested-to-join-at
|
||||
:isMember :is-member?
|
||||
:outroMessage :outro-message
|
||||
:adminSettings :admin-settings
|
||||
:tokenPermissions :token-permissions
|
||||
:communityTokensMetadata :tokens-metadata
|
||||
:introMessage :intro-message
|
||||
:muteTill :muted-till
|
||||
:lastOpenedAt :last-opened-at
|
||||
:joinedAt :joined-at})
|
||||
(update :admin-settings
|
||||
set/rename-keys
|
||||
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
|
||||
(update :members walk/stringify-keys)
|
||||
(update :chats <-chats-rpc)
|
||||
(update :token-permissions seq)
|
||||
(update :categories <-categories-rpc)
|
||||
(assoc :role-permissions?
|
||||
(->> c
|
||||
:tokenPermissions
|
||||
vals
|
||||
(some role-permission?)))
|
||||
(assoc :membership-permissions?
|
||||
(->> c
|
||||
:tokenPermissions
|
||||
vals
|
||||
(some membership-permission?)))
|
||||
(assoc :token-images
|
||||
(reduce (fn [acc {sym :symbol image :image}]
|
||||
(assoc acc sym image))
|
||||
{}
|
||||
(:communityTokensMetadata c)))))
|
||||
[c-js]
|
||||
(let [community (transforms/<-js-map
|
||||
c-js
|
||||
{:key-fn rename-community-key
|
||||
:val-fn (fn [k v]
|
||||
(case k
|
||||
"members" v
|
||||
"chats" (<-chats-rpc v)
|
||||
(transforms/js->clj v)))})]
|
||||
(-> community
|
||||
(update :admin-settings
|
||||
set/rename-keys
|
||||
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
|
||||
(update :token-permissions seq)
|
||||
(update :categories <-categories-rpc)
|
||||
(assoc :role-permissions?
|
||||
(->> community
|
||||
:tokenPermissions
|
||||
vals
|
||||
(some role-permission?)))
|
||||
(assoc :membership-permissions?
|
||||
(->> community
|
||||
:tokenPermissions
|
||||
vals
|
||||
(some membership-permission?)))
|
||||
(assoc :token-images
|
||||
(reduce (fn [acc {sym :symbol image :image}]
|
||||
(assoc acc sym image))
|
||||
{}
|
||||
(:communityTokensMetadata community))))))
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
(when (and can-manage-users? (= constants/community-on-request-access (:access permissions)))
|
||||
[requests-to-join community-id])
|
||||
[rn/flat-list
|
||||
{:data (keys sorted-members)
|
||||
{:data sorted-members
|
||||
:render-data {:community-id community-id
|
||||
:my-public-key my-public-key
|
||||
:can-kick-users? (and can-manage-users?
|
||||
|
|
|
@ -332,7 +332,9 @@
|
|||
(defn set-blank-preview-flag
|
||||
[flag]
|
||||
(log/debug "[native-module] set-blank-preview-flag")
|
||||
(.setBlankPreviewFlag ^js (encryption) flag))
|
||||
;; Sometimes the app crashes during logout because `flag` is nil.
|
||||
(when flag
|
||||
(.setBlankPreviewFlag ^js (encryption) flag)))
|
||||
|
||||
(defn get-device-model-info
|
||||
[]
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
(ns status-im.contexts.chat.messenger.messages.link-preview.events
|
||||
(:require [camel-snake-kebab.core :as csk]
|
||||
[status-im.common.json-rpc.events :as json-rpc]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.collection]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
|
@ -35,17 +33,6 @@
|
|||
{:fx [[:dispatch
|
||||
[:profile.settings/profile-update :link-preview-request-enabled (boolean enabled?)]]]}))
|
||||
|
||||
(rf/reg-event-fx :chat.ui/link-preview-whitelist-received
|
||||
(fn [{:keys [db]} [whitelist]]
|
||||
{:db (assoc db :link-previews-whitelist whitelist)}))
|
||||
|
||||
(rf/reg-fx :chat.ui/request-link-preview-whitelist
|
||||
(fn []
|
||||
(json-rpc/call {:method "wakuext_getLinkPreviewWhitelist"
|
||||
:params []
|
||||
:on-success [:chat.ui/link-preview-whitelist-received]
|
||||
:on-error #(log/error "Failed to get link preview whitelist")})))
|
||||
|
||||
(rf/reg-event-fx :chat.ui/enable-link-previews
|
||||
(fn [{{:profile/keys [profile]} :db} [site enabled?]]
|
||||
(let [enabled-sites (if enabled?
|
||||
|
|
|
@ -103,11 +103,11 @@
|
|||
(models.contact/process-js-contacts cofx response-js)
|
||||
|
||||
(seq communities)
|
||||
(let [communities-clj (types/js->clj communities)]
|
||||
(do
|
||||
(js-delete response-js "communities")
|
||||
(rf/merge cofx
|
||||
(process-next response-js sync-handler)
|
||||
(communities/handle-communities communities-clj)))
|
||||
(communities/handle-communities communities)))
|
||||
|
||||
(seq bookmarks)
|
||||
(let [bookmarks-clj (types/js->clj bookmarks)]
|
||||
|
|
|
@ -57,19 +57,24 @@
|
|||
:index index})
|
||||
|
||||
(defn- members
|
||||
[items theme]
|
||||
[rn/section-list
|
||||
{:key-fn :public-key
|
||||
:content-container-style {:padding-bottom 20}
|
||||
:get-item-layout get-item-layout
|
||||
:content-inset-adjustment-behavior :never
|
||||
:sections items
|
||||
:sticky-section-headers-enabled false
|
||||
:render-section-header-fn contact-list/contacts-section-header
|
||||
:render-section-footer-fn footer
|
||||
:render-data {:theme theme}
|
||||
:render-fn contact-item
|
||||
:scroll-event-throttle 32}])
|
||||
[community-id chat-id theme]
|
||||
(let [online-members (rf/sub [:communities/chat-members-sorted community-id chat-id :online])
|
||||
offline-members (rf/sub [:communities/chat-members-sorted community-id chat-id :offline])]
|
||||
[rn/section-list
|
||||
{:key-fn :public-key
|
||||
:content-container-style {:padding-bottom 20}
|
||||
:get-item-layout get-item-layout
|
||||
:content-inset-adjustment-behavior :never
|
||||
:sections [{:title (i18n/label :t/online)
|
||||
:data online-members}
|
||||
{:title (i18n/label :t/offline)
|
||||
:data offline-members}]
|
||||
:sticky-section-headers-enabled false
|
||||
:render-section-header-fn contact-list/contacts-section-header
|
||||
:render-section-footer-fn footer
|
||||
:render-data {:theme theme}
|
||||
:render-fn contact-item
|
||||
:scroll-event-throttle 32}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
|
@ -78,8 +83,6 @@
|
|||
{:keys [description chat-name emoji muted chat-type color]
|
||||
:as chat} (rf/sub [:chats/chat-by-id chat-id])
|
||||
pins-count (rf/sub [:chats/pin-messages-count chat-id])
|
||||
items (rf/sub [:communities/sorted-community-members-section-list
|
||||
community-id chat-id])
|
||||
theme (quo.theme/use-theme)]
|
||||
(rn/use-mount (fn []
|
||||
(rf/dispatch [:pin-message/load-pin-messages chat-id])))
|
||||
|
@ -133,4 +136,4 @@
|
|||
(if muted
|
||||
(home.actions/unmute-chat-action chat-id)
|
||||
(home.actions/mute-chat-action chat-id chat-type muted)))}]}]]]
|
||||
[members items theme]]))
|
||||
[members community-id chat-id theme]]))
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
:as item}]
|
||||
(let [user-selected? (rf/sub [:is-contact-selected? public-key])
|
||||
{:keys [id]} (rf/sub [:get-screen-params])
|
||||
community-members-keys (set (keys (rf/sub [:communities/community-members id])))
|
||||
community-members-keys (set (rf/sub [:communities/community-members id]))
|
||||
community-member? (boolean (community-members-keys public-key))
|
||||
on-toggle (fn []
|
||||
(when-not community-member?
|
||||
|
|
|
@ -39,17 +39,6 @@
|
|||
|
||||
(rf/reg-event-fx :communities/handle-community handle-community)
|
||||
|
||||
(schema/=> handle-community
|
||||
[:=>
|
||||
[:catn
|
||||
[:cofx :schema.re-frame/cofx]
|
||||
[:args
|
||||
[:schema [:catn [:community-js map?]]]]]
|
||||
[:maybe
|
||||
[:map
|
||||
[:db [:map [:communities map?]]]
|
||||
[:fx vector?]]]])
|
||||
|
||||
(rf/defn handle-removed-chats
|
||||
[{:keys [db]} chat-ids]
|
||||
{:db (reduce (fn [db chat-id]
|
||||
|
@ -84,10 +73,9 @@
|
|||
|
||||
(rf/defn handle-communities
|
||||
{:events [:community/fetch-success]}
|
||||
[{:keys [db]} communities]
|
||||
{:fx
|
||||
(->> communities
|
||||
(map #(vector :dispatch [:communities/handle-community %])))})
|
||||
[{:keys [db]} communities-js]
|
||||
{:fx (map (fn [c] [:dispatch [:communities/handle-community c]])
|
||||
communities-js)})
|
||||
|
||||
(rf/reg-event-fx :communities/request-to-join-result
|
||||
(fn [{:keys [db]} [community-id request-id response-js]]
|
||||
|
@ -136,21 +124,30 @@
|
|||
{}
|
||||
categories))}))
|
||||
|
||||
(rf/reg-event-fx :community/fetch-low-priority
|
||||
(fn []
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_checkAndDeletePendingRequestToJoinCommunity"
|
||||
:params []
|
||||
:js-response true
|
||||
:on-success [:sanitize-messages-and-process-response]
|
||||
:on-error #(log/info "failed to fetch communities" %)}
|
||||
{:method "wakuext_collapsedCommunityCategories"
|
||||
:params []
|
||||
:on-success [:communities/fetched-collapsed-categories-success]
|
||||
:on-error #(log/error "failed to fetch collapsed community categories" %)}]]]}))
|
||||
|
||||
(rf/reg-event-fx :community/fetch
|
||||
(fn [_]
|
||||
{:json-rpc/call [{:method "wakuext_serializedCommunities"
|
||||
:params []
|
||||
:on-success #(rf/dispatch [:community/fetch-success %])
|
||||
:on-error #(log/error "failed to fetch communities" %)}
|
||||
{:method "wakuext_checkAndDeletePendingRequestToJoinCommunity"
|
||||
:params []
|
||||
:js-response true
|
||||
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])
|
||||
:on-error #(log/info "failed to fetch communities" %)}
|
||||
{:method "wakuext_collapsedCommunityCategories"
|
||||
:params []
|
||||
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
|
||||
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_serializedCommunities"
|
||||
:params []
|
||||
:on-success [:community/fetch-success]
|
||||
:js-response true
|
||||
:on-error #(log/error "failed to fetch communities" %)}]]
|
||||
;; Dispatch a little after 1000ms because other higher-priority events
|
||||
;; after login are being processed at the 1000ms mark.
|
||||
[:dispatch-later [{:ms 1200 :dispatch [:community/fetch-low-priority]}]]]}))
|
||||
|
||||
(defn update-previous-permission-addresses
|
||||
[{:keys [db]} [community-id]]
|
||||
|
|
|
@ -151,7 +151,7 @@
|
|||
(-> effects :json-rpc/call first (select-keys [:method :params]))))))))
|
||||
|
||||
(deftest handle-community-test
|
||||
(let [community {:id community-id :clock 2}]
|
||||
(let [community #js {:id community-id :clock 2}]
|
||||
(testing "given a unjoined community"
|
||||
(let [effects (events/handle-community {} [community])]
|
||||
(is (match? community-id
|
||||
|
@ -163,7 +163,7 @@
|
|||
(filter some? (:fx effects))))))
|
||||
|
||||
(testing "given a joined community"
|
||||
(let [community (assoc community :joined true)
|
||||
(let [community #js {:id community-id :clock 2 :joined true}
|
||||
effects (events/handle-community {} [community])]
|
||||
(is (match?
|
||||
[[:dispatch
|
||||
|
@ -172,16 +172,19 @@
|
|||
(filter some? (:fx effects))))))
|
||||
|
||||
(testing "given a community with token-permissions-check"
|
||||
(let [community (assoc community :token-permissions-check :fake-token-permissions-check)
|
||||
(let [community #js
|
||||
{:id community-id :clock 2 :token-permissions-check :fake-token-permissions-check}
|
||||
effects (events/handle-community {} [community])]
|
||||
(is (match?
|
||||
[[:dispatch
|
||||
[:communities/check-permissions-to-join-community-with-all-addresses community-id]]]
|
||||
(filter some? (:fx effects))))))
|
||||
|
||||
(testing "given a community with lower clock"
|
||||
(let [effects (events/handle-community {:db {:communities {community-id {:clock 3}}}} [community])]
|
||||
(is (nil? effects))))
|
||||
|
||||
(testing "given a community without clock"
|
||||
(let [community (dissoc community :clock)
|
||||
(let [community #js {:id community-id}
|
||||
effects (events/handle-community {} [community])]
|
||||
(is (nil? effects))))))
|
||||
|
|
|
@ -75,46 +75,51 @@
|
|||
:banner (resources/get-image :discover)
|
||||
:accessibility-label :communities-home-discover-card}})
|
||||
|
||||
(defn on-tab-change
|
||||
[tab]
|
||||
(rf/dispatch [:communities/select-tab tab]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [flat-list-ref (atom nil)
|
||||
set-flat-list-ref #(reset! flat-list-ref %)]
|
||||
(fn []
|
||||
(let [theme (quo.theme/use-theme)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
|
||||
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
|
||||
selected-items (case selected-tab
|
||||
:joined joined
|
||||
:pending pending
|
||||
:opened opened)
|
||||
scroll-shared-value (reanimated/use-shared-value 0)]
|
||||
[:<>
|
||||
(if (empty? selected-items)
|
||||
[common.empty-state/view
|
||||
{:selected-tab selected-tab
|
||||
:tab->content (empty-state-content theme)}]
|
||||
[reanimated/flat-list
|
||||
{:ref set-flat-list-ref
|
||||
:key-fn :id
|
||||
:content-inset-adjustment-behavior :never
|
||||
:header [common.header-spacing/view]
|
||||
:render-fn item-render
|
||||
:style {:margin-top -1}
|
||||
:data selected-items
|
||||
:scroll-event-throttle 8
|
||||
:content-container-style {:padding-bottom
|
||||
jump-to.constants/floating-shell-button-height}
|
||||
:on-scroll #(common.banner/set-scroll-shared-value
|
||||
{:scroll-input (oops/oget
|
||||
%
|
||||
"nativeEvent.contentOffset.y")
|
||||
:shared-value scroll-shared-value})}])
|
||||
[:f> common.banner/animated-banner
|
||||
{:content banner-data
|
||||
:customization-color customization-color
|
||||
:scroll-ref flat-list-ref
|
||||
:tabs tabs-data
|
||||
:selected-tab selected-tab
|
||||
:on-tab-change (fn [tab] (rf/dispatch [:communities/select-tab tab]))
|
||||
:scroll-shared-value scroll-shared-value}]]))))
|
||||
(let [flat-list-ref (rn/use-ref-atom nil)
|
||||
set-flat-list-ref (rn/use-callback #(reset! flat-list-ref %))
|
||||
theme (quo.theme/use-theme)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
|
||||
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
|
||||
selected-items (case selected-tab
|
||||
:joined joined
|
||||
:pending pending
|
||||
:opened opened)
|
||||
scroll-shared-value (reanimated/use-shared-value 0)
|
||||
on-scroll (rn/use-callback
|
||||
(fn [event]
|
||||
(common.banner/set-scroll-shared-value
|
||||
{:scroll-input (oops/oget event
|
||||
"nativeEvent.contentOffset.y")
|
||||
:shared-value scroll-shared-value})))]
|
||||
[:<>
|
||||
(if (empty? selected-items)
|
||||
[common.empty-state/view
|
||||
{:selected-tab selected-tab
|
||||
:tab->content (empty-state-content theme)}]
|
||||
[reanimated/flat-list
|
||||
{:ref set-flat-list-ref
|
||||
:key-fn :id
|
||||
:content-inset-adjustment-behavior :never
|
||||
:header [common.header-spacing/view]
|
||||
:render-fn item-render
|
||||
:style {:margin-top -1}
|
||||
:data selected-items
|
||||
:scroll-event-throttle 8
|
||||
:content-container-style {:padding-bottom
|
||||
jump-to.constants/floating-shell-button-height}
|
||||
:on-scroll on-scroll}])
|
||||
[common.banner/animated-banner
|
||||
{:content banner-data
|
||||
:customization-color customization-color
|
||||
:scroll-ref flat-list-ref
|
||||
:tabs tabs-data
|
||||
:selected-tab selected-tab
|
||||
:on-tab-change on-tab-change
|
||||
:scroll-shared-value scroll-shared-value}]]))
|
||||
|
|
|
@ -57,14 +57,15 @@
|
|||
[{:method "wakuext_startMessenger"
|
||||
:on-success [:profile.login/messenger-started]
|
||||
:on-error #(log/error "failed to start messenger" %)}]]
|
||||
[:dispatch [:universal-links/generate-profile-url]]
|
||||
[:dispatch [:community/fetch]]
|
||||
[:push-notifications/load-preferences]
|
||||
[:profile.config/get-node-config]
|
||||
|
||||
;; Wallet initialization can be delayed a little bit because we
|
||||
;; need to free the queue for heavier events first, such as
|
||||
;; loading chats and communities. This globally helps alleviate
|
||||
;; stuttering immediately after login.
|
||||
[:dispatch-later [{:ms 500 :dispatch [:wallet/initialize]}]]
|
||||
|
||||
[:logs/set-level log-level]
|
||||
[:activity-center.notifications/fetch-pending-contact-requests-fx]
|
||||
[:activity-center/update-seen-state]
|
||||
[:activity-center.notifications/fetch-unread-count]
|
||||
|
||||
;; Immediately try to open last chat. We can't wait until the
|
||||
;; messenger has started and has processed all chats because
|
||||
|
@ -90,24 +91,45 @@
|
|||
;; login phase 2: we want to load and show chats faster, so we split login into 2 phases
|
||||
(rf/reg-event-fx :profile.login/get-chats-callback
|
||||
(fn [{:keys [db]}]
|
||||
(let [{:keys [notifications-enabled? key-uid
|
||||
preview-privacy?]} (:profile/profile db)]
|
||||
(let [{:keys [notifications-enabled? key-uid]} (:profile/profile db)]
|
||||
{:db db
|
||||
:fx [[:effects.profile/enable-local-notifications]
|
||||
[:contacts/initialize-contacts]
|
||||
[:browser/initialize-browser]
|
||||
;; The delay is arbitrary. We just want to give some time for the
|
||||
;; thread to process more important events first, but we can't delay
|
||||
;; too much otherwise the UX may degrade due to stale data.
|
||||
[:dispatch-later [{:ms 1500 :dispatch [:profile.login/non-critical-initialization]}]]
|
||||
[:dispatch [:mobile-network/on-network-status-change]]
|
||||
[:group-chats/get-group-chat-invitations]
|
||||
[:profile.settings/get-profile-picture key-uid]
|
||||
[:profile.settings/blank-preview-flag-changed preview-privacy?]
|
||||
[:chat.ui/request-link-preview-whitelist]
|
||||
[:visibility-status-updates/fetch]
|
||||
[:switcher-cards/fetch]
|
||||
(when (ff/enabled? ::ff/wallet.wallet-connect)
|
||||
[:dispatch [:wallet-connect/init]])
|
||||
(when notifications-enabled?
|
||||
[:effects/push-notifications-enable])]})))
|
||||
|
||||
;; Login phase 3: events at this phase can wait a bit longer to be processed in
|
||||
;; order to leave room for higher-priority or heavy weight events.
|
||||
(rf/reg-event-fx :profile.login/non-critical-initialization
|
||||
(fn [{:keys [db]}]
|
||||
(let [{:keys [preview-privacy?]} (:profile/profile db)]
|
||||
{:fx [[:browser/initialize-browser]
|
||||
[:logging/initialize-web3-client-version]
|
||||
[:group-chats/get-group-chat-invitations]
|
||||
[:profile.settings/blank-preview-flag-changed preview-privacy?]
|
||||
(when (ff/enabled? ::ff/shell.jump-to)
|
||||
[:switcher-cards/fetch])
|
||||
[:visibility-status-updates/fetch]
|
||||
[:dispatch [:universal-links/generate-profile-url]]
|
||||
[:push-notifications/load-preferences]
|
||||
[:profile.config/get-node-config]
|
||||
[:activity-center.notifications/fetch-pending-contact-requests-fx]
|
||||
[:activity-center/update-seen-state]
|
||||
[:activity-center.notifications/fetch-unread-count]
|
||||
[:pairing/get-our-installations]
|
||||
[:json-rpc/call
|
||||
[{:method "admin_nodeInfo"
|
||||
:on-success [:profile.login/node-info-fetched]
|
||||
:on-error #(log/error "node-info: failed error" %)}]]]})))
|
||||
|
||||
(rf/reg-event-fx :profile.login/messenger-started
|
||||
(fn [{:keys [db]} [{:keys [mailservers]}]]
|
||||
(let [new-account? (get db :onboarding/new-account?)]
|
||||
|
@ -119,11 +141,6 @@
|
|||
(rf/dispatch [:chats-list/load-success result])
|
||||
(rf/dispatch [:communities/get-user-requests-to-join])
|
||||
(rf/dispatch [:profile.login/get-chats-callback]))}]
|
||||
[:json-rpc/call
|
||||
[{:method "admin_nodeInfo"
|
||||
:on-success [:profile.login/node-info-fetched]
|
||||
:on-error #(log/error "node-info: failed error" %)}]]
|
||||
[:pairing/get-our-installations]
|
||||
(when-not new-account?
|
||||
[:dispatch [:universal-links/process-stored-event]])]})))
|
||||
|
||||
|
@ -139,11 +156,9 @@
|
|||
(if error
|
||||
{:db (update db :profile/login #(-> % (dissoc :processing) (assoc :error error)))}
|
||||
{:db (dissoc db :profile/login)
|
||||
:fx [[:logging/initialize-web3-client-version]
|
||||
(when (and new-account? (not recovered-account?))
|
||||
[:dispatch [:wallet-legacy/set-initial-blocks-range]])
|
||||
[:dispatch [:ens/update-usernames ensUsernames]]
|
||||
[:dispatch [:wallet/initialize]]
|
||||
:fx [(when (and new-account? (not recovered-account?))
|
||||
[:dispatch-later [{:ms 1000 :dispatch [:wallet-legacy/set-initial-blocks-range]}]])
|
||||
[:dispatch-later [{:ms 2000 :dispatch [:ens/update-usernames ensUsernames]}]]
|
||||
[:dispatch [:profile.login/login-existing-profile settings account]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require
|
||||
["react-native" :refer (DevSettings LogBox NativeModules)]
|
||||
[react-native.platform :as platform]
|
||||
[status-im.setup.oops :as setup.oops]
|
||||
[status-im.setup.schema :as schema]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
|
@ -47,6 +48,7 @@
|
|||
:utils/dispatch-later
|
||||
:json-rpc/call})
|
||||
(when ^:boolean js/goog.DEBUG
|
||||
(setup.oops/setup!)
|
||||
(schema/setup!)
|
||||
(when platform/ios?
|
||||
;; on Android this method doesn't work
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
(ns status-im.setup.oops
|
||||
(:require [oops.config]))
|
||||
|
||||
(defn setup!
|
||||
"Change oops defaults to warn and print instead of throwing exceptions during
|
||||
development."
|
||||
[]
|
||||
(oops.config/update-current-runtime-config!
|
||||
merge
|
||||
{:error-reporting :console
|
||||
:expected-function-value :warn
|
||||
:invalid-selector :warn
|
||||
:missing-object-key :warn
|
||||
:object-is-frozen :warn
|
||||
:object-is-sealed :warn
|
||||
:object-key-not-writable :warn
|
||||
:unexpected-empty-selector :warn
|
||||
:unexpected-object-value :warn
|
||||
:unexpected-punching-selector :warn
|
||||
:unexpected-soft-selector :warn}))
|
|
@ -2,7 +2,8 @@
|
|||
(:require
|
||||
["bignumber.js" :as BigNumber]
|
||||
[matcher-combinators.core :as matcher-combinators]
|
||||
[matcher-combinators.model :as matcher.model]))
|
||||
[matcher-combinators.model :as matcher.model]
|
||||
[status-im.setup.oops :as setup.oops]))
|
||||
|
||||
;; We must implement Matcher in order for tests to work with the `match?`
|
||||
;; directive.
|
||||
|
@ -16,3 +17,5 @@
|
|||
:matcher-combinators.result/value actual}
|
||||
{:matcher-combinators.result/type :mismatch
|
||||
:matcher-combinators.result/value (matcher.model/->Mismatch this actual)})))
|
||||
|
||||
(setup.oops/setup!)
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.communities.utils :as utils]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[status-im.subs.chat.utils :as subs.utils]
|
||||
[status-im.subs.contact.utils :as contact.utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.money :as money]))
|
||||
|
||||
|
@ -69,7 +71,7 @@
|
|||
(fn [[_ community-id]]
|
||||
[(re-frame/subscribe [:communities/community community-id])])
|
||||
(fn [[{:keys [members]}] _]
|
||||
members))
|
||||
(js-keys members)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:communities/community-chat-members
|
||||
|
@ -92,51 +94,80 @@
|
|||
{}
|
||||
public-keys))
|
||||
|
||||
(defn- sort-members-by-name
|
||||
(defn- sort-members-by-name-old
|
||||
[names descending? members]
|
||||
(if descending?
|
||||
(sort-by #(get names (first %)) #(compare %2 %1) members)
|
||||
(sort-by #(get names (first %)) members)))
|
||||
(sort-by #(get names %) #(compare %2 %1) members)
|
||||
(sort-by #(get names %) members)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:communities/sorted-community-members
|
||||
(defn- sort-members-by-name
|
||||
[names members-keys]
|
||||
(let [forced-last-key "zzzzzz"
|
||||
sort-keyfn (fn [k]
|
||||
(if-let [[primary-name secondary-name] (get names k)]
|
||||
(or (some-> primary-name
|
||||
string/lower-case)
|
||||
(some-> secondary-name
|
||||
string/lower-case))
|
||||
;; Sort unknown keys at the end.
|
||||
forced-last-key))]
|
||||
(sort-by sort-keyfn members-keys)))
|
||||
|
||||
;; This implementation is wrong, but since it's only used in a legacy view, we
|
||||
;; can ignore it for now.
|
||||
(re-frame/reg-sub :communities/sorted-community-members
|
||||
(fn [[_ community-id]]
|
||||
(let [profile (re-frame/subscribe [:profile/profile])
|
||||
members (re-frame/subscribe [:communities/community-members community-id])]
|
||||
[profile members]))
|
||||
[(re-frame/subscribe [:profile/profile])
|
||||
(re-frame/subscribe [:communities/community-members community-id])])
|
||||
(fn [[profile members] _]
|
||||
(let [names (keys->names (keys members) profile)]
|
||||
(let [names (keys->names members profile)]
|
||||
(->> members
|
||||
(sort-members-by-name names false)
|
||||
(sort-members-by-name-old names false)
|
||||
(sort-by #(visibility-status-utils/visibility-status-order (get % 0)))))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:communities/sorted-community-members-section-list
|
||||
(re-frame/reg-sub :communities/chat-members
|
||||
(fn [[_ community-id chat-id]]
|
||||
(let [profile (re-frame/subscribe [:profile/profile])
|
||||
members (re-frame/subscribe [:communities/community-chat-members
|
||||
community-id chat-id])
|
||||
visibility-status-updates (re-frame/subscribe
|
||||
[:visibility-status-updates])
|
||||
my-status-update (re-frame/subscribe
|
||||
[:multiaccount/current-user-visibility-status])]
|
||||
[profile members visibility-status-updates my-status-update]))
|
||||
(fn [[profile members visibility-status-updates my-status-update] _]
|
||||
(let [online? (fn [public-key]
|
||||
(let [{visibility-status-type :status-type}
|
||||
(if (or (string/blank? (:public-key profile))
|
||||
(= (:public-key profile) public-key))
|
||||
my-status-update
|
||||
(get visibility-status-updates public-key))]
|
||||
(subs.utils/online? visibility-status-type)))
|
||||
names (keys->names (keys members) profile)]
|
||||
(->> members
|
||||
(sort-members-by-name names true)
|
||||
keys
|
||||
(group-by online?)
|
||||
(map (fn [[k v]]
|
||||
{:title (if k (i18n/label :t/online) (i18n/label :t/offline))
|
||||
:data v}))))))
|
||||
[(re-frame/subscribe [:profile/public-key])
|
||||
(re-frame/subscribe [:communities/community-chat-members community-id chat-id])
|
||||
(re-frame/subscribe [:visibility-status-updates])
|
||||
(re-frame/subscribe [:multiaccount/current-user-visibility-status])])
|
||||
(fn [[profile-pub-key members-js visibility-status-updates my-status-update] [_ _ _ visibility-status]]
|
||||
(let [members-keys (js-keys members-js)
|
||||
online? (fn [public-key]
|
||||
(let [{visibility-status-type :status-type}
|
||||
(if (or (string/blank? profile-pub-key)
|
||||
(= profile-pub-key public-key))
|
||||
my-status-update
|
||||
(get visibility-status-updates public-key))]
|
||||
(subs.utils/online? visibility-status-type)))]
|
||||
(filter (if (= :online visibility-status)
|
||||
online?
|
||||
(complement online?))
|
||||
members-keys))))
|
||||
|
||||
(defn- names-by-key
|
||||
[contacts profile public-keys]
|
||||
(let [names (reduce (fn [acc k]
|
||||
(if-let [contact (get contacts k)]
|
||||
(assoc acc k (contact.utils/contact-two-names contact profile))
|
||||
acc))
|
||||
{}
|
||||
public-keys)]
|
||||
(assoc names
|
||||
(:public-key profile)
|
||||
[(profile.utils/displayed-name profile) nil])))
|
||||
|
||||
;; This is a potentially expensive subscription because we don't control how
|
||||
;; many members and contacts exist in the app-db. Future improvements include
|
||||
;; removing members from the payload and paginating them from status-go.
|
||||
(re-frame/reg-sub :communities/chat-members-sorted
|
||||
(fn [[_ community-id chat-id visibility-status]]
|
||||
[(re-frame/subscribe [:profile/profile])
|
||||
(re-frame/subscribe [:contacts/contacts-raw])
|
||||
(re-frame/subscribe [:communities/chat-members community-id chat-id visibility-status])])
|
||||
(fn [[profile contacts ^js members-keys]]
|
||||
(sort-members-by-name (names-by-key contacts profile members-keys)
|
||||
members-keys)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:communities/featured-contract-communities
|
||||
|
@ -177,6 +208,13 @@
|
|||
(if (or (empty? @memo-communities-stack-items) (= view-id :communities-stack))
|
||||
(let [grouped-communities (->> communities
|
||||
vals
|
||||
;; Remove data that can grow fast or is
|
||||
;; reliably not needed to list communities.
|
||||
;; We could use an allowlist of keys for
|
||||
;; optimal performance of this sub, but
|
||||
;; that's harder to maintain in case we miss
|
||||
;; any key.
|
||||
(map #(dissoc % :members :chats :token-permissions :tokens-metadata))
|
||||
(group-by #(group-communities-by-status requests %))
|
||||
merge-opened-communities
|
||||
(map (fn [[k v]]
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
[utils.re-frame :as rf]))
|
||||
|
||||
(def community-id "0x02b5bdaf5a25fcfe2ee14c501fab1836b8de57f61621080c3d52073d16de0d98d6")
|
||||
(def channel-id "0x1-channel-id")
|
||||
(def chat-id (str community-id channel-id))
|
||||
|
||||
(h/deftest-sub :communities
|
||||
[sub-name]
|
||||
|
@ -457,65 +459,117 @@
|
|||
(match? []
|
||||
(rf/sub [sub-name community-id]))))))
|
||||
|
||||
(h/deftest-sub :communities/sorted-community-members-section-list
|
||||
(h/deftest-sub :communities/chat-members-sorted
|
||||
[sub-name]
|
||||
(testing "returns sorted community members per online status"
|
||||
(let [token-image-eth "data:image/jpeg;base64,/9j/2w"
|
||||
channel-id-1 "89f98a1e-6776-4e5f-8626-8ab9f855253f"
|
||||
channel-id-2 "a076358e-4638-470e-a3fb-584d0a542ce6"
|
||||
chat-id-1 (str community-id channel-id-1)
|
||||
chat-id-2 (str community-id channel-id-2)
|
||||
community {:id community-id
|
||||
:permissions {:access 3}
|
||||
:token-images {"ETH" token-image-eth}
|
||||
:name "Community super name"
|
||||
:chats {channel-id-1
|
||||
{:description "x"
|
||||
:emoji "🎲"
|
||||
:permissions {:access 1}
|
||||
:color "#88B0FF"
|
||||
:name "random"
|
||||
:categoryID "0c3c64e7-d56e-439b-a3fb-a946d83cb056"
|
||||
:id channel-id-1
|
||||
:position 4
|
||||
:can-post? false
|
||||
:members nil}
|
||||
channel-id-2
|
||||
{:description "General channel for the community"
|
||||
:emoji "🥔"
|
||||
:permissions {:access 1}
|
||||
:color "#4360DF"
|
||||
:name "general"
|
||||
:categoryID "0c3c64e7-d56e-439b-a3fb-a946d83cb056"
|
||||
:id channel-id-2
|
||||
:position 0
|
||||
:token-gated? true
|
||||
:can-post? false
|
||||
:members {"0x01" {"roles" [1]}
|
||||
"0x02" {"roles" [1]}
|
||||
"0x05" {"roles" [1]}}}}
|
||||
:members {"0x01" {"roles" [1]}
|
||||
"0x02" {"roles" [1]}
|
||||
"0x03" {"roles" [1]}
|
||||
"0x04" {"roles" [1]}}
|
||||
:can-request-access? false
|
||||
:outroMessage "bla"
|
||||
:verified false}]
|
||||
(let [token-image-eth "data:image/jpeg;base64,/9j/2w"
|
||||
channel-id-1 "89f98a1e-6776-4e5f-8626-8ab9f855253f"
|
||||
channel-id-2 "a076358e-4638-470e-a3fb-584d0a542ce6"
|
||||
chat-id-2 (str community-id channel-id-2)
|
||||
|
||||
member-id-1 "0x01"
|
||||
member-id-2 "0x02"
|
||||
|
||||
visibility-status-updates
|
||||
{member-id-1 {:status-type constants/visibility-status-always-online}
|
||||
member-id-2 {:status-type constants/visibility-status-always-online}}
|
||||
|
||||
contacts
|
||||
{member-id-1 {:display-name "John Marston"}
|
||||
member-id-2 {:display-name "Arthur Morgan"}}
|
||||
|
||||
community {:id community-id
|
||||
:permissions {:access 3}
|
||||
:token-images {"ETH" token-image-eth}
|
||||
:name "Community super name"
|
||||
:chats {channel-id-1
|
||||
{:description "x"
|
||||
:emoji "🎲"
|
||||
:permissions {:access 1}
|
||||
:color "#88B0FF"
|
||||
:name "random"
|
||||
:categoryID "0c3c64e7-d56e-439b-a3fb-a946d83cb056"
|
||||
:id channel-id-1
|
||||
:position 4
|
||||
:can-post? false
|
||||
:members nil}
|
||||
channel-id-2
|
||||
{:description "General channel for the community"
|
||||
:emoji "🥔"
|
||||
:permissions {:access 1}
|
||||
:color "#4360DF"
|
||||
:name "general"
|
||||
:categoryID "0c3c64e7-d56e-439b-a3fb-a946d83cb056"
|
||||
:id channel-id-2
|
||||
:position 0
|
||||
:token-gated? true
|
||||
:can-post? false
|
||||
:members (clj->js {member-id-1 {"roles" [1]}
|
||||
member-id-2 {"roles" [1]}
|
||||
"0x05" {"roles" [1]}})}}
|
||||
:members (js->clj {member-id-1 {"roles" [1]}
|
||||
member-id-2 {"roles" [1]}
|
||||
"0x03" {"roles" [1]}
|
||||
"0x04" {"roles" [1]}})}]
|
||||
(testing "returns sorted community members who are online"
|
||||
(swap! rf-db/app-db assoc :contacts/contacts contacts)
|
||||
(swap! rf-db/app-db assoc-in [:communities community-id] community)
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc :visibility-status-updates visibility-status-updates)
|
||||
(is (= [member-id-2 member-id-1]
|
||||
(rf/sub [sub-name community-id chat-id-2 :online]))))
|
||||
|
||||
(testing "returns sorted community members per offline status"
|
||||
(swap! rf-db/app-db assoc-in [:communities community-id] community)
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc :visibility-status-updates visibility-status-updates)
|
||||
(is (= ["0x05"] (rf/sub [sub-name community-id chat-id-2 :offline]))))))
|
||||
|
||||
(h/deftest-sub :communities/chat-members
|
||||
[sub-name]
|
||||
(let [member-1-id "0x1-member"
|
||||
member-2-id "0x2-member"
|
||||
|
||||
visibility-status-updates
|
||||
{member-1-id {:status-type constants/visibility-status-always-online}
|
||||
member-2-id {:status-type constants/visibility-status-always-online}}
|
||||
|
||||
communities
|
||||
{community-id {:id community-id
|
||||
:chats {channel-id {:token-gated? false
|
||||
:members (clj->js {member-2-id {}})}}
|
||||
:members (clj->js {member-1-id {}
|
||||
member-2-id {}})}}]
|
||||
(testing "members from non token-gated channels and online"
|
||||
(swap! rf-db/app-db assoc :visibility-status-updates visibility-status-updates)
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc :communities communities)
|
||||
|
||||
;; When channel is not token-gated, all community members are considered.
|
||||
(is (= [member-1-id member-2-id]
|
||||
(rf/sub [sub-name community-id chat-id :online]))))
|
||||
|
||||
(testing "members from token-gated channels and online"
|
||||
(swap! rf-db/app-db assoc :visibility-status-updates visibility-status-updates)
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc
|
||||
:visibility-status-updates
|
||||
{"0x01" {:status-type constants/visibility-status-always-online}
|
||||
"0x02" {:status-type constants/visibility-status-always-online}})
|
||||
(testing "a non-token gated community should look at all members of a community"
|
||||
(is (= [{:title (i18n/label :t/online)
|
||||
:data ["0x01" "0x02"]}
|
||||
{:title (i18n/label :t/offline)
|
||||
:data ["0x03" "0x04"]}]
|
||||
(rf/sub [sub-name community-id chat-id-1]))))
|
||||
(testing "a token gated community should use the members option in the channel"
|
||||
(is (= [{:title (i18n/label :t/online)
|
||||
:data ["0x01" "0x02"]}
|
||||
{:title (i18n/label :t/offline)
|
||||
:data ["0x05"]}]
|
||||
(rf/sub [sub-name community-id chat-id-2])))))))
|
||||
:communities
|
||||
(assoc-in communities [community-id :chats channel-id :token-gated?] true))
|
||||
|
||||
;; When channel is token-gated, only its members are considered.
|
||||
(is (= [member-2-id]
|
||||
(rf/sub [sub-name community-id chat-id :online]))))
|
||||
|
||||
(testing "members from token-gated channels and offline"
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc
|
||||
:communities
|
||||
(assoc-in communities [community-id :chats channel-id :token-gated?] true))
|
||||
|
||||
(is (= [member-2-id] (rf/sub [sub-name community-id chat-id :offline]))))
|
||||
|
||||
(testing "members from non token-gated channels and offline"
|
||||
(swap! rf-db/app-db assoc :profile/profile profile-test/sample-profile)
|
||||
(swap! rf-db/app-db assoc :communities communities)
|
||||
|
||||
(is (= [member-1-id member-2-id]
|
||||
(rf/sub [sub-name community-id chat-id :offline]))))))
|
||||
|
|
|
@ -2,29 +2,15 @@
|
|||
(:require
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[legacy.status-im.ui.screens.profile.visibility-status.utils :as visibility-status-utils]
|
||||
[quo.theme]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[status-im.subs.chat.utils :as chat.utils]
|
||||
[status-im.subs.contact.utils :as contact.utils]
|
||||
[utils.address :as address]
|
||||
[utils.collection]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn query-chat-contacts
|
||||
[{:keys [contacts]} all-contacts query-fn]
|
||||
(let [participant-set (into #{} (filter identity) contacts)]
|
||||
(query-fn (comp participant-set :public-key) (vals all-contacts))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::query-current-chat-contacts
|
||||
:<- [:chats/current-chat]
|
||||
:<- [:contacts/contacts]
|
||||
(fn [[chat contacts] [_ query-fn]]
|
||||
(query-chat-contacts chat contacts query-fn)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:multiaccount/profile-pictures-show-to
|
||||
:<- [:profile/profile]
|
||||
|
@ -124,15 +110,6 @@
|
|||
sort
|
||||
vals)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/sorted-contacts
|
||||
:<- [:contacts/active]
|
||||
(fn [active-contacts]
|
||||
(->> active-contacts
|
||||
(sort-by :primary-name)
|
||||
(sort-by
|
||||
#(visibility-status-utils/visibility-status-order (:public-key %))))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/sorted-and-grouped-by-first-letter
|
||||
:<- [:contacts/active]
|
||||
|
@ -150,12 +127,6 @@
|
|||
{:title title
|
||||
:data data})))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/active-count
|
||||
:<- [:contacts/active]
|
||||
(fn [active-contacts]
|
||||
(count active-contacts)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/blocked
|
||||
:<- [:contacts/contacts]
|
||||
|
@ -171,12 +142,6 @@
|
|||
(fn [contacts]
|
||||
(into #{} (map :public-key contacts))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/blocked-count
|
||||
:<- [:contacts/blocked]
|
||||
(fn [blocked-contacts]
|
||||
(count blocked-contacts)))
|
||||
|
||||
(defn public-key-and-ens-name->new-contact
|
||||
[public-key ens-name]
|
||||
(let [contact {:public-key public-key}]
|
||||
|
@ -226,24 +191,8 @@
|
|||
(fn [[_ contact-identity] _]
|
||||
[(re-frame/subscribe [:contacts/contact-by-identity contact-identity])
|
||||
(re-frame/subscribe [:profile/profile])])
|
||||
(fn [[{:keys [primary-name] :as contact}
|
||||
{:keys [public-key preferred-name display-name name]}]
|
||||
[_ contact-identity]]
|
||||
[(if (= public-key contact-identity)
|
||||
(cond
|
||||
(not (string/blank? preferred-name)) preferred-name
|
||||
(not (string/blank? display-name)) display-name
|
||||
(not (string/blank? primary-name)) primary-name
|
||||
(not (string/blank? name)) name
|
||||
:else public-key)
|
||||
(profile.utils/displayed-name contact))
|
||||
(:secondary-name contact)]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/all-contacts-not-in-current-chat
|
||||
:<- [::query-current-chat-contacts remove]
|
||||
(fn [contacts]
|
||||
(filter :added? contacts)))
|
||||
(fn [[contact profile] [_ _]]
|
||||
(contact.utils/contact-two-names contact profile)))
|
||||
|
||||
(defn get-all-contacts-in-group-chat
|
||||
[members admins contacts {:keys [public-key preferred-name name display-name] :as current-account}]
|
||||
|
@ -300,8 +249,10 @@
|
|||
:<- [:multiaccount/contact]
|
||||
(fn [[contacts multiaccount] [_ address]]
|
||||
(if (address/address= address (:public-key multiaccount))
|
||||
multiaccount
|
||||
(find-contact-by-address contacts address))))
|
||||
(merge (contact.utils/build-contact-from-public-key address)
|
||||
multiaccount)
|
||||
(or (find-contact-by-address contacts address)
|
||||
(contact.utils/build-contact-from-public-key address)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/contact-customization-color-by-address
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
(ns status-im.subs.contact.utils
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[status-im.common.pixel-ratio :as pixel-ratio]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[utils.address :as address]))
|
||||
|
||||
(defn replace-contact-image-uri
|
||||
|
@ -44,11 +46,29 @@
|
|||
|
||||
(assoc contact :images images)))
|
||||
|
||||
|
||||
(defn build-contact-from-public-key
|
||||
(defn- build-contact-from-public-key*
|
||||
[public-key]
|
||||
(when public-key
|
||||
(let [compressed-key (native-module/serialize-legacy-key public-key)]
|
||||
{:public-key public-key
|
||||
:compressed-key compressed-key
|
||||
:primary-name (address/get-shortened-compressed-key (or compressed-key public-key))})))
|
||||
|
||||
(def build-contact-from-public-key
|
||||
"The result of this function is stable because it relies exclusively on the
|
||||
public key, but it's not cheap to be performed hundreds of times in a row,
|
||||
such as when displaying a long list of channel members."
|
||||
(memoize build-contact-from-public-key*))
|
||||
|
||||
(defn contact-two-names
|
||||
[{:keys [primary-name] :as contact}
|
||||
{:keys [public-key preferred-name display-name name]}]
|
||||
[(if (= public-key (:public-key contact))
|
||||
(cond
|
||||
(not (string/blank? preferred-name)) preferred-name
|
||||
(not (string/blank? display-name)) display-name
|
||||
(not (string/blank? primary-name)) primary-name
|
||||
(not (string/blank? name)) name
|
||||
:else public-key)
|
||||
(profile.utils/displayed-name contact))
|
||||
(:secondary-name contact)])
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
:preferred-name "Preferred Name"}
|
||||
|
||||
:contacts/contacts
|
||||
{profile-key {:primary-name "Primary Name"}
|
||||
{profile-key {:primary-name "Primary Name" :public-key profile-key}
|
||||
"contact-key" {:secondary-name "Secondary Name"}})
|
||||
|
||||
(is (= ["Preferred Name" nil] (rf/sub [sub-name profile-key])))
|
||||
|
|
|
@ -15,6 +15,34 @@
|
|||
|
||||
(defn clj->json [data] (clj->pretty-json data 0))
|
||||
|
||||
(defn <-js-map
|
||||
"Shallowly transforms JS Object keys/values with `key-fn`/`val-fn`.
|
||||
|
||||
Returns nil if `m` is not an instance of `js/Object`.
|
||||
|
||||
Implementation taken from `js->clj`, but with the ability to customize how
|
||||
keys and/or values are transformed in one loop.
|
||||
|
||||
This function is useful when you don't want to recursively apply the same
|
||||
transformation to keys/values. For example, many maps in the app-db are
|
||||
indexed by ID, like `community.members`. If we convert the entire community
|
||||
with (js->clj m :keywordize-keys true), then IDs will be converted to
|
||||
keywords, but we want them as strings. Instead of transforming to keywords and
|
||||
then transforming back to strings, it's better to not transform them at all.
|
||||
"
|
||||
([^js m]
|
||||
(<-js-map m nil))
|
||||
([^js m {:keys [key-fn val-fn]}]
|
||||
(when (identical? (type m) js/Object)
|
||||
(persistent!
|
||||
(reduce (fn [r k]
|
||||
(let [v (oops/oget+ m k)
|
||||
new-key (if key-fn (key-fn k v) k)
|
||||
new-val (if val-fn (val-fn k v) v)]
|
||||
(assoc! r new-key new-val)))
|
||||
(transient {})
|
||||
(js-keys m))))))
|
||||
|
||||
(defn js-stringify
|
||||
[js-object spaces]
|
||||
(.stringify js/JSON js-object nil spaces))
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
(ns utils.transforms-test
|
||||
(:require
|
||||
[cljs.test :refer [are deftest is testing]]
|
||||
[utils.transforms :as sut]))
|
||||
|
||||
(defn equals-as-json
|
||||
[m1 m2]
|
||||
(= (js/JSON.stringify (clj->js m1))
|
||||
(js/JSON.stringify (clj->js m2))))
|
||||
|
||||
(deftest <-js-map-test
|
||||
(testing "without transforming keys/values"
|
||||
(are [expected m]
|
||||
(is (equals-as-json expected (sut/<-js-map m)))
|
||||
nil nil
|
||||
nil #js []
|
||||
#js {} #js {}
|
||||
#js {"a" 1 "b" 2} #js {"a" 1 "b" 2}))
|
||||
|
||||
(testing "with key/value transformation"
|
||||
(is (equals-as-json {"aa" [1] "bb" [2]}
|
||||
(sut/<-js-map #js {"a" 1 "b" 2}
|
||||
{:key-fn (fn [k] (str k k))
|
||||
:val-fn (fn [_ v] (vector v))}))))
|
||||
|
||||
(testing "it is non-recursive"
|
||||
(is (equals-as-json {:a 1 :b #js {"c" 3}}
|
||||
(sut/<-js-map #js {"a" 1 "b" #js {"c" 3}}
|
||||
{:key-fn (fn [k] (keyword k))}))))
|
||||
|
||||
(testing "value transformation based on the key"
|
||||
(is (equals-as-json {"a" 1 "b" "banana"}
|
||||
(sut/<-js-map #js {"a" 1 "b" 2}
|
||||
{:val-fn (fn [k v]
|
||||
(if (= "b" k)
|
||||
"banana"
|
||||
v))})))))
|
Loading…
Reference in New Issue