[#16709] chat home banner animation (#16823)

* Small typo fix
* Move header-spacing & empty-state to common.home namespace
* Create common animated banner component for chats and communities
* Add animated banner to chats tab
This commit is contained in:
Ulises Manuel Cárdenas 2023-08-03 13:29:44 -06:00 committed by GitHub
parent f6ce63734c
commit 7ad378e9c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 315 additions and 254 deletions

View File

@ -32,7 +32,7 @@
(defn section-list (defn section-list
"A wrapper for SectionList. "A wrapper for SectionList.
To render something on empty sections, use renderSectionFooter and conditionaly To render something on empty sections, use renderSectionFooter and conditionally
render on empty data render on empty data
See https://facebook.github.io/react-native/docs/sectionlist.html" See https://facebook.github.io/react-native/docs/sectionlist.html"
[{:keys [sections render-section-header-fn render-section-footer-fn style] :as props}] [{:keys [sections render-section-header-fn render-section-footer-fn style] :as props}]

View File

@ -0,0 +1,63 @@
(ns status-im2.common.home.banner.style
(:require [react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]))
(def ^:private card-height (+ 56 16))
(def ^:private max-scroll (+ card-height 8))
(def fill-space
{:position :absolute
:top 0
:right 0
:left 0
:bottom 0})
(defn- animated-card-translation-y
[scroll-shared-value]
(reanimated/interpolate scroll-shared-value [0 max-scroll] [0 (- max-scroll)] :clamp))
(defn banner-card-blur-layer
[scroll-shared-value]
(reanimated/apply-animations-to-style
{:transform [{:translate-y (animated-card-translation-y scroll-shared-value)}]}
{:overflow (if platform/ios? :visible :hidden)
:z-index 1
:position :absolute
:top 0
:right 0
:left 0
:height (+ (safe-area/get-top) 244)}))
(defn banner-card-hiding-layer
[]
{:z-index 2
:position :absolute
:top 0
:right 0
:left 0
:padding-top (safe-area/get-top)})
(def animated-banner-card-container {:overflow :hidden})
(defn animated-banner-card
[scroll-shared-value]
(reanimated/apply-animations-to-style
{:opacity (reanimated/interpolate scroll-shared-value [0 card-height] [1 0] :clamp)
:transform [{:translate-y (animated-card-translation-y scroll-shared-value)}]}
{}))
(defn banner-card-tabs-layer
[scroll-shared-value]
(reanimated/apply-animations-to-style
{:transform [{:translate-y (animated-card-translation-y scroll-shared-value)}]}
{:z-index 3
:position :absolute
:top (+ (safe-area/get-top) 192)
:right 0
:left 0}))
(def banner-card-tabs
{:padding-horizontal 20
:padding-top 8
:padding-bottom 12})

View File

@ -0,0 +1,78 @@
(ns status-im2.common.home.banner.view
(:require [oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im2.common.home.banner.style :as style]
[status-im2.common.home.view :as common.home]
[utils.re-frame :as rf]))
(defn- reset-banner-animation
[scroll-shared-value]
(reanimated/animate-shared-value-with-timing scroll-shared-value 0 200 :easing3))
(defn- reset-scroll
[scroll-ref]
(cond
(.-scrollToLocation scroll-ref)
(oops/ocall! scroll-ref "scrollToLocation" #js {:itemIndex 0 :sectionIndex 0 :viewOffset 0})
(.-scrollToOffset scroll-ref)
(oops/ocall! scroll-ref "scrollToOffset" #js {:offset 0})))
(defn- banner-card-blur-layer
[scroll-shared-value]
(let [open-sheet? (-> (rf/sub [:bottom-sheet]) :sheets seq)]
[reanimated/view {:style (style/banner-card-blur-layer scroll-shared-value)}
[blur/view
{:style style/fill-space
:blur-amount (if platform/ios? 20 10)
:blur-type (theme/theme-value (if platform/ios? :light :xlight) :dark)
:overlay-color (if open-sheet?
(colors/theme-colors colors/white colors/neutral-95-opa-70)
(theme/theme-value nil colors/neutral-95-opa-70))}]]))
(defn- banner-card-hiding-layer
[{:keys [title-props card-props scroll-shared-value]}]
(let [customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style (style/banner-card-hiding-layer)}
[common.home/top-nav {:type :grey}]
[common.home/title-column (assoc title-props :customization-color customization-color)]
[rn/view {:style style/animated-banner-card-container}
[reanimated/view {:style (style/animated-banner-card scroll-shared-value)}
[quo/discover-card card-props]]]]))
(defn- banner-card-tabs-layer
[{:keys [selected-tab tabs on-tab-change scroll-ref scroll-shared-value]}]
[reanimated/view {:style (style/banner-card-tabs-layer scroll-shared-value)}
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:style style/banner-card-tabs
:size 32
:default-active selected-tab
:data tabs
:on-change (fn [tab]
(reset-banner-animation scroll-shared-value)
(some-> scroll-ref
deref
reset-scroll)
(on-tab-change tab))}]])
(defn animated-banner
[{:keys [scroll-ref tabs selected-tab on-tab-change scroll-shared-value content]}]
[:<>
[:f> banner-card-blur-layer scroll-shared-value]
[:f> banner-card-hiding-layer (assoc content :scroll-shared-value scroll-shared-value)]
[:f> banner-card-tabs-layer
{:scroll-shared-value scroll-shared-value
:selected-tab selected-tab
:tabs tabs
:on-tab-change on-tab-change
:scroll-ref scroll-ref}]])
(defn set-scroll-shared-value
[{:keys [shared-value scroll-input]}]
(reanimated/set-shared-value shared-value scroll-input))

View File

@ -1,4 +1,5 @@
(ns status-im2.common.home.style) (ns status-im2.common.home.style
(:require [react-native.safe-area :as safe-area]))
(def title-column (def title-column
{:flex-direction :row {:flex-direction :row
@ -44,3 +45,16 @@
(def top-nav-container (def top-nav-container
{:height 56}) {:height 56})
(def header-height 245)
(defn header-spacing
[]
{:height (+ header-height (safe-area/get-top))})
(defn empty-state-container
[]
{:flex 1
:margin-top (+ header-height (safe-area/get-top))
:margin-bottom 44
:justify-content :center})

View File

@ -3,12 +3,12 @@
[quo2.core :as quo] [quo2.core :as quo]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[status-im2.common.home.style :as style]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im2.common.home.style :as style]
[status-im2.common.plus-button.view :as plus-button] [status-im2.common.plus-button.view :as plus-button]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[utils.re-frame :as rf] [utils.debounce :refer [dispatch-and-chill]]
[utils.debounce :refer [dispatch-and-chill]])) [utils.re-frame :as rf]))
(defn title-column (defn title-column
[{:keys [label handler accessibility-label customization-color]}] [{:keys [label handler accessibility-label customization-color]}]
@ -130,3 +130,18 @@
[rn/view {:style (merge style/top-nav-container style)} [rn/view {:style (merge style/top-nav-container style)}
[left-section {:avatar avatar}] [left-section {:avatar avatar}]
[right-section {:button-type type :button-background background :search? search?}]])) [right-section {:button-type type :button-background background :search? search?}]]))
(defn header-spacing
[]
[rn/view {:style (style/header-spacing)}])
(defn empty-state-image
[{:keys [selected-tab tab->content]}]
(let [{:keys [image title description]} (tab->content selected-tab)
customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style (style/empty-state-container)}
[quo/empty-state
{:customization-color customization-color
:image image
:title title
:description description}]]))

