#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:
Ulises Manuel Cárdenas 2023-07-04 16:25:08 -06:00 committed by GitHub
parent 2b4b357c32
commit ca0915c940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 83 deletions

View File

@ -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

View File

@ -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)))))

View File

@ -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%"

View File

@ -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 %})))))

View File

@ -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 []