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
This commit is contained in:
Alexander 2023-06-01 16:08:47 +01:00 committed by GitHub
parent adb50fa0ee
commit 396ee208bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 544 additions and 165 deletions

View File

@ -103,7 +103,7 @@
(rf/defn messages-loaded (rf/defn messages-loaded
"Loads more messages for current chat" "Loads more messages for current chat"
{:events [::messages-loaded]} {: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?]) (when-not (and (get-in db [:pagination-info chat-id :messages-initialized?])
(not= session-id (not= session-id
(get-in db [:pagination-info chat-id :messages-initialized?]))) (get-in db [:pagination-info chat-id :messages-initialized?])))
@ -141,6 +141,8 @@
[:pagination-info chat-id [:pagination-info chat-id
:cursor-clock-value]) :cursor-clock-value])
clock-value (when cursor (cursor->clock-value cursor))] clock-value (when cursor (cursor->clock-value cursor))]
(when on-loaded
(on-loaded (count new-messages)))
{:db (-> db {:db (-> db
(update-in [:pagination-info chat-id :cursor-clock-value] (update-in [:pagination-info chat-id :cursor-clock-value]
#(if (and (seq cursor) (or (not %) (< clock-value %))) #(if (and (seq cursor) (or (not %) (< clock-value %)))
@ -161,7 +163,7 @@
(rf/defn load-more-messages (rf/defn load-more-messages
{:events [:chat.ui/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-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])]
(when (and (when (and
(not (get-in db [:pagination-info chat-id :all-loaded?])) (not (get-in db [:pagination-info chat-id :all-loaded?]))
@ -175,13 +177,13 @@
chat-id chat-id
cursor cursor
constants/default-number-of-messages 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 %])))))))) #(re-frame/dispatch [::failed-loading-messages chat-id session-id %]))))))))
(rf/defn load-more-messages-for-current-chat (rf/defn load-more-messages-for-current-chat
{:events [:chat.ui/load-more-messages-for-current-chat]} {:events [:chat.ui/load-more-messages-for-current-chat]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx} on-loaded]
(load-more-messages cofx (:current-chat-id db) false)) (load-more-messages cofx (:current-chat-id db) false on-loaded))
(rf/defn load-messages (rf/defn load-messages
[{:keys [db now] :as cofx} chat-id] [{: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]} :utils/dispatch-later [{:ms 50 :dispatch [:chat.ui/mark-all-read-pressed chat-id]}
(when-not (get-in cofx [:db :chats chat-id :public?]) (when-not (get-in cofx [:db :chats chat-id :public?])
{:ms 100 :dispatch [:pin-message/load-pin-messages chat-id]})]} {:ms 100 :dispatch [:pin-message/load-pin-messages chat-id]})]}
(load-more-messages chat-id true)))) (load-more-messages chat-id true nil))))

View File

