[#16446] Communities banner animation (#16567)

* Simplifies community banner and adds style property
* Add animation for communities banner card
* Moves value-in-range to `utils.number` and removes `bounded-val`
* Moves animated values to view namespace
* Make `community.banner` component themeable
This commit is contained in:
Ulises Manuel Cárdenas 2023-07-28 13:18:33 -06:00 committed by GitHub
parent 3b8f66d2ec
commit c4b142ea68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 208 additions and 112 deletions

View File

@ -2,18 +2,21 @@
(:require [quo2.foundations.colors :as colors]))
(defn community-card
[radius]
{:shadow-offset {:width 0
:height 2}
:shadow-radius radius
:shadow-opacity 1
:shadow-color colors/shadow
:elevation 1
:border-radius radius
:justify-content :space-between
:background-color (colors/theme-colors
colors/white
colors/neutral-90)})
[theme]
{:shadow-offset {:width 0 :height 2}
:shadow-radius 16
:shadow-opacity 1
:shadow-color colors/shadow
:elevation 1
:border-radius 16
:justify-content :space-between
:background-color (colors/theme-colors colors/white colors/neutral-90 theme)
:flex-direction :row
:margin-horizontal 20
:margin-top 8
:margin-bottom 8
:height 56
:padding-right 12})
(def banner-content
{:flex 1
@ -25,13 +28,6 @@
{:flex 1
:padding-horizontal 12})
(def banner-card
{:flex-direction :row
:margin-horizontal 20
:margin-vertical 8
:height 56
:padding-right 12})
(def discover-illustration
{:position :absolute
:top -4

View File

@ -2,14 +2,13 @@
(:require [quo2.components.community.banner.style :as style]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]))
(defn- card-title-and-description
[title description]
[rn/view
{:style style/banner-content}
[rn/view
{:style style/banner-title}
[title description theme]
[rn/view {:style style/banner-content}
[rn/view {:style style/banner-title}
[text/text
{:accessibility-label :community-name-text
:ellipsize-mode :tail
@ -21,26 +20,21 @@
{:accessibility-label :community-name-text
:ellipsize-mode :tail
:number-of-lines 1
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
:weight :regular
:size :paragraph-2}
description]]])
(defn view
[{:keys [title description on-press accessibility-label banner]}]
(defn view-internal
[{:keys [title description on-press accessibility-label banner style theme]}]
[rn/touchable-without-feedback
{:on-press on-press
:accessibility-label accessibility-label}
[rn/view
(merge (style/community-card 16)
{:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
style/banner-card)
[card-title-and-description title description]
[rn/view {:style (merge (style/community-card theme) style)}
[card-title-and-description title description theme]
[rn/image
{:style style/discover-illustration
:source banner
:accessibility-label :discover-communities-illustration}]]])
(def view (theme/with-theme view-internal))

View File

@ -6,7 +6,7 @@
[react-native.masked-view :as masked-view]
[reagent.core :as reagent]
[utils.collection :as utils.collection]
[utils.number :as utils.number]
[utils.number]
[react-native.gesture :as gesture]))
(def default-tab-size 32)

View File

@ -9,7 +9,7 @@
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.utils :as utils]
[utils.number :as utils.number]
[utils.number]
[utils.re-frame :as rf]))
(defn reenter-screen-effect

View File

@ -1,10 +1,11 @@
(ns status-im2.contexts.chat.composer.gesture
(:require
[oops.core :as oops]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[oops.core :as oops]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.utils :as utils]
[utils.number]
[utils.re-frame :as rf]))
(defn set-opacity
@ -83,7 +84,7 @@
(let [translation (oops/oget event "translationY")
min-height (utils/get-min-height lines images link-previews?)
new-height (- (reanimated/get-shared-value saved-height) translation)
bounded-height (utils/bounded-val new-height min-height max-height)]
bounded-height (utils.number/value-in-range new-height min-height max-height)]
(when keyboard-shown
(if (>= new-height min-height)
(do ; expand sheet

View File

@ -10,6 +10,7 @@
[status-im2.contexts.chat.composer.selection :as selection]
[status-im2.contexts.chat.composer.utils :as utils]
[utils.debounce :as debounce]
[utils.number]
[utils.re-frame :as rf]))
(defn focus
@ -82,7 +83,9 @@
content-size (if (or (= lines 1) (empty? @text-value))
constants/input-height
(if (= lines 2) constants/multiline-minimized-height content-size))
new-height (utils/bounded-val content-size constants/input-height max-height)
new-height (utils.number/value-in-range content-size
constants/input-height
max-height)
bottom-content-height (utils/calc-bottom-content-height images link-previews?)
new-height (min (+ new-height bottom-content-height) max-height)]
(reset! content-height content-size)

View File

@ -8,6 +8,7 @@
[reagent.core :as reagent]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.selection :as selection]
[utils.number]
[utils.re-frame :as rf]))
(defn bounded-val
@ -142,7 +143,7 @@
base (+ base (- curr-height constants/input-height))
base (+ base (calc-top-content-height reply edit))
view-height (- window-height keyboard-height (:top insets))
container-height (bounded-val
container-height (utils.number/value-in-range
(* (/ constants/mentions-max-height 4) size)
(/ constants/mentions-max-height 4)
constants/mentions-max-height)]
@ -228,7 +229,7 @@
:saved-height (reanimated/use-shared-value
initial-height)
:last-height (reanimated/use-shared-value
(bounded-val
(utils.number/value-in-range
(+ @content-height bottom-content-height)
constants/input-height
max-height))

View File

@ -1,12 +1,14 @@
(ns status-im2.contexts.communities.home.style
(: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]))
(def header-height 245)
(def tabs
{:padding-horizontal 20
:padding-top 16
:padding-top 8
:padding-bottom 12})
(def blur
@ -17,8 +19,8 @@
:bottom 0})
(defn empty-state-container
[top]
{:margin-top (+ header-height top)
[]
{:margin-top (+ header-height (safe-area/get-top))
:margin-bottom 44
:flex 1
:justify-content :center})
@ -29,16 +31,46 @@
:background-color colors/danger-50})
(defn header-spacing
[top]
{:height (+ header-height top)})
[]
{:height (+ header-height (safe-area/get-top))})
(defn blur-container
[top]
{:overflow (if platform/ios? :visible :hidden)
(defn blur-banner-layer
[animated-translation-y]
(let [fixed-height (+ (safe-area/get-top) 244)]
(reanimated/apply-animations-to-style
{:transform [{:translate-y animated-translation-y}]}
{:overflow (if platform/ios? :visible :hidden)
:z-index 1
:position :absolute
:top 0
:right 0
:left 0
:height fixed-height})))
(defn hiding-banner-layer
[]
{:z-index 2
:position :absolute
:z-index 1
:top 0
:right 0
:left 0
:padding-top top})
:padding-top (safe-area/get-top)})
(defn tabs-banner-layer
[animated-translation-y]
(let [top-offset (+ (safe-area/get-top) 192)]
(reanimated/apply-animations-to-style
{:transform [{:translate-y animated-translation-y}]}
{:z-index 3
:position :absolute
:top top-offset
:right 0
:left 0})))
(def animated-card-container {:overflow :hidden})
(defn animated-card
[opacity translate-y]
(reanimated/apply-animations-to-style {:opacity opacity
:transform [{:translate-y translate-y}]}
{}))

