Compare commits

...
Author SHA1 Message Date
alwx 3246ffba73 Fix 2023-03-03 22:04:55 +01:00
alwx 2f1a6cc94d Fix 2023-03-03 09:53:03 +01:00
alwx a5c872cc1f Another attempt 2023-03-03 09:24:43 +01:00
alwx 85d4cddf9e Animations for reverse list 2023-03-02 10:52:31 +01:00
alwx 3574fb206c More updates 2023-03-01 09:12:03 +01:00
alwx ce85a3102d More style update 2023-02-28 19:38:44 +01:00
alwx 595d0c7df0 Footer is back now 2023-02-28 13:20:32 +01:00
alwx 73b09af800 Better style 2023-02-28 13:08:24 +01:00
alwx ff11e10229 Updated top bar styles 2023-02-28 12:54:31 +01:00
alwx 1f245cbe78 Updated top bar styles 2023-02-28 11:43:13 +01:00
alwx 68adf56f49 Top bar chat UI 2023-02-27 20:21:21 +01:00
alwx 3574911986 Top bar chat UI 2023-02-27 18:26:50 +01:00
5 changed files with 399 additions and 100 deletions
@@ -19,37 +19,34 @@
:border-radius 10
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/white-opa-40 colors/neutral-80-opa-40)
:position :absolute
:top 56
:z-index 3}
:background-color (colors/theme-colors colors/white-opa-40 colors/neutral-80-opa-40)}
position))
(defn blur-view
[animation]
(reanimated/apply-animations-to-style
{:opacity animation}
{:opacity animation}
{:position :absolute
:top 0
:left 0
:right 0
:height 100
:width "100%"
:display :flex
:flex-direction :row
:z-index 2
:overflow :hidden}))
(defn entity-picture
[animation]
(reanimated/apply-animations-to-style
{:width animation
:height animation}
{}
{:transform [{:scale 1}]
:border-radius 40
:position :absolute
:bottom 42
:bottom 72
:left 20
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/white colors/neutral-100)
:overflow :hidden}))
(defn header-bottom-part
@@ -57,18 +54,18 @@
(reanimated/apply-animations-to-style
{:border-top-right-radius animation
:border-top-left-radius animation}
{:position :absolute
:bottom 0
:height 86
:left 0
:right 0
:background-color (colors/theme-colors colors/white colors/neutral-100)}))
{:background-color (colors/theme-colors colors/white colors/neutral-100)
:margin-top -8
:shadow-offset {:width 0
:height -16}
:shadow-radius 12
:shadow-opacity 1
:shadow-color "#09101C14"}))
(defn header-comp
[y-animation opacity-animation]
(reanimated/apply-animations-to-style
;; here using `left` won't work on Android, so we are using `translateX`
{:transform [{:translateX (reanimated/use-shared-value 64)} {:translateY y-animation}]
{:transform [{:translateY y-animation}]
:opacity opacity-animation}
{:position :absolute
:z-index 3}))
{:flex 1}))
@@ -17,84 +17,110 @@
(def blur-view-height 100)
(def threshold (- header-height blur-view-height))
(defn interpolate
[value input-range output-range]
(reanimated/interpolate value
input-range
output-range
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"}))
(defn scroll-handler
[event initial-y scroll-y]
(let [current-y (- (oops/oget event "nativeEvent.contentOffset.y") initial-y)]
(reanimated/set-shared-value scroll-y current-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") initial-y)]
(reanimated/set-shared-value scroll-y (- content-size-y current-y))))
(defn header
[{:keys [theme-color display-picture-comp cover-uri title-comp]} top-inset scroll-y]
(let [input-range [0 (* threshold 0.33)]
picture-scale-down 0.4
size-animation (interpolate scroll-y input-range [80 (* 80 picture-scale-down)])
image-animation (interpolate scroll-y input-range [72 (* 72 picture-scale-down)])
border-animation (interpolate scroll-y input-range [12 0])]
[{:keys [theme-color cover-uri cover-bg-color title-comp]} top-inset scroll-y]
(let [input-range [0 (* threshold 0.33)]
border-animation (reanimated/interpolate scroll-y input-range [12 0]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
[rn/view
{:style {:height header-height
:background-color (or theme-color (colors/theme-colors colors/white colors/neutral-95))
{:style {:background-color (or theme-color (colors/theme-colors colors/white colors/neutral-95))
:margin-top (when platform/ios? (- top-inset))}}
(when cover-uri
[fast-image/fast-image
{:style {:width "100%"
:height cover-height}
:source {:uri cover-uri}}])
(when cover-bg-color
[rn/view
{:style {:width "100%"
:height cover-height
:background-color cover-bg-color}}])
[reanimated/view {:style (style/header-bottom-part border-animation)}
[title-comp]]
[reanimated/view {:style (style/entity-picture size-animation)}
[display-picture-comp image-animation]]]))
[title-comp]]]))
(defn animated-header-list
[{:keys [header-comp main-comp] :as parameters}]
[{:keys [header-comp main-comp footer-comp title-comp] :as parameters}]
[safe-area/consumer
(fn [insets]
(let [window-height (:height (rn/get-window))
(let [window-height (:height (rn/get-window))
status-bar-height (rn/status-bar-height)
bottom-inset (:bottom insets)
;; view height calculation is different because window height is different on iOS and Android:
view-height (if platform/ios?
(- window-height bottom-inset)
(+ window-height status-bar-height))
initial-y (if platform/ios? (- (:top insets)) 0)]
bottom-inset (:bottom insets)
initial-y (if platform/ios? (- (:top insets)) 0)]
[:f>
(fn []
(let [scroll-y (reanimated/use-shared-value initial-y)
opacity-animation (interpolate scroll-y
[(* threshold 0.33) (* threshold 0.66)]
[0 1])
translate-animation (interpolate scroll-y [(* threshold 0.66) threshold] [100 56])
title-opacity-animation (interpolate scroll-y [(* threshold 0.66) threshold] [0 1])]
[rn/view {:style (style/container-view view-height)}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
:style (style/button-container {:left 20})}
[quo/icon :i/arrow-left {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
[rn/touchable-opacity
{:active-opacity 1
:style (style/button-container {:right 20})}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
(let [scroll-y (reanimated/use-shared-value initial-y)
opacity-animation (reanimated/interpolate scroll-y
[(* threshold 0.33) (* threshold 0.66)]
[0 1]
{:extrapolateLeft "clamp"
:extrapolateRight "extend"})
translate-animation (reanimated/interpolate scroll-y [(* threshold 0.66) threshold] [50 0]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
title-opacity-animation (reanimated/interpolate scroll-y [(* threshold 0.66) threshold] [0 1]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
[rn/keyboard-avoiding-view
{:style {:position :absolute
:flex 1
:top 0
:left 0
:height window-height
:bottom 0
:right 0}
:keyboardVerticalOffset (- bottom-inset)}
[reanimated/blur-view
{:blurAmount 32
:blurType :light
:overlayColor (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-view opacity-animation)}
:style (style/blur-view opacity-animation)}]
[rn/view {:style {:position :absolute
:top 56
:left 0
:right 0
:padding-bottom 12
:width "100%"
:display :flex
:flex-direction :row
:z-index 2
:overflow :hidden}}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
: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/header-comp translate-animation title-opacity-animation)}
[header-comp]]]
[reanimated/flat-list
{:data [nil]
:render-fn main-comp
:key-fn str
:header (reagent/as-element (header parameters (:top insets) scroll-y))
[header-comp]]
[rn/touchable-opacity
{:active-opacity 1
:style (style/button-container {:margin-right 20})}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]]
(when main-comp
[main-comp])
#_[reanimated/flat-list
{:data [nil]
:render-fn main-comp
:key-fn str
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})
:footer (reagent/as-element (header parameters (:top insets) scroll-y))
;; TODO: https://github.com/status-im/status-mobile/issues/14924
:scroll-event-throttle 8
:on-scroll (fn [event] (scroll-handler event initial-y scroll-y))}]]))]))])
:scroll-event-throttle 8
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:on-scroll (fn [event] (scroll-handler event initial-y scroll-y))}]
(when footer-comp
(footer-comp insets))]))]))])
@@ -223,7 +223,9 @@
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:height parent-height}
{})}
{:position :absolute
:bottom 0
:width "100%"})}
;;;;input
[gesture/gesture-detector {:gesture bottom-sheet-gesture}
[reanimated/view
@@ -1,10 +1,14 @@
(ns status-im2.contexts.chat.messages.list.view
(:require [oops.core :as oops]
[quo2.core :as quo]
[react-native.safe-area :as safe-area]
[react-native.background-timer :as background-timer]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[quo2.foundations.colors :as colors]
[quo2.components.avatars.user-avatar :as user-avatar]
[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]
@@ -13,7 +17,10 @@
[status-im2.contexts.chat.messages.content.view :as message]
[status-im2.contexts.chat.messages.list.state :as state]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[status-im.ui.components.fast-image :as fast-image]
[quo2.components.animated-header-flatlist.style :as style]))
(defonce messages-list-ref (atom nil))
@@ -101,13 +108,84 @@
[evt]
(reset! messages-view-height (oops/oget evt "nativeEvent.layout.height")))
;; TODO(alwx): revisit
(def header-height 234)
(def cover-height 192)
(def blur-view-height 100)
(def threshold (- header-height blur-view-height))
(defn header
[{:keys [theme-color cover-uri cover-bg-color title-comp]} top-inset scroll-y]
(let [input-range [0 (* threshold 0.33)]
border-animation (reanimated/interpolate scroll-y input-range [12 0]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
[rn/view
{:style {:background-color (or theme-color (colors/theme-colors colors/white colors/neutral-95))
:margin-top (when platform/ios? (- top-inset))}}
(when cover-uri
[fast-image/fast-image
{:style {:width "100%"
:height cover-height}
:source {:uri cover-uri}}])
(when cover-bg-color
[rn/view
{:style {:width "100%"
:height cover-height
:background-color cover-bg-color}}])
[reanimated/view {:style (style/header-bottom-part border-animation)}
[title-comp]]]))
(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]])))
[{:keys [chat-id chat-name emoji chat-type group-chat]} top-inset scroll-y cover-bg-color]
(let [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))
loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? 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]))]
[:f>
(fn []
(let [_ (js/console.log "ALWX scroll-y" scroll-y)
border-animation (reanimated/interpolate scroll-y [0 (* threshold 0.33)] [12 0]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
[:<>
[rn/view
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
:margin-top (when platform/ios? (- top-inset))}}
#_(when cover-uri
[fast-image/fast-image
{:style {:width "100%"
:height cover-height}
:source {:uri cover-uri}}])
(when cover-bg-color
[rn/view
{:style {:width "100%"
:height cover-height
:background-color cover-bg-color}}])
[reanimated/view {:style (style/header-bottom-part border-animation)}
[rn/view {:style {:margin-top -36
:margin-left 20
:margin-right 20
:margin-bottom 20}}
[user-avatar/user-avatar {:full-name display-name
:online? online?
:profile-picture photo-path
:size :big}]
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:margin-top 12}
:number-of-lines 1}
display-name]
[quo/text {:style {:margin-top 8}}
"Web 3.0 Designer @ethstatus • DJ • Producer • Dad • YouTuber."]]]]
(when (or loading-messages? (not chat-id) (not all-loaded?))
[rn/view {:style (when platform/android? {:scaleY -1})}
[quo/skeleton @messages-view-height]])]))]))
(defn list-header
[{:keys [chat-id chat-type invitation-admin]}]
@@ -131,7 +209,8 @@
(defn messages-list
[{:keys [chat
pan-responder
show-input?]}]
show-input?
header-comp]}]
(let [{:keys [group-chat chat-id public? community-id admins]} chat
messages (rf/sub [:chats/raw-chat-messages-stream
chat-id])
@@ -145,7 +224,7 @@
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:footer [list-footer chat header-comp]
:data messages
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
@@ -172,7 +251,7 @@
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})
:on-layout on-messages-view-layout})]
[quo/floating-shell-button
#_[quo/floating-shell-button
(merge {:jump-to
{:on-press #(do
(rf/dispatch [:chat/close true])
@@ -182,3 +261,124 @@
{:scroll-to-bottom {:on-press scroll-to-bottom}}))
{:position :absolute
:bottom 6}]]))
(defn scroll-handler
[event initial-y 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") initial-y)]
(reanimated/set-shared-value scroll-y (- content-size-y current-y))))
(defn messages-list-with-animated-header
[{:keys [chat show-input? cover-bg-color header-comp footer-comp]}]
(let [{:keys [group-chat chat-id public? community-id admins]} chat
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
bottom-space 15]
[safe-area/consumer
(fn [insets]
(let [window-height (:height (rn/get-window))
status-bar-height (rn/status-bar-height)
bottom-inset (:bottom insets)
initial-y (if platform/ios? (- (:top insets)) 0)]
[:f>
(fn []
(let [scroll-y (reanimated/use-shared-value initial-y)
opacity-animation (reanimated/interpolate scroll-y
[(* threshold 0.33) (* threshold 0.66)]
[0 1]
{:extrapolateLeft "clamp"
:extrapolateRight "extend"})
translate-animation (reanimated/interpolate scroll-y [(* threshold 0.66) threshold] [50 0]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
title-opacity-animation (reanimated/interpolate scroll-y [(* threshold 0.66) threshold] [0 1]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
[rn/keyboard-avoiding-view
{:style {:position :absolute
:flex 1
:top 0
:left 0
:height window-height
:bottom 0
:right 0}
:keyboardVerticalOffset (- bottom-inset)}
[reanimated/blur-view
{:blurAmount 32
:blurType :light
:overlayColor (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-view opacity-animation)}]
[rn/view {:style {:position :absolute
:top 56
:left 0
:right 0
:padding-bottom 12
:width "100%"
:display :flex
:flex-direction :row
:z-index 2
:overflow :hidden}}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
: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/header-comp translate-animation title-opacity-animation)}
[header-comp]]
[rn/touchable-opacity
{:active-opacity 1
:style (style/button-container {:margin-right 20})}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]]
[reanimated/flat-list
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer (reagent/as-element (list-footer chat (:top insets) scroll-y cover-bg-color))
:data messages
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
: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 (+ bottom-space 32)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
:scrollEventThrottle 16
:on-scroll (fn [event]
(scroll-handler event initial-y scroll-y)
(when on-scroll
(on-scroll event)))
;;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}]
#_[reanimated/flat-list
{:data [nil]
:render-fn main-comp
:key-fn str
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})
:footer (reagent/as-element (header parameters (:top insets) scroll-y))
;; TODO: https://github.com/status-im/status-mobile/issues/14924
:scroll-event-throttle 8
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:on-scroll (fn [event] (scroll-handler event initial-y scroll-y))}]
(when footer-comp
(footer-comp insets))]))]))]))
+88 -14
View File
@@ -2,15 +2,16 @@
(:require [quo2.core :as quo]
[re-frame.db]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.contexts.chat.messages.composer.view :as composer]
[status-im2.constants :as constants]
[quo2.components.avatars.user-avatar :as user-avatar]
[status-im2.contexts.chat.messages.list.view :as messages.list]
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as
contact-requests.bottom-drawer]
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner]
[status-im2.navigation.state :as navigation.state]
[quo2.components.animated-header-flatlist.view :as animated-header-list]
[utils.debounce :as debounce]
[utils.re-frame :as rf]))
@@ -64,19 +65,92 @@
(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 show-input?] :as chat}
(rf/sub [:chats/current-chat-chat-view])]
[safe-area/consumer
(fn [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 chat :show-input? show-input?}]
(if-not show-input?
[contact-requests.bottom-drawer/view chat-id contact-request-state]
[composer/composer chat-id insets])])]))
{:keys [chat-id contact-request-state show-input? group-chat chat-id chat-name emoji chat-type] :as chat} (rf/sub [:chats/current-chat-chat-view])
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]))]
[messages.list/messages-list-with-animated-header
{:cover-bg-color "#2A799B33"
:chat chat
:show-input? show-input?
:header-comp (fn []
[rn/view
{:style {:flex-direction :row
:align-items :center
:margin-left 8
:margin-right 8}}
[user-avatar/user-avatar {:full-name display-name
:online? online?
:profile-picture photo-path
:size :small}]
[quo/text {:weight :semi-bold
:number-of-lines 1
:style {:margin-left 8
:flex 1}}
display-name]])
:footer-comp (fn [insets]
(if-not show-input?
[contact-requests.bottom-drawer/view chat-id contact-request-state]
[composer/composer chat-id insets]))}]
#_[animated-header-list/animated-header-list
{:cover-bg-color "#2A799B33"
:chat chat
:show-input? show-input?
:header-comp (fn []
[rn/view
{:style {:flex-direction :row
:align-items :center
:margin-left 8
:margin-right 8}}
[user-avatar/user-avatar {:full-name display-name
:online? online?
:profile-picture photo-path
:size :small}]
[quo/text {:weight :semi-bold
:number-of-lines 1
:style {:margin-left 8
:flex 1}}
display-name]])
:title-comp (fn []
[rn/view {:style {:margin-top -36
:margin-left 20
:margin-right 20
:margin-bottom 20}}
[user-avatar/user-avatar {:full-name display-name
:online? online?
:profile-picture photo-path
:size :big}]
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:margin-top 12}
:number-of-lines 1}
display-name]
[quo/text {:style {:margin-top 8}}
"Web 3.0 Designer @ethstatus • DJ • Producer • Dad • YouTuber."]])
:main-comp (fn []
[messages.list/messages-list
{:chat chat
:show-input? show-input?}])
:footer-comp (fn [insets]
(if-not show-input?
[contact-requests.bottom-drawer/view chat-id contact-request-state]
[composer/composer chat-id 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 chat :show-input? show-input?}]
(if-not show-input?
[contact-requests.bottom-drawer/view chat-id contact-request-state]
[composer/composer chat-id insets])]
(defn chat
[]