From 073371c4edf667488d4dea65fb9c6c7042930249 Mon Sep 17 00:00:00 2001 From: andrey Date: Tue, 13 Apr 2021 16:14:50 +0200 Subject: [PATCH] chat list optimization --- src/status_im/i18n/i18n.cljs | 6 +- .../ui/components/chat_icon/screen.cljs | 23 +-- src/status_im/ui/components/icons/icons.cljs | 28 ++-- src/status_im/ui/components/list/views.cljs | 47 +++--- src/status_im/ui/screens/chat/photos.cljs | 21 ++- src/status_im/ui/screens/chat/sheets.cljs | 13 +- src/status_im/ui/screens/chat/views.cljs | 73 ++++----- .../ui/screens/communities/views.cljs | 69 ++++----- src/status_im/ui/screens/home/styles.cljs | 5 +- src/status_im/ui/screens/home/views.cljs | 7 +- .../ui/screens/home/views/inner_item.cljs | 138 ++++++++++-------- src/status_im/utils/core.cljc | 4 +- 12 files changed, 237 insertions(+), 197 deletions(-) diff --git a/src/status_im/i18n/i18n.cljs b/src/status_im/i18n/i18n.cljs index d9f1b40b79..f9340ead75 100644 --- a/src/status_im/i18n/i18n.cljs +++ b/src/status_im/i18n/i18n.cljs @@ -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)) diff --git a/src/status_im/ui/components/chat_icon/screen.cljs b/src/status_im/ui/components/chat_icon/screen.cljs index e0b432aded..6da43e31ed 100644 --- a/src/status_im/ui/components/chat_icon/screen.cljs +++ b/src/status_im/ui/components/chat_icon/screen.cljs @@ -10,19 +10,24 @@ ;;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] (when-not (string/blank? name) [react/view (:default-chat-icon styles) [react/text {:style (:default-chat-icon-text styles)} - ;; 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)))]])) + (get-name-first-char name)]])) (defn chat-icon-view [chat-id group-chat name styles] diff --git a/src/status_im/ui/components/icons/icons.cljs b/src/status_im/ui/components/icons/icons.cljs index 314e5e2c05..5996079a6e 100644 --- a/src/status_im/ui/components/icons/icons.cljs +++ b/src/status_im/ui/components/icons/icons.cljs @@ -26,27 +26,25 @@ :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) - :height (or height 24)} + [react/image {:style (merge (cond-> {:width (or width 24) + :height (or height 24)} - resize-mode - (assoc :resize-mode resize-mode) + resize-mode + (assoc :resize-mode resize-mode) - :always - (assoc :tint-color (match-color color))) - :source (icon-source name)}]])) + :always + (assoc :tint-color (match-color color))) + container-style) + :accessibility-label accessibility-label + :source (icon-source name)}])) + +(def icon (memoize memo-icon-fn)) (defn tiny-icon ([name] (tiny-icon name {})) diff --git a/src/status_im/ui/components/list/views.cljs b/src/status_im/ui/components/list/views.cljs index 1121d17872..b9563a4c13 100644 --- a/src/status_im/ui/components/list/views.cljs +++ b/src/status_im/ui/components/list/views.cljs @@ -62,30 +62,41 @@ [react/view {:style (merge style styles/item-checkbox)} [radio/radio (:checked? props)]])]) -(defn- wrap-render-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))) +(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]))))) (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)) diff --git a/src/status_im/ui/screens/chat/photos.cljs b/src/status_im/ui/screens/chat/photos.cljs index 6b56e26936..9bf5f881a9 100644 --- a/src/status_im/ui/screens/chat/photos.cljs +++ b/src/status_im/ui/screens/chat/photos.cljs @@ -6,15 +6,20 @@ [status-im.multiaccounts.core :as multiaccounts] [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]}] - (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)}])])) + [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] diff --git a/src/status_im/ui/screens/chat/sheets.cljs b/src/status_im/ui/screens/chat/sheets.cljs index 0191f773d2..e75e5bd92a 100644 --- a/src/status_im/ui/screens/chat/sheets.cljs +++ b/src/status_im/ui/screens/chat/sheets.cljs @@ -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 diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index f2aae10301..93b1b3d83c 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -330,38 +330,41 @@ 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)] - (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 - @(re-frame/subscribe [:chats/current-chat-chat-view]) - max-bottom-space (max @bottom-space @panel-space)] - [:<> - [topbar] - [connectivity/loading-indicator] - (when chat-id - (if group-chat - [invitation-requests chat-id admins] - [add-contact-bar chat-id])) - ;;MESSAGES LIST - [messages-view {:chat chat - :bottom-space max-bottom-space - :pan-responder pan-responder - :space-keeper space-keeper - :show-input? show-input?}] - (when (and group-chat invitation-admin) - [accessory/view {:y position-y - :on-update-inset on-update} - [invitation-bar chat-id]]) - [components/autocomplete-mentions text-input-ref max-bottom-space] - (when show-input? - [accessory/view {:y position-y - :pan-state pan-state - :has-panel (boolean @active-panel) - :on-close on-close - :on-update-inset on-update} - [components/chat-toolbar - {:chat-id chat-id - :active-panel @active-panel - :set-active-panel set-active-panel - :text-input-ref text-input-ref}] - [bottom-sheet @active-panel]])])))) + (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 + @(re-frame/subscribe [:chats/current-chat-chat-view]) + max-bottom-space (max @bottom-space @panel-space)] + [:<> + [topbar] + [connectivity/loading-indicator] + (when chat-id + (if group-chat + [invitation-requests chat-id admins] + [add-contact-bar chat-id])) + ;;MESSAGES LIST + [messages-view {:chat chat + :bottom-space max-bottom-space + :pan-responder pan-responder + :space-keeper space-keeper + :show-input? show-input?}] + (when (and group-chat invitation-admin) + [accessory/view {:y position-y + :on-update-inset on-update} + [invitation-bar chat-id]]) + [components/autocomplete-mentions text-input-ref max-bottom-space] + (when show-input? + [accessory/view {:y position-y + :pan-state pan-state + :has-panel (boolean @active-panel) + :on-close on-close + :on-update-inset on-update} + [components/chat-toolbar + {:chat-id chat-id + :active-panel @active-panel + :set-active-panel set-active-panel + :text-input-ref text-input-ref}] + [bottom-sheet @active-panel]])]))}))) diff --git a/src/status_im/ui/screens/communities/views.cljs b/src/status_im/ui/screens/communities/views.cljs index dfe2f6bd5d..538a711cbc 100644 --- a/src/status_im/ui/screens/communities/views.cljs +++ b/src/status_im/ui/screens/communities/views.cljs @@ -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,36 +31,35 @@ :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 - :flex 1} - [react/view {:flex-direction :row - :flex 1 - :padding-right 16 - :align-items :center} - [quo/text {:weight :medium - :accessibility-label :chat-name-text - :font-size 17 - :ellipsize-mode :tail - :number-of-lines 1} - name]] - [react/view {:flex-direction :row - :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])]) + [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 + :align-items :center} + [quo/text {:weight :medium + :accessibility-label :chat-name-text + :font-size 17 + :ellipsize-mode :tail + :number-of-lines 1} + name]] + [react/view {:flex-direction :row + :flex 1 + :justify-content :flex-end + :align-items :center} + [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} diff --git a/src/status_im/ui/screens/home/styles.cljs b/src/status_im/ui/screens/home/styles.cljs index 537dedef94..deebd9e05c 100644 --- a/src/status_im/ui/screens/home/styles.cljs +++ b/src/status_im/ui/screens/home/styles.cljs @@ -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 diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index 4347679af6..1f47c17b55 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -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))) diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index 6a54ab13c8..7b84a80462 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -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])) @@ -39,7 +41,7 @@ "mention" {: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" (truncate-literal (str "#" literal)) @@ -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,76 +111,88 @@ (: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 - :height 15 - :margin-right 2}}) + :container-style {:top 13 :left 72 + :position :absolute + :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] - (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} - (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)]) - [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)])) + (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))] + [message-content-text (select-keys last-message [:content :content-type :community-id])] + [unviewed-indicator home-item]]])) diff --git a/src/status_im/utils/core.cljc b/src/status_im/utils/core.cljc index 60c051de25..53b5091ec6 100644 --- a/src/status_im/utils/core.cljc +++ b/src/status_im/utils/core.cljc @@ -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" "")