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:
parent
adb50fa0ee
commit
396ee208bf
|
@ -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))))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})
|
|
@ -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
|
||||
[]
|
||||
[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])
|
||||
(background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat])
|
||||
(if platform/low-device? 700 200))))
|
||||
(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]}]
|
||||
(defn loading-view
|
||||
[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]])))
|
||||
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 []
|
||||
[{: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])
|
||||
recording? (rf/sub [:chats/recording?])]
|
||||
[rn/view
|
||||
{:style {:flex 1}}
|
||||
;; NOTE: DO NOT use anonymous functions for handlers
|
||||
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 [list-header chat]
|
||||
:footer [list-footer chat]
|
||||
: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}
|
||||
: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))}
|
||||
: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 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?)}]])))
|
||||
: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])
|
||||
|
|
|
@ -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)})
|
|
@ -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])
|
|
@ -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]
|
||||
(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])]))
|
||||
|
||||
[:f> composer/composer insets])])}]))
|
||||
|
||||
(defn chat
|
||||
[]
|
||||
|
|
Loading…
Reference in New Issue