View File

@ -1,18 +1,20 @@
(ns status-im2.contexts.communities.home.view
(:require [quo2.core :as quo]
(:require [oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[utils.debounce :as debounce]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[react-native.reanimated :as reanimated]
[status-im2.common.home.view :as common.home]
[status-im2.common.resources :as resources]
[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.home.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.number]
[utils.re-frame :as rf]))
(defn item-render
@ -60,9 +62,10 @@
:no-opened-communities-dark))}
nil))
(defn empty-state
[{:keys [style selected-tab customization-color]}]
(let [{:keys [image title description]} (empty-state-content selected-tab)]
(defn- empty-state
[{:keys [style selected-tab]}]
(let [{:keys [image title description]} (empty-state-content selected-tab)
customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style style}
[quo/empty-state
{:customization-color customization-color
@ -70,56 +73,116 @@
:title title
:description description}]]))
(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
[]
(let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
customization-color (rf/sub [:profile/customization-color])
selected-items (case selected-tab
:joined joined
:pending pending
:opened opened)
top (safe-area/get-top)]
[:<>
(if (empty? selected-items)
[empty-state
{:style (style/empty-state-container top)
:selected-tab selected-tab
:customization-color customization-color}]
[rn/flat-list
{:key-fn :id
:content-inset-adjustment-behavior :never
:header [rn/view {:style (style/header-spacing top)}]
:render-fn item-render
:data selected-items}])
[rn/view {:style (style/blur-container top)}
(let [{:keys [sheets]} (rf/sub [:bottom-sheet])]
[blur/view
{:blur-amount (if platform/ios? 20 10)
:blur-type (if (colors/dark?) :dark (if platform/ios? :light :xlight))
:style style/blur
:overlay-color (if (seq sheets)
(theme/theme-value colors/white colors/neutral-95-opa-70)
(when (colors/dark?)
colors/neutral-95-opa-70))}])
[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}]
[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}]
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:size 32
:style style/tabs
:on-change (fn [tab]
(rf/dispatch [:communities/select-tab tab]))
:default-active selected-tab
:data tabs-data}]]]))
(let [flat-list-ref (atom nil)]
(fn []
(let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
selected-items (case selected-tab
:joined joined
:pending pending
:opened opened)
animated-opacity (reanimated/use-shared-value 1)
animated-translation-y (reanimated/use-shared-value 0)]
[:<>
(if (empty? selected-items)
[empty-state
{:style (style/empty-state-container)
:selected-tab selected-tab}]
[rn/flat-list
{:ref #(reset! flat-list-ref %)
:key-fn :id
:content-inset-adjustment-behavior :never
:header [rn/view {:style (style/header-spacing)}]
:render-fn item-render
:data selected-items
:on-scroll #(set-animated-banner-values
{:scroll-offset (oops/oget
%
"nativeEvent.contentOffset.y")
:translation-y animated-translation-y
:opacity animated-opacity})}])
[:f> animated-banner
{:selected-tab selected-tab
:animated-translation-y animated-translation-y
:animated-opacity animated-opacity
:flat-list-ref flat-list-ref}]]))))

View File

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

View File

@ -23,3 +23,9 @@
(if (integer? maybe-int)
maybe-int
default))))
(defn value-in-range
"Returns `num` if is in the range [`lower-bound` `upper-bound`]
if `num` exceeds a given bound, then returns the bound exceeded."
[number lower-bound upper-bound]
(max lower-bound (min number upper-bound)))