chat list optimization

This commit is contained in:
andrey 2021-04-13 16:14:50 +02:00
parent db848a1f19
commit 073371c4ed
No known key found for this signature in database
GPG Key ID: 89B67245FD2F0272
12 changed files with 237 additions and 197 deletions

View File

@ -45,14 +45,16 @@
;; i18n ignores nil value, leading to misleading messages ;; i18n ignores nil value, leading to misleading messages
(into {} (for [[k v] options] [k (or v default-option-value)]))) (into {} (for [[k v] options] [k (or v default-option-value)])))
(defn label (defn label-fn
([path] (label path {})) ([path] (label-fn path {}))
([path options] ([path options]
(if (exists? (.t i18n)) (if (exists? (.t i18n))
(let [options (update options :amount label-number)] (let [options (update options :amount label-number)]
(.t i18n (name path) (clj->js (label-options options)))) (.t i18n (name path) (clj->js (label-options options))))
(name path)))) (name path))))
(def label (memoize label-fn))
(defn label-pluralize [count path & options] (defn label-pluralize [count path & options]
(if (exists? (.t i18n)) (if (exists? (.t i18n))
(.p i18n count (name path) (clj->js options)) (.p i18n count (name path) (clj->js options))

View File

@ -10,19 +10,24 @@
;;TODO REWORK THIS NAMESPACE ;;TODO REWORK THIS NAMESPACE
(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
;; a broader refactoring should clean up upstream params
;; for default-chat-icon
(string/capitalize (if (and (= "#" (first name))
(< 1 (count name)))
(second name)
(first name))))))
(defn default-chat-icon [name styles] (defn default-chat-icon [name styles]
(when-not (string/blank? name) (when-not (string/blank? name)
[react/view (:default-chat-icon styles) [react/view (:default-chat-icon styles)
[react/text {:style (:default-chat-icon-text styles)} [react/text {:style (:default-chat-icon-text styles)}
;; TODO: for now we check if the first letter is a # (get-name-first-char name)]]))
;; which means it is most likely a public chat and
;; use the second letter if that is the case
;; a broader refactoring should clean up upstream params
;; for default-chat-icon
(string/capitalize (if (and (= "#" (first name))
(< 1 (count name)))
(second name)
(first name)))]]))
(defn chat-icon-view (defn chat-icon-view
[chat-id group-chat name styles] [chat-id group-chat name styles]

View File

@ -26,27 +26,25 @@
:else :else
colors/black)) colors/black))
(defn icon (defn memo-icon-fn
([name] (icon name nil)) ([name] (memo-icon-fn name nil))
([name {:keys [color resize-mode container-style ([name {:keys [color resize-mode container-style
accessibility-label width height] accessibility-label width height]
:or {accessibility-label :icon}}] :or {accessibility-label :icon}}]
^{:key name} ^{:key name}
[react/view [react/image {:style (merge (cond-> {:width (or width 24)
{:style (or :height (or height 24)}
container-style
{:width (or width 24)
:height (or height 24)})
:accessibility-label accessibility-label}
[react/image {:style (cond-> {:width (or width 24)
:height (or height 24)}
resize-mode resize-mode
(assoc :resize-mode resize-mode) (assoc :resize-mode resize-mode)
:always :always
(assoc :tint-color (match-color color))) (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 (defn tiny-icon
([name] (tiny-icon name {})) ([name] (tiny-icon name {}))

View File

@ -62,30 +62,41 @@
[react/view {:style (merge style styles/item-checkbox)} [react/view {:style (merge style styles/item-checkbox)}
[radio/radio (:checked? props)]])]) [radio/radio (:checked? props)]])])
(defn- wrap-render-fn [f render-data] (def memo-wrap-render-fn
(fn [^js data] (memoize
(reagent/as-element [f (.-item data) (.-index data) (.-separators data) render-data]))) (fn [f render-data]
(fn [^js data]
(defn- wrap-key-fn [f] (reagent/as-element [f (.-item data) (.-index data) (.-separators data) render-data])))))
(fn [data index]
{:post [(some? %)]}
(f data index)))
(def base-separator [react/view styles/base-separator]) (def base-separator [react/view styles/base-separator])
(def default-separator [react/view styles/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 (defn- base-list-props
[{:keys [key-fn render-fn empty-component header footer separator default-separator? render-data]}] [{: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 (memo-wrap-key-fn key-fn)})
(merge (when key-fn {:keyExtractor (wrap-key-fn key-fn)}) (when render-fn {:renderItem (memo-wrap-render-fn render-fn render-data)})
(when render-fn {:renderItem (wrap-render-fn render-fn render-data)}) (when separator {:ItemSeparatorComponent (memo-separator-fn separator default-separator?)})
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))}) (when empty-component {:ListEmptyComponent (memo-as-element empty-component)})
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))}) (when header {:ListHeaderComponent (memo-as-element header)})
;; header and footer not wrapped in anonymous function to prevent re-creation on every re-render (when footer {:ListFooterComponent (memo-as-element footer)})))
;; 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)}))))
(defn flat-list (defn flat-list
"A wrapper for FlatList. "A wrapper for FlatList.
@ -115,7 +126,7 @@
(defn- wrap-per-section-render-fn [props] (defn- wrap-per-section-render-fn [props]
(update (update
(if-let [f (:render-fn props)] (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) props)
:data to-array)) :data to-array))

View File

@ -6,15 +6,20 @@
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im.utils.image :as utils.image])) [status-im.utils.image :as utils.image]))
(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)
:style (style/photo size)
:resize-mode :cover
:accessibility-label (or accessibility-label :chat-icon)}]
(when identicon?
[react/view {:style (style/photo-border size)}])]))))
(defn photo [photo-path {:keys [size accessibility-label]}] (defn photo [photo-path {:keys [size accessibility-label]}]
(let [identicon? (when photo-path (profile.db/base64-png? photo-path))] [memo-photo-rend photo-path size accessibility-label])
[react/view {:style (style/photo-container size)}
[react/image {:source (utils.image/source photo-path)
:style (style/photo size)
:resize-mode :cover
:accessibility-label (or accessibility-label :chat-icon)}]
(when identicon?
[react/view {:style (style/photo-border size)}])]))
;; We optionally pass identicon for perfomance reason, so it does not have to be calculated for each message ;; We optionally pass identicon for perfomance reason, so it does not have to be calculated for each message
(defn member-photo [pub-key identicon] (defn member-photo [pub-key identicon]

View File

@ -14,7 +14,7 @@
(re-frame/dispatch [:bottom-sheet/hide]) (re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch event)) (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]) (let [photo @(re-frame/subscribe [:chats/photo-path chat-id])
contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity chat-id])] contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity chat-id])]
[react/view [react/view
@ -45,7 +45,7 @@
:icon :main-icons/delete :icon :main-icons/delete
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]])) :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) (let [link (universal-links/generate-link :public-chat :external chat-id)
message (i18n/label :t/share-public-chat-text {:link link})] message (i18n/label :t/share-public-chat-text {:link link})]
[react/view [react/view
@ -147,13 +147,13 @@
:icon :main-icons/delete :icon :main-icons/delete
:on-press #(hide-sheet-and-dispatch [:group-chats.ui/remove-chat-confirmed chat-id])}])])))) :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}] :as current-chat}]
(cond (cond
(#{constants/public-chat-type (#{constants/public-chat-type
constants/profile-chat-type constants/profile-chat-type
constants/timeline-chat-type} chat-type) constants/timeline-chat-type} chat-type)
[public-chat-accents current-chat] [public-chat-accents chat-id]
(= chat-type constants/community-chat-type) (= chat-type constants/community-chat-type)
[community-chat-accents current-chat] [community-chat-accents current-chat]
@ -161,11 +161,14 @@
(= chat-type constants/private-group-chat-type) (= chat-type constants/private-group-chat-type)
[group-chat-accents current-chat] [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 [] (defn current-chat-actions []
[actions @(re-frame/subscribe [:chats/current-chat])]) [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] (defn options [chat-id message-id]
(fn [] (fn []
[react/view [react/view

View File

@ -330,38 +330,41 @@
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref) space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
set-active-panel (get-set-active-panel active-panel) set-active-panel (get-set-active-panel active-panel)
on-close #(set-active-panel nil)] on-close #(set-active-panel nil)]
(fn [] (reagent/create-class
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as chat} {:component-will-unmount #(re-frame/dispatch-sync [:close-chat])
;;we want to react only on these fields, do not use full chat map here :reagent-render
@(re-frame/subscribe [:chats/current-chat-chat-view]) (fn []
max-bottom-space (max @bottom-space @panel-space)] (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
[topbar] @(re-frame/subscribe [:chats/current-chat-chat-view])
[connectivity/loading-indicator] max-bottom-space (max @bottom-space @panel-space)]
(when chat-id [:<>
(if group-chat [topbar]
[invitation-requests chat-id admins] [connectivity/loading-indicator]
[add-contact-bar chat-id])) (when chat-id
;;MESSAGES LIST (if group-chat
[messages-view {:chat chat [invitation-requests chat-id admins]
:bottom-space max-bottom-space [add-contact-bar chat-id]))
:pan-responder pan-responder ;;MESSAGES LIST
:space-keeper space-keeper [messages-view {:chat chat
:show-input? show-input?}] :bottom-space max-bottom-space
(when (and group-chat invitation-admin) :pan-responder pan-responder
[accessory/view {:y position-y :space-keeper space-keeper
:on-update-inset on-update} :show-input? show-input?}]
[invitation-bar chat-id]]) (when (and group-chat invitation-admin)
[components/autocomplete-mentions text-input-ref max-bottom-space] [accessory/view {:y position-y
(when show-input? :on-update-inset on-update}
[accessory/view {:y position-y [invitation-bar chat-id]])
:pan-state pan-state [components/autocomplete-mentions text-input-ref max-bottom-space]
:has-panel (boolean @active-panel) (when show-input?
:on-close on-close [accessory/view {:y position-y
:on-update-inset on-update} :pan-state pan-state
[components/chat-toolbar :has-panel (boolean @active-panel)
{:chat-id chat-id :on-close on-close
:active-panel @active-panel :on-update-inset on-update}
:set-active-panel set-active-panel [components/chat-toolbar
:text-input-ref text-input-ref}] {:chat-id chat-id
[bottom-sheet @active-panel]])])))) :active-panel @active-panel
:set-active-panel set-active-panel
:text-input-ref text-input-ref}]
[bottom-sheet @active-panel]])]))})))