@ -85,7 +85,7 @@
(let [show-delivery-state? (reagent/atom false)] (let [show-delivery-state? (reagent/atom false)]
(fn [{:keys [content-type quoted-message content outgoing outgoing-status] :as message-data} (fn [{:keys [content-type quoted-message content outgoing outgoing-status] :as message-data}
context context
keyboard-shown keyboard-shown?
message-reaction-view?] message-reaction-view?]
(let [first-image (first (:album message-data)) (let [first-image (first (:album message-data))
outgoing-status (if (= content-type constants/content-type-album) outgoing-status (if (= content-type constants/content-type-album)
@ -105,7 +105,7 @@
:style {:border-radius 16 :style {:border-radius 16
:opacity (if (and outgoing (= outgoing-status :sending)) 0.5 1)} :opacity (if (and outgoing (= outgoing-status :sending)) 0.5 1)}
:on-press (fn [] :on-press (fn []
(if (and platform/ios? @keyboard-shown) (if (and platform/ios? keyboard-shown?)
(rn/dismiss-keyboard!) (rn/dismiss-keyboard!)
(when (and outgoing (when (and outgoing
(not= outgoing-status :sending) (not= outgoing-status :sending)

View File

@ -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})

View File

@ -3,8 +3,12 @@
[quo2.core :as quo] [quo2.core :as quo]
[react-native.background-timer :as background-timer] [react-native.background-timer :as background-timer]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.hooks :as hooks]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent] [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.group :as chat.group]
[status-im.ui.screens.chat.message.gap :as message.gap] [status-im.ui.screens.chat.message.gap :as message.gap]
[status-im2.common.not-implemented :as not-implemented] [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.deleted.view :as content.deleted]
[status-im2.contexts.chat.messages.content.view :as message] [status-im2.contexts.chat.messages.content.view :as message]
[status-im2.contexts.chat.messages.list.state :as state] [status-im2.contexts.chat.messages.list.state :as state]
[utils.re-frame :as rf] [status-im2.contexts.chat.messages.list.style :as style]
[status-im2.contexts.chat.composer.constants :as composer.constants])) [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-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-key-fn [{:keys [message-id value]}] (or message-id value))
(defn list-ref [ref] (reset! messages-list-ref ref)) (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 (defn scroll-to-bottom
[] []
(some-> ^js @messages-list-ref (scroll-to-offset (- 0 style/messages-list-bottom-offset)))
(.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))
(defn on-scroll (defn on-scroll
[evt] [evt]
@ -40,11 +58,11 @@
(reset! show-floating-scroll-down-button reached-threshold?)))) (reset! show-floating-scroll-down-button reached-threshold?))))
(defn on-viewable-items-changed (defn on-viewable-items-changed
[evt] [e]
(when @messages-list-ref (when @messages-list-ref
(reset! state/first-not-visible-item (reset! state/first-not-visible-item
(when-let [last-visible-element (aget (oops/oget evt "viewableItems") (when-let [last-visible-element (aget (oops/oget e "viewableItems")
(dec (oops/oget evt "viewableItems.length")))] (dec (oops/oget e "viewableItems.length")))]
(let [index (oops/oget last-visible-element "index") (let [index (oops/oget last-visible-element "index")
;; Get first not visible element, if it's a datemark/gap ;; Get first not visible element, if it's a datemark/gap
;; we might unnecessarely add messages on receiving as ;; we might unnecessarely add messages on receiving as
@ -55,43 +73,152 @@
(= :message (:type first-not-visible))) (= :message (:type first-not-visible)))
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 (defn list-on-end-reached
[] [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 (if @state/scrolling
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat]) (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]) (background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat
(if platform/low-device? 700 200)))) 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 (def header-extrapolation-option
[evt] {:extrapolateLeft "clamp"
(reset! messages-view-height (oops/oget evt "nativeEvent.layout.height"))) :extrapolateRight "clamp"})
(defn list-footer (defn loading-view
[{:keys [chat-id]}] [chat-id]
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id]) (let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? chat-id])] all-loaded? (rf/sub [:chats/all-loaded? chat-id])
(when (or loading-messages? (not chat-id) (not all-loaded?)) messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
[rn/view {:style (when platform/android? {:scaleY -1})} loading-first-page? (= (count messages) 0)
[quo/skeleton @messages-view-height]]))) 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 (defn list-header
[{:keys [chat-id chat-type invitation-admin]}] [insets]
(when (= chat-type constants/private-group-chat-type) [rn/view
[rn/view {:style (when platform/android? {:scaleY -1})} {:background-color (colors/theme-colors colors/white colors/neutral-95)
[chat.group/group-chat-footer chat-id invitation-admin]])) :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 (defn render-fn
[{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _ [{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _
{:keys [context keyboard-shown]}] {:keys [context keyboard-shown?]}]
[rn/view {:style (when platform/android? {:scaleY -1})} [rn/view {:background-color (colors/theme-colors colors/white colors/neutral-95)}
(if (= type :datemark) (if (= type :datemark)
[quo/divider-date value] [quo/divider-date value]
(if (= content-type constants/content-type-gap) (if (= content-type constants/content-type-gap)
@ -100,71 +227,95 @@
[rn/view {:padding-horizontal 8} [rn/view {:padding-horizontal 8}
(if (or deleted? deleted-for-me?) (if (or deleted? deleted-for-me?)
[content.deleted/deleted-message message-data context] [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 (defn messages-list-content
[{:keys [chat-id] :as chat} insets keyboard-shown] [{:keys [chat insets scroll-y cover-bg-color keyboard-shown?]}]
(fn []
(let [context (rf/sub [:chats/current-chat-message-list-view-context]) (let [context (rf/sub [:chats/current-chat-message-list-view-context])
messages (rf/sub [:chats/raw-chat-messages-stream chat-id]) messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)])
recording? (rf/sub [:chats/recording?])] recording? (rf/sub [:chats/recording?])
[rn/view all-loaded? (rf/sub [:chats/all-loaded? (:chat-id chat)])]
{:style {:flex 1}} [rn/view {:style {:flex 1}}
;; NOTE: DO NOT use anonymous functions for handlers
[rn/flat-list [rn/flat-list
{:key-fn list-key-fn {:key-fn list-key-fn
:ref list-ref :ref list-ref
:header [list-header chat] :header [:<>
:footer [list-footer chat] (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 :data messages
:render-data {:context context :render-data {:context context
:keyboard-shown keyboard-shown} :keyboard-shown? keyboard-shown?}
:render-fn render-fn :render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed :on-viewable-items-changed on-viewable-items-changed
:on-end-reached list-on-end-reached :on-end-reached #(list-on-end-reached scroll-y)
:on-scroll-to-index-failed identity ; don't remove this :on-scroll-to-index-failed identity
:content-container-style {:padding-top (+ composer.constants/composer-default-height :content-container-style {:padding-bottom style/messages-list-bottom-offset}
(:bottom insets) :scroll-indicator-insets {:top (- composer.constants/composer-default-height 16)}
32)
:padding-bottom 16}
:scroll-indicator-insets {:top (+ composer.constants/composer-default-height
(:bottom insets))}
:keyboard-dismiss-mode :interactive :keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled :keyboard-should-persist-taps :handled
:on-momentum-scroll-begin state/start-scrolling :on-momentum-scroll-begin state/start-scrolling
:on-momentum-scroll-end state/stop-scrolling :on-momentum-scroll-end state/stop-scrolling
:scroll-event-throttle 16 :scroll-event-throttle 16
:on-scroll on-scroll :on-scroll (fn [event]
;; TODO https://github.com/facebook/react-native/issues/30034 (scroll-handler event scroll-y)
:inverted (when platform/ios? true) (when on-scroll
:style (when platform/android? {:scaleY -1}) (on-scroll event)))
:on-layout on-messages-view-layout :style {:background-color (if all-loaded?
:scroll-enabled (not recording?)}]]))) 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 (defn f-messages-list
;; value updates in the parent component, but does not get passed to the children. [{:keys [chat cover-bg-color header-comp footer-comp]}]
;; When using listeners and resetting the value on an atom it works. (let [insets (safe-area/get-insets)
(defn use-keyboard-visibility scroll-y (reanimated/use-shared-value 0)
[] {:keys [keyboard-height keyboard-shown]} (hooks/use-keyboard)]
(let [show-listener (atom nil)
hide-listener (atom nil)
shown? (atom nil)]
(rn/use-effect (rn/use-effect
(fn [] (fn []
(reset! show-listener (when keyboard-shown
(.addListener rn/keyboard "keyboardWillShow" #(reset! shown? true))) (reanimated/set-shared-value scroll-y
(reset! hide-listener (+ (reanimated/get-shared-value scroll-y)
(.addListener rn/keyboard "keyboardWillHide" #(reset! shown? false))) keyboard-height))))
(fn [] [keyboard-shown keyboard-height])
(.remove ^js @show-listener) [rn/keyboard-avoiding-view
(.remove ^js @hide-listener)))) {:style (style/keyboard-avoiding-container insets)
{:shown? shown?})) :keyboard-vertical-offset (- (:bottom insets))}
(defn- f-messages-list (when header-comp
[chat insets] [header-comp {:scroll-y scroll-y}])
(let [{keyboard-shown? :shown?} (use-keyboard-visibility)]
[messages-list-content chat insets keyboard-shown?])) [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 (defn messages-list
[chat insets] [props]
[:f> f-messages-list chat insets]) [:f> f-messages-list props])

View File

@ -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)})

View File

@ -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])

View File

@ -1,17 +1,14 @@
(ns status-im2.contexts.chat.messages.view (ns status-im2.contexts.chat.messages.view
(:require [quo2.core :as quo] (:require [quo2.foundations.colors :as colors]
[re-frame.db] [re-frame.db]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.constants :as constants]
[status-im2.contexts.chat.composer.view :as composer] [status-im2.contexts.chat.composer.view :as composer]
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as [status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as
contact-requests.bottom-drawer] contact-requests.bottom-drawer]
[status-im2.contexts.chat.messages.list.view :as messages.list] [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] [status-im2.navigation.state :as navigation.state]
[utils.debounce :as debounce]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn navigate-back-handler (defn navigate-back-handler
@ -24,62 +21,23 @@
;; and will call system back button action ;; and will call system back button action
true)) 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 (defn chat-render
[] []
(let [;;NOTE: we want to react only on these fields, do not use full chat map here (let [{:keys [chat-id
{:keys [chat-id contact-request-state group-chat able-to-send-message?] :as chat} contact-request-state
(rf/sub [:chats/current-chat-chat-view]) group-chat
insets (safe-area/get-insets)] able-to-send-message?]
[rn/keyboard-avoiding-view :as chat} (rf/sub [:chats/current-chat-chat-view])]
{:style {:position :relative :flex 1} [messages.list/messages-list
:keyboardVerticalOffset (- (:bottom insets))} {:cover-bg-color (colors/custom-color :turquoise 50 20)
[page-nav] :chat chat
[pin.banner/banner chat-id] :header-comp (fn [{:keys [scroll-y]}]
[messages.list/messages-list chat insets] [messages.navigation/navigation-view {:scroll-y scroll-y}])
:footer-comp (fn [{:keys [insets]}]
[rn/view
(if-not able-to-send-message? (if-not able-to-send-message?
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat] [contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]
[:f> composer/composer insets])])) [:f> composer/composer insets])])}]))
(defn chat (defn chat
[] []