chore: refactor scroll page component to be more generic (#14453)

chore: refactor scroll page component to be more generic
This commit is contained in:
Jamie Caprani 2022-12-09 12:07:21 +00:00 committed by GitHub
parent bd84a36582
commit 53ef1ef4b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 321 additions and 266 deletions

View File

@ -28,13 +28,9 @@
[community-stats {:icon :i/group
:members-count "629.2K" ;;TODO here should be formatted value, use money/format-members from outside this component
:icon-color icon-color}]
[community-stats {:icon :i/lightning
[community-stats {:icon :i/lightning
:members-count "112.1K"
:icon-color icon-color}]
(when (= type :card-view)
[community-stats {:icon :i/placeholder
:count 4
:icon-color icon-color}])]))
:icon-color icon-color}]]))
(defn community-tags [tags]
[rn/view (style/community-tags-container)

View File

@ -15,6 +15,7 @@
quo2.components.community.community-list-view
quo2.components.community.community-view
quo2.components.community.discover-card
quo2.components.community.token-gating
quo2.components.counter.counter
quo2.components.dividers.divider-label
quo2.components.dividers.new-messages
@ -85,8 +86,12 @@
(def community-title quo2.components.community.community-view/community-title)
(def permission-tag-container quo2.components.community.community-view/permission-tag-container)
(def discover-card quo2.components.community.discover-card/discover-card)
(def token-gating quo2.components.community.token-gating/token-gating)
;;;; DIVIDERS
(def divider-label quo2.components.dividers.divider-label/divider-label)
(def new-messages quo2.components.dividers.new-messages/new-messages)

View File

@ -0,0 +1,32 @@
(ns status-im2.common.scroll-page.style
(:require
[quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(defn image-slider [height]
{:top (if platform/ios? 0 -64)
;; -64 is needed on android as the scroll doesn't
;; bounce so this slider won't disapear otherwise
:height height
:z-index 4
:flex 1})
(defn blur-slider [height]
{:blur-amount 32
:blur-type :xlight
:overlay-color (if platform/ios? colors/white-opa-70 :transparent)
:style {:z-index 5
:top (if platform/ios? 0 -64)
;; -64 is needed on android as the scroll doesn't
;; bounce so this slider won't disapear otherwise
:position :absolute
:height height
:width "100%"
:flex 1}})
(defn scroll-view-container [border-radius]
{:position :absolute
:top -48
:overflow :scroll
:border-radius border-radius
:height "100%"})

View File

@ -0,0 +1,98 @@
(ns status-im2.common.scroll-page.view
(:require
[react-native.core :as rn]
[quo2.core :as quo]
[utils.re-frame :as rf]
[quo2.foundations.colors :as colors]
[status-im2.common.scroll-page.style :as style]
[oops.core :as oops]
[reagent.core :as reagent]
[react-native.platform :as platform]))
(defn icon-color []
(colors/theme-colors
colors/white-opa-40
colors/neutral-80-opa-40))
(defn get-platform-value [value] (if platform/ios? (+ value 44) value))
(def negative-scroll-position-0 (if platform/ios? -44 0))
(def scroll-position-0 (if platform/ios? 44 0))
(def scroll-position-1 (if platform/ios? 86 134))
(def scroll-position-2 (if platform/ios? -26 18))
(defn get-header-size [scroll-height]
(if (<= scroll-height scroll-position-2)
0
(->>
(+ (get-platform-value -17) scroll-height)
(* (if platform/ios? 3 1))
(max 0)
(min (if platform/ios? 100 124)))))
(def max-image-size 80)
(def min-image-size 32)
(defn diff-with-max-min [value maximum minimum]
(->>
(+ value scroll-position-0)
(- maximum)
(max minimum)
(min maximum)))
(defn icon-top-fn [scroll-height]
(if (<= scroll-height negative-scroll-position-0)
-40
(->> (+ scroll-position-0 scroll-height)
(* (if platform/ios? 3 1))
(+ -40)
(min 8))))
(defn icon-size-fn [scroll-height]
(->> (+ scroll-position-0 scroll-height)
(* (if platform/ios? 3 1))
(- max-image-size)
(max min-image-size)
(min max-image-size)))
(defn scroll-page [icon cover page-nav name]
(let [scroll-height (reagent/atom negative-scroll-position-0)]
(fn [sticky-header children]
[:<>
[:<>
[rn/image
{:source cover
:position :absolute
:style (style/image-slider (get-header-size @scroll-height))}]
[rn/blur-view (style/blur-slider (get-header-size @scroll-height))]]
[rn/view {:style {:z-index 6 :margin-top (if platform/ios? 44 0)}}
[quo/page-nav
{:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:page-nav-background-uri ""
:mid-section {:type :text-with-description
:main-text (when (>= @scroll-height scroll-position-1) name)
:description-img (when (>= @scroll-height scroll-position-1) icon)}
:right-section-buttons (:right-section-buttons page-nav)
:left-section {:icon :i/close
:icon-background-color (icon-color)
:on-press #(rf/dispatch [:navigate-back])}}]
(when sticky-header [sticky-header @scroll-height])]
[rn/scroll-view {:style (style/scroll-view-container (diff-with-max-min @scroll-height 16 0))
:shows-vertical-scroll-indicator false
:scroll-event-throttle 4
:on-scroll #(swap! scroll-height (fn [] (int (oops/oget % "nativeEvent.contentOffset.y"))))}
[rn/view {:style {:height 151}}
[rn/image
{:source cover
:style {:overflow :visible
:flex 1}}]]
(when children
[rn/view {:flex 1
:border-radius (diff-with-max-min @scroll-height 16 0)
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[children @scroll-height icon-top-fn icon-size-fn]])]])))

View File

@ -1,5 +1,5 @@
(ns status-im2.contexts.communities.overview.style
(:require [quo.platform :as platform]
(:require [react-native.platform :as platform]
[quo2.foundations.colors :as colors]))
(def preview-user
@ -7,32 +7,11 @@
:align-items :center
:margin-top 20})
(defn image-slider [height] {:top (if platform/ios? 0 -64) ; -44 -20 (the 20 is needed on android as the scroll doesn't bounce so this won't disapear otherwise)
:height height
:z-index 4
:flex 1})
(defn blur-slider [height] {:blur-amount 32
:blur-type :xlight
:overlay-color (if platform/ios? colors/white-opa-70 "transparent")
:style {:z-index 5
:top (if platform/ios? 0 -64) ; -44 -20 (the 20 is needed on android as the scroll doesn't bounce so this won't disapear otherwise)
:position :absolute
:height height
:width "100%"
:flex 1}})
(def blur-channel-header {:blur-amount 32
:blur-type :xlight
:overlay-color (if platform/ios? colors/white-opa-70 "transparent")
:overlay-color (if platform/ios? colors/white-opa-70 :transparent)
:style {:position :absolute
:top (if platform/ios? 44 48)
:top (if platform/ios? 56 60)
:height 34
:width "100%"
:flex 1}})
(defn scroll-view-container [border-radius] {:position :absolute
:top -48
:overflow :scroll
:border-radius border-radius
:height "100%"})
:flex 1}})

View File

@ -8,12 +8,12 @@
[status-im2.contexts.communities.overview.style :as style]
;; TODO move to status-im2 when reimplemented
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im2.common.scroll-page.view :as scroll-page]
[oops.core :as oops]
[reagent.core :as reagent]
[quo.platform :as platform]
[react-native.platform :as platform]
[status-im2.contexts.communities.requests.actions.view :as requests.actions]
[status-im2.contexts.communities.home.actions.view :as home.actions]
[quo2.components.community.token-gating :as token-gating]
[status-im.constants :as constants]
[status-im.react-native.resources :as resources]
[status-im.utils.utils :as utils]))
@ -44,7 +44,7 @@
#(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
(constantly [quo/token-gating
{:channel {:name name
:community-color (colors/custom-color :pink 50)
:emoji emoji
@ -78,55 +78,58 @@
:is-sufficient? false}]}}}])
:content-height 210}]))
(def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🍑"
(colors/custom-color :pink 50)))}
{:name "intro"
:emoji "🦄"
:locked? true
:on-press #((open-token-gating-mocked
"intro"
"🦄"
(colors/custom-color :pink 50)))}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🌏"
(colors/custom-color :blue 50)))}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
:emoji "👽"}
{:name "mobile-ui"
:emoji "👽"}
{:name "mobile-ui-reviews"
:emoji "👽"}]
:Desktop [{:name "desktop"
:emoji "👽"}
{:name "desktop-ui"
:emoji "👽"}
{:name "desktop-ui-reviews"
:emoji "👽"}
{:name "desktop2"
:emoji "👽"}
{:name "desktop-ui2"
:emoji "👽"}
{:name "desktop-ui2-reviews"
:emoji "👽"}]})
(def mock-list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🍑"
(colors/custom-color :pink 50)))}
{:name "intro"
:emoji "🦄"
:locked? true
:on-press #((open-token-gating-mocked
"intro"
"🦄"
(colors/custom-color :pink 50)))}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🌏"
(colors/custom-color :blue 50)))}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
:emoji "👽"}
{:name "mobile-ui"
:emoji "👽"}
{:name "mobile-ui-reviews"
:emoji "👽"}]
:Desktop [{:name "desktop"
:emoji "👽"}
{:name "desktop-ui"
:emoji "👽"}
{:name "desktop-ui-reviews"
:emoji "👽"}
{:name "desktop2"
:emoji "👽"}
{:name "desktop-ui2"
:emoji "👽"}
{:name "desktop-ui2-reviews"
:emoji "👽"}]})
(defn channel-list-component [channel-heights first-channel-height]
(defn channel-list-component-fn [channel-heights first-channel-height]
[rn/view {:on-layout #(swap! first-channel-height
(fn [] (+ (if platform/ios? 0 38) (int (Math/ceil (oops/oget % "nativeEvent.layout.y"))))))
(fn [] (+ (if platform/ios?
0
38)
(int (Math/ceil (oops/oget % "nativeEvent.layout.y"))))))
:style {:margin-top 20 :flex 1}}
(map-indexed (fn [index category]
(let [first-category (first category)]
@ -139,7 +142,7 @@
(sort-by :height
(conj @channel-heights
{:height (int (oops/oget % "nativeEvent.layout.y"))
:label first-category}))))}
:label first-category}))))}
[quo/divider-label
{:label first-category
@ -148,200 +151,142 @@
{:margin-left 8
:margin-top 10
:margin-bottom 8}
(map-indexed (fn [inner-index channel-data] [rn/view {:key (str inner-index (:name channel-data)) :margin-top 4}
[quo/channel-list-item channel-data]]) (first-category list-of-channels))]]))
list-of-channels)])
(map-indexed (fn [inner-index channel-data]
[rn/view {:key (str inner-index (:name channel-data))
:margin-top 4}
[quo/channel-list-item channel-data]]) (first-category mock-list-of-channels))]]))
mock-list-of-channels)])
(defn icon-color []
(colors/theme-colors
colors/white-opa-40
colors/neutral-80-opa-40))
(def channel-list-component (memoize channel-list-component-fn))
(defn get-platform-value [value] (if platform/ios? (+ value 44) value))
(def scroll-0 (if platform/ios? -44 0))
(def scroll0 (if platform/ios? 44 0))
(def scroll1 (if platform/ios? 86 134))
(def scroll2 (if platform/ios? -26 18))
(def max-image-size 80)
(def min-image-size 32)
(defn diff-with-max-min [value maximum minimum]
(->>
(+ value scroll0)
(- maximum)
(max minimum)
(min maximum)))
(defn get-header-size [scroll-height]
(if (<= scroll-height scroll2)
0
(->>
(+ (get-platform-value -17) scroll-height)
(* (if platform/ios? 3 1))
(max 0)
(min (if platform/ios? 100 124)))))
(defn community-card-page-view [{:keys [name id description locked joined
status tokens cover images tags community-color] :as community}]
(let [community-icon (memoize (fn [] [communities.icon/community-icon-redesign community 24]))
thumbnail-image (get-in images [:thumbnail])
scroll-height (reagent/atom scroll-0)
channel-heights (reagent/atom [])
first-channel-height (reagent/atom 0)]
(fn []
[:<>
[:<>
[rn/image
{:source cover
:position :absolute
:style (style/image-slider (get-header-size @scroll-height))}]
[rn/blur-view (style/blur-slider (get-header-size @scroll-height))]]
[rn/view {:style {:z-index 6 :margin-top (if platform/ios? 56 12)}}
[quo/page-nav
{:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:page-nav-background-uri ""
:mid-section {:type :text-with-description
:main-text (when (>= @scroll-height scroll1) name)
:description-img (when (>= @scroll-height scroll1) community-icon)}
:right-section-buttons [{:icon :i/search
:background-color (icon-color)}
{:icon :i/options
:background-color (icon-color)
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [home.actions/actions community])
:content-height 400}])}]
:left-section {:icon :i/close
:icon-background-color (icon-color)
:on-press #(rf/dispatch [:navigate-back])}}]
(when (>= @scroll-height @first-channel-height)
[rn/blur-view style/blur-channel-header
[quo/divider-label
{:label (:label (last (filter (fn [{:keys [height]}]
(>= @scroll-height (+ height @first-channel-height)))
@channel-heights)))
:chevron-position :left}]])]
[rn/scroll-view {:style (style/scroll-view-container (diff-with-max-min @scroll-height 16 0))
:shows-vertical-scroll-indicator false
:scroll-event-throttle 1
:on-scroll #(swap! scroll-height (fn [] (int (oops/oget % "nativeEvent.contentOffset.y"))))}
[rn/view {:style {:height 151}}
[rn/image
{:source cover
:style {:overflow :visible
:flex 1}}]]
[rn/view {:flex 1
:border-radius (diff-with-max-min @scroll-height 16 0)
(defn render-page-content [{:keys [name description locked joined id images
status tokens tags community-color]
:as community}
channel-heights first-channel-height]
(let [thumbnail-image (get-in images [:thumbnail])]
(fn [scroll-height icon-top icon-size]
[rn/view
[rn/view {:padding-horizontal 20}
[rn/view {:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (icon-top scroll-height)
:left 17
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[rn/view
[rn/view {:padding-horizontal 20}
[rn/view {:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (if (<= @scroll-height scroll-0)
-40
(->> (+ scroll0 @scroll-height)
(* (if platform/ios? 3 1))
(+ -40)
(min 8)))
[communities.icon/community-icon-redesign community
(icon-size scroll-height)]]
(when (and (not joined)
(= status :gated))
[rn/view {:position :absolute
:top 8
:right 8}
[quo/permission-tag-container
{:locked locked
:status status
:tokens tokens
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content-height 210
:content
(constantly [quo/token-gating
{:community {:name name
:community-color colors/primary-50
:community-avatar (cond
(= id constants/status-community-id)
(resources/get-image :status-logo)
(seq thumbnail-image)
thumbnail-image)
:gates {:join [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]}}}])}])}]])
(when joined
[rn/view {:position :absolute
:top 12
:right 12}
[quo/status-tag {:status {:type :positive} :label (i18n/label :t/joined)}]])
[rn/view {:margin-top 56}
[quo/text
{:accessibility-label :chat-name-text
:number-of-lines 1
:ellipsize-mode :tail
:weight :semi-bold
:size :heading-1} name]]
:left 17
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[communities.icon/community-icon-redesign community
(->> (+ scroll0 @scroll-height)
(* (if platform/ios? 3 1))
(- max-image-size)
(max min-image-size)
(min max-image-size))]]
(when (and (not joined)
(= status :gated))
[rn/view {:position :absolute
:top 8
:right 8}
[quo/permission-tag-container
{:locked locked
:status status
:tokens tokens
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
{:community {:name name
:community-color colors/primary-50
:community-avatar (cond
(= id constants/status-community-id)
(resources/get-image :status-logo)
(seq thumbnail-image)
thumbnail-image)
:gates {:join [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]}}}])
:content-height 210}])}]])
[quo/text
{:accessibility-label :community-description-text
:number-of-lines 2
:ellipsize-mode :tail
:weight :regular
:size :paragraph-1
:style {:margin-top 8 :margin-bottom 12}}
description]
[quo/community-stats-column :card-view]
[rn/view {:margin-top 12}]
[quo/community-tags tags]
[preview-user-list]
(when-not joined
[quo/button
{:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [requests.actions/request-to-join community])
:content-height 300}])
:override-background-color community-color
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :i/communities}
(i18n/label :t/join-open-community)])]
[channel-list-component channel-heights first-channel-height]])))
(when joined
[rn/view {:position :absolute
:top 12
:right 12}
[quo/status-tag {:status {:type :positive} :label (i18n/label :joined)}]])
[rn/view {:margin-top 56}
[quo/text
{:accessibility-label :chat-name-text
:number-of-lines 1
:ellipsize-mode :tail
:weight :semi-bold
:size :heading-1} name]]
(defn render-sticky-header [channel-heights first-channel-height]
(fn [scroll-height]
(when (> scroll-height @first-channel-height)
[rn/blur-view style/blur-channel-header
[quo/divider-label
{:label (:label (last (filter (fn [{:keys [height]}]
(>= scroll-height (+ height @first-channel-height)))
@channel-heights)))
:chevron-position :left}]])))
[quo/text
{:accessibility-label :community-description-text
:number-of-lines 2
:ellipsize-mode :tail
:weight :regular
:size :paragraph-1
:style {:margin-top 8 :margin-bottom 12}}
description]
[quo/community-stats-column :card-view]
[rn/view {:margin-top 12}]
[quo/community-tags tags]
[preview-user-list]
(when-not joined
[quo/button
{:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [requests.actions/request-to-join community])
:content-height 300}])
:override-background-color community-color
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :i/communities}
(i18n/label :join-open-community)])]
[channel-list-component channel-heights first-channel-height]]]]])))
(defn community-card-page-view [{:keys [name cover] :as community}]
(let [channel-heights (reagent/atom [])
first-channel-height (reagent/atom 0)
scroll-component (scroll-page/scroll-page
(fn [] [communities.icon/community-icon-redesign community 24])
cover
{:right-section-buttons [{:icon :i/search
:background-color (scroll-page/icon-color)}
{:icon :i/options
:background-color (scroll-page/icon-color)
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (constantly [home.actions/actions community])
:content-height 400}])}]}
name)]
(fn []
(let [page-component (memoize (render-page-content community channel-heights first-channel-height))
sticky-header (memoize (render-sticky-header channel-heights first-channel-height))]
(fn []
(scroll-component
sticky-header
page-component))))))
(defn overview []
(let [community-mock (rf/sub [:get-screen-params :community-overview]) ;;TODO stop using mock data and only pass community id
(let [community-mock (rf/sub [:get-screen-params :community-overview]) ;TODO stop using mock data and only pass community id
community (rf/sub [:communities/community (:id community-mock)])]
[rn/view {:style