Rework bottom-sheet component

Fixes #9848

Update initial content height

Review followup

Fix sheet disappear during drag

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-01-24 19:34:59 +03:00
parent 8abea59196
commit 3ad9cfa269
No known key found for this signature in database
GPG Key ID: C9A094959935A952
5 changed files with 160 additions and 149 deletions

View File

@ -299,6 +299,7 @@
comp])
(def safe-area-provider (adapt-class (object/get js-dependencies/safe-area-context "SafeAreaProvider")))
(def safe-area-consumer (adapt-class (object/get js-dependencies/safe-area-context "SafeAreaConsumer")))
(defn create-main-screen-view [current-view]
(fn [props & children]

View File

@ -1,10 +1,9 @@
(ns status-im.ui.components.bottom-sheet.styles
(:require [status-im.ui.components.colors :as colors]
[status-im.utils.platform :as platform]))
(:require [status-im.ui.components.colors :as colors]))
(def border-radius 16)
(def bottom-padding (if platform/iphone-x? 34 8))
(def bottom-view-height 1000)
(def vertical-padding 8)
(def margin-top 56)
(def container
{:position :absolute
@ -26,16 +25,16 @@
:background-color colors/black-transparent-40})
(defn content-container
[content-height bottom-value]
[window-height content-height bottom-value]
{:background-color colors/white
:border-top-left-radius border-radius
:border-top-right-radius border-radius
:height (+ content-height bottom-view-height)
:bottom (- bottom-view-height)
:align-self :stretch
:transform [{:translateY bottom-value}]
:justify-content :flex-start
:padding-bottom bottom-padding})
:height (+ content-height window-height)
:bottom (- window-height)
:transform [{:translateY bottom-value}]})
(def sheet-wrapper {:flex 1
:justify-content :flex-end})
(def content-header
{:height border-radius
@ -48,8 +47,3 @@
:height 4
:background-color colors/gray-transparent-40
:border-radius 2})
(def bottom-view
{:background-color colors/white
:height bottom-view-height
:align-self :stretch})

View File