View File

@ -1,5 +1,6 @@
(ns status-im2.contexts.chat.home.style (ns status-im2.contexts.chat.home.style
(:require [react-native.platform :as platform])) (:require [react-native.platform :as platform]
[react-native.safe-area :as safe-area]))
(def tabs (def tabs
{:padding-horizontal 20 {:padding-horizontal 20
@ -14,24 +15,11 @@
:bottom 0}) :bottom 0})
(defn blur-container (defn blur-container
[top] []
{:overflow (if platform/ios? :visible :hidden) {:overflow (if platform/ios? :visible :hidden)
:position :absolute :position :absolute
:z-index 1 :z-index 1
:top 0 :top 0
:right 0 :right 0
:left 0 :left 0
:padding-top top}) :padding-top (safe-area/get-top)})
(def header-height 245)
(defn header-space
[top]
{:height (+ header-height top)})
(defn empty-content-container
[top]
{:flex 1
:margin-top (+ header-height top)
:margin-bottom 44
:justify-content :center})

View File

@ -1,21 +1,18 @@
(ns status-im2.contexts.chat.home.view (ns status-im2.contexts.chat.home.view
(:require [quo2.core :as quo] (:require [oops.core :as oops]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme] [quo2.theme :as theme]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[react-native.blur :as blur]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform] [react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im2.common.contact-list-item.view :as contact-list-item] [status-im2.common.contact-list-item.view :as contact-list-item]
[status-im2.common.contact-list.view :as contact-list] [status-im2.common.contact-list.view :as contact-list]
[status-im2.common.home.actions.view :as actions] [status-im2.common.home.actions.view :as actions]
[status-im2.common.home.banner.view :as common.home.banner]
[status-im2.common.home.view :as common.home] [status-im2.common.home.view :as common.home]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im2.contexts.chat.actions.view :as home.sheet] [status-im2.contexts.chat.actions.view :as chat.actions.view]
[status-im2.contexts.chat.home.chat-list-item.view :as chat-list-item] [status-im2.contexts.chat.home.chat-list-item.view :as chat-list-item]
[status-im2.contexts.chat.home.contact-request.view :as contact-request] [status-im2.contexts.chat.home.contact-request.view :as contact-request]
[status-im2.contexts.chat.home.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -30,50 +27,45 @@
(filter k) (filter k)
(sort-by :timestamp >)))) (sort-by :timestamp >))))
(defn empty-state-content (def empty-state-content
[selected-tab] #:tab{:contacts
(case selected-tab {:title (i18n/label :t/no-contacts)
:tab/contacts :description (i18n/label :t/no-contacts-description)
{:title (i18n/label :t/no-contacts) :image (resources/get-image
:description (i18n/label :t/no-contacts-description) (theme/theme-value :no-contacts-light :no-contacts-dark))}
:image (resources/get-image :groups
(theme/theme-value :no-contacts-light :no-contacts-dark))} {:title (i18n/label :t/no-group-chats)
:tab/groups :description (i18n/label :t/no-group-chats-description)
{:title (i18n/label :t/no-group-chats) :image (resources/get-image
:description (i18n/label :t/no-group-chats-description) (theme/theme-value :no-group-chats-light :no-group-chats-dark))}
:image (resources/get-image :recent
(theme/theme-value :no-group-chats-light :no-group-chats-dark))} {:title (i18n/label :t/no-messages)
:tab/recent :description (i18n/label :t/no-messages-description)
{:title (i18n/label :t/no-messages) :image (resources/get-image
:description (i18n/label :t/no-messages-description) (theme/theme-value :no-messages-light :no-messages-dark))}})
:image (resources/get-image
(theme/theme-value :no-messages-light :no-messages-dark))}
nil))
(defn empty-state
[{:keys [selected-tab top]}]
(let [{:keys [image title description]} (empty-state-content selected-tab)]
[rn/view {:style (style/empty-content-container top)}
[quo/empty-state
{:image image
:title title
:description description}]]))
(defn chats (defn chats
[selected-tab top] [{:keys [selected-tab set-scroll-ref scroll-shared-value]}]
(let [unfiltered-items (rf/sub [:chats-stack-items]) (let [unfiltered-items (rf/sub [:chats-stack-items])
items (filter-and-sort-items-by-tab selected-tab unfiltered-items)] items (filter-and-sort-items-by-tab selected-tab unfiltered-items)]
(if (empty? items) (if (empty? items)
[empty-state {:top top :selected-tab selected-tab}] [common.home/empty-state-image
[rn/flat-list {:selected-tab selected-tab
{:key-fn #(or (:chat-id %) (:public-key %) (:id %)) :tab->content empty-state-content}]
[reanimated/flat-list
{:ref set-scroll-ref
:key-fn #(or (:chat-id %) (:public-key %) (:id %))
:content-inset-adjustment-behavior :never :content-inset-adjustment-behavior :never
:header [rn/view {:style (style/header-space top)}] :header [common.home/header-spacing]
:get-item-layout get-item-layout :get-item-layout get-item-layout
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats]) :on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
:keyboard-should-persist-taps :always :keyboard-should-persist-taps :always
:data items :data items
:render-fn chat-list-item/chat-list-item}]))) :render-fn chat-list-item/chat-list-item
:scroll-event-throttle 8
:on-scroll #(common.home.banner/set-scroll-shared-value
{:scroll-input (oops/oget % "nativeEvent.contentOffset.y")
:shared-value scroll-shared-value})}])))
(defn contact-item-render (defn contact-item-render
[{:keys [public-key] :as item}] [{:keys [public-key] :as item}]
@ -89,23 +81,30 @@
item])) item]))
(defn contacts (defn contacts
[pending-contact-requests top] [{:keys [pending-contact-requests set-scroll-ref scroll-shared-value]}]
(let [items (rf/sub [:contacts/active-sections])] (let [items (rf/sub [:contacts/active-sections])]
(if (and (empty? items) (empty? pending-contact-requests)) (if (and (empty? items) (empty? pending-contact-requests))
[empty-state {:top top :selected-tab :tab/contacts}] [common.home/empty-state-image
{:selected-tab :tab/contacts
:tab->content empty-state-content}]
[rn/section-list [rn/section-list
{:key-fn :public-key {:ref set-scroll-ref
:key-fn :public-key
:get-item-layout get-item-layout :get-item-layout get-item-layout
:content-inset-adjustment-behavior :never :content-inset-adjustment-behavior :never
:header [:<> :header [:<>
[rn/view {:style (style/header-space top)}] [common.home/header-spacing]
(when (seq pending-contact-requests) (when (seq pending-contact-requests)
[contact-request/contact-requests [contact-request/contact-requests
pending-contact-requests])] pending-contact-requests])]
:sections items :sections items
:sticky-section-headers-enabled false :sticky-section-headers-enabled false
:render-section-header-fn contact-list/contacts-section-header :render-section-header-fn contact-list/contacts-section-header
:render-fn contact-item-render}]))) :render-fn contact-item-render
:scroll-event-throttle 8
:on-scroll #(common.home.banner/set-scroll-shared-value
{:scroll-input (oops/oget % "nativeEvent.contentOffset.y")
:shared-value scroll-shared-value})}])))
(defn get-tabs-data (defn get-tabs-data
[dot?] [dot?]
@ -116,41 +115,39 @@
:accessibility-label :tab-contacts :accessibility-label :tab-contacts
:notification-dot? dot?}]) :notification-dot? dot?}])
(def ^:private banner-data
{:title-props
{:label (i18n/label :t/messages)
:handler #(rf/dispatch
[:show-bottom-sheet {:content chat.actions.view/new-chat}])
:accessibility-label :new-chat-button}
:card-props
{:banner (resources/get-image :invite-friends)
:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}})
(defn home (defn home
[] []
(let [pending-contact-requests (rf/sub [:activity-center/pending-contact-requests]) (let [scroll-ref (atom nil)
selected-tab (or (rf/sub [:messages-home/selected-tab]) :tab/recent) set-scroll-ref #(reset! scroll-ref %)]
customization-color (rf/sub [:profile/customization-color]) (fn []
top (safe-area/get-top)] (let [pending-contact-requests (rf/sub [:activity-center/pending-contact-requests])
[:<> selected-tab (or (rf/sub [:messages-home/selected-tab]) :tab/recent)
(if (= selected-tab :tab/contacts) scroll-shared-value (reanimated/use-shared-value 0)]
[contacts pending-contact-requests top] [:<>
[chats selected-tab top]) (if (= selected-tab :tab/contacts)
[rn/view {:style (style/blur-container top)} [contacts
(let [{:keys [sheets]} (rf/sub [:bottom-sheet])] {:pending-contact-requests pending-contact-requests
[blur/view :set-scroll-ref set-scroll-ref
{:blur-amount (if platform/ios? 20 10) :scroll-shared-value scroll-shared-value}]
:blur-type (if (colors/dark?) :dark (if platform/ios? :light :xlight)) [chats
:style style/blur {:selected-tab selected-tab
:overlay-color (if (seq sheets) :set-scroll-ref set-scroll-ref
(theme/theme-value colors/white colors/neutral-95-opa-70) :scroll-shared-value scroll-shared-value}])
(when (colors/dark?) [:f> common.home.banner/animated-banner
colors/neutral-95-opa-70))}]) {:content banner-data
[common.home/top-nav {:type :grey}] :scroll-ref scroll-ref
[common.home/title-column :tabs (get-tabs-data (pos? (count pending-contact-requests)))
{:label (i18n/label :t/messages) :selected-tab selected-tab
:handler #(rf/dispatch [:show-bottom-sheet {:content home.sheet/new-chat}]) :on-tab-change (fn [tab] (rf/dispatch [:messages-home/select-tab tab]))
:accessibility-label :new-chat-button :scroll-shared-value scroll-shared-value}]]))))
:customization-color customization-color}]
[quo/discover-card
{:banner (resources/get-image :invite-friends)
:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:style style/tabs
:size 32
:on-change (fn [tab]
(rf/dispatch [:messages-home/select-tab tab]))
:default-active selected-tab
:data (get-tabs-data (pos? (count pending-contact-requests)))}]]]))

View File

@ -1,10 +1,8 @@
(ns status-im2.contexts.communities.home.style (ns status-im2.contexts.communities.home.style
(:require [quo2.foundations.colors :as colors] (:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.safe-area :as safe-area] [react-native.reanimated :as reanimated]
[react-native.reanimated :as reanimated])) [react-native.safe-area :as safe-area]))
(def header-height 245)
(def tabs (def tabs
{:padding-horizontal 20 {:padding-horizontal 20
@ -18,22 +16,11 @@
:left 0 :left 0
:bottom 0}) :bottom 0})
(defn empty-state-container
[]
{:margin-top (+ header-height (safe-area/get-top))
:margin-bottom 44
:flex 1
:justify-content :center})
(def empty-state-placeholder (def empty-state-placeholder
{:height 120 {:height 120
:width 120 :width 120
:background-color colors/danger-50}) :background-color colors/danger-50})
(defn header-spacing
[]
{:height (+ header-height (safe-area/get-top))})
(defn blur-banner-layer (defn blur-banner-layer
[animated-translation-y] [animated-translation-y]
(let [fixed-height (+ (safe-area/get-top) 244)] (let [fixed-height (+ (safe-area/get-top) 244)]

View File

@ -1,17 +1,14 @@
(ns status-im2.contexts.communities.home.view (ns status-im2.contexts.communities.home.view
(:require [oops.core :as oops] (:require [oops.core :as oops]
[quo2.core :as quo] [quo2.core :as quo]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme] [quo2.theme :as theme]
[react-native.blur :as blur]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[status-im2.common.home.banner.view :as common.home.banner]
[status-im2.common.home.view :as common.home] [status-im2.common.home.view :as common.home]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im2.contexts.communities.actions.community-options.view :as options] [status-im2.contexts.communities.actions.community-options.view :as options]
[status-im2.contexts.communities.actions.home-plus.view :as actions.home-plus] [status-im2.contexts.communities.actions.home-plus.view :as actions.home-plus]
[status-im2.contexts.communities.home.style :as style]
[utils.debounce :as debounce] [utils.debounce :as debounce]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.number] [utils.number]
@ -40,122 +37,43 @@
{:id :pending :label (i18n/label :t/pending) :accessibility-label :pending-tab} {:id :pending :label (i18n/label :t/pending) :accessibility-label :pending-tab}
{:id :opened :label (i18n/label :t/opened) :accessibility-label :opened-tab}]) {:id :opened :label (i18n/label :t/opened) :accessibility-label :opened-tab}])
(defn empty-state-content (def empty-state-content
[selected-tab] {:joined
(case selected-tab {:title (i18n/label :t/no-communities)
:joined :description [:<>
{:title (i18n/label :t/no-communities) [rn/text {:style {:text-decoration-line :line-through}}
:description [:<> (i18n/label :t/no-communities-description-strikethrough)]
[rn/text {:style {:text-decoration-line :line-through}} " "
(i18n/label :t/no-communities-description-strikethrough)] (i18n/label :t/no-communities-description)]
" " :image (resources/get-image (theme/theme-value :no-communities-light
(i18n/label :t/no-communities-description)] :no-communities-dark))}
:image (resources/get-image (theme/theme-value :no-communities-light :pending
:no-communities-dark))} {:title (i18n/label :t/no-pending-communities)
:pending :description (i18n/label :t/no-pending-communities-description)
{:title (i18n/label :t/no-pending-communities) :image (resources/get-image (theme/theme-value :no-pending-communities-light
:description (i18n/label :t/no-pending-communities-description) :no-pending-communities-dark))}
:image (resources/get-image (theme/theme-value :no-pending-communities-light :opened
:no-pending-communities-dark))} {:title (i18n/label :t/no-opened-communities)
:opened :description (i18n/label :t/no-opened-communities-description)
{:title (i18n/label :t/no-opened-communities) :image (resources/get-image (theme/theme-value :no-opened-communities-light
:description (i18n/label :t/no-opened-communities-description) :no-opened-communities-dark))}})
:image (resources/get-image (theme/theme-value :no-opened-communities-light
:no-opened-communities-dark))}
nil))
(defn- empty-state (def ^:private banner-data
[{:keys [style selected-tab]}] {:title-props
(let [{:keys [image title description]} (empty-state-content selected-tab) {:label (i18n/label :t/communities)
customization-color (rf/sub [:profile/customization-color])] :handler #(rf/dispatch [:show-bottom-sheet {:content actions.home-plus/view}])
[rn/view {:style style} :accessibility-label :new-communities-button}
[quo/empty-state :card-props
{:customization-color customization-color {:on-press #(rf/dispatch [:navigate-to :discover-communities])
:image image :title (i18n/label :t/discover)
:title title :description (i18n/label :t/favorite-communities)
:description description}]])) :banner (resources/get-image :discover)
:accessibility-label :communities-home-discover-card}})
(defn- blur-banner-layer
[animated-translation-y]
(let [open-sheet? (-> (rf/sub [:bottom-sheet]) :sheets seq)]
[reanimated/view {:style (style/blur-banner-layer animated-translation-y)}
[blur/view
{:blur-amount (if platform/ios? 20 10)
:blur-type (theme/theme-value (if platform/ios? :light :xlight) :dark)
:style style/blur
:overlay-color (if open-sheet?
(colors/theme-colors colors/white colors/neutral-95-opa-70)
(theme/theme-value nil colors/neutral-95-opa-70))}]]))
(defn- hiding-banner-layer
[animated-translation-y animated-opacity]
(let [customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style (style/hiding-banner-layer)}
[common.home/top-nav {:type :grey}]
[common.home/title-column
{:label (i18n/label :t/communities)
:handler #(rf/dispatch
[:show-bottom-sheet {:content actions.home-plus/view}])
:accessibility-label :new-communities-button
:customization-color customization-color}]
[rn/view {:style style/animated-card-container}
[reanimated/view {:style (style/animated-card animated-opacity animated-translation-y)}
[quo/discover-card
{:on-press #(rf/dispatch [:navigate-to :discover-communities])
:title (i18n/label :t/discover)
:description (i18n/label :t/favorite-communities)
:banner (resources/get-image :discover)
:accessibility-label :communities-home-discover-card}]]]]))
(defn- reset-banner-animation
[animated-opacity animated-translation-y]
(reanimated/animate-shared-value-with-timing animated-opacity 1 200 :easing3)
(reanimated/animate-shared-value-with-timing animated-translation-y 0 200 :easing3))
(defn- reset-scroll
[flat-list-ref]
(some-> flat-list-ref
(.scrollToOffset #js {:offset 0 :animated? true})))
(defn- tabs-banner-layer
[animated-translation-y animated-opacity selected-tab flat-list-ref]
(let [on-tab-change (fn [tab]
(if (empty? (get (rf/sub [:communities/grouped-by-status]) tab))
(reset-banner-animation animated-opacity animated-translation-y)
(reset-scroll @flat-list-ref))
(rf/dispatch [:communities/select-tab tab]))]
[reanimated/view {:style (style/tabs-banner-layer animated-translation-y)}
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:size 32
:style style/tabs
:on-change on-tab-change
:default-active selected-tab
:data tabs-data}]]))
(defn- animated-banner
[{:keys [selected-tab animated-translation-y animated-opacity flat-list-ref]}]
[:<>
[:f> blur-banner-layer animated-translation-y]
[:f> hiding-banner-layer animated-translation-y animated-opacity]
[:f> tabs-banner-layer animated-translation-y animated-opacity selected-tab flat-list-ref]])
(def ^:private card-height (+ 56 16)) ; Card height + its vertical margins
(def ^:private card-opacity-factor (/ 100 card-height 100))
(def ^:private max-scroll (- (+ card-height 8))) ; added 8 from tabs top padding
(defn- set-animated-banner-values
[{:keys [scroll-offset translation-y opacity]}]
(let [new-opacity (-> (* (- card-height scroll-offset) card-opacity-factor)
(utils.number/value-in-range 0 1))
new-translation-y (-> (- scroll-offset)
(utils.number/value-in-range max-scroll 0))]
(reanimated/animate-shared-value-with-timing opacity new-opacity 80 :easing4)
(reanimated/animate-shared-value-with-timing translation-y new-translation-y 80 :easing4)))
(defn home (defn home
[] []
(let [flat-list-ref (atom nil)] (let [flat-list-ref (atom nil)
set-flat-list-ref #(reset! flat-list-ref %)]
(fn [] (fn []
(let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined) (let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status]) {:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
@ -163,28 +81,29 @@
:joined joined :joined joined
:pending pending :pending pending
:opened opened) :opened opened)
animated-opacity (reanimated/use-shared-value 1) scroll-shared-value (reanimated/use-shared-value 0)]
animated-translation-y (reanimated/use-shared-value 0)]
[:<> [:<>
(if (empty? selected-items) (if (empty? selected-items)
[empty-state [common.home/empty-state-image
{:style (style/empty-state-container) {:selected-tab selected-tab
:selected-tab selected-tab}] :tab->content empty-state-content}]
[rn/flat-list [reanimated/flat-list
{:ref #(reset! flat-list-ref %) {:ref set-flat-list-ref
:key-fn :id :key-fn :id
:content-inset-adjustment-behavior :never :content-inset-adjustment-behavior :never
:header [rn/view {:style (style/header-spacing)}] :header [common.home/header-spacing]
:render-fn item-render :render-fn item-render
:data selected-items :data selected-items
:on-scroll #(set-animated-banner-values :scroll-event-throttle 8
{:scroll-offset (oops/oget :on-scroll #(common.home.banner/set-scroll-shared-value
% {:scroll-input (oops/oget
"nativeEvent.contentOffset.y") %
:translation-y animated-translation-y "nativeEvent.contentOffset.y")
:opacity animated-opacity})}]) :shared-value scroll-shared-value})}])
[:f> animated-banner [:f> common.home.banner/animated-banner
{:selected-tab selected-tab {:content banner-data
:animated-translation-y animated-translation-y :scroll-ref flat-list-ref
:animated-opacity animated-opacity :tabs tabs-data
:flat-list-ref flat-list-ref}]])))) :selected-tab selected-tab
:on-tab-change (fn [tab] (rf/dispatch [:communities/select-tab tab]))
:scroll-shared-value scroll-shared-value}]]))))

View File

@ -28,7 +28,7 @@
(get shell.constants/stacks-z-index-keywords stack-id))})} (get shell.constants/stacks-z-index-keywords stack-id))})}
(case stack-id (case stack-id
:communities-stack [:f> communities/home] :communities-stack [:f> communities/home]
:chats-stack [chat/home] :chats-stack [:f> chat/home]
:wallet-stack [wallet.accounts/accounts-overview-old] :wallet-stack [wallet.accounts/accounts-overview-old]
:browser-stack [browser.stack/browser-stack] :browser-stack [browser.stack/browser-stack]
[:<>])]) [:<>])])