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:
parent
8abea59196
commit
3ad9cfa269
|
@ -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]
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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 []
|
||||
(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}
|
||||
(defn- on-open [{:keys [bottom-value internal-atom opacity-value]}]
|
||||
(when-not @internal-atom
|
||||
(react/dismiss-keyboard!)
|
||||
(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
|
||||
: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)
|
||||
(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
|
||||
: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))]))])
|
||||
|
|
|
@ -15,36 +15,34 @@
|
|||
(re-frame/dispatch event))
|
||||
|
||||
(defn add-new-view []
|
||||
[react/view {:flex 1 :flex-direction :row}
|
||||
[react/view {:flex 1}
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/start-new-chat
|
||||
:accessibility-label :start-1-1-chat-button
|
||||
:icon :main-icons/one-on-one-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-chat])}]
|
||||
(when config/group-chat-enabled?
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/start-group-chat
|
||||
:accessibility-label :start-group-chat-button
|
||||
:icon :main-icons/group-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:contact.ui/start-group-chat-pressed])}])
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/new-public-group-chat
|
||||
:accessibility-label :join-public-chat-button
|
||||
:icon :main-icons/public-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}]
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/invite-friends
|
||||
:accessibility-label :chats-menu-invite-friends-button
|
||||
: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)}))}]]])
|
||||
[react/view
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/start-new-chat
|
||||
:accessibility-label :start-1-1-chat-button
|
||||
:icon :main-icons/one-on-one-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-chat])}]
|
||||
(when config/group-chat-enabled?
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/start-group-chat
|
||||
:accessibility-label :start-group-chat-button
|
||||
:icon :main-icons/group-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:contact.ui/start-group-chat-pressed])}])
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/new-public-group-chat
|
||||
:accessibility-label :join-public-chat-button
|
||||
:icon :main-icons/public-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}]
|
||||
[list-item/list-item
|
||||
{:theme :action
|
||||
:title :t/invite-friends
|
||||
:accessibility-label :chats-menu-invite-friends-button
|
||||
: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)}))}]])
|
||||
|
||||
(def add-new
|
||||
{:content add-new-view
|
||||
:content-height (if config/group-chat-enabled? 256 192)})
|
||||
{:content add-new-view})
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue