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])
|
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]
|
||||||
|
|
|
@ -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})
|
|
||||||
|
|
|
@ -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))]))])
|
||||||
|
|
|
@ -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)})
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue