chore: add floating button page component (#17737)

Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
This commit is contained in:
Jamie Caprani 2023-11-17 17:16:55 +00:00 committed by GitHub
parent 1d1d586223
commit 2e2d15adfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 370 additions and 10 deletions

View File

@ -131,6 +131,13 @@
:accessibility-label :appearance-settings-button
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :quo-preview])}])
(when config/quo-preview-enabled?
[list.item/list-item
{:icon :main-icons/appearance
:title "Status IM Components"
:accessibility-label :status-im-common-components
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :status-im-preview])}])
[list.item/list-item
{:icon :main-icons/appearance
:title (i18n/label :t/appearance)

View File

@ -0,0 +1,32 @@
(ns status-im2.common.floating-button-page.component-spec
(:require [quo.core :as quo]
[status-im2.common.floating-button-page.view :as floating-button-page]
[test-helpers.component :as h]))
(h/describe "floating button page"
(h/test "renders with a header and standard button"
(h/render [floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:background :blur
:icon-name :i/close}]
:footer [quo/button {} "continue"]}])
(h/is-truthy (h/get-by-text "continue"))
(h/is-truthy (h/get-by-text "floating button page")))
(h/test "renders with a header and a slide button"
(h/render [floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:background :blur
:icon-name :i/close}]
:footer [quo/slide-button
{:track-text "We gotta slide"
:track-icon :face-id}]}])
(h/is-truthy (h/get-by-text "We gotta slide"))))

View File

@ -0,0 +1,17 @@
(ns status-im2.common.floating-button-page.floating-container.style
(:require [react-native.safe-area :as safe-area]))
(defn content-container
[blur? keyboard-shown?]
(let [margin-bottom (if keyboard-shown? 0 (safe-area/get-bottom))]
(cond-> {:margin-top :auto
:overflow :hidden
:margin-bottom margin-bottom
:padding-vertical 12
:padding-horizontal 20}
blur? (dissoc :padding-vertical :padding-horizontal))))
(def blur-inner-container
{:background-color :transparent ; required, otherwise blur-view will shrink
:padding-vertical 12
:padding-horizontal 20})

View File

@ -0,0 +1,25 @@
(ns status-im2.common.floating-button-page.floating-container.view
(:require [quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[status-im2.common.floating-button-page.floating-container.style :as style]))
(defn- blur-container
[child theme]
[blur/view
{:blur-amount 12
:blur-radius 12
:blur-type (quo.theme/theme-value :light :dark theme)}
[rn/view {:style style/blur-inner-container}
child]])
(defn view-internal
[{:keys [theme on-layout keyboard-shown? blur?]} child]
[rn/view
{:style (style/content-container blur? keyboard-shown?)
:on-layout on-layout}
(if blur?
[blur-container child theme]
child)])
(def view (quo.theme/with-theme view-internal))

View File

@ -0,0 +1,15 @@
(ns status-im2.common.floating-button-page.style)
(def page-container
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def keyboard-avoiding-view
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})

View File

@ -0,0 +1,102 @@
(ns status-im2.common.floating-button-page.view
(:require
[oops.core :as oops]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.common.floating-button-page.floating-container.view :as floating-container]
[status-im2.common.floating-button-page.style :as style]))
(defn- show-background
[{:keys [window-height keyboard-height footer-container-height content-scroll-y
content-container-height header-height keyboard-shown?]}]
(let [available-space (- window-height
(safe-area/get-top)
header-height
keyboard-height ; Already contains the bottom safe area value
footer-container-height)
scroll-view-height (- content-container-height content-scroll-y)
overlap? (< available-space scroll-view-height)]
(and keyboard-shown? overlap?)))
(defn- set-height-on-layout
[ratom]
(fn [event]
(let [height (oops/oget event "nativeEvent.layout.height")]
(reset! ratom height))))
(defn- init-keyboard-listeners
[{:keys [on-did-show]}]
(let [keyboard-will-show? (reagent/atom false)
keyboard-did-show? (reagent/atom false)
add-listener (fn [listener callback]
(oops/ocall rn/keyboard "addListener" listener callback))
will-show-listener (add-listener "keyboardWillShow"
#(reset! keyboard-will-show? true))
did-show-listener (add-listener "keyboardDidShow"
(fn [e]
(reset! keyboard-did-show? true)
(when on-did-show (on-did-show e))))
will-hide-listener (add-listener "keyboardWillHide"
#(reset! keyboard-will-show? false))
did-hide-listener (add-listener "keyboardDidHide"
#(reset! keyboard-did-show? false))
remove-listeners (fn []
(doseq [listener [will-show-listener will-hide-listener
did-show-listener did-hide-listener]]
(oops/ocall listener "remove")))]
{:keyboard-will-show? keyboard-will-show?
:keyboard-did-show? keyboard-did-show?
:remove-listeners remove-listeners}))
(defn view
[{:keys [header footer]} & children]
(reagent/with-let [window-height (:height (rn/get-window))
footer-container-height (reagent/atom 0)
header-height (reagent/atom 0)
content-container-height (reagent/atom 0)
content-scroll-y (reagent/atom 0)
keyboard-height (reagent/atom 0)
{:keys [keyboard-will-show?
keyboard-did-show?
remove-listeners]} (init-keyboard-listeners
{:on-did-show
(fn [e]
(reset! keyboard-height
(oops/oget e "endCoordinates.height")))})
set-header-height (set-height-on-layout header-height)
set-content-container-height (set-height-on-layout content-container-height)
set-footer-container-height (set-height-on-layout footer-container-height)
set-content-y-scroll (fn [event]
(reset! content-scroll-y
(oops/oget event "nativeEvent.contentOffset.y")))]
(let [keyboard-shown? (if platform/ios? @keyboard-will-show? @keyboard-did-show?)
show-background? (show-background {:window-height window-height
:footer-container-height @footer-container-height
:keyboard-height @keyboard-height
:content-scroll-y @content-scroll-y
:content-container-height @content-container-height
:header-height @header-height
:keyboard-shown? keyboard-shown?})]
[rn/view {:style style/page-container}
[rn/view {:on-layout set-header-height}
header]
[rn/scroll-view
{:on-scroll set-content-y-scroll
:scroll-event-throttle 64
:content-container-style {:flex-grow 1}}
(into [rn/view {:on-layout set-content-container-height}]
children)]
[rn/keyboard-avoiding-view
{:style style/keyboard-avoiding-view
:keyboard-vertical-offset (if platform/ios? (safe-area/get-top) 0)
:pointer-events :box-none}
[floating-container/view
{:on-layout set-footer-container-height
:keyboard-shown? keyboard-shown?
:blur? show-background?}
footer]]])
(finally
(remove-listeners))))

View File

@ -6,14 +6,14 @@
(defn- view-internal
[{:keys [theme]}]
[{:keys [theme title]}]
(let [logged-in? (rf/sub [:multiaccount/logged-in?])
has-profiles? (boolean (rf/sub [:profile/profiles-overview]))
root (if has-profiles? :profiles :intro)
light? (= theme :light)]
[quo/page-nav
{:type :title
:title "quo components preview"
:title title
:text-align :left
:icon-name :i/close
:right-side [{:icon-name (if light? :i/dark :i/light)

View File

@ -487,7 +487,7 @@
(defn- main-screen
[]
[:<>
[common/navigation-bar]
[common/navigation-bar {:title "Quo components preview"}]
[rn/scroll-view {:style (style/main)}
(for [category (sort screens-categories)]
^{:key (first category)}

View File

@ -317,10 +317,10 @@
children)])
(defn- f-preview-container
[{:keys [state descriptor blur? blur-dark-only?
[{:keys [title state descriptor blur? blur-dark-only?
component-container-style
blur-container-style blur-view-props blur-height show-blur-background?]
:or {blur-height 200}}
:or {blur-height 200 title "quo component"}}
& children]
(let [theme (quo.theme/use-theme-value)]
(rn/use-effect (fn []
@ -332,7 +332,7 @@
[rn/view
{:style {:top (safe-area/get-top)
:flex 1}}
[common/navigation-bar]
[common/navigation-bar {:title title}]
[rn/scroll-view
{:style (style/panel-basic)
:shows-vertical-scroll-indicator false}

View File

@ -0,0 +1,22 @@
(ns status-im2.contexts.status-im-preview.common.floating-button-page.style
(:require [quo.foundations.colors :as colors]
[react-native.safe-area :as safe-area]))
(defn container
[]
{:flex 1
:margin-top (safe-area/get-top)})
(def background-image
{:position :absolute
:top (- (safe-area/get-top))
:left 0
:right 0
:bottom 200})
(defn page-content
[height]
{:flex 1
:height height
:overflow :hidden
:background-color (colors/resolve-color :army 30)})

View File

@ -0,0 +1,55 @@
(ns status-im2.contexts.status-im-preview.common.floating-button-page.view
(:require [quo.core :as quo]
[re-frame.core :as rf]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.floating-button-page.view :as floating-button-page]
[status-im2.common.resources :as resources]
[status-im2.contexts.status-im-preview.common.floating-button-page.style :as style]))
(defn view
[]
(let [content-height (reagent/atom 450)
slide? (reagent/atom false)]
(fn []
[rn/view {:style (style/container)}
(when-not @slide?
[rn/image
{:style style/background-image
:source (resources/get-mock-image :dark-blur-bg)}])
[floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:right-side [{:icon-name :i/swap
:on-press #(swap! slide? not)}]
:background :blur
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
:footer (if @slide?
[quo/slide-button
{:track-text "We gotta slide"
:track-icon :face-id
:container-style {:z-index 2}
:customization-color :blue
:on-complete #(js/alert "button slid")}
"Save address"]
[quo/button
{:container-style {:z-index 2}
:on-press #(js/alert "button pressed")}
"Save address"])}
[rn/view {:style (style/page-content @content-height)}
[quo/text {:size :heading-1} "Page Content"]
[quo/input
{:auto-focus true
:value ""}]
[quo/button
{:type :outline
:on-press #(swap! content-height (fn [v] (+ v 10)))}
"increase height"]
[quo/button
{:type :outline
:on-press #(swap! content-height (fn [v] (- v 10)))}
"decrease height"]]]])))

View File

@ -0,0 +1,59 @@
(ns status-im2.contexts.status-im-preview.main
(:refer-clojure :exclude [filter])
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.common :as common]
[status-im2.contexts.status-im-preview.common.floating-button-page.view :as floating-button-page]
[status-im2.contexts.status-im-preview.style :as style]
[utils.re-frame :as rf]))
(def screens-categories
{:common [{:name :floating-button-page
:component floating-button-page/view}]})
(defn- category-view
[]
(let [open? (reagent/atom false)
on-press #(swap! open? not)]
(fn [category]
[rn/view {:style {:margin-vertical 8}}
[quo/dropdown
{:type :grey
:state (if @open? :active :default)
:on-press on-press}
(name (key category))]
(when @open?
(for [{category-name :name} (val category)]
^{:key category-name}
[quo/button
{:type :outline
:container-style {:margin-vertical 8}
:on-press #(rf/dispatch [:navigate-to category-name])}
(name category-name)]))])))
(defn- main-screen
[]
[:<>
[common/navigation-bar {:title "Status IM components"}]
[rn/scroll-view {:style (style/main)}
(for [category (sort screens-categories)]
^{:key (first category)}
[category-view category])]])
(def screens
(->> screens-categories
(map val)
flatten
(map (fn [subcategory]
(update-in subcategory
[:options :topBar]
merge
{:visible false})))))
(def main-screens
[{:name :status-im-preview
:options {:topBar {:visible false}
:insets {:top? true}}
:component main-screen}])

View File

@ -0,0 +1,10 @@
(ns status-im2.contexts.status-im-preview.style
(:require
[quo.foundations.colors :as colors]))
(defn main
[]
{:flex 1
:padding-bottom 8
:padding-horizontal 16
:background-color (colors/theme-colors colors/white colors/neutral-90)})

View File

@ -1,5 +1,6 @@
(ns status-im2.core-spec
(:require
[status-im2.common.floating-button-page.component-spec]
[status-im2.contexts.chat.messages.content.audio.component-spec]
[status-im2.contexts.communities.actions.community-options.component-spec]
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]))

View File

@ -32,6 +32,7 @@
[status-im2.contexts.shell.activity-center.view :as activity-center]
[status-im2.contexts.shell.jump-to.view :as shell]
[status-im2.contexts.shell.share.view :as share]
[status-im2.contexts.status-im-preview.main :as status-im-preview]
[status-im2.contexts.syncing.find-sync-code.view :as find-sync-code]
[status-im2.contexts.syncing.how-to-pair.view :as how-to-pair]
[status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page]
@ -46,7 +47,7 @@
[status-im2.contexts.wallet.create-account.select-keypair.view :as wallet-select-keypair]
[status-im2.contexts.wallet.create-account.view :as wallet-create-account]
[status-im2.contexts.wallet.edit-account.view :as wallet-edit-account]
[status-im2.contexts.wallet.saved-address.view :as wallet-saved-address]
[status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses]
[status-im2.contexts.wallet.scan-account.view :as scan-address]
[status-im2.contexts.wallet.send.select-address.view :as wallet-select-address]
[status-im2.navigation.options :as options]
@ -283,8 +284,8 @@
:options {:insets {:top? true}}
:component wallet-create-account/view}
{:name :wallet-saved-address
:component wallet-saved-address/view}
{:name :wallet-saved-addresses
:component wallet-saved-addresses/view}
{:name :wallet-select-address
:options {:modalPresentationStyle :overCurrentContext}
@ -305,4 +306,11 @@
quo.preview/screens)
(when config/quo-preview-enabled?
quo.preview/main-screens)))
quo.preview/main-screens)
(when config/quo-preview-enabled?
status-im-preview/screens)
(when config/quo-preview-enabled?
status-im-preview/main-screens)))

View File

@ -25,6 +25,13 @@ jest.mock('react-native-languages', () => ({
},
}));
jest.mock('react-native-static-safe-area-insets', () => ({
default: {
safeAreaInsetsTop: 0,
safeAreaInsetsBottom: 0,
},
}));
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'));
jest.mock('@react-native-community/audio-toolkit', () => ({