#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
|
||||
[duration on-press override-theme]
|
||||
[toast-action-container
|
||||
{:on-press on-press :accessibility-label :toast-undo-action}
|
||||
[toast-action-container {:on-press on-press :accessibility-label :toast-undo-action}
|
||||
[rn/view {:style {:margin-right 5}}
|
||||
[count-down-circle/circle-timer {:duration duration}]]
|
||||
[text/text
|
||||
|
@ -31,17 +30,17 @@
|
|||
|
||||
(defn- toast-container
|
||||
[{:keys [left title text right container-style override-theme]}]
|
||||
[rn/view
|
||||
{:style (merge style/box-container container-style)}
|
||||
[rn/view {:style (merge style/box-container container-style)}
|
||||
[blur/view
|
||||
{:style style/blur-container
|
||||
:blur-amount 13
|
||||
:blur-radius 10
|
||||
:blur-type :transparent
|
||||
:overlay-color :transparent}]
|
||||
[rn/view
|
||||
{:style (style/content-container override-theme)}
|
||||
[rn/view {:style style/left-side-container} left]
|
||||
|
||||
[rn/view {:style (style/content-container override-theme)}
|
||||
[rn/view {:style style/left-side-container}
|
||||
left]
|
||||
[rn/view {:style style/right-side-container}
|
||||
(when title
|
||||
[text/text
|
||||
|
@ -57,7 +56,7 @@
|
|||
:style (style/text override-theme)
|
||||
:accessibility-label :toast-content}
|
||||
text])]
|
||||
(when right right)]])
|
||||
right]])
|
||||
|
||||
(defn toast
|
||||
[{: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)
|
||||
|
||||
(def outmost-transparent-container
|
||||
{:elevation 2
|
||||
:pointer-events :box-none
|
||||
:padding-top 52
|
||||
:flex-direction :column
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color :transparent})
|
||||
{:elevation 2
|
||||
:pointer-events :box-none
|
||||
:padding-top 52
|
||||
:flex-direction :column
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def each-toast-container
|
||||
{:width "100%"
|
||||
|
|
|
@ -1,89 +1,56 @@
|
|||
(ns status-im2.common.toasts.view
|
||||
(:require [quo2.core :as quo]
|
||||
[react-native.background-timer :as background-timer]
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[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]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn toast
|
||||
[id]
|
||||
(let [{:keys [type] :as toast-opts} (rf/sub [:toasts/toast id])]
|
||||
[toast-id]
|
||||
(let [{:keys [type] :as toast-opts} (rf/sub [:toasts/toast toast-id])]
|
||||
(if (= type :notification)
|
||||
[quo/notification 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
|
||||
[id]
|
||||
(let [dismissed-locally? (reagent/atom false)
|
||||
close! #(rf/dispatch [:toasts/close id])
|
||||
timer (reagent/atom nil)
|
||||
clear-timer #(background-timer/clear-timeout @timer)]
|
||||
[toast-id]
|
||||
(let [dismissed-locally? (reagent/atom false)
|
||||
set-dismissed-locally #(reset! dismissed-locally? true)
|
||||
close-toast #(rf/dispatch [:toasts/close toast-id])
|
||||
timer (reagent/atom nil)
|
||||
clear-timer #(background-timer/clear-timeout @timer)]
|
||||
(fn []
|
||||
(let [duration (or (rf/sub [:toasts/toast-cursor id :duration]) 3000)
|
||||
on-dismissed (or (rf/sub [:toasts/toast-cursor id :on-dismissed]) identity)
|
||||
create-timer (fn []
|
||||
(reset! timer (background-timer/set-timeout close! duration)))
|
||||
translate-y (reanimated/use-shared-value 0)
|
||||
pan
|
||||
(->
|
||||
(gesture/gesture-pan)
|
||||
(gesture/on-start clear-timer)
|
||||
(gesture/on-update
|
||||
(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)))))]
|
||||
(let [duration (or (rf/sub [:toasts/toast-cursor toast-id :duration]) 3000)
|
||||
on-dismissed (or (rf/sub [:toasts/toast-cursor toast-id :on-dismissed]) identity)
|
||||
create-timer #(reset! timer (background-timer/set-timeout close-toast duration))
|
||||
translate-y (reanimated/use-shared-value 0)
|
||||
pan-gesture (animation/pan-gesture {:clear-timer clear-timer
|
||||
:create-timer create-timer
|
||||
:translate-y translate-y
|
||||
:close-toast close-toast
|
||||
:set-dismissed-locally set-dismissed-locally
|
||||
:dismissed-locally? @dismissed-locally?})]
|
||||
;; create auto dismiss timer, clear timer when unmount or duration changed
|
||||
(rn/use-effect (fn [] (create-timer) clear-timer) [duration])
|
||||
(rn/use-unmount #(on-dismissed id))
|
||||
[gesture/gesture-detector {:gesture pan}
|
||||
(rn/use-unmount #(on-dismissed toast-id))
|
||||
|
||||
[gesture/gesture-detector {:gesture pan-gesture}
|
||||
[reanimated/view
|
||||
{;; TODO: this will enable layout animation at runtime and causing flicker on android
|
||||
;; we need to resolve this and re-enable layout animation
|
||||
;; issue at https://github.com/status-im/status-mobile/issues/14752
|
||||
;; :entering slide-in-up-animation
|
||||
;; :exiting slide-out-up-animation
|
||||
;; :layout reanimated/linear-transition
|
||||
:style (reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}]}
|
||||
style/each-toast-container)}
|
||||
[toast id]]]))))
|
||||
{:entering animation/slide-in-up-animation
|
||||
:exiting animation/slide-out-up-animation
|
||||
:layout animation/linear-transition
|
||||
:style (reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}]}
|
||||
style/each-toast-container)}
|
||||
[toast toast-id]]]))))
|
||||
|
||||
(defn toasts
|
||||
[]
|
||||
(let [toasts-ordered (:ordered (rf/sub [:toasts]))]
|
||||
[into
|
||||
[rn/view
|
||||
{:style style/outmost-transparent-container}]
|
||||
(doall
|
||||
(map (fn [id] ^{:key id} [:f> f-container id]) toasts-ordered))]))
|
||||
(->> (rf/sub [:toasts])
|
||||
:ordered
|
||||
(into [rn/view {:style style/outmost-transparent-container}]
|
||||
(map #(with-meta [:f> f-container %] {:key %})))))
|
||||
|
|
|
@ -212,7 +212,15 @@
|
|||
opts)}})))
|
||||
|
||||
;; 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
|
||||
(fn []
|
||||
|
|
Loading…
Reference in New Issue