View File

@ -13,7 +13,8 @@
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.toolbar :as toolbar] [status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.react :as react] [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] (defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide]) (>evt [:bottom-sheet/hide])
@ -30,36 +31,35 @@
:accessibility-label :unviewed-messages-public}])) :accessibility-label :unviewed-messages-public}]))
(defn community-home-list-item [{:keys [id name last?] :as community}] (defn community-home-list-item [{:keys [id name last?] :as community}]
[react/view [react/touchable-opacity {:style (merge {:height 64}
[quo/list-item (when last?
{:icon [communities.icon/community-icon community] {:border-bottom-color (quo.colors/get-color :ui-01)
:title [react/view {:flex-direction :row :border-bottom-width 1}))
:flex 1} :on-press (fn [id]
[react/view {:flex-direction :row (>evt [:dismiss-keyboard])
:flex 1 (>evt [:navigate-to :community {:community-id id}]))}
:padding-right 16 [:<>
:align-items :center} [react/view {:top 12 :left 16 :position :absolute}
[quo/text {:weight :medium [communities.icon/community-icon community]]
:accessibility-label :chat-name-text [react/view {:style {:margin-left 72
:font-size 17 :flex-direction :row
:ellipsize-mode :tail :flex 1}
:number-of-lines 1} :accessibility-label :chat-name-text}
name]] [react/view {:flex-direction :row
[react/view {:flex-direction :row :flex 1
:flex 1 :padding-right 16
:justify-content :flex-end :align-items :center}
:align-items :center} [quo/text {:weight :medium
[community-unviewed-count id]]] :accessibility-label :chat-name-text
:title-accessibility-label :chat-name-text :font-size 17
:on-press #(do :ellipsize-mode :tail
(>evt [:dismiss-keyboard]) :number-of-lines 1}
(>evt [:navigate-to :community {:community-id id}]))}] name]]
;; TODO: actions [react/view {:flex-direction :row
;; :on-long-press #(>evt [:bottom-sheet/show-sheet :flex 1
;; nil]) :justify-content :flex-end
:align-items :center}
(when last? [community-unviewed-count id]]]]])
[quo/separator])])
(defn community-list-item [{:keys [id permissions members name description] :as community}] (defn community-list-item [{:keys [id permissions members name description] :as community}]
(let [members-count (count members) (let [members-count (count members)
@ -103,13 +103,6 @@
:icon :main-icons/add :icon :main-icons/add
:on-press #(hide-sheet-and-dispatch [::communities/open-create-community])}]]) :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] (defn communities-list [communities]
[list/section-list [list/section-list
{:content-container-style {:padding-vertical 8} {:content-container-style {:padding-vertical 8}

View File

@ -21,7 +21,10 @@
:text-align :right :text-align :right
:letter-spacing 0.4 :letter-spacing 0.4
:align-items :center :align-items :center
:line-height 12}) :line-height 12
:position :absolute
:top 10
:right 16})
(defn chat-tooltip [] (defn chat-tooltip []
{:align-items :center {:align-items :center

View File

@ -101,7 +101,7 @@
(defonce search-active? (reagent/atom false)) (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 [react/view {:padding-horizontal 16
:padding-vertical 10} :padding-vertical 10}
[search-input/search-input [search-input/search-input
@ -109,7 +109,7 @@
:search-filter search-filter :search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil]) :on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-blur (fn [] :on-blur (fn []
(when-not (seq chats) (when chats-empty
(re-frame/dispatch [:search/home-filter-changed nil])) (re-frame/dispatch [:search/home-filter-changed nil]))
(re-frame/dispatch [::new-chat/clear-new-identity])) (re-frame/dispatch [::new-chat/clear-new-identity]))
:on-focus (fn [search-filter] :on-focus (fn [search-filter]
@ -174,12 +174,13 @@
[welcome-blank-page] [welcome-blank-page]
[list/flat-list [list/flat-list
{:key-fn chat-list-key-fn {:key-fn chat-list-key-fn
:initialNumToRender 5
:keyboard-should-persist-taps :always :keyboard-should-persist-taps :always
:data items :data items
:render-fn render-fn :render-fn render-fn
:header [:<> :header [:<>
(when (or (seq items) @search-active? (seq search-filter)) (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] [referral-item/list-item]
(when (and (empty? items) (when (and (empty? items)
(or @search-active? (seq search-filter))) (or @search-active? (seq search-filter)))

View File

@ -12,7 +12,9 @@
[status-im.ui.components.icons.icons :as icons] [status-im.ui.components.icons.icons :as icons]
[status-im.utils.contenthash :as contenthash] [status-im.utils.contenthash :as contenthash]
[status-im.utils.core :as utils] [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] (defn mention-element [from]
@(re-frame/subscribe [:contacts/contact-name-by-identity from])) @(re-frame/subscribe [:contacts/contact-name-by-identity from]))
@ -39,7 +41,7 @@
"mention" "mention"
{:components [react/text-class [mention-element literal]] {:components [react/text-class [mention-element literal]]
:length 4} ;; we can't predict name length so take the smallest possible :length 4} ;; we can't predict name length so take the smallest possible
"status-tag" "status-tag"
(truncate-literal (str "#" literal)) (truncate-literal (str "#" literal))
@ -72,7 +74,7 @@
(:components result))) (:components result)))
(defn message-content-text [{:keys [content content-type community-id]}] (defn message-content-text [{:keys [content content-type community-id]}]
[:<> [react/view {:position :absolute :left 72 :top 32 :right 80}
(cond (cond
(not (and content content-type)) (not (and content content-type))
@ -109,76 +111,88 @@
(:text content) (:text content)
(render-subheader (:parsed-text content)))]) (render-subheader (:parsed-text content)))])
(defn message-timestamp [timestamp] (def memo-timestamp
[react/view (memoize
(when timestamp (fn [timestamp]
[react/text {:style styles/datetime-text (string/upper-case (time/to-short-str timestamp)))))
:number-of-lines 1
:accessibility-label :last-message-time-text}
;;TODO (perf) move to event
(string/upper-case (time/to-short-str timestamp))])])
(defn unviewed-indicator [{:keys [unviewed-messages-count public?]}] (defn unviewed-indicator [{:keys [unviewed-messages-count public?]}]
(when (pos? unviewed-messages-count) (when (pos? unviewed-messages-count)
[react/view {:padding-left 16 [react/view {:position :absolute :right 16 :bottom 12}
:justify-content :flex-end
:align-items :flex-end}
(if public? (if public?
[react/view {:style styles/public-unread [react/view {:style styles/public-unread
:accessibility-label :unviewed-messages-public}] :accessibility-label :unviewed-messages-public}]
[badge/message-counter unviewed-messages-count])])) [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 [] (defn icon-style []
{:color colors/black {:color colors/black
:width 15 :width 15
:height 15 :height 15
:container-style {:width 15 :container-style {:top 13 :left 72
:height 15 :position :absolute
:margin-right 2}}) :width 15
:height 15
:margin-right 2}})
(defn chat-item-icon [muted private-group? public-group?]
(cond
muted
[icons/icon :main-icons/tiny-muted (assoc (icon-style) :color colors/gray)]
private-group?
[icons/icon :main-icons/tiny-group (icon-style)]
public-group?
[icons/icon :main-icons/tiny-public (icon-style)]
:else
[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
: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])))])
(defn home-list-item [home-item opts] (defn home-list-item [home-item opts]
(let [{:keys [chat-id chat-name color online group-chat (let [{:keys [chat-id chat-name color group-chat public? timestamp last-message muted]} home-item]
public? timestamp last-message muted]} [react/touchable-opacity (merge {:style {:height 64}} opts)
home-item [:<>
private-group? (and group-chat (not public?)) [chat-item-icon muted (and group-chat (not public?)) (and group-chat public?)]
public-group? (and group-chat public?)] [chat-icon.screen/chat-icon-view chat-id group-chat chat-name
[quo/list-item {:container (assoc chat-icon.styles/container-chat-list
(merge {:icon [chat-icon.screen/chat-icon-view-chat-list :top 12 :left 16 :position :absolute)
chat-id group-chat chat-name color online false] :size 40
:title [react/view {:flex-direction :row :chat-icon chat-icon.styles/chat-icon-chat-list
:flex 1} :default-chat-icon (chat-icon.styles/default-chat-icon-chat-list color)
[react/view {:flex-direction :row :default-chat-icon-text (chat-icon.styles/default-chat-icon-text 40)}]
:flex 1 [chat-item-title chat-id muted group-chat chat-name]
:padding-right 16 [react/text {:style styles/datetime-text
:align-items :center} :number-of-lines 1
(cond :accessibility-label :last-message-time-text}
muted ;;TODO (perf) move to event
[icons/icon :main-icons/tiny-muted (assoc (icon-style) :color colors/gray)] (memo-timestamp (if (pos? (:whisper-timestamp last-message))
private-group? (:whisper-timestamp last-message)
[icons/icon :main-icons/tiny-group (icon-style)] timestamp))]
public-group? [message-content-text (select-keys last-message [:content :content-type :community-id])]
[icons/icon :main-icons/tiny-public (icon-style)] [unviewed-indicator home-item]]]))
:else
[icons/icon :main-icons/tiny-new-contact (icon-style)])
[quo/text {:weight :medium
:color (when muted :secondary)
:accessibility-label :chat-name-text
:ellipsize-mode :tail
:number-of-lines 1}
(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))
(: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)]))

View File

@ -2,7 +2,7 @@
(:require [clojure.string :as string] (:require [clojure.string :as string]
#?(:cljs [taoensso.timbre :as log]))) #?(:cljs [taoensso.timbre :as log])))
(defn truncate-str (defn truncate-str-memo
"Given string and max threshold, trims the string to threshold length with `...` "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, appended to end or in the middle if length of the string exceeds max threshold,
returns the same string if threshold is not exceeded" returns the same string if threshold is not exceeded"
@ -19,6 +19,8 @@
(str (subs s 0 (- threshold 3)) "...")) (str (subs s 0 (- threshold 3)) "..."))
s)) s))
(def truncate-str (memoize truncate-str-memo))
(defn clean-text [s] (defn clean-text [s]
(-> s (-> s
(string/replace #"\n" "") (string/replace #"\n" "")