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]) comp])
(def safe-area-provider (adapt-class (object/get js-dependencies/safe-area-context "SafeAreaProvider"))) (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] (defn create-main-screen-view [current-view]
(fn [props & children] (fn [props & children]

View File

@ -1,10 +1,9 @@
(ns status-im.ui.components.bottom-sheet.styles (ns status-im.ui.components.bottom-sheet.styles
(:require [status-im.ui.components.colors :as colors] (:require [status-im.ui.components.colors :as colors]))
[status-im.utils.platform :as platform]))
(def border-radius 16) (def border-radius 16)
(def bottom-padding (if platform/iphone-x? 34 8)) (def vertical-padding 8)
(def bottom-view-height 1000) (def margin-top 56)
(def container (def container
{:position :absolute {:position :absolute
@ -26,16 +25,16 @@
:background-color colors/black-transparent-40}) :background-color colors/black-transparent-40})
(defn content-container (defn content-container
[content-height bottom-value] [window-height content-height bottom-value]
{:background-color colors/white {:background-color colors/white
:border-top-left-radius border-radius :border-top-left-radius border-radius
:border-top-right-radius border-radius :border-top-right-radius border-radius
:height (+ content-height bottom-view-height) :height (+ content-height window-height)
:bottom (- bottom-view-height) :bottom (- window-height)
:align-self :stretch :transform [{:translateY bottom-value}]})
:transform [{:translateY bottom-value}]
:justify-content :flex-start (def sheet-wrapper {:flex 1
:padding-bottom bottom-padding}) :justify-content :flex-end})
(def content-header (def content-header
{:height border-radius {:height border-radius
@ -48,8 +47,3 @@
:height 4 :height 4
:background-color colors/gray-transparent-40 :background-color colors/gray-transparent-40
:border-radius 2}) :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] [reagent.core :as reagent]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
(def initial-animation-duration 300) (def initial-animation-duration 400)
(def release-animation-duration 150) (def release-animation-duration 150)
(def cancellation-animation-duration 100) (def cancellation-animation-duration 100)
(def swipe-opacity-range 100) (def swipe-opacity-range 100)
(def cancellation-height 180) (def cancellation-coefficient 0.3)
(def min-opacity 0.05) (def min-opacity 0.05)
(def min-velocity 0.1) (def min-velocity 0.4)
(defn- animate (defn- animate
[{:keys [opacity new-opacity-value [{:keys [opacity new-opacity-value
@ -32,13 +32,6 @@
:friction 6 :friction 6
:useNativeDriver true})]))) :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 (defn- on-move
[{:keys [height bottom-value opacity-value]}] [{:keys [height bottom-value opacity-value]}]
(fn [_ state] (fn [_ state]
@ -48,12 +41,12 @@
(animation/set-value bottom-value dy) (animation/set-value bottom-value dy)
(animation/set-value opacity-value opacity)) (animation/set-value opacity-value opacity))
(neg? dy) (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 (or
(<= min-velocity vy) (<= min-velocity vy)
(> cancellation-height (- height dy)))) (> (* cancellation-coefficient height) (- height dy))))
(defn- cancel (defn- cancel
([opts] (cancel opts nil)) ([opts] (cancel opts nil))
@ -68,18 +61,18 @@
(when (fn? callback) (callback)))}))) (when (fn? callback) (callback)))})))
(defn- on-release (defn- on-release
[{:keys [height bottom-value opacity-value on-cancel] :as opts}] [{:keys [height bottom-value close-sheet opacity-value]}]
(fn [_ state] (fn [_ state]
(let [{:strs [dy vy]} (js->clj state)] (let [{:strs [dy vy]} (js->clj state)]
(if (cancelled? height dy vy) (if (cancelled? height dy vy)
(cancel opts on-cancel) (close-sheet)
(animate {:bottom bottom-value (animate {:bottom bottom-value
:new-bottom-value 0 :new-bottom-value 0
:opacity opacity-value :opacity opacity-value
:new-opacity-value 1 :new-opacity-value 1
:duration release-animation-duration}))))) :duration release-animation-duration})))))
(defn swipe-pan-responder [opts] (defn- swipe-pan-responder [opts]
(.create (.create
react/pan-responder react/pan-responder
(clj->js (clj->js
@ -90,75 +83,115 @@
:onPanResponderRelease (on-release opts) :onPanResponderRelease (on-release opts)
:onPanResponderTerminate (on-release opts)}))) :onPanResponderTerminate (on-release opts)})))
(defn pan-handlers [pan-responder] (defn- pan-handlers [pan-responder]
(js->clj (.-panHandlers pan-responder))) (js->clj (.-panHandlers pan-responder)))
(defn- bottom-sheet-view (defn- on-open [{:keys [bottom-value internal-atom opacity-value]}]
[{:keys [opacity-value bottom-value]}] (when-not @internal-atom
(reagent.core/create-class (react/dismiss-keyboard!)
{:component-did-mount (reset! internal-atom true)
(fn [] (animate {:bottom bottom-value
(react/dismiss-keyboard!) :new-bottom-value 0
(animate-panel-open opacity-value bottom-value)) :opacity opacity-value
:reagent-render :new-opacity-value 1
(fn [{:keys [opacity-value bottom-value height :duration initial-animation-duration})))
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}
[react/animated-view (styles/shadow opacity-value)]] (defn- on-close
[react/animated-view (merge [{:keys [bottom-value opacity-value on-cancel internal-atom height]}]
{:style (styles/content-container height bottom-value)} (when @internal-atom
(when-not disable-drag? (animate {:bottom bottom-value
(pan-handlers (swipe-pan-responder opts)))) :new-bottom-value height
[react/view {:style styles/content-header} :opacity opacity-value
[react/view styles/handle]] :new-opacity-value 0
[react/view {:style {:flex 1 :duration cancellation-animation-duration
:height height}} :callback (fn []
[content]] (when (fn? on-cancel)
[react/view {:style styles/bottom-view}]]])})) (animation/set-value bottom-value height)
(animation/set-value opacity-value 0)
(reset! internal-atom false)
(on-cancel)))})))
(defn bottom-sheet (defn bottom-sheet-view [{:keys [window-height]}]
[{:keys [show? content-height on-cancel]}] (let [opacity-value (animation/create-value 0)
(let [show-sheet? (reagent/atom show?) bottom-value (animation/create-value window-height)
total-content-height (+ content-height styles/border-radius content-height (reagent/atom (* 0.4 window-height))
styles/bottom-padding) internal-visible (reagent/atom false)
bottom-value (animation/create-value total-content-height) external-visible (reagent/atom false)]
opacity-value (animation/create-value 0) (fn [{:keys [content on-cancel disable-drag? show-handle? show?
opts {:height total-content-height backdrop-dismiss? safe-area window-height]
:bottom-value bottom-value :or {show-handle? true
:opacity-value opacity-value backdrop-dismiss? true
:show-sheet? show-sheet? on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}}]
:on-cancel on-cancel}] (let [height (+ @content-height
(reagent.core/create-class styles/border-radius)
{:component-will-update max-height (- window-height
(fn [this [_ new-args]] (:top safe-area)
(let [old-args (second (.-argv (.-props this))) styles/margin-top)
old-show? (:show? old-args) sheet-height (min max-height height)
new-show? (:show? new-args) close-sheet (fn []
old-height (:content-height old-args) (on-close {:opacity-value opacity-value
new-height (:content-height new-args) :bottom-value bottom-value
total-content-height (+ new-height :height height
styles/border-radius :internal-atom internal-visible
styles/bottom-padding) :on-cancel on-cancel}))]
opts' (assoc opts :height total-content-height)] (when-not (= @external-visible show?)
(when (and new-show? (not= old-height new-height)) (reset! external-visible show?)
(animation/set-value bottom-value new-height)) (cond
(cond (and (not old-show?) new-show?) (true? show?)
(reset! show-sheet? true) (on-open {:bottom-value bottom-value
:opacity-value opacity-value
:internal-atom internal-visible
:height height})
(and old-show? (false? new-show?) (true? @show-sheet?)) (false? show?)
(cancel opts')))) (close-sheet)))
:reagent-render (when @internal-visible
(fn [{:keys [content content-height]}] [react/view {:style styles/container}
(let [total-content-height (+ content-height [react/touchable-highlight (merge {:style styles/container}
styles/border-radius (when backdrop-dismiss?
styles/bottom-padding)] {:on-press #(close-sheet)}))
(when @show-sheet? [react/animated-view {:style (styles/shadow opacity-value)}]]
[bottom-sheet-view (assoc opts
:content content [react/keyboard-avoiding-view {:pointer-events "box-none"
:height total-content-height)])))}))) :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,36 +15,34 @@
(re-frame/dispatch event)) (re-frame/dispatch event))
(defn add-new-view [] (defn add-new-view []
[react/view {:flex 1 :flex-direction :row} [react/view
[react/view {:flex 1} [list-item/list-item
[list-item/list-item {:theme :action
{:theme :action :title :t/start-new-chat
:title :t/start-new-chat :accessibility-label :start-1-1-chat-button
:accessibility-label :start-1-1-chat-button :icon :main-icons/one-on-one-chat
:icon :main-icons/one-on-one-chat :on-press #(hide-sheet-and-dispatch [:navigate-to :new-chat])}]
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-chat])}] (when config/group-chat-enabled?
(when config/group-chat-enabled? [list-item/list-item
[list-item/list-item {:theme :action
{:theme :action :title :t/start-group-chat
:title :t/start-group-chat :accessibility-label :start-group-chat-button
:accessibility-label :start-group-chat-button :icon :main-icons/group-chat
:icon :main-icons/group-chat :on-press #(hide-sheet-and-dispatch [:contact.ui/start-group-chat-pressed])}])
:on-press #(hide-sheet-and-dispatch [:contact.ui/start-group-chat-pressed])}]) [list-item/list-item
[list-item/list-item {:theme :action
{:theme :action :title :t/new-public-group-chat
:title :t/new-public-group-chat :accessibility-label :join-public-chat-button
:accessibility-label :join-public-chat-button :icon :main-icons/public-chat
:icon :main-icons/public-chat :on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}]
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}] [list-item/list-item
[list-item/list-item {:theme :action
{:theme :action :title :t/invite-friends
:title :t/invite-friends :accessibility-label :chats-menu-invite-friends-button
:accessibility-label :chats-menu-invite-friends-button :icon :main-icons/share
:icon :main-icons/share :on-press #(do
:on-press #(do (re-frame/dispatch [:bottom-sheet/hide-sheet])
(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 (def add-new
{:content add-new-view {:content add-new-view})
:content-height (if config/group-chat-enabled? 256 192)})

View File

@ -31,22 +31,8 @@
(defonce initial-view-id (atom nil)) (defonce initial-view-id (atom nil))
(defn bottom-sheet-comp [opts height-atom] (defview bottom-sheet []
;; We compute bottom sheet height dynamically by rendering it (letsubs [{:keys [show? view]} [:bottom-sheet]]
;; 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]]
(let [opts (cond-> {:show? show? (let [opts (cond-> {:show? show?
:on-cancel #(re-frame/dispatch [:bottom-sheet/hide])} :on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}
@ -69,9 +55,8 @@
(merge about-app/learn-more) (merge about-app/learn-more)
(= view :recover-sheet) (= view :recover-sheet)
(merge (recover.views/bottom-sheet))) (merge (recover.views/bottom-sheet)))]
height-atom (reagent/atom (if (:content-height opts) (:content-height opts) nil))] [bottom-sheet/bottom-sheet opts])))
[bottom-sheet-comp opts height-atom])))
(defn reset-component-on-mount [view-id component two-pane?] (defn reset-component-on-mount [view-id component two-pane?]
(when (and @initial-view-id (when (and @initial-view-id