#16111 - Toast animation & dismiss gesture
* Enable toast animations and refactor * Add gesture-handler-root-HOC to detect toast's gestures on Android * Add comment about flex 0 style
This commit is contained in:
parent
2b4b357c32
commit
ca0915c940
|
@ -21,8 +21,7 @@
|
||||||
|
|
||||||
(defn toast-undo-action
|
(defn toast-undo-action
|
||||||
[duration on-press override-theme]
|
[duration on-press override-theme]
|
||||||
[toast-action-container
|
[toast-action-container {:on-press on-press :accessibility-label :toast-undo-action}
|
||||||
{:on-press on-press :accessibility-label :toast-undo-action}
|
|
||||||
[rn/view {:style {:margin-right 5}}
|
[rn/view {:style {:margin-right 5}}
|
||||||
[count-down-circle/circle-timer {:duration duration}]]
|
[count-down-circle/circle-timer {:duration duration}]]
|
||||||
[text/text
|
[text/text
|
||||||
|
@ -31,17 +30,17 @@
|
||||||
|
|
||||||
(defn- toast-container
|
(defn- toast-container
|
||||||
[{:keys [left title text right container-style override-theme]}]
|
[{:keys [left title text right container-style override-theme]}]
|
||||||
[rn/view
|
[rn/view {:style (merge style/box-container container-style)}
|
||||||
{:style (merge style/box-container container-style)}
|
|
||||||
[blur/view
|
[blur/view
|
||||||
{:style style/blur-container
|
{:style style/blur-container
|
||||||
:blur-amount 13
|
:blur-amount 13
|
||||||
:blur-radius 10
|
:blur-radius 10
|
||||||
:blur-type :transparent
|
:blur-type :transparent
|
||||||
:overlay-color :transparent}]
|
:overlay-color :transparent}]
|
||||||
[rn/view
|
|
||||||
{:style (style/content-container override-theme)}
|
[rn/view {:style (style/content-container override-theme)}
|
||||||
[rn/view {:style style/left-side-container} left]
|
[rn/view {:style style/left-side-container}
|
||||||
|
left]
|
||||||
[rn/view {:style style/right-side-container}
|
[rn/view {:style style/right-side-container}
|
||||||
(when title
|
(when title
|
||||||
[text/text
|
[text/text
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
:style (style/text override-theme)
|
:style (style/text override-theme)
|
||||||
:accessibility-label :toast-content}
|
:accessibility-label :toast-content}
|
||||||
text])]
|
text])]
|
||||||
(when right right)]])
|
right]])
|
||||||
|
|
||||||
(defn toast
|
(defn toast
|
||||||
[{:keys [icon icon-color title text action undo-duration undo-on-press container-style
|
[{:keys [icon icon-color title text action undo-duration undo-on-press container-style
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
(ns status-im2.common.toasts.animation
|
||||||
|
(:require [react-native.gesture :as gesture]
|
||||||
|
[react-native.reanimated :as reanimated]))
|
||||||
|
|
||||||
|
(def slide-out-up-animation
|
||||||
|
(-> ^js reanimated/slide-out-up-animation
|
||||||
|
(.springify)
|
||||||
|
(.damping 20)
|
||||||
|
(.stiffness 12)))
|
||||||
|
|
||||||
|
(def slide-in-up-animation
|
||||||
|
(-> ^js reanimated/slide-in-up-animation
|
||||||
|
(.springify)
|
||||||
|
(.damping 20)
|
||||||
|
(.stiffness 150)))
|
||||||
|
|
||||||
|
(def linear-transition
|
||||||
|
(-> ^js reanimated/linear-transition
|
||||||
|
.springify
|
||||||
|
(.damping 20)
|
||||||
|
(.stiffness 170)))
|
||||||
|
|
||||||
|
(defn- reset-translate-y
|
||||||
|
([translate-y]
|
||||||
|
(reset-translate-y translate-y 0))
|
||||||
|
([translate-y spring-value]
|
||||||
|
(reanimated/animate-shared-value-with-spring
|
||||||
|
translate-y
|
||||||
|
spring-value
|
||||||
|
{:mass 1 :damping 20 :stiffness 300})))
|
||||||
|
|
||||||
|
(defn- dismiss
|
||||||
|
[translate-y set-dismissed-locally close-toast]
|
||||||
|
(reset-translate-y translate-y -500)
|
||||||
|
(set-dismissed-locally)
|
||||||
|
(close-toast))
|
||||||
|
|
||||||
|
(defn on-update-gesture
|
||||||
|
[translate-y set-dismissed-locally close-toast]
|
||||||
|
(fn [^js evt]
|
||||||
|
(let [evt-translation-y (.-translationY evt)
|
||||||
|
pan-down? (> evt-translation-y 100)
|
||||||
|
pan-up? (< evt-translation-y -30)]
|
||||||
|
(cond
|
||||||
|
pan-down? (reset-translate-y translate-y)
|
||||||
|
pan-up? (dismiss translate-y set-dismissed-locally close-toast)
|
||||||
|
:else (reanimated/set-shared-value translate-y evt-translation-y)))))
|
||||||
|
|
||||||
|
(defn pan-gesture
|
||||||
|
[{:keys [clear-timer create-timer translate-y close-toast set-dismissed-locally
|
||||||
|
dismissed-locally?]}]
|
||||||
|
(-> (gesture/gesture-pan)
|
||||||
|
(gesture/on-start clear-timer)
|
||||||
|
(gesture/on-update (on-update-gesture translate-y set-dismissed-locally close-toast))
|
||||||
|
(gesture/on-end #(when-not dismissed-locally?
|
||||||
|
(reanimated/set-shared-value translate-y 0)
|
||||||
|
(create-timer)))))
|
|
@ -1,13 +1,12 @@
|
||||||
(ns status-im2.common.toasts.style)
|
(ns status-im2.common.toasts.style)
|
||||||
|
|
||||||
(def outmost-transparent-container
|
(def outmost-transparent-container
|
||||||
{:elevation 2
|
{:elevation 2
|
||||||
:pointer-events :box-none
|
:pointer-events :box-none
|
||||||
:padding-top 52
|
:padding-top 52
|
||||||
:flex-direction :column
|
:flex-direction :column
|
||||||
:justify-content :center
|
:justify-content :center
|
||||||
:align-items :center
|
:align-items :center})
|
||||||
:background-color :transparent})
|
|
||||||
|
|
||||||
(def each-toast-container
|
(def each-toast-container
|
||||||
{:width "100%"
|
{:width "100%"
|
||||||
|
|
|
@ -1,89 +1,56 @@
|
||||||
(ns status-im2.common.toasts.view
|
(ns status-im2.common.toasts.view
|
||||||
(:require [quo2.core :as quo]
|
(:require [quo2.core :as quo]
|
||||||
|
[react-native.background-timer :as background-timer]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[react-native.gesture :as gesture]
|
[react-native.gesture :as gesture]
|
||||||
[react-native.reanimated :as reanimated]
|
[react-native.reanimated :as reanimated]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[react-native.background-timer :as background-timer]
|
[status-im2.common.toasts.animation :as animation]
|
||||||
[status-im2.common.toasts.style :as style]
|
[status-im2.common.toasts.style :as style]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn toast
|
(defn toast
|
||||||
[id]
|
[toast-id]
|
||||||
(let [{:keys [type] :as toast-opts} (rf/sub [:toasts/toast id])]
|
(let [{:keys [type] :as toast-opts} (rf/sub [:toasts/toast toast-id])]
|
||||||
(if (= type :notification)
|
(if (= type :notification)
|
||||||
[quo/notification toast-opts]
|
[quo/notification toast-opts]
|
||||||
[quo/toast toast-opts])))
|
[quo/toast toast-opts])))
|
||||||
|
|
||||||
(defn reset-translate-y
|
|
||||||
[translate-y]
|
|
||||||
(reanimated/animate-shared-value-with-spring translate-y
|
|
||||||
0
|
|
||||||
{:mass 1
|
|
||||||
:damping 20
|
|
||||||
:stiffness 300}))
|
|
||||||
|
|
||||||
(defn dismiss
|
|
||||||
[translate-y dismissed-locally? close!]
|
|
||||||
(reanimated/animate-shared-value-with-spring
|
|
||||||
translate-y
|
|
||||||
-500
|
|
||||||
{:mass 1 :damping 20 :stiffness 300})
|
|
||||||
(reset! dismissed-locally? true)
|
|
||||||
(close!))
|
|
||||||
|
|
||||||
(defn f-container
|
(defn f-container
|
||||||
[id]
|
[toast-id]
|
||||||
(let [dismissed-locally? (reagent/atom false)
|
(let [dismissed-locally? (reagent/atom false)
|
||||||
close! #(rf/dispatch [:toasts/close id])
|
set-dismissed-locally #(reset! dismissed-locally? true)
|
||||||
timer (reagent/atom nil)
|
close-toast #(rf/dispatch [:toasts/close toast-id])
|
||||||
clear-timer #(background-timer/clear-timeout @timer)]
|
timer (reagent/atom nil)
|
||||||
|
clear-timer #(background-timer/clear-timeout @timer)]
|
||||||
(fn []
|
(fn []
|
||||||
(let [duration (or (rf/sub [:toasts/toast-cursor id :duration]) 3000)
|
(let [duration (or (rf/sub [:toasts/toast-cursor toast-id :duration]) 3000)
|
||||||
on-dismissed (or (rf/sub [:toasts/toast-cursor id :on-dismissed]) identity)
|
on-dismissed (or (rf/sub [:toasts/toast-cursor toast-id :on-dismissed]) identity)
|
||||||
create-timer (fn []
|
create-timer #(reset! timer (background-timer/set-timeout close-toast duration))
|
||||||
(reset! timer (background-timer/set-timeout close! duration)))
|
translate-y (reanimated/use-shared-value 0)
|
||||||
translate-y (reanimated/use-shared-value 0)
|
pan-gesture (animation/pan-gesture {:clear-timer clear-timer
|
||||||
pan
|
:create-timer create-timer
|
||||||
(->
|
:translate-y translate-y
|
||||||
(gesture/gesture-pan)
|
:close-toast close-toast
|
||||||
(gesture/on-start clear-timer)
|
:set-dismissed-locally set-dismissed-locally
|
||||||
(gesture/on-update
|
:dismissed-locally? @dismissed-locally?})]
|
||||||
(fn [^js evt]
|
|
||||||
(let [evt-translation-y (.-translationY evt)
|
|
||||||
pan-down? (> evt-translation-y 100)
|
|
||||||
pan-up? (< evt-translation-y -30)]
|
|
||||||
(cond
|
|
||||||
pan-down? (reset-translate-y translate-y)
|
|
||||||
pan-up? (dismiss translate-y dismissed-locally? close!)
|
|
||||||
:else
|
|
||||||
(reanimated/set-shared-value translate-y
|
|
||||||
evt-translation-y)))))
|
|
||||||
(gesture/on-end (fn [_]
|
|
||||||
(when-not @dismissed-locally?
|
|
||||||
(reanimated/set-shared-value translate-y 0)
|
|
||||||
(create-timer)))))]
|
|
||||||
;; create auto dismiss timer, clear timer when unmount or duration changed
|
;; create auto dismiss timer, clear timer when unmount or duration changed
|
||||||
(rn/use-effect (fn [] (create-timer) clear-timer) [duration])
|
(rn/use-effect (fn [] (create-timer) clear-timer) [duration])
|
||||||
(rn/use-unmount #(on-dismissed id))
|
(rn/use-unmount #(on-dismissed toast-id))
|
||||||
[gesture/gesture-detector {:gesture pan}
|
|
||||||
|
[gesture/gesture-detector {:gesture pan-gesture}
|
||||||
[reanimated/view
|
[reanimated/view
|
||||||
{;; TODO: this will enable layout animation at runtime and causing flicker on android
|
{:entering animation/slide-in-up-animation
|
||||||
;; we need to resolve this and re-enable layout animation
|
:exiting animation/slide-out-up-animation
|
||||||
;; issue at https://github.com/status-im/status-mobile/issues/14752
|
:layout animation/linear-transition
|
||||||
;; :entering slide-in-up-animation
|
:style (reanimated/apply-animations-to-style
|
||||||
;; :exiting slide-out-up-animation
|
{:transform [{:translateY translate-y}]}
|
||||||
;; :layout reanimated/linear-transition
|
style/each-toast-container)}
|
||||||
:style (reanimated/apply-animations-to-style
|
[toast toast-id]]]))))
|
||||||
{:transform [{:translateY translate-y}]}
|
|
||||||
style/each-toast-container)}
|
|
||||||
[toast id]]]))))
|
|
||||||
|
|
||||||
(defn toasts
|
(defn toasts
|
||||||
[]
|
[]
|
||||||
(let [toasts-ordered (:ordered (rf/sub [:toasts]))]
|
(->> (rf/sub [:toasts])
|
||||||
[into
|
:ordered
|
||||||
[rn/view
|
(into [rn/view {:style style/outmost-transparent-container}]
|
||||||
{:style style/outmost-transparent-container}]
|
(map #(with-meta [:f> f-container %] {:key %})))))
|
||||||
(doall
|
|
||||||
(map (fn [id] ^{:key id} [:f> f-container id]) toasts-ordered))]))
|
|
||||||
|
|
|
@ -212,7 +212,15 @@
|
||||||
opts)}})))
|
opts)}})))
|
||||||
|
|
||||||
;; toast
|
;; toast
|
||||||
(navigation/register-component "toasts" (fn [] views/toasts) js/undefined)
|
(navigation/register-component "toasts"
|
||||||
|
; `:flex 0` is the same as `flex: 0 0 auto` in CSS.
|
||||||
|
; We need this to override the HOC default layout which is
|
||||||
|
; flex 1. If we don't override this property, this HOC
|
||||||
|
; will catch all touches/gestures while the toast is shown,
|
||||||
|
; preventing the user doing any action in the app
|
||||||
|
#(gesture/gesture-handler-root-hoc views/toasts
|
||||||
|
#js {:flex 0})
|
||||||
|
(fn [] views/toasts))
|
||||||
|
|
||||||
(re-frame/reg-fx :show-toasts
|
(re-frame/reg-fx :show-toasts
|
||||||
(fn []
|
(fn []
|
||||||
|
|
Loading…
Reference in New Issue