diff --git a/src/quo2/components/notifications/toast/view.cljs b/src/quo2/components/notifications/toast/view.cljs index 2af9878ab0..7583e8371f 100644 --- a/src/quo2/components/notifications/toast/view.cljs +++ b/src/quo2/components/notifications/toast/view.cljs @@ -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 diff --git a/src/status_im2/common/toasts/animation.cljs b/src/status_im2/common/toasts/animation.cljs new file mode 100644 index 0000000000..1cc34e218c --- /dev/null +++ b/src/status_im2/common/toasts/animation.cljs @@ -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))))) diff --git a/src/status_im2/common/toasts/style.cljs b/src/status_im2/common/toasts/style.cljs index bafcfc4a95..8179f1c85e 100644 --- a/src/status_im2/common/toasts/style.cljs +++ b/src/status_im2/common/toasts/style.cljs @@ -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%" diff --git a/src/status_im2/common/toasts/view.cljs b/src/status_im2/common/toasts/view.cljs index 8dd9f13222..5df33033a3 100644 --- a/src/status_im2/common/toasts/view.cljs +++ b/src/status_im2/common/toasts/view.cljs @@ -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 %}))))) diff --git a/src/status_im2/navigation/core.cljs b/src/status_im2/navigation/core.cljs index 8432e6ff00..5b51182682 100644 --- a/src/status_im2/navigation/core.cljs +++ b/src/status_im2/navigation/core.cljs @@ -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 []