From c4b142ea68b7be0de70b29a9a15e79745479cf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulises=20Manuel=20C=C3=A1rdenas?= <90291778+ulisesmac@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:18:33 -0600 Subject: [PATCH] [#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 --- .../components/community/banner/style.cljs | 34 ++-- .../components/community/banner/view.cljs | 28 ++- src/quo2/components/tabs/tabs.cljs | 2 +- .../contexts/chat/composer/effects.cljs | 2 +- .../contexts/chat/composer/gesture.cljs | 5 +- .../contexts/chat/composer/handlers.cljs | 5 +- .../contexts/chat/composer/utils.cljs | 5 +- .../contexts/communities/home/style.cljs | 54 ++++-- .../contexts/communities/home/view.cljs | 177 ++++++++++++------ .../jump_to/components/home_stack/view.cljs | 2 +- src/utils/number.cljs | 6 + 11 files changed, 208 insertions(+), 112 deletions(-) diff --git a/src/quo2/components/community/banner/style.cljs b/src/quo2/components/community/banner/style.cljs index 267b6eb8fa..8c05dac580 100644 --- a/src/quo2/components/community/banner/style.cljs +++ b/src/quo2/components/community/banner/style.cljs @@ -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 diff --git a/src/quo2/components/community/banner/view.cljs b/src/quo2/components/community/banner/view.cljs index e1e72cb515..c6e43fa222 100644 --- a/src/quo2/components/community/banner/view.cljs +++ b/src/quo2/components/community/banner/view.cljs @@ -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)) diff --git a/src/quo2/components/tabs/tabs.cljs b/src/quo2/components/tabs/tabs.cljs index 7816bf3b0e..d8e1418099 100644 --- a/src/quo2/components/tabs/tabs.cljs +++ b/src/quo2/components/tabs/tabs.cljs @@ -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) diff --git a/src/status_im2/contexts/chat/composer/effects.cljs b/src/status_im2/contexts/chat/composer/effects.cljs index 2305277438..24f7c624a4 100644 --- a/src/status_im2/contexts/chat/composer/effects.cljs +++ b/src/status_im2/contexts/chat/composer/effects.cljs @@ -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 diff --git a/src/status_im2/contexts/chat/composer/gesture.cljs b/src/status_im2/contexts/chat/composer/gesture.cljs index f1c4d8fbf4..6a127a39c5 100644 --- a/src/status_im2/contexts/chat/composer/gesture.cljs +++ b/src/status_im2/contexts/chat/composer/gesture.cljs @@ -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 diff --git a/src/status_im2/contexts/chat/composer/handlers.cljs b/src/status_im2/contexts/chat/composer/handlers.cljs index 73b4555735..4053513cd7 100644 --- a/src/status_im2/contexts/chat/composer/handlers.cljs +++ b/src/status_im2/contexts/chat/composer/handlers.cljs @@ -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) diff --git a/src/status_im2/contexts/chat/composer/utils.cljs b/src/status_im2/contexts/chat/composer/utils.cljs index e900aa4e97..3bc9e5d196 100644 --- a/src/status_im2/contexts/chat/composer/utils.cljs +++ b/src/status_im2/contexts/chat/composer/utils.cljs @@ -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)) diff --git a/src/status_im2/contexts/communities/home/style.cljs b/src/status_im2/contexts/communities/home/style.cljs index e562e26b29..cc74242fab 100644 --- a/src/status_im2/contexts/communities/home/style.cljs +++ b/src/status_im2/contexts/communities/home/style.cljs @@ -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}]} + {})) diff --git a/src/status_im2/contexts/communities/home/view.cljs b/src/status_im2/contexts/communities/home/view.cljs index bc24c3f375..d3e984e54c 100644 --- a/src/status_im2/contexts/communities/home/view.cljs +++ b/src/status_im2/contexts/communities/home/view.cljs @@ -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}]])))) diff --git a/src/status_im2/contexts/shell/jump_to/components/home_stack/view.cljs b/src/status_im2/contexts/shell/jump_to/components/home_stack/view.cljs index 915fa84c1d..2aa4ad81a3 100644 --- a/src/status_im2/contexts/shell/jump_to/components/home_stack/view.cljs +++ b/src/status_im2/contexts/shell/jump_to/components/home_stack/view.cljs @@ -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] diff --git a/src/utils/number.cljs b/src/utils/number.cljs index 63ddf6aa15..f3406fea39 100644 --- a/src/utils/number.cljs +++ b/src/utils/number.cljs @@ -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)))