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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,14 @@
(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]))
(def header-height 245) (def header-height 245)
(def tabs (def tabs
{:padding-horizontal 20 {:padding-horizontal 20
:padding-top 16 :padding-top 8
:padding-bottom 12}) :padding-bottom 12})
(def blur (def blur
@ -17,8 +19,8 @@
:bottom 0}) :bottom 0})
(defn empty-state-container (defn empty-state-container
[top] []
{:margin-top (+ header-height top) {:margin-top (+ header-height (safe-area/get-top))
:margin-bottom 44 :margin-bottom 44
:flex 1 :flex 1
:justify-content :center}) :justify-content :center})
@ -29,16 +31,46 @@
:background-color colors/danger-50}) :background-color colors/danger-50})
(defn header-spacing (defn header-spacing
[top] []
{:height (+ header-height top)}) {:height (+ header-height (safe-area/get-top))})
(defn blur-container (defn blur-banner-layer
[top] [animated-translation-y]
{:overflow (if platform/ios? :visible :hidden) (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 :position :absolute
:z-index 1
:top 0 :top 0
:right 0 :right 0
:left 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 (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.foundations.colors :as colors]
[quo2.theme :as theme] [quo2.theme :as theme]
[utils.debounce :as debounce]
[react-native.blur :as blur] [react-native.blur :as blur]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform] [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.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] [status-im2.contexts.communities.home.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.number]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn item-render (defn item-render
@ -60,9 +62,10 @@
:no-opened-communities-dark))} :no-opened-communities-dark))}
nil)) nil))
(defn empty-state (defn- empty-state
[{:keys [style selected-tab customization-color]}] [{:keys [style selected-tab]}]
(let [{:keys [image title description]} (empty-state-content selected-tab)] (let [{:keys [image title description]} (empty-state-content selected-tab)
customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style style} [rn/view {:style style}
[quo/empty-state [quo/empty-state
{:customization-color customization-color {:customization-color customization-color
@ -70,56 +73,116 @@
:title title :title title
:description description}]])) :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 (defn home
[] []
(let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined) (let [flat-list-ref (atom nil)]
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status]) (fn []
customization-color (rf/sub [:profile/customization-color]) (let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
selected-items (case selected-tab {:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
:joined joined selected-items (case selected-tab
:pending pending :joined joined
:opened opened) :pending pending
top (safe-area/get-top)] :opened opened)
[:<> animated-opacity (reanimated/use-shared-value 1)
(if (empty? selected-items) animated-translation-y (reanimated/use-shared-value 0)]
[empty-state [:<>
{:style (style/empty-state-container top) (if (empty? selected-items)
:selected-tab selected-tab [empty-state
:customization-color customization-color}] {:style (style/empty-state-container)
[rn/flat-list :selected-tab selected-tab}]
{:key-fn :id [rn/flat-list
:content-inset-adjustment-behavior :never {:ref #(reset! flat-list-ref %)
:header [rn/view {:style (style/header-spacing top)}] :key-fn :id
:render-fn item-render :content-inset-adjustment-behavior :never
:data selected-items}]) :header [rn/view {:style (style/header-spacing)}]
:render-fn item-render
[rn/view {:style (style/blur-container top)} :data selected-items
(let [{:keys [sheets]} (rf/sub [:bottom-sheet])] :on-scroll #(set-animated-banner-values
[blur/view {:scroll-offset (oops/oget
{:blur-amount (if platform/ios? 20 10) %
:blur-type (if (colors/dark?) :dark (if platform/ios? :light :xlight)) "nativeEvent.contentOffset.y")
:style style/blur :translation-y animated-translation-y
:overlay-color (if (seq sheets) :opacity animated-opacity})}])
(theme/theme-value colors/white colors/neutral-95-opa-70) [:f> animated-banner
(when (colors/dark?) {:selected-tab selected-tab
colors/neutral-95-opa-70))}]) :animated-translation-y animated-translation-y
[common.home/top-nav {:type :grey}] :animated-opacity animated-opacity
[common.home/title-column :flat-list-ref flat-list-ref}]]))))
{: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}]]]))

View File

@ -27,7 +27,7 @@
:z-index (get shared-values :z-index (get shared-values
(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 [communities/home] :communities-stack [:f> communities/home]
:chats-stack [chat/home] :chats-stack [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]

View File

@ -23,3 +23,9 @@
(if (integer? maybe-int) (if (integer? maybe-int)
maybe-int maybe-int
default)))) 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)))