@ -5,13 +5,13 @@
[reagent.core :as reagent]
[re-frame.core :as re-frame]))
(def initial-animation-duration 300)
(def initial-animation-duration 400)
(def release-animation-duration 150)
(def cancellation-animation-duration 100)
(def swipe-opacity-range 100)
(def cancellation-height 180)
(def cancellation-coefficient 0.3)
(def min-opacity 0.05)
(def min-velocity 0.1)
(def min-velocity 0.4)
(defn- animate
[{:keys [opacity new-opacity-value
@ -32,13 +32,6 @@
:friction 6
:useNativeDriver true})])))
(defn animate-panel-open [opacity-value bottom-value]
(animate {:bottom bottom-value
:new-bottom-value 0
:opacity opacity-value
:new-opacity-value 1
:duration initial-animation-duration}))
(defn- on-move
[{:keys [height bottom-value opacity-value]}]
(fn [_ state]
@ -48,12 +41,12 @@
(animation/set-value bottom-value dy)
(animation/set-value opacity-value opacity))
(neg? dy)
(animation/set-value bottom-value (/ dy 2))))))
(animation/set-value bottom-value dy)))))
(defn cancelled? [height dy vy]
(defn- cancelled? [height dy vy]
(or
(<= min-velocity vy)
(> cancellation-height (- height dy))))
(> (* cancellation-coefficient height) (- height dy))))
(defn- cancel
([opts] (cancel opts nil))
@ -68,18 +61,18 @@
(when (fn? callback) (callback)))})))
(defn- on-release
[{:keys [height bottom-value opacity-value on-cancel] :as opts}]
[{:keys [height bottom-value close-sheet opacity-value]}]
(fn [_ state]
(let [{:strs [dy vy]} (js->clj state)]
(if (cancelled? height dy vy)
(cancel opts on-cancel)
(close-sheet)
(animate {:bottom bottom-value
:new-bottom-value 0
:opacity opacity-value
:new-opacity-value 1
:duration release-animation-duration})))))
(defn swipe-pan-responder [opts]
(defn- swipe-pan-responder [opts]
(.create
react/pan-responder
(clj->js
@ -90,75 +83,115 @@
:onPanResponderRelease (on-release opts)
:onPanResponderTerminate (on-release opts)})))
(defn pan-handlers [pan-responder]
(defn- pan-handlers [pan-responder]
(js->clj (.-panHandlers pan-responder)))
(defn- bottom-sheet-view
[{:keys [opacity-value bottom-value]}]
(reagent.core/create-class
{:component-did-mount
(fn []
(defn- on-open [{:keys [bottom-value internal-atom opacity-value]}]
(when-not @internal-atom
(react/dismiss-keyboard!)
(animate-panel-open opacity-value bottom-value))
:reagent-render
(fn [{:keys [opacity-value bottom-value height
content on-cancel disable-drag?]
:or {on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}
:as opts}]
[react/keyboard-avoiding-view {:style styles/container}
[react/touchable-highlight
{:on-press #(cancel opts on-cancel)
:style styles/container}
(reset! internal-atom true)
(animate {:bottom bottom-value
:new-bottom-value 0
:opacity opacity-value
:new-opacity-value 1
:duration initial-animation-duration})))
[react/animated-view (styles/shadow opacity-value)]]
[react/animated-view (merge
{:style (styles/content-container height bottom-value)}
(when-not disable-drag?
(pan-handlers (swipe-pan-responder opts))))
[react/view {:style styles/content-header}
[react/view styles/handle]]
[react/view {:style {:flex 1
:height height}}
[content]]
[react/view {:style styles/bottom-view}]]])}))
(defn- on-close
[{:keys [bottom-value opacity-value on-cancel internal-atom height]}]
(when @internal-atom
(animate {:bottom bottom-value
:new-bottom-value height
:opacity opacity-value
:new-opacity-value 0
:duration cancellation-animation-duration
:callback (fn []
(when (fn? on-cancel)
(animation/set-value bottom-value height)
(animation/set-value opacity-value 0)
(reset! internal-atom false)
(on-cancel)))})))
(defn bottom-sheet
[{:keys [show? content-height on-cancel]}]
(let [show-sheet? (reagent/atom show?)
total-content-height (+ content-height styles/border-radius
styles/bottom-padding)
bottom-value (animation/create-value total-content-height)
opacity-value (animation/create-value 0)
opts {:height total-content-height
(defn bottom-sheet-view [{:keys [window-height]}]
(let [opacity-value (animation/create-value 0)
bottom-value (animation/create-value window-height)
content-height (reagent/atom (* 0.4 window-height))
internal-visible (reagent/atom false)
external-visible (reagent/atom false)]
(fn [{:keys [content on-cancel disable-drag? show-handle? show?
backdrop-dismiss? safe-area window-height]
:or {show-handle? true
backdrop-dismiss? true
on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}}]
(let [height (+ @content-height
styles/border-radius)
max-height (- window-height
(:top safe-area)
styles/margin-top)
sheet-height (min max-height height)
close-sheet (fn []
(on-close {:opacity-value opacity-value
:bottom-value bottom-value
:height height
:internal-atom internal-visible
:on-cancel on-cancel}))]
(when-not (= @external-visible show?)
(reset! external-visible show?)
(cond
(true? show?)
(on-open {:bottom-value bottom-value
:opacity-value opacity-value
:show-sheet? show-sheet?
:on-cancel on-cancel}]
(reagent.core/create-class
{:component-will-update
(fn [this [_ new-args]]
(let [old-args (second (.-argv (.-props this)))
old-show? (:show? old-args)
new-show? (:show? new-args)
old-height (:content-height old-args)
new-height (:content-height new-args)
total-content-height (+ new-height
styles/border-radius
styles/bottom-padding)
opts' (assoc opts :height total-content-height)]
(when (and new-show? (not= old-height new-height))
(animation/set-value bottom-value new-height))
(cond (and (not old-show?) new-show?)
(reset! show-sheet? true)
:internal-atom internal-visible
:height height})
(and old-show? (false? new-show?) (true? @show-sheet?))
(cancel opts'))))
:reagent-render
(fn [{:keys [content content-height]}]
(let [total-content-height (+ content-height
styles/border-radius
styles/bottom-padding)]
(when @show-sheet?
[bottom-sheet-view (assoc opts
:content content
:height total-content-height)])))})))
(false? show?)
(close-sheet)))
(when @internal-visible
[react/view {:style styles/container}
[react/touchable-highlight (merge {:style styles/container}
(when backdrop-dismiss?
{:on-press #(close-sheet)}))
[react/animated-view {:style (styles/shadow opacity-value)}]]
[react/keyboard-avoiding-view {:pointer-events "box-none"
:behaviour "position"
:style styles/sheet-wrapper}
[react/animated-view (merge
{:style (styles/content-container window-height sheet-height bottom-value)}
(when-not (or disable-drag? (= max-height sheet-height))
(pan-handlers
(swipe-pan-responder {:bottom-value bottom-value
:opacity-value opacity-value
:height height
:close-sheet #(close-sheet)}))))
[react/view (merge {:style styles/content-header}
(when (and (not disable-drag?)
(= max-height sheet-height))
(pan-handlers
(swipe-pan-responder {:bottom-value bottom-value
:opacity-value opacity-value
:height height
:close-sheet #(close-sheet)}))))
(when show-handle?
[react/view styles/handle])]
[react/animated-view {:style {:height sheet-height}}
;; NOTE(Ferossgp): For a better UX on onScrollBeginDrag we can start dragging the sheet.
[react/scroll-view {:bounces false
:scroll-enabled true
:style {:flex 1}}
[react/view {:style {:padding-top styles/vertical-padding
:padding-bottom (+ styles/vertical-padding
(:bottom safe-area))}
:on-layout #(->> %
.-nativeEvent
.-layout
.-height
(reset! content-height))}
[content]]]]]]])))))
(defn bottom-sheet [props]
[react/safe-area-consumer
(fn [insets]
(reagent/as-element
[bottom-sheet-view (assoc props
:window-height @(re-frame/subscribe [:dimensions/window-height])
:safe-area (js->clj insets :keywordize-keys true))]))])

View File

@ -15,8 +15,7 @@
(re-frame/dispatch event))
(defn add-new-view []
[react/view {:flex 1 :flex-direction :row}
[react/view {:flex 1}
[react/view
[list-item/list-item
{:theme :action
:title :t/start-new-chat
@ -43,8 +42,7 @@
:icon :main-icons/share
:on-press #(do
(re-frame/dispatch [:bottom-sheet/hide-sheet])
(list-selection/open-share {:message (i18n/label :t/get-status-at)}))}]]])
(list-selection/open-share {:message (i18n/label :t/get-status-at)}))}]])
(def add-new
{:content add-new-view
:content-height (if config/group-chat-enabled? 256 192)})
{:content add-new-view})

View File

@ -31,22 +31,8 @@
(defonce initial-view-id (atom nil))
(defn bottom-sheet-comp [opts height-atom]
;; We compute bottom sheet height dynamically by rendering it
;; on an invisible view; then, if height is already available
;; (either because it is statically provided or computed),
;; we render the sheet itself
(if (or (not @height-atom) (= 0 @height-atom))
[react/view {:style {:position :absolute :opacity 0}
:on-layout (fn [e]
(let [h (-> e .-nativeEvent .-layout .-height)]
(reset! height-atom h)))}
(when (:content opts)
[(:content opts)])]
[bottom-sheet/bottom-sheet (assoc opts :content-height @height-atom)]))
(views/defview bottom-sheet []
(views/letsubs [{:keys [show? view]} [:bottom-sheet]]
(defview bottom-sheet []
(letsubs [{:keys [show? view]} [:bottom-sheet]]
(let [opts (cond-> {:show? show?
:on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}
@ -69,9 +55,8 @@
(merge about-app/learn-more)
(= view :recover-sheet)
(merge (recover.views/bottom-sheet)))
height-atom (reagent/atom (if (:content-height opts) (:content-height opts) nil))]
[bottom-sheet-comp opts height-atom])))
(merge (recover.views/bottom-sheet)))]
[bottom-sheet/bottom-sheet opts])))
(defn reset-component-on-mount [view-id component two-pane?]
(when (and @initial-view-id