chat list optimization
This commit is contained in:
parent
db848a1f19
commit
073371c4ed
|
@ -45,14 +45,16 @@
|
|||
;; i18n ignores nil value, leading to misleading messages
|
||||
(into {} (for [[k v] options] [k (or v default-option-value)])))
|
||||
|
||||
(defn label
|
||||
([path] (label path {}))
|
||||
(defn label-fn
|
||||
([path] (label-fn path {}))
|
||||
([path options]
|
||||
(if (exists? (.t i18n))
|
||||
(let [options (update options :amount label-number)]
|
||||
(.t i18n (name path) (clj->js (label-options options))))
|
||||
(name path))))
|
||||
|
||||
(def label (memoize label-fn))
|
||||
|
||||
(defn label-pluralize [count path & options]
|
||||
(if (exists? (.t i18n))
|
||||
(.p i18n count (name path) (clj->js options))
|
||||
|
|
|
@ -10,10 +10,9 @@
|
|||
|
||||
;;TODO REWORK THIS NAMESPACE
|
||||
|
||||
(defn default-chat-icon [name styles]
|
||||
(when-not (string/blank? name)
|
||||
[react/view (:default-chat-icon styles)
|
||||
[react/text {:style (:default-chat-icon-text styles)}
|
||||
(def get-name-first-char
|
||||
(memoize
|
||||
(fn [name]
|
||||
;; TODO: for now we check if the first letter is a #
|
||||
;; which means it is most likely a public chat and
|
||||
;; use the second letter if that is the case
|
||||
|
@ -22,7 +21,13 @@
|
|||
(string/capitalize (if (and (= "#" (first name))
|
||||
(< 1 (count name)))
|
||||
(second name)
|
||||
(first name)))]]))
|
||||
(first name))))))
|
||||
|
||||
(defn default-chat-icon [name styles]
|
||||
(when-not (string/blank? name)
|
||||
[react/view (:default-chat-icon styles)
|
||||
[react/text {:style (:default-chat-icon-text styles)}
|
||||
(get-name-first-char name)]]))
|
||||
|
||||
(defn chat-icon-view
|
||||
[chat-id group-chat name styles]
|
||||
|
|
|
@ -26,19 +26,13 @@
|
|||
:else
|
||||
colors/black))
|
||||
|
||||
(defn icon
|
||||
([name] (icon name nil))
|
||||
(defn memo-icon-fn
|
||||
([name] (memo-icon-fn name nil))
|
||||
([name {:keys [color resize-mode container-style
|
||||
accessibility-label width height]
|
||||
:or {accessibility-label :icon}}]
|
||||
^{:key name}
|
||||
[react/view
|
||||
{:style (or
|
||||
container-style
|
||||
{:width (or width 24)
|
||||
:height (or height 24)})
|
||||
:accessibility-label accessibility-label}
|
||||
[react/image {:style (cond-> {:width (or width 24)
|
||||
[react/image {:style (merge (cond-> {:width (or width 24)
|
||||
:height (or height 24)}
|
||||
|
||||
resize-mode
|
||||
|
@ -46,7 +40,11 @@
|
|||
|
||||
:always
|
||||
(assoc :tint-color (match-color color)))
|
||||
:source (icon-source name)}]]))
|
||||
container-style)
|
||||
:accessibility-label accessibility-label
|
||||
:source (icon-source name)}]))
|
||||
|
||||
(def icon (memoize memo-icon-fn))
|
||||
|
||||
(defn tiny-icon
|
||||
([name] (tiny-icon name {}))
|
||||
|
|
|
@ -62,30 +62,41 @@
|
|||
[react/view {:style (merge style styles/item-checkbox)}
|
||||
[radio/radio (:checked? props)]])])
|
||||
|
||||
(defn- wrap-render-fn [f render-data]
|
||||
(def memo-wrap-render-fn
|
||||
(memoize
|
||||
(fn [f render-data]
|
||||
(fn [^js data]
|
||||
(reagent/as-element [f (.-item data) (.-index data) (.-separators data) render-data])))
|
||||
|
||||
(defn- wrap-key-fn [f]
|
||||
(fn [data index]
|
||||
{:post [(some? %)]}
|
||||
(f data index)))
|
||||
(reagent/as-element [f (.-item data) (.-index data) (.-separators data) render-data])))))
|
||||
|
||||
(def base-separator [react/view styles/base-separator])
|
||||
|
||||
(def default-separator [react/view styles/separator])
|
||||
|
||||
(def memo-separator-fn
|
||||
(memoize
|
||||
(fn [separator default-separator?]
|
||||
(reagent/as-element (or separator (when (and platform/ios? default-separator?) default-separator))))))
|
||||
|
||||
(def memo-as-element
|
||||
(memoize
|
||||
(fn [element]
|
||||
(reagent/as-element element))))
|
||||
|
||||
(def memo-wrap-key-fn
|
||||
(memoize
|
||||
(fn [f]
|
||||
(fn [data index]
|
||||
{:post [(some? %)]}
|
||||
(f data index)))))
|
||||
|
||||
(defn- base-list-props
|
||||
[{:keys [key-fn render-fn empty-component header footer separator default-separator? render-data]}]
|
||||
(let [separator (or separator (when (and platform/ios? default-separator?) default-separator))]
|
||||
(merge (when key-fn {:keyExtractor (wrap-key-fn key-fn)})
|
||||
(when render-fn {:renderItem (wrap-render-fn render-fn render-data)})
|
||||
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))})
|
||||
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))})
|
||||
;; header and footer not wrapped in anonymous function to prevent re-creation on every re-render
|
||||
;; More details can be found here - https://github.com/facebook/react-native/issues/13602#issuecomment-300608431
|
||||
(when header {:ListHeaderComponent (reagent/as-element header)})
|
||||
(when footer {:ListFooterComponent (reagent/as-element footer)}))))
|
||||
(merge (when key-fn {:keyExtractor (memo-wrap-key-fn key-fn)})
|
||||
(when render-fn {:renderItem (memo-wrap-render-fn render-fn render-data)})
|
||||
(when separator {:ItemSeparatorComponent (memo-separator-fn separator default-separator?)})
|
||||
(when empty-component {:ListEmptyComponent (memo-as-element empty-component)})
|
||||
(when header {:ListHeaderComponent (memo-as-element header)})
|
||||
(when footer {:ListFooterComponent (memo-as-element footer)})))
|
||||
|
||||
(defn flat-list
|
||||
"A wrapper for FlatList.
|
||||
|
@ -115,7 +126,7 @@
|
|||
(defn- wrap-per-section-render-fn [props]
|
||||
(update
|
||||
(if-let [f (:render-fn props)]
|
||||
(assoc (dissoc props :render-fn :render-data) :renderItem (wrap-render-fn f (:render-data props)))
|
||||
(assoc (dissoc props :render-fn :render-data) :renderItem (memo-wrap-render-fn f (:render-data props)))
|
||||
props)
|
||||
:data to-array))
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.utils.image :as utils.image]))
|
||||
|
||||
(defn photo [photo-path {:keys [size accessibility-label]}]
|
||||
(def memo-photo-rend
|
||||
(memoize
|
||||
(fn [photo-path size accessibility-label]
|
||||
(let [identicon? (when photo-path (profile.db/base64-png? photo-path))]
|
||||
[react/view {:style (style/photo-container size)}
|
||||
[react/image {:source (utils.image/source photo-path)
|
||||
|
@ -14,7 +16,10 @@
|
|||
:resize-mode :cover
|
||||
:accessibility-label (or accessibility-label :chat-icon)}]
|
||||
(when identicon?
|
||||
[react/view {:style (style/photo-border size)}])]))
|
||||
[react/view {:style (style/photo-border size)}])]))))
|
||||
|
||||
(defn photo [photo-path {:keys [size accessibility-label]}]
|
||||
[memo-photo-rend photo-path size accessibility-label])
|
||||
|
||||
;; We optionally pass identicon for perfomance reason, so it does not have to be calculated for each message
|
||||
(defn member-photo [pub-key identicon]
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(re-frame/dispatch event))
|
||||
|
||||
(defn one-to-one-chat-accents [{:keys [chat-id]}]
|
||||
(defn one-to-one-chat-accents [chat-id]
|
||||
(let [photo @(re-frame/subscribe [:chats/photo-path chat-id])
|
||||
contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity chat-id])]
|
||||
[react/view
|
||||
|
@ -45,7 +45,7 @@
|
|||
:icon :main-icons/delete
|
||||
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
|
||||
|
||||
(defn public-chat-accents [{:keys [chat-id]}]
|
||||
(defn public-chat-accents [chat-id]
|
||||
(let [link (universal-links/generate-link :public-chat :external chat-id)
|
||||
message (i18n/label :t/share-public-chat-text {:link link})]
|
||||
[react/view
|
||||
|
@ -147,13 +147,13 @@
|
|||
:icon :main-icons/delete
|
||||
:on-press #(hide-sheet-and-dispatch [:group-chats.ui/remove-chat-confirmed chat-id])}])]))))
|
||||
|
||||
(defn actions [{:keys [chat-type]
|
||||
(defn actions [{:keys [chat-type chat-id]
|
||||
:as current-chat}]
|
||||
(cond
|
||||
(#{constants/public-chat-type
|
||||
constants/profile-chat-type
|
||||
constants/timeline-chat-type} chat-type)
|
||||
[public-chat-accents current-chat]
|
||||
[public-chat-accents chat-id]
|
||||
|
||||
(= chat-type constants/community-chat-type)
|
||||
[community-chat-accents current-chat]
|
||||
|
@ -161,11 +161,14 @@
|
|||
(= chat-type constants/private-group-chat-type)
|
||||
[group-chat-accents current-chat]
|
||||
|
||||
:else [one-to-one-chat-accents current-chat]))
|
||||
:else [one-to-one-chat-accents chat-id]))
|
||||
|
||||
(defn current-chat-actions []
|
||||
[actions @(re-frame/subscribe [:chats/current-chat])])
|
||||
|
||||
(defn chat-actions [chat-id]
|
||||
[actions @(re-frame/subscribe [:chat-by-id chat-id])])
|
||||
|
||||
(defn options [chat-id message-id]
|
||||
(fn []
|
||||
[react/view
|
||||
|
|
|
@ -330,6 +330,9 @@
|
|||
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
|
||||
set-active-panel (get-set-active-panel active-panel)
|
||||
on-close #(set-active-panel nil)]
|
||||
(reagent/create-class
|
||||
{:component-will-unmount #(re-frame/dispatch-sync [:close-chat])
|
||||
:reagent-render
|
||||
(fn []
|
||||
(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
|
||||
|
@ -364,4 +367,4 @@
|
|||
:active-panel @active-panel
|
||||
:set-active-panel set-active-panel
|
||||
:text-input-ref text-input-ref}]
|
||||
[bottom-sheet @active-panel]])]))))
|
||||
[bottom-sheet @active-panel]])]))})))
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.communities.icon :as communities.icon]))
|
||||
[status-im.ui.screens.communities.icon :as communities.icon]
|
||||
[quo.design-system.colors :as quo.colors]))
|
||||
|
||||
(defn hide-sheet-and-dispatch [event]
|
||||
(>evt [:bottom-sheet/hide])
|
||||
|
@ -30,11 +31,20 @@
|
|||
:accessibility-label :unviewed-messages-public}]))
|
||||
|
||||
(defn community-home-list-item [{:keys [id name last?] :as community}]
|
||||
[react/view
|
||||
[quo/list-item
|
||||
{:icon [communities.icon/community-icon community]
|
||||
:title [react/view {:flex-direction :row
|
||||
[react/touchable-opacity {:style (merge {:height 64}
|
||||
(when last?
|
||||
{:border-bottom-color (quo.colors/get-color :ui-01)
|
||||
:border-bottom-width 1}))
|
||||
:on-press (fn [id]
|
||||
(>evt [:dismiss-keyboard])
|
||||
(>evt [:navigate-to :community {:community-id id}]))}
|
||||
[:<>
|
||||
[react/view {:top 12 :left 16 :position :absolute}
|
||||
[communities.icon/community-icon community]]
|
||||
[react/view {:style {:margin-left 72
|
||||
:flex-direction :row
|
||||
:flex 1}
|
||||
:accessibility-label :chat-name-text}
|
||||
[react/view {:flex-direction :row
|
||||
:flex 1
|
||||
:padding-right 16
|
||||
|
@ -49,17 +59,7 @@
|
|||
:flex 1
|
||||
:justify-content :flex-end
|
||||
:align-items :center}
|
||||
[community-unviewed-count id]]]
|
||||
:title-accessibility-label :chat-name-text
|
||||
:on-press #(do
|
||||
(>evt [:dismiss-keyboard])
|
||||
(>evt [:navigate-to :community {:community-id id}]))}]
|
||||
;; TODO: actions
|
||||
;; :on-long-press #(>evt [:bottom-sheet/show-sheet
|
||||
;; nil])
|
||||
|
||||
(when last?
|
||||
[quo/separator])])
|
||||
[community-unviewed-count id]]]]])
|
||||
|
||||
(defn community-list-item [{:keys [id permissions members name description] :as community}]
|
||||
(let [members-count (count members)
|
||||
|
@ -103,13 +103,6 @@
|
|||
:icon :main-icons/add
|
||||
:on-press #(hide-sheet-and-dispatch [::communities/open-create-community])}]])
|
||||
|
||||
(defn communities-home-list [communities]
|
||||
[list/flat-list
|
||||
{:key-fn :id
|
||||
:keyboard-should-persist-taps :always
|
||||
:data communities
|
||||
:render-fn community-home-list-item}])
|
||||
|
||||
(defn communities-list [communities]
|
||||
[list/section-list
|
||||
{:content-container-style {:padding-vertical 8}
|
||||
|
|
|
@ -21,7 +21,10 @@
|
|||
:text-align :right
|
||||
:letter-spacing 0.4
|
||||
:align-items :center
|
||||
:line-height 12})
|
||||
:line-height 12
|
||||
:position :absolute
|
||||
:top 10
|
||||
:right 16})
|
||||
|
||||
(defn chat-tooltip []
|
||||
{:align-items :center
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
|
||||
(defonce search-active? (reagent/atom false))
|
||||
|
||||
(defn search-input-wrapper [search-filter chats]
|
||||
(defn search-input-wrapper [search-filter chats-empty]
|
||||
[react/view {:padding-horizontal 16
|
||||
:padding-vertical 10}
|
||||
[search-input/search-input
|
||||
|
@ -109,7 +109,7 @@
|
|||
:search-filter search-filter
|
||||
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
|
||||
:on-blur (fn []
|
||||
(when-not (seq chats)
|
||||
(when chats-empty
|
||||
(re-frame/dispatch [:search/home-filter-changed nil]))
|
||||
(re-frame/dispatch [::new-chat/clear-new-identity]))
|
||||
:on-focus (fn [search-filter]
|
||||
|
@ -174,12 +174,13 @@
|
|||
[welcome-blank-page]
|
||||
[list/flat-list
|
||||
{:key-fn chat-list-key-fn
|
||||
:initialNumToRender 5
|
||||
:keyboard-should-persist-taps :always
|
||||
:data items
|
||||
:render-fn render-fn
|
||||
:header [:<>
|
||||
(when (or (seq items) @search-active? (seq search-filter))
|
||||
[search-input-wrapper search-filter items])
|
||||
[search-input-wrapper search-filter (empty? items)])
|
||||
[referral-item/list-item]
|
||||
(when (and (empty? items)
|
||||
(or @search-active? (seq search-filter)))
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.utils.contenthash :as contenthash]
|
||||
[status-im.utils.core :as utils]
|
||||
[status-im.utils.datetime :as time]))
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]
|
||||
[status-im.ui.screens.chat.sheets :as sheets]))
|
||||
|
||||
(defn mention-element [from]
|
||||
@(re-frame/subscribe [:contacts/contact-name-by-identity from]))
|
||||
|
@ -72,7 +74,7 @@
|
|||
(:components result)))
|
||||
|
||||
(defn message-content-text [{:keys [content content-type community-id]}]
|
||||
[:<>
|
||||
[react/view {:position :absolute :left 72 :top 32 :right 80}
|
||||
(cond
|
||||
|
||||
(not (and content content-type))
|
||||
|
@ -109,48 +111,45 @@
|
|||
(:text content)
|
||||
(render-subheader (:parsed-text content)))])
|
||||
|
||||
(defn message-timestamp [timestamp]
|
||||
[react/view
|
||||
(when timestamp
|
||||
[react/text {:style styles/datetime-text
|
||||
:number-of-lines 1
|
||||
:accessibility-label :last-message-time-text}
|
||||
;;TODO (perf) move to event
|
||||
(string/upper-case (time/to-short-str timestamp))])])
|
||||
(def memo-timestamp
|
||||
(memoize
|
||||
(fn [timestamp]
|
||||
(string/upper-case (time/to-short-str timestamp)))))
|
||||
|
||||
(defn unviewed-indicator [{:keys [unviewed-messages-count public?]}]
|
||||
(when (pos? unviewed-messages-count)
|
||||
[react/view {:padding-left 16
|
||||
:justify-content :flex-end
|
||||
:align-items :flex-end}
|
||||
[react/view {:position :absolute :right 16 :bottom 12}
|
||||
(if public?
|
||||
[react/view {:style styles/public-unread
|
||||
:accessibility-label :unviewed-messages-public}]
|
||||
[badge/message-counter unviewed-messages-count])]))
|
||||
|
||||
(def memo-on-long-press
|
||||
(memoize
|
||||
(fn [chat-id]
|
||||
(fn []
|
||||
(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn [] [sheets/chat-actions chat-id])}])))))
|
||||
|
||||
(def memo-on-press
|
||||
(memoize
|
||||
(fn [chat-id]
|
||||
(fn []
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:search/home-filter-changed nil])))))
|
||||
|
||||
(defn icon-style []
|
||||
{:color colors/black
|
||||
:width 15
|
||||
:height 15
|
||||
:container-style {:width 15
|
||||
:container-style {:top 13 :left 72
|
||||
:position :absolute
|
||||
:width 15
|
||||
:height 15
|
||||
:margin-right 2}})
|
||||
|
||||
(defn home-list-item [home-item opts]
|
||||
(let [{:keys [chat-id chat-name color online group-chat
|
||||
public? timestamp last-message muted]}
|
||||
home-item
|
||||
private-group? (and group-chat (not public?))
|
||||
public-group? (and group-chat public?)]
|
||||
[quo/list-item
|
||||
(merge {:icon [chat-icon.screen/chat-icon-view-chat-list
|
||||
chat-id group-chat chat-name color online false]
|
||||
:title [react/view {:flex-direction :row
|
||||
:flex 1}
|
||||
[react/view {:flex-direction :row
|
||||
:flex 1
|
||||
:padding-right 16
|
||||
:align-items :center}
|
||||
(defn chat-item-icon [muted private-group? public-group?]
|
||||
(cond
|
||||
muted
|
||||
[icons/icon :main-icons/tiny-muted (assoc (icon-style) :color colors/gray)]
|
||||
|
@ -159,26 +158,41 @@
|
|||
public-group?
|
||||
[icons/icon :main-icons/tiny-public (icon-style)]
|
||||
:else
|
||||
[icons/icon :main-icons/tiny-new-contact (icon-style)])
|
||||
[icons/icon :main-icons/tiny-new-contact (icon-style)]))
|
||||
|
||||
(defn chat-item-title [chat-id muted group-chat chat-name]
|
||||
[quo/text {:weight :medium
|
||||
:color (when muted :secondary)
|
||||
:accessibility-label :chat-name-text
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1}
|
||||
:number-of-lines 1
|
||||
:style {:position :absolute :left 92 :top 10 :right 90}}
|
||||
(if group-chat
|
||||
(utils/truncate-str chat-name 30)
|
||||
;; This looks a bit odd, but I would like only to subscribe
|
||||
;; if it's a one-to-one. If wrapped in a component styling
|
||||
;; won't be applied correctly.
|
||||
(first @(re-frame/subscribe [:contacts/contact-two-names-by-identity chat-id])))]]
|
||||
[message-timestamp (if (pos? (:whisper-timestamp last-message))
|
||||
(first @(re-frame/subscribe [:contacts/contact-two-names-by-identity chat-id])))])
|
||||
|
||||
(defn home-list-item [home-item opts]
|
||||
(let [{:keys [chat-id chat-name color group-chat public? timestamp last-message muted]} home-item]
|
||||
[react/touchable-opacity (merge {:style {:height 64}} opts)
|
||||
[:<>
|
||||
[chat-item-icon muted (and group-chat (not public?)) (and group-chat public?)]
|
||||
[chat-icon.screen/chat-icon-view chat-id group-chat chat-name
|
||||
{:container (assoc chat-icon.styles/container-chat-list
|
||||
:top 12 :left 16 :position :absolute)
|
||||
: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)}]
|
||||
[chat-item-title chat-id muted group-chat chat-name]
|
||||
[react/text {:style styles/datetime-text
|
||||
:number-of-lines 1
|
||||
:accessibility-label :last-message-time-text}
|
||||
;;TODO (perf) move to event
|
||||
(memo-timestamp (if (pos? (:whisper-timestamp last-message))
|
||||
(:whisper-timestamp last-message)
|
||||
timestamp)]]
|
||||
:title-accessibility-label :chat-name-text
|
||||
:subtitle [react/view {:flex-direction :row}
|
||||
[react/view {:flex 1}
|
||||
[message-content-text (select-keys last-message [:content
|
||||
:content-type
|
||||
:community-id])]]
|
||||
[unviewed-indicator home-item]]}
|
||||
opts)]))
|
||||
timestamp))]
|
||||
[message-content-text (select-keys last-message [:content :content-type :community-id])]
|
||||
[unviewed-indicator home-item]]]))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:require [clojure.string :as string]
|
||||
#?(:cljs [taoensso.timbre :as log])))
|
||||
|
||||
(defn truncate-str
|
||||
(defn truncate-str-memo
|
||||
"Given string and max threshold, trims the string to threshold length with `...`
|
||||
appended to end or in the middle if length of the string exceeds max threshold,
|
||||
returns the same string if threshold is not exceeded"
|
||||
|
@ -19,6 +19,8 @@
|
|||
(str (subs s 0 (- threshold 3)) "..."))
|
||||
s))
|
||||
|
||||
(def truncate-str (memoize truncate-str-memo))
|
||||
|
||||
(defn clean-text [s]
|
||||
(-> s
|
||||
(string/replace #"\n" "")
|
||||
|
|
Loading…
Reference in New Issue