From 2741fd141e40cff56d87d39d990be083ff652607 Mon Sep 17 00:00:00 2001 From: Gheorghe Pinzaru Date: Fri, 24 Jan 2020 19:34:59 +0300 Subject: [PATCH] Rework bottom-sheet component Fixes #9848 --- .../src/status_im/ui/components/react.cljs | 1 + .../ui/components/bottom_sheet/styles.cljs | 26 +-- .../ui/components/bottom_sheet/view.cljs | 198 ++++++++++-------- .../ui/screens/home/sheet/views.cljs | 60 +++--- src/status_im/ui/screens/views.cljs | 68 +++--- 5 files changed, 183 insertions(+), 170 deletions(-) diff --git a/components/src/status_im/ui/components/react.cljs b/components/src/status_im/ui/components/react.cljs index 3be989f7b23..d3957ce58b1 100644 --- a/components/src/status_im/ui/components/react.cljs +++ b/components/src/status_im/ui/components/react.cljs @@ -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] diff --git a/src/status_im/ui/components/bottom_sheet/styles.cljs b/src/status_im/ui/components/bottom_sheet/styles.cljs index 08ccaabf8e0..29a741093fa 100644 --- a/src/status_im/ui/components/bottom_sheet/styles.cljs +++ b/src/status_im/ui/components/bottom_sheet/styles.cljs @@ -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}) diff --git a/src/status_im/ui/components/bottom_sheet/view.cljs b/src/status_im/ui/components/bottom_sheet/view.cljs index 36e7e13f071..26e80f9fb93 100644 --- a/src/status_im/ui/components/bottom_sheet/view.cljs +++ b/src/status_im/ui/components/bottom_sheet/view.cljs @@ -5,11 +5,11 @@ [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) @@ -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,116 @@ :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 opacity-value]}] + (when-not @internal + (react/dismiss-keyboard!) + (reset! internal true) + (animate {:bottom bottom-value + :new-bottom-value 0 + :opacity opacity-value + :new-opacity-value 1 + :duration initial-animation-duration}))) + +(defn- on-close + [{:keys [bottom-value opacity-value on-cancel internal height]}] + (when @internal + (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 false) + (on-cancel)))}))) + +(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 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 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 internal-visible + :height height}) - [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}]]])})) + (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)}]] -(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) + [react/keyboard-avoiding-view {:pointer-events "box-none" + :behaviour "position" + :style styles/sheet-wrapper} + [react/animated-view (merge + {:pointer-events "box-none" + :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-not disable-drag? + (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: 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]]]]]]]))))) - (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)])))}))) +(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))]))]) diff --git a/src/status_im/ui/screens/home/sheet/views.cljs b/src/status_im/ui/screens/home/sheet/views.cljs index 39fadd47f22..5d8c3382275 100644 --- a/src/status_im/ui/screens/home/sheet/views.cljs +++ b/src/status_im/ui/screens/home/sheet/views.cljs @@ -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}) diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 1503cdae428..cb545f239d4 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -31,47 +31,33 @@ (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]] - (let [opts (cond-> {:show? show? - :on-cancel #(re-frame/dispatch [:bottom-sheet/hide])} - - (map? view) - (merge view) - - (= view :mobile-network) - (merge mobile-network-settings/settings-sheet) - - (= view :mobile-network-offline) - (merge mobile-network-settings/offline-sheet) - - (= view :add-new) - (merge home.sheet/add-new) - - (= view :keycard.login/more) - (merge keycard/more-sheet) - - (= view :learn-more) - (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]))) +(defn bottom-sheet [] + (fn [] + (let [{:keys [show? view]} @(re-frame/subscribe [:bottom-sheet]) + opts (cond-> {:show? show? + :on-cancel #(re-frame/dispatch [:bottom-sheet/hide])} + + (map? view) + (merge view) + + (= view :mobile-network) + (merge mobile-network-settings/settings-sheet) + + (= view :mobile-network-offline) + (merge mobile-network-settings/offline-sheet) + + (= view :add-new) + (merge home.sheet/add-new) + + (= view :keycard.login/more) + (merge keycard/more-sheet) + + (= view :learn-more) + (merge about-app/learn-more) + + (= view :recover-sheet) + (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