From 396ee208bff686a6faeece34eab0d0260e1010b6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 1 Jun 2023 16:08:47 +0100 Subject: [PATCH] Chat Screen Top Bar UI + new UI for user details (#15204) * Fixes * Reformatting + fixes * Functions rewrite * f-function * One more f-function * Minor constants fixes * Jump to button removal * Footer insets fix * Better loading indicator * Review fixes * Fixes for Android * More fixes * More fixes * Fix * Fixes for scaling * Overscroll fixes * Better empty view on Android * Android fixes, scrolling fixes * Value fix * Code style fixes * Fix for scroll indicator insets * Fixes * Accessibility-ids * Code style fixes * Footer fix * Style update --- src/status_im/chat/models/loading.cljs | 14 +- .../contexts/chat/messages/content/view.cljs | 4 +- .../contexts/chat/messages/list/style.cljs | 62 ++++ .../contexts/chat/messages/list/view.cljs | 347 +++++++++++++----- .../chat/messages/navigation/style.cljs | 101 +++++ .../chat/messages/navigation/view.cljs | 105 ++++++ .../contexts/chat/messages/view.cljs | 76 +--- 7 files changed, 544 insertions(+), 165 deletions(-) create mode 100644 src/status_im2/contexts/chat/messages/list/style.cljs create mode 100644 src/status_im2/contexts/chat/messages/navigation/style.cljs create mode 100644 src/status_im2/contexts/chat/messages/navigation/view.cljs diff --git a/src/status_im/chat/models/loading.cljs b/src/status_im/chat/models/loading.cljs index 9459b7e75c..8418f9b841 100644 --- a/src/status_im/chat/models/loading.cljs +++ b/src/status_im/chat/models/loading.cljs @@ -103,7 +103,7 @@ (rf/defn messages-loaded "Loads more messages for current chat" {:events [::messages-loaded]} - [{db :db} chat-id session-id {:keys [cursor messages]}] + [{db :db} chat-id session-id {:keys [cursor messages]} on-loaded] (when-not (and (get-in db [:pagination-info chat-id :messages-initialized?]) (not= session-id (get-in db [:pagination-info chat-id :messages-initialized?]))) @@ -141,6 +141,8 @@ [:pagination-info chat-id :cursor-clock-value]) clock-value (when cursor (cursor->clock-value cursor))] + (when on-loaded + (on-loaded (count new-messages))) {:db (-> db (update-in [:pagination-info chat-id :cursor-clock-value] #(if (and (seq cursor) (or (not %) (< clock-value %))) @@ -161,7 +163,7 @@ (rf/defn load-more-messages {:events [:chat.ui/load-more-messages]} - [{:keys [db]} chat-id first-request] + [{:keys [db]} chat-id first-request on-loaded] (when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])] (when (and (not (get-in db [:pagination-info chat-id :all-loaded?])) @@ -175,13 +177,13 @@ chat-id cursor constants/default-number-of-messages - #(re-frame/dispatch [::messages-loaded chat-id session-id %]) + #(re-frame/dispatch [::messages-loaded chat-id session-id % on-loaded]) #(re-frame/dispatch [::failed-loading-messages chat-id session-id %])))))))) (rf/defn load-more-messages-for-current-chat {:events [:chat.ui/load-more-messages-for-current-chat]} - [{:keys [db] :as cofx}] - (load-more-messages cofx (:current-chat-id db) false)) + [{:keys [db] :as cofx} on-loaded] + (load-more-messages cofx (:current-chat-id db) false on-loaded)) (rf/defn load-messages [{:keys [db now] :as cofx} chat-id] @@ -191,4 +193,4 @@ :utils/dispatch-later [{:ms 50 :dispatch [:chat.ui/mark-all-read-pressed chat-id]} (when-not (get-in cofx [:db :chats chat-id :public?]) {:ms 100 :dispatch [:pin-message/load-pin-messages chat-id]})]} - (load-more-messages chat-id true)))) + (load-more-messages chat-id true nil)))) diff --git a/src/status_im2/contexts/chat/messages/content/view.cljs b/src/status_im2/contexts/chat/messages/content/view.cljs index dca6787d40..6f7110cc3d 100644 --- a/src/status_im2/contexts/chat/messages/content/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/view.cljs @@ -85,7 +85,7 @@ (let [show-delivery-state? (reagent/atom false)] (fn [{:keys [content-type quoted-message content outgoing outgoing-status] :as message-data} context - keyboard-shown + keyboard-shown? message-reaction-view?] (let [first-image (first (:album message-data)) outgoing-status (if (= content-type constants/content-type-album) @@ -105,7 +105,7 @@ :style {:border-radius 16 :opacity (if (and outgoing (= outgoing-status :sending)) 0.5 1)} :on-press (fn [] - (if (and platform/ios? @keyboard-shown) + (if (and platform/ios? keyboard-shown?) (rn/dismiss-keyboard!) (when (and outgoing (not= outgoing-status :sending) diff --git a/src/status_im2/contexts/chat/messages/list/style.cljs b/src/status_im2/contexts/chat/messages/list/style.cljs new file mode 100644 index 0000000000..f150834fe8 --- /dev/null +++ b/src/status_im2/contexts/chat/messages/list/style.cljs @@ -0,0 +1,62 @@ +(ns status-im2.contexts.chat.messages.list.style + (:require [quo2.foundations.colors :as colors] + [react-native.reanimated :as reanimated])) + +(defonce ^:const cover-height 168) +(defonce ^:const overscroll-cover-height 2000) +(defonce ^:const header-avatar-top-offset -36) +(defonce ^:const messages-list-bottom-offset 16) + +(defn keyboard-avoiding-container + [{:keys [top]}] + {:position :relative + :flex 1 + :top (- top) + :margin-bottom (- top)}) + +(def list-container + {:padding-vertical 16}) + +(defn header-container + [show?] + {:display (if show? :flex :none) + :background-color (colors/theme-colors colors/white colors/neutral-95) + :top (- overscroll-cover-height) + :margin-bottom (- overscroll-cover-height)}) + +(defn header-cover + [cover-bg-color] + {:flex 1 + :height (+ overscroll-cover-height cover-height) + :background-color cover-bg-color}) + +(defn header-bottom-part + [animation] + (reanimated/apply-animations-to-style + {:border-top-right-radius animation + :border-top-left-radius animation} + {:top -16 + :margin-bottom -16 + :padding-bottom 24 + :background-color (colors/theme-colors colors/white colors/neutral-100)})) + +(def header-avatar + {:top header-avatar-top-offset + :margin-horizontal 20 + :margin-bottom header-avatar-top-offset}) + +(defn header-image + [scale-animation top-margin-animation side-margin-animation] + (reanimated/apply-animations-to-style + {:transform [{:scale scale-animation}] + :margin-top top-margin-animation + :margin-left side-margin-animation + :margin-bottom side-margin-animation} + {:align-items :flex-start})) + +(def name-container + {:flex-direction :row + :align-items :center}) + +(def bio + {:margin-top 8}) diff --git a/src/status_im2/contexts/chat/messages/list/view.cljs b/src/status_im2/contexts/chat/messages/list/view.cljs index f2e7e4f3fd..e39c8dabba 100644 --- a/src/status_im2/contexts/chat/messages/list/view.cljs +++ b/src/status_im2/contexts/chat/messages/list/view.cljs @@ -3,8 +3,12 @@ [quo2.core :as quo] [react-native.background-timer :as background-timer] [react-native.core :as rn] + [react-native.hooks :as hooks] [react-native.platform :as platform] + [react-native.safe-area :as safe-area] [reagent.core :as reagent] + [quo2.foundations.colors :as colors] + [react-native.reanimated :as reanimated] [status-im.ui.screens.chat.group :as chat.group] [status-im.ui.screens.chat.message.gap :as message.gap] [status-im2.common.not-implemented :as not-implemented] @@ -12,21 +16,35 @@ [status-im2.contexts.chat.messages.content.deleted.view :as content.deleted] [status-im2.contexts.chat.messages.content.view :as message] [status-im2.contexts.chat.messages.list.state :as state] - [utils.re-frame :as rf] - [status-im2.contexts.chat.composer.constants :as composer.constants])) + [status-im2.contexts.chat.messages.list.style :as style] + [status-im2.contexts.chat.messages.navigation.style :as navigation.style] + [status-im2.contexts.chat.composer.constants :as composer.constants] + [utils.re-frame :as rf])) + +(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75) +(defonce ^:const loading-indicator-extra-spacing 250) +(defonce ^:const loading-indicator-page-loading-height 100) +(defonce ^:const scroll-animation-input-range [50 125]) +(defonce ^:const spacing-between-composer-and-content 64) (defonce messages-list-ref (atom nil)) +(defonce messages-view-height (reagent/atom 0)) +(defonce messages-view-header-height (reagent/atom 0)) +(defonce show-floating-scroll-down-button (reagent/atom false)) (defn list-key-fn [{:keys [message-id value]}] (or message-id value)) (defn list-ref [ref] (reset! messages-list-ref ref)) +(defn scroll-to-offset + [position] + (some-> ^js @messages-list-ref + (.scrollToOffset #js + {:offset position + :animated true}))) + (defn scroll-to-bottom [] - (some-> ^js @messages-list-ref - (.scrollToOffset #js {:y 0 :animated true}))) - -(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75) -(defonce show-floating-scroll-down-button (reagent/atom false)) + (scroll-to-offset (- 0 style/messages-list-bottom-offset))) (defn on-scroll [evt] @@ -40,11 +58,11 @@ (reset! show-floating-scroll-down-button reached-threshold?)))) (defn on-viewable-items-changed - [evt] + [e] (when @messages-list-ref (reset! state/first-not-visible-item - (when-let [last-visible-element (aget (oops/oget evt "viewableItems") - (dec (oops/oget evt "viewableItems.length")))] + (when-let [last-visible-element (aget (oops/oget e "viewableItems") + (dec (oops/oget e "viewableItems.length")))] (let [index (oops/oget last-visible-element "index") ;; Get first not visible element, if it's a datemark/gap ;; we might unnecessarely add messages on receiving as @@ -55,43 +73,152 @@ (= :message (:type first-not-visible))) first-not-visible)))))) -;;TODO this is not really working in pair with inserting new messages because we stop inserting new -;;messages -;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because -;;we -;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will -;;work wrong + (defn list-on-end-reached - [] - (if @state/scrolling - (rf/dispatch [:chat.ui/load-more-messages-for-current-chat]) - (background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat]) - (if platform/low-device? 700 200)))) + [scroll-y] + ;; FIXME: that's a bit of a hack but we need to update `scroll-y` once the new messages + ;; are fetched in order for the header to work properly + (let [on-loaded (fn [n] + (reanimated/set-shared-value scroll-y + (+ (reanimated/get-shared-value scroll-y) + (* n 200))))] + (if @state/scrolling + (rf/dispatch [:chat.ui/load-more-messages-for-current-chat on-loaded]) + (background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat + on-loaded]) + (if platform/low-device? 700 100))))) -(defonce messages-view-height (reagent/atom 0)) +(defn contact-icon + [{:keys [ens-verified added?]}] + (when (or ens-verified added?) + [rn/view + {:style {:padding-left 10 + :margin-top 2}} + (if ens-verified + [quo/icon :i/verified + {:no-color true + :size 20 + :color (colors/theme-colors colors/success-50 colors/success-60)}] + (when added? + [quo/icon :i/contact + {:no-color true + :size 20 + :color (colors/theme-colors colors/primary-50 colors/primary-60)}]))])) -(defn on-messages-view-layout - [evt] - (reset! messages-view-height (oops/oget evt "nativeEvent.layout.height"))) +(def header-extrapolation-option + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"}) -(defn list-footer - [{:keys [chat-id]}] - (let [loading-messages? (rf/sub [:chats/loading-messages? chat-id]) - all-loaded? (rf/sub [:chats/all-loaded? chat-id])] - (when (or loading-messages? (not chat-id) (not all-loaded?)) - [rn/view {:style (when platform/android? {:scaleY -1})} - [quo/skeleton @messages-view-height]]))) +(defn loading-view + [chat-id] + (let [loading-messages? (rf/sub [:chats/loading-messages? chat-id]) + all-loaded? (rf/sub [:chats/all-loaded? chat-id]) + messages (rf/sub [:chats/raw-chat-messages-stream chat-id]) + loading-first-page? (= (count messages) 0) + top-spacing (if loading-first-page? 0 navigation.style/navigation-bar-height)] + (when (or loading-messages? (not all-loaded?)) + [rn/view {:padding-top top-spacing} + [quo/skeleton + (if loading-first-page? + (- @messages-view-height + @messages-view-header-height + composer.constants/composer-default-height + loading-indicator-extra-spacing) + loading-indicator-page-loading-height)]]))) (defn list-header - [{:keys [chat-id chat-type invitation-admin]}] - (when (= chat-type constants/private-group-chat-type) - [rn/view {:style (when platform/android? {:scaleY -1})} - [chat.group/group-chat-footer chat-id invitation-admin]])) + [insets] + [rn/view + {:background-color (colors/theme-colors colors/white colors/neutral-95) + :margin-bottom (- 0 + (:top insets) + (when platform/ios? style/overscroll-cover-height)) + :height (+ composer.constants/composer-default-height + (:bottom insets) + spacing-between-composer-and-content + (when platform/ios? style/overscroll-cover-height))}]) + +(defn f-list-footer + [{:keys [chat scroll-y cover-bg-color on-layout]}] + (let [{:keys [chat-id chat-name emoji chat-type + group-chat]} chat + all-loaded? (rf/sub [:chats/all-loaded? chat-id]) + display-name (if (= chat-type constants/one-to-one-chat-type) + (first (rf/sub [:contacts/contact-two-names-by-identity chat-id])) + (str emoji " " chat-name)) + {:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id]) + online? (rf/sub [:visibility-status-updates/online? chat-id]) + contact (when-not group-chat + (rf/sub [:contacts/contact-by-address chat-id])) + photo-path (when-not (empty? (:images contact)) + (rf/sub [:chats/photo-path chat-id])) + border-animation (reanimated/interpolate scroll-y + [30 125] + [14 0] + header-extrapolation-option) + image-scale-animation (reanimated/interpolate scroll-y + scroll-animation-input-range + [1 0.5] + header-extrapolation-option) + image-top-margin-animation (reanimated/interpolate scroll-y + scroll-animation-input-range + [0 40] + header-extrapolation-option) + image-side-margin-animation (reanimated/interpolate scroll-y + scroll-animation-input-range + [0 -20] + header-extrapolation-option)] + [rn/view {:flex 1} + [rn/view + {:style (style/header-container all-loaded?) + :on-layout on-layout} + (when cover-bg-color + [rn/view {:style (style/header-cover cover-bg-color)}]) + [reanimated/view {:style (style/header-bottom-part border-animation)} + [rn/view {:style style/header-avatar} + (when-not group-chat + [rn/view {:style {:align-items :flex-start}} + [reanimated/view + {:style (style/header-image image-scale-animation + image-top-margin-animation + image-side-margin-animation)} + [quo/user-avatar + {:full-name display-name + :online? online? + :profile-picture photo-path + :size :big}]]]) + [rn/view {:style style/name-container} + [quo/text + {:weight :semi-bold + :size :heading-1 + :style {:margin-top (if group-chat 54 12)} + :number-of-lines 1} + display-name + [contact-icon contact]]] + (when bio + [quo/text {:style style/bio} + bio])]]] + [loading-view chat-id]])) + +(defn list-footer + [props] + [:f> f-list-footer props]) + +(defn list-group-chat-header + [{:keys [chat-id invitation-admin]}] + [rn/view + [chat.group/group-chat-footer chat-id invitation-admin]]) + +(defn footer-on-layout + [e] + (let [height (oops/oget e "nativeEvent.layout.height") + y (oops/oget e "nativeEvent.layout.y")] + (reset! messages-view-header-height (+ height y)))) (defn render-fn [{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _ - {:keys [context keyboard-shown]}] - [rn/view {:style (when platform/android? {:scaleY -1})} + {:keys [context keyboard-shown?]}] + [rn/view {:background-color (colors/theme-colors colors/white colors/neutral-95)} (if (= type :datemark) [quo/divider-date value] (if (= content-type constants/content-type-gap) @@ -100,71 +227,95 @@ [rn/view {:padding-horizontal 8} (if (or deleted? deleted-for-me?) [content.deleted/deleted-message message-data context] - [message/message-with-reactions message-data context keyboard-shown])]))]) + [message/message-with-reactions message-data context keyboard-shown?])]))]) + +(defn scroll-handler + [event scroll-y] + (let [content-size-y (- (oops/oget event "nativeEvent.contentSize.height") + (oops/oget event "nativeEvent.layoutMeasurement.height")) + current-y (oops/oget event "nativeEvent.contentOffset.y")] + (reanimated/set-shared-value scroll-y (- content-size-y current-y)))) (defn messages-list-content - [{:keys [chat-id] :as chat} insets keyboard-shown] - (fn [] - (let [context (rf/sub [:chats/current-chat-message-list-view-context]) - messages (rf/sub [:chats/raw-chat-messages-stream chat-id]) - recording? (rf/sub [:chats/recording?])] - [rn/view - {:style {:flex 1}} - ;; NOTE: DO NOT use anonymous functions for handlers - [rn/flat-list - {:key-fn list-key-fn - :ref list-ref - :header [list-header chat] - :footer [list-footer chat] - :data messages - :render-data {:context context - :keyboard-shown keyboard-shown} - :render-fn render-fn - :on-viewable-items-changed on-viewable-items-changed - :on-end-reached list-on-end-reached - :on-scroll-to-index-failed identity ; don't remove this - :content-container-style {:padding-top (+ composer.constants/composer-default-height - (:bottom insets) - 32) - :padding-bottom 16} - :scroll-indicator-insets {:top (+ composer.constants/composer-default-height - (:bottom insets))} - :keyboard-dismiss-mode :interactive - :keyboard-should-persist-taps :handled - :on-momentum-scroll-begin state/start-scrolling - :on-momentum-scroll-end state/stop-scrolling - :scroll-event-throttle 16 - :on-scroll on-scroll - ;; TODO https://github.com/facebook/react-native/issues/30034 - :inverted (when platform/ios? true) - :style (when platform/android? {:scaleY -1}) - :on-layout on-messages-view-layout - :scroll-enabled (not recording?)}]]))) + [{:keys [chat insets scroll-y cover-bg-color keyboard-shown?]}] + (let [context (rf/sub [:chats/current-chat-message-list-view-context]) + messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)]) + recording? (rf/sub [:chats/recording?]) + all-loaded? (rf/sub [:chats/all-loaded? (:chat-id chat)])] + [rn/view {:style {:flex 1}} + [rn/flat-list + {:key-fn list-key-fn + :ref list-ref + :header [:<> + (when (= (:chat-type chat) constants/private-group-chat-type) + [list-group-chat-header chat]) + [list-header insets]] + :footer [list-footer + {:chat chat + :scroll-y scroll-y + :cover-bg-color cover-bg-color + :on-layout footer-on-layout}] + :data messages + :render-data {:context context + :keyboard-shown? keyboard-shown?} + :render-fn render-fn + :on-viewable-items-changed on-viewable-items-changed + :on-end-reached #(list-on-end-reached scroll-y) + :on-scroll-to-index-failed identity + :content-container-style {:padding-bottom style/messages-list-bottom-offset} + :scroll-indicator-insets {:top (- composer.constants/composer-default-height 16)} + :keyboard-dismiss-mode :interactive + :keyboard-should-persist-taps :handled + :on-momentum-scroll-begin state/start-scrolling + :on-momentum-scroll-end state/stop-scrolling + :scroll-event-throttle 16 + :on-scroll (fn [event] + (scroll-handler event scroll-y) + (when on-scroll + (on-scroll event))) + :style {:background-color (if all-loaded? + cover-bg-color + (colors/theme-colors colors/white + colors/neutral-95))} + :inverted true + :on-layout (fn [e] + (when platform/android? + ;; FIXME: this is due to Android not triggering the initial + ;; scrollTo event + (scroll-to-offset 1)) + (let [layout-height (oops/oget e "nativeEvent.layout.height")] + (reset! messages-view-height layout-height))) + :scroll-enabled (not recording?)}]])) -;; This should be replaced with keyboard hook. It has to do with flat-list probably. The keyboard-shown -;; value updates in the parent component, but does not get passed to the children. -;; When using listeners and resetting the value on an atom it works. -(defn use-keyboard-visibility - [] - (let [show-listener (atom nil) - hide-listener (atom nil) - shown? (atom nil)] +(defn f-messages-list + [{:keys [chat cover-bg-color header-comp footer-comp]}] + (let [insets (safe-area/get-insets) + scroll-y (reanimated/use-shared-value 0) + {:keys [keyboard-height keyboard-shown]} (hooks/use-keyboard)] (rn/use-effect (fn [] - (reset! show-listener - (.addListener rn/keyboard "keyboardWillShow" #(reset! shown? true))) - (reset! hide-listener - (.addListener rn/keyboard "keyboardWillHide" #(reset! shown? false))) - (fn [] - (.remove ^js @show-listener) - (.remove ^js @hide-listener)))) - {:shown? shown?})) + (when keyboard-shown + (reanimated/set-shared-value scroll-y + (+ (reanimated/get-shared-value scroll-y) + keyboard-height)))) + [keyboard-shown keyboard-height]) + [rn/keyboard-avoiding-view + {:style (style/keyboard-avoiding-container insets) + :keyboard-vertical-offset (- (:bottom insets))} -(defn- f-messages-list - [chat insets] - (let [{keyboard-shown? :shown?} (use-keyboard-visibility)] - [messages-list-content chat insets keyboard-shown?])) + (when header-comp + [header-comp {:scroll-y scroll-y}]) + + [messages-list-content + {:chat chat + :insets insets + :scroll-y scroll-y + :cover-bg-color cover-bg-color + :keyboard-shown? keyboard-shown}] + + (when footer-comp + (footer-comp {:insets insets}))])) (defn messages-list - [chat insets] - [:f> f-messages-list chat insets]) + [props] + [:f> f-messages-list props]) diff --git a/src/status_im2/contexts/chat/messages/navigation/style.cljs b/src/status_im2/contexts/chat/messages/navigation/style.cljs new file mode 100644 index 0000000000..4dfac7262a --- /dev/null +++ b/src/status_im2/contexts/chat/messages/navigation/style.cljs @@ -0,0 +1,101 @@ +(ns status-im2.contexts.chat.messages.navigation.style + (:require [quo2.foundations.colors :as colors] + [react-native.platform :as platform] + [react-native.reanimated :as reanimated])) + +(defonce ^:const navigation-bar-height 100) +(defonce ^:const header-offset 56) + +(defn button-container + [position] + (merge + {:width 32 + :height 32 + :border-radius 10 + :justify-content :center + :align-items :center + :background-color (colors/theme-colors colors/white-opa-40 colors/neutral-80-opa-40)} + position)) + +(defn blur-view + [status-bar-height] + {:position :absolute + :top 0 + :left 0 + :right 0 + :height (- navigation-bar-height + (if platform/ios? 0 status-bar-height)) + :display :flex + :flex-direction :row + :overflow :hidden}) + +(defn animated-blur-view + [enabled? animation status-bar-height] + (reanimated/apply-animations-to-style + (when enabled? + {:opacity animation}) + (blur-view status-bar-height))) + +(def navigation-view + {:z-index 4}) + +(defn header-container + [status-bar-height] + {:position :absolute + :top (- header-offset + (if platform/ios? 0 status-bar-height)) + :left 0 + :right 0 + :padding-bottom 8 + :display :flex + :flex-direction :row + :overflow :hidden}) + +(def header + {:flex 1}) + +(defn animated-header + [enabled? y-animation opacity-animation] + (reanimated/apply-animations-to-style + ;; here using `top` won't work on Android, so we are using `translateY` + (when enabled? + {:transform [{:translateY y-animation}] + :opacity opacity-animation}) + header)) + +(defn pinned-banner + [status-bar-height] + {:position :absolute + :left 0 + :right 0 + :top (- navigation-bar-height + (if platform/ios? 0 status-bar-height))}) + +(defn animated-pinned-banner + [enabled? animation status-bar-height] + (reanimated/apply-animations-to-style + (when enabled? + {:opacity animation}) + (pinned-banner status-bar-height))) + +(def header-content-container + {:flex-direction :row + :align-items :center + :margin-left 8 + :margin-right 8 + :margin-top -4 + :height 40}) + +(def header-avatar-container + {:margin-right 8}) + +(def header-text-container + {:flex 1}) + +(defn header-display-name + [] + {:color (colors/theme-colors colors/black colors/white)}) + +(defn header-online + [] + {:color (colors/theme-colors colors/neutral-80-opa-50 colors/white-opa-50)}) \ No newline at end of file diff --git a/src/status_im2/contexts/chat/messages/navigation/view.cljs b/src/status_im2/contexts/chat/messages/navigation/view.cljs new file mode 100644 index 0000000000..3e717731e6 --- /dev/null +++ b/src/status_im2/contexts/chat/messages/navigation/view.cljs @@ -0,0 +1,105 @@ +(ns status-im2.contexts.chat.messages.navigation.view + (:require [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [re-frame.db] + [react-native.core :as rn] + [react-native.reanimated :as reanimated] + [react-native.safe-area :as safe-area] + [status-im2.contexts.chat.messages.navigation.style :as style] + [status-im2.contexts.chat.messages.pin.banner.view :as pin.banner] + [status-im2.constants :as constants] + [utils.re-frame :as rf] + [utils.i18n :as i18n])) + +(defn f-navigation-view + [{:keys [scroll-y]}] + (let [insets (safe-area/get-insets) + status-bar-height (:top insets) + {:keys [group-chat chat-id chat-name emoji + chat-type]} (rf/sub [:chats/current-chat-chat-view]) + all-loaded? (rf/sub [:chats/all-loaded? chat-id]) + display-name (if (= chat-type constants/one-to-one-chat-type) + (first (rf/sub [:contacts/contact-two-names-by-identity chat-id])) + (str emoji " " chat-name)) + online? (rf/sub [:visibility-status-updates/online? chat-id]) + contact (when-not group-chat + (rf/sub [:contacts/contact-by-address chat-id])) + photo-path (when-not (empty? (:images contact)) + (rf/sub [:chats/photo-path chat-id])) + opacity-animation (reanimated/interpolate scroll-y + [style/navigation-bar-height + (+ style/navigation-bar-height 30)] + [0 1] + {:extrapolateLeft "clamp" + :extrapolateRight "extend"}) + banner-opacity-animation (reanimated/interpolate scroll-y + [(+ style/navigation-bar-height 50) + (+ style/navigation-bar-height 100)] + [0 1] + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"}) + translate-animation (reanimated/interpolate scroll-y + [(+ style/navigation-bar-height 25) + (+ style/navigation-bar-height 100)] + [50 0] + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"}) + title-opacity-animation (reanimated/interpolate scroll-y + [0 50] + [0 1] + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"})] + [rn/view {:style style/navigation-view} + [reanimated/blur-view + {:blurAmount 32 + :blurType (colors/theme-colors :xlight :dark) + :overlayColor :transparent + :style (style/animated-blur-view all-loaded? opacity-animation status-bar-height)}] + + [rn/view + [rn/view {:style (style/header-container status-bar-height)} + [rn/touchable-opacity + {:active-opacity 1 + :on-press #(rf/dispatch [:navigate-back]) + :accessibility-label :back-button + :style (style/button-container {:margin-left 20})} + [quo/icon :i/arrow-left + {:size 20 :color (colors/theme-colors colors/black colors/white)}]] + [reanimated/view + {:style (style/animated-header all-loaded? translate-animation title-opacity-animation)} + [rn/view {:style style/header-content-container} + (when-not group-chat + [rn/view {:style style/header-avatar-container} + [quo/user-avatar + {:full-name display-name + :online? online? + :profile-picture photo-path + :size :small}]]) + [rn/view {:style style/header-text-container} + [rn/view {:style {:flex-direction :row}} + [quo/text + {:weight :semi-bold + :size :paragraph-1 + :number-of-lines 1 + :style (style/header-display-name)} + display-name]] + (when online? + [quo/text + {:number-of-lines 1 + :weight :regular + :size :paragraph-2 + :style (style/header-online)} + (i18n/label :t/online)])]]] + [rn/touchable-opacity + {:active-opacity 1 + :style (style/button-container {:margin-right 20}) + :accessibility-label :options-button} + [quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]] + + [reanimated/view + {:style (style/animated-pinned-banner all-loaded? banner-opacity-animation status-bar-height)} + [pin.banner/banner chat-id]]]])) + +(defn navigation-view + [props] + [:f> f-navigation-view props]) diff --git a/src/status_im2/contexts/chat/messages/view.cljs b/src/status_im2/contexts/chat/messages/view.cljs index 809df97393..7ca99af026 100644 --- a/src/status_im2/contexts/chat/messages/view.cljs +++ b/src/status_im2/contexts/chat/messages/view.cljs @@ -1,17 +1,14 @@ (ns status-im2.contexts.chat.messages.view - (:require [quo2.core :as quo] + (:require [quo2.foundations.colors :as colors] [re-frame.db] [react-native.core :as rn] - [react-native.safe-area :as safe-area] [reagent.core :as reagent] - [status-im2.constants :as constants] [status-im2.contexts.chat.composer.view :as composer] [status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as contact-requests.bottom-drawer] [status-im2.contexts.chat.messages.list.view :as messages.list] - [status-im2.contexts.chat.messages.pin.banner.view :as pin.banner] + [status-im2.contexts.chat.messages.navigation.view :as messages.navigation] [status-im2.navigation.state :as navigation.state] - [utils.debounce :as debounce] [utils.re-frame :as rf])) (defn navigate-back-handler @@ -24,62 +21,23 @@ ;; and will call system back button action true)) -(defn page-nav - [] - (let [{:keys [group-chat chat-id chat-name emoji - chat-type]} (rf/sub [:chats/current-chat]) - display-name (if (= chat-type constants/one-to-one-chat-type) - (first (rf/sub [:contacts/contact-two-names-by-identity chat-id])) - (str emoji " " chat-name)) - online? (rf/sub [:visibility-status-updates/online? chat-id]) - contact (when-not group-chat - (rf/sub [:contacts/contact-by-address chat-id])) - photo-path (rf/sub [:chats/photo-path chat-id]) - avatar-image-key (if (seq (:images contact)) - :profile-picture - :ring-background)] - [quo/page-nav - {:align-mid? true - :mid-section (if group-chat - {:type :text-only - :main-text display-name} - {:type :user-avatar - :avatar {:full-name display-name - :online? online? - :size :medium - avatar-image-key photo-path} - :main-text display-name - :on-press #(debounce/dispatch-and-chill [:chat.ui/show-profile chat-id] - 1000)}) - - :left-section {:on-press #(do - (rf/dispatch [:chat/close]) - (rf/dispatch [:navigate-back])) - :icon :i/arrow-left - :accessibility-label :back-button} - - :right-section-buttons [{:on-press #() - :style {:border-width 1 - :border-color :red} - :icon :i/options - :accessibility-label :options-button}]}])) - (defn chat-render [] - (let [;;NOTE: we want to react only on these fields, do not use full chat map here - {:keys [chat-id contact-request-state group-chat able-to-send-message?] :as chat} - (rf/sub [:chats/current-chat-chat-view]) - insets (safe-area/get-insets)] - [rn/keyboard-avoiding-view - {:style {:position :relative :flex 1} - :keyboardVerticalOffset (- (:bottom insets))} - [page-nav] - [pin.banner/banner chat-id] - [messages.list/messages-list chat insets] - (if-not able-to-send-message? - [contact-requests.bottom-drawer/view chat-id contact-request-state group-chat] - [:f> composer/composer insets])])) - + (let [{:keys [chat-id + contact-request-state + group-chat + able-to-send-message?] + :as chat} (rf/sub [:chats/current-chat-chat-view])] + [messages.list/messages-list + {:cover-bg-color (colors/custom-color :turquoise 50 20) + :chat chat + :header-comp (fn [{:keys [scroll-y]}] + [messages.navigation/navigation-view {:scroll-y scroll-y}]) + :footer-comp (fn [{:keys [insets]}] + [rn/view + (if-not able-to-send-message? + [contact-requests.bottom-drawer/view chat-id contact-request-state group-chat] + [:f> composer/composer insets])])}])) (defn chat []