From 37909c2d81e658a73574f58987e0d4f1133956d9 Mon Sep 17 00:00:00 2001 From: yqrashawn Date: Tue, 20 Dec 2022 21:52:28 +0800 Subject: [PATCH] feat: toast component (#14376) --- src/mocks/js_dependencies.cljs | 23 +++- .../notifications/count_down_circle.cljs | 106 ++++++++++++++++ src/quo2/components/notifications/toast.cljs | 82 +++++++++++++ src/quo2/core.cljs | 6 +- src/react_native/core.cljs | 17 ++- src/react_native/reanimated.cljs | 16 ++- src/react_native/svg.cljs | 7 ++ src/status_im2/common/toasts/events.cljs | 48 ++++++++ src/status_im2/common/toasts/view.cljs | 104 ++++++++++++++++ src/status_im2/contexts/quo_preview/main.cljs | 6 +- .../quo_preview/notifications/toast.cljs | 116 ++++++++++++++++++ src/status_im2/navigation/core.cljs | 37 ++++-- src/status_im2/navigation/view.cljs | 8 ++ src/status_im2/setup/events.cljs | 13 +- src/status_im2/subs/root.cljs | 7 +- src/status_im2/subs/toasts.cljs | 9 ++ 16 files changed, 577 insertions(+), 28 deletions(-) create mode 100644 src/quo2/components/notifications/count_down_circle.cljs create mode 100644 src/quo2/components/notifications/toast.cljs create mode 100644 src/react_native/svg.cljs create mode 100644 src/status_im2/common/toasts/events.cljs create mode 100644 src/status_im2/common/toasts/view.cljs create mode 100644 src/status_im2/contexts/quo_preview/notifications/toast.cljs create mode 100644 src/status_im2/subs/toasts.cljs diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index 5da4160f25..9c58117db5 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -4,6 +4,20 @@ (:require [status-im.utils.test :as utils.test]) (:require [status-im.chat.default-chats :refer (default-chats)])) +;; to generate a js Proxy at js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ that accept any (.xxx) call and return itself +;; For the convenience to mock eg. +;; (-> reanimated/slide-out-up-animation .springify (.damping 20) (.stiffness 300)) +;; (-> reanimated/slide-out-up-animation (.damping 20) .springify (.stiffness 300)) +(js/eval " +var globalThis +if (typeof window === \"undefined\") { + globalThis = global +} else { + globalThis = window +} +globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return () => globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__}}) +") + (def action-button #js {:default #js {:Item #js {}}}) (def config #js {:default #js {}}) (def camera #js {:RNCamera #js {:Constants #js {}}}) @@ -111,7 +125,9 @@ (def react-native-shake #js {}) (def react-native-share #js {:default {}}) (def react-native-svg #js {:SvgUri #js {:render identity} - :SvgXml #js {:render identity}}) + :SvgXml #js {:render identity} + :default #js {:render identity} + :Path #js {:render identity}}) (def react-native-webview #js {:default {}}) (def react-native-audio-toolkit #js {:MediaStates {}}) (def net-info #js {}) @@ -207,7 +223,10 @@ :withTiming (fn []) :withDelay (fn []) :Easing #js {:bezier identity} - :Keyframe (fn [])}) + :Keyframe (fn []) + :SlideOutUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ + :SlideInUp js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ + :LinearTransition js/__STATUS_MOBILE_JS_IDENTITY_PROXY__}) (def react-native-gesture-handler #js {:default #js {} :State #js {:BEGAN nil :ACTIVE nil diff --git a/src/quo2/components/notifications/count_down_circle.cljs b/src/quo2/components/notifications/count_down_circle.cljs new file mode 100644 index 0000000000..2512e9c110 --- /dev/null +++ b/src/quo2/components/notifications/count_down_circle.cljs @@ -0,0 +1,106 @@ +(ns quo2.components.notifications.count-down-circle + (:require + [goog.string :as gstring] + [quo2.foundations.colors :as colors] + [quo2.theme :as theme] + [react-native.core :as rn] + [react-native.svg :as svg] + [reagent.core :as reagent])) + +(defn- get-path-props + [size stroke-width rotation] + (let [half-size (/ size 2) + half-stroke-width (/ stroke-width 2) + arc-radius (- half-size half-stroke-width) + arc-diameter (* arc-radius 2) + rotation-indicator (if (= rotation :clockwise) "1,0" "0,1") + path-length (* js/Math.PI arc-diameter) + path (gstring/format + "m %s,%s a %s,%s 0 %s 0,%s a %s,%s 0 %s 0 ,-%s" + half-size + half-stroke-width + arc-radius + arc-radius + rotation-indicator + arc-diameter + arc-radius + arc-radius + rotation-indicator + arc-diameter)] + {:path path + :path-length path-length})) + +(defn- linear-ease + [time start goal duration] + (if (zero? duration) + start + (-> time + (/ duration) + (* goal) + (+ start)))) + +(defn- get-start-at + [duration initial-remaining-time] + (cond (or (zero? duration) (= duration initial-remaining-time)) 0 + (number? initial-remaining-time) (- duration initial-remaining-time) + :else 0)) + +(def ^:private themes + {:color {:light colors/neutral-80-opa-40 + :dark colors/white-opa-40}}) + +(defn circle-timer + [{:keys [color duration size stroke-width trail-color rotation initial-remaining-time]}] + (let [rotation (or rotation :clockwise) + duration (or duration 4) + stroke-width (or stroke-width 1) + size (or size 9) + max-stroke-width (max stroke-width 0) + {:keys [path path-length]} (get-path-props size max-stroke-width rotation) + start-at (get-start-at duration initial-remaining-time) + elapsed-time (reagent/atom 0) + prev-frame-time (reagent/atom nil) + frame-request (reagent/atom nil) + display-time (reagent/atom start-at) + ;; get elapsed frame time + swap-elapsed-time-each-frame (fn swap-elapsed-time-each-frame [frame-time] + (if (nil? @prev-frame-time) + (do (reset! prev-frame-time frame-time) + (reset! frame-request (js/requestAnimationFrame + swap-elapsed-time-each-frame))) + (let [delta (- (/ frame-time 1000) + (/ @prev-frame-time 1000)) + current-elapsed (swap! elapsed-time + delta) + current-display-time (+ start-at current-elapsed) + completed? (>= current-display-time duration)] + (reset! display-time (if completed? + duration + current-display-time)) + (when-not completed? + (reset! prev-frame-time frame-time) + (reset! frame-request (js/requestAnimationFrame + swap-elapsed-time-each-frame))))))] + (reagent/create-class + {:component-will-unmount #(js/cancelAnimationFrame @frame-request) + :reagent-render + (fn [] + (reset! frame-request (js/requestAnimationFrame swap-elapsed-time-each-frame)) + [rn/view + {:style {:position :relative + :width size + :height size}} + [svg/svg + {:view-box (str "0 0 " size " " size) + :width size + :height size} + [svg/path + {:d path :fill :none :stroke (or trail-color :transparent) :stroke-width stroke-width}] + (when-not (= @display-time duration) + [svg/path + {:d path + :fill :none + :stroke (or color (get-in themes [:color (theme/get-theme)])) + :stroke-linecap :square + :stroke-width stroke-width + :stroke-dasharray path-length + :stroke-dashoffset (linear-ease @display-time 0 path-length duration)}])]])}))) diff --git a/src/quo2/components/notifications/toast.cljs b/src/quo2/components/notifications/toast.cljs new file mode 100644 index 0000000000..45ba42d823 --- /dev/null +++ b/src/quo2/components/notifications/toast.cljs @@ -0,0 +1,82 @@ +(ns quo2.components.notifications.toast + (:require + [i18n.i18n :as i18n] + [quo2.components.icon :as icon] + [quo2.components.markdown.text :as text] + [quo2.components.notifications.count-down-circle :as count-down-circle] + [quo2.foundations.colors :as colors] + [quo2.theme :as theme] + [react-native.core :as rn])) + +(def ^:private themes + {:container {:light {:background-color colors/white-opa-70} + :dark {:background-color colors/neutral-80-opa-70}} + :text {:light {:color colors/neutral-100} + :dark {:color colors/white}} + :icon {:light {:color colors/neutral-100} + :dark {:color colors/white}} + :action-container {:light {:background-color :colors/neutral-80-opa-5} + :dark {:background-color :colors/white-opa-5}}}) + +(defn- merge-theme-style + [component-key styles] + (merge (get-in themes [component-key (theme/get-theme)]) styles)) + +(defn toast-action-container + [{:keys [on-press style]} & children] + [rn/touchable-highlight {:on-press on-press} + [into + [rn/view + {:style (merge + {:flex-direction :row + :padding-vertical 3 + :padding-horizontal 8 + :align-items :center + :border-radius 8 + :background-color (get-in themes + [:action-container (theme/get-theme) + :background-color])} + style)}] + children]]) + +(defn toast-undo-action + [duration on-press] + [toast-action-container {:on-press on-press} + [rn/view {:style {:margin-right 5}} + [count-down-circle/circle-timer {:duration duration}]] + [text/text + {:size :paragraph-2 :weight :medium :style (merge-theme-style :text {})} + [i18n/label :undo]]]) + +(defn- toast-container + [{:keys [left middle right]}] + [rn/view {:style {:padding-left 12 :padding-right 12}} + [rn/view + {:style (merge-theme-style :container + {:flex-direction :row + :width "100%" + :margin :auto + :justify-content :space-between + :padding-vertical 8 + :padding-left 10 + :padding-right 8 + :border-radius 12})} + [rn/view {:style {:padding 2}} left] + [rn/view {:style {:padding 4 :flex 1}} + [text/text + {:size :paragraph-2 :weight :medium :style (merge-theme-style :text {})} + middle]] + (when right right)]]) + +(defn toast + [{:keys [icon icon-color text action undo-duration undo-on-press]}] + [toast-container + {:left (when icon + [icon/icon icon + {:container-style {:width 20 :height 20} + :color (or icon-color + (get-in themes [:icon (theme/get-theme) :color]))}]) + :middle text + :right (if undo-duration + [toast-undo-action undo-duration undo-on-press] + action)}]) diff --git a/src/quo2/core.cljs b/src/quo2/core.cljs index e565859ebd..8e742dc27a 100644 --- a/src/quo2/core.cljs +++ b/src/quo2/core.cljs @@ -36,6 +36,7 @@ quo2.components.tags.tags quo2.components.tags.context-tags quo2.components.tabs.tabs + quo2.components.notifications.toast quo2.components.tabs.account-selector quo2.components.navigation.floating-shell-button quo2.components.tags.status-tags @@ -43,8 +44,10 @@ quo2.components.selectors.disclaimer quo2.components.selectors.selectors quo2.components.settings.privacy-option - quo2.components.loaders.skeleton)) + quo2.components.loaders.skeleton + quo2.components.notifications.count-down-circle)) +(def toast quo2.components.notifications.toast/toast) (def button quo2.components.buttons.button/button) (def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button) (def text quo2.components.markdown.text/text) @@ -107,6 +110,7 @@ (def activity-log quo2.components.notifications.activity-log.view/view) (def info-count quo2.components.notifications.info-count/info-count) (def notification-dot quo2.components.notifications.notification-dot/notification-dot) +(def count-down-circle quo2.components.notifications.count-down-circle/circle-timer) ;;;; SETTINGS (def privacy-option quo2.components.settings.privacy-option/card) diff --git a/src/react_native/core.cljs b/src/react_native/core.cljs index b13d8b6c32..945ba5b4f2 100644 --- a/src/react_native/core.cljs +++ b/src/react_native/core.cljs @@ -1,9 +1,12 @@ (ns react-native.core - (:require [reagent.core :as reagent] + (:require ["react" :as react] ["react-native" :as react-native] + [cljs-bean.core :as bean] ["@react-native-community/blur" :as blur] + [oops.core :as oops] [react-native.flat-list :as flat-list] [react-native.section-list :as section-list] + [reagent.core :as reagent] [react-native.platform :as platform])) (def app-state ^js (.-AppState ^js react-native)) @@ -73,3 +76,15 @@ (merge (when platform/ios? {:behavior :padding}) props)] children)) + +(defn use-effect + ([effect] (use-effect effect [])) + ([effect deps] + (react/useEffect effect (bean/->js deps)))) + +(def use-ref react/useRef) +(defn use-effect-once [effect] (use-effect effect)) +(defn use-unmount [f] + (let [fn-ref (use-ref f)] + (oops/oset! fn-ref "current" f) + (use-effect-once (fn [] #((oops/oget fn-ref "current")))))) diff --git a/src/react_native/reanimated.cljs b/src/react_native/reanimated.cljs index d542700dd6..3d24fe5d9c 100644 --- a/src/react_native/reanimated.cljs +++ b/src/react_native/reanimated.cljs @@ -1,10 +1,15 @@ (ns react-native.reanimated (:require ["react-native" :as rn] - [reagent.core :as reagent] - [clojure.string :as string] ["react-native-linear-gradient" :default LinearGradient] ["react-native-reanimated" :default reanimated - :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe cancelAnimation)])) + :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe cancelAnimation SlideInUp SlideOutUp LinearTransition)] + [clojure.string :as string] + [reagent.core :as reagent])) + +;; Animations +(def slide-in-up-animation SlideInUp) +(def slide-out-up-animation SlideOutUp) +(def linear-transition LinearTransition) ;; Animated Components (def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated))) @@ -95,3 +100,8 @@ #js {:duration duration :easing (get easings easing)}) number-of-repetitions reverse?))))) + +(defn animate-shared-value-with-spring [anim val {:keys [mass stiffness damping]}] + (set-shared-value anim (with-spring val (js-obj "mass" mass + "damping" damping + "stiffness" stiffness)))) diff --git a/src/react_native/svg.cljs b/src/react_native/svg.cljs new file mode 100644 index 0000000000..720745bb06 --- /dev/null +++ b/src/react_native/svg.cljs @@ -0,0 +1,7 @@ +(ns react-native.svg + (:require + ["react-native-svg" :as Svg] + [reagent.core :as reagent])) + +(def svg (reagent/adapt-react-class Svg/default)) +(def path (reagent/adapt-react-class Svg/Path)) diff --git a/src/status_im2/common/toasts/events.cljs b/src/status_im2/common/toasts/events.cljs new file mode 100644 index 0000000000..bac5f2cdc2 --- /dev/null +++ b/src/status_im2/common/toasts/events.cljs @@ -0,0 +1,48 @@ +(ns status-im2.common.toasts.events + (:require + [taoensso.encore :as enc] + [utils.re-frame :as rf])) + +(def ^:private next-toast-id (atom 0)) + +(rf/defn upsert + {:events [:toasts/upsert]} + [{:keys [db]} id opts] + (let [{:toasts/keys [index toasts]} db + update? (some #{id} index) + index (enc/conj-when index (and (not update?) id)) + toasts (assoc toasts id opts) + db (-> db + (assoc + :toasts/index index + :toasts/toasts toasts) + (dissoc :toasts/hide-toasts-timer-set))] + (if (and (not update?) (= (count index) 1)) + {:show-toasts [] + :db db} + {:db db}))) + +(rf/defn create + {:events [:toasts/create]} + [{:keys [db]} opts] + {:dispatch [:toasts/upsert (str "toast-" (swap! next-toast-id inc)) opts]}) + +(rf/defn hide-toasts-with-check + {:events [:toasts/hide-with-check]} + [{:keys [db]}] + (when (:toasts/hide-toasts-timer-set db) + {:db (dissoc db :toasts/hide-toasts-timer-set) + :hide-toasts nil})) + +(rf/defn close + {:events [:toasts/close]} + [{:keys [db]} id] + (when (get-in db [:toasts/toasts id]) + (let [{:toasts/keys [toasts index]} db + toasts (dissoc toasts id) + index (remove #{id} index) + empty-index? (not (seq index)) + db (assoc db :toasts/index index :toasts/toasts toasts)] + (cond-> {:db db} + empty-index? (update :db assoc :toasts/hide-toasts-timer-set true) + empty-index? (assoc :dispatch-later [{:ms 500 :dispatch [:toasts/hide-with-check]}]))))) diff --git a/src/status_im2/common/toasts/view.cljs b/src/status_im2/common/toasts/view.cljs new file mode 100644 index 0000000000..34f0eb020c --- /dev/null +++ b/src/status_im2/common/toasts/view.cljs @@ -0,0 +1,104 @@ +(ns status-im2.common.toasts.view + (:require + [quo2.core :as quo2] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.reanimated :as reanimated] + [reagent.core :as reagent] + [status-im.utils.utils :as utils.utils] + [utils.re-frame :as rf])) + +(def ^:private slide-out-up-animation + (-> reanimated/slide-out-up-animation + .springify + (.damping 20) + (.stiffness 300))) + +(def ^:private slide-in-up-animation + (-> reanimated/slide-in-up-animation + .springify + (.damping 20) + (.stiffness 300))) + +(def ^:private linear-transition + (-> reanimated/linear-transition + .springify + (.damping 20) + (.stiffness 300))) + +(defn container + [id] + (let [dismissed-locally? (reagent/atom false) + close! #(rf/dispatch [:toasts/close id]) + timer (reagent/atom nil) + clear-timer #(utils.utils/clear-timeout @timer)] + (fn [] + [:f> + (fn [] + (let [toast-opts (rf/sub [:toasts/toast id]) + duration (get toast-opts :duration 3000) + on-dismissed #((get toast-opts :on-dismissed identity) id) + translate-y (reanimated/use-shared-value 0) + create-timer (fn [] + (reset! timer (utils.utils/set-timeout #(do (close!) (on-dismissed)) + duration))) + pan + (-> + (gesture/gesture-pan) + ;; remove timer on pan start + (gesture/on-start clear-timer) + (gesture/on-update + (fn [evt] + (let [evt-translation-y (.-translationY evt)] + (cond + ;; reset translate y on pan down + (> evt-translation-y 100) + (reanimated/animate-shared-value-with-spring translate-y + 0 + {:mass 1 + :damping 20 + :stiffness 300}) + ;; dismiss on pan up + (< evt-translation-y -30) + (do (reanimated/animate-shared-value-with-spring + translate-y + -500 + {:mass 1 :damping 20 :stiffness 300}) + (reset! dismissed-locally? true) + (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 + (rn/use-effect (fn [] (create-timer) clear-timer) [duration]) + (rn/use-unmount on-dismissed) + [gesture/gesture-detector {:gesture pan} + [reanimated/view + {:entering slide-in-up-animation + :exiting slide-out-up-animation + :layout reanimated/linear-transition + :style (reanimated/apply-animations-to-style + {:transform [{:translateY translate-y}]} + {:width "100%" + :margin-bottom 5})} + [quo2/toast toast-opts]]]))]))) + +(defn toasts + [] + (let [toasts-index (rf/sub [:toasts/index])] + [into + [rn/view + {:style {:elevation 2 + :pointer-events :box-none + :padding-top 52 + :flex-direction :column + :justify-content :center + :align-items :center + :background-color :transparent}}] + (->> toasts-index + reverse + (map (fn [id] ^{:key id} [container id])))])) diff --git a/src/status_im2/contexts/quo_preview/main.cljs b/src/status_im2/contexts/quo_preview/main.cljs index 8c747dbe09..27d1353973 100644 --- a/src/status_im2/contexts/quo_preview/main.cljs +++ b/src/status_im2/contexts/quo_preview/main.cljs @@ -56,6 +56,7 @@ [status-im2.contexts.quo-preview.wallet.network-amount :as network-amount] [status-im2.contexts.quo-preview.navigation.page-nav :as page-nav] [status-im2.contexts.quo-preview.avatars.account-avatar :as account-avatar] + [status-im2.contexts.quo-preview.notifications.toast :as toast] [status-im2.contexts.quo-preview.community.token-gating :as token-gating] [re-frame.core :as re-frame])) @@ -158,7 +159,10 @@ :component floating-shell-button/preview-floating-shell-button}] :notifications [{:name :activity-logs :insets {:top false} - :component activity-logs/preview-activity-logs}] + :component activity-logs/preview-activity-logs} + {:name :toast + :insets {:top false} + :component toast/preview-toasts}] :posts-and-attachments [{:name :messages-skeleton :insets {:top false} :component messages-skeleton/preview-messages-skeleton}] diff --git a/src/status_im2/contexts/quo_preview/notifications/toast.cljs b/src/status_im2/contexts/quo_preview/notifications/toast.cljs new file mode 100644 index 0000000000..5b7a98266c --- /dev/null +++ b/src/status_im2/contexts/quo_preview/notifications/toast.cljs @@ -0,0 +1,116 @@ +(ns status-im2.contexts.quo-preview.notifications.toast + (:require + [quo2.components.buttons.button :as button] + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + [reagent.core :as reagent] + [utils.re-frame :as rf])) + +(defn toast-button + ([id opts] (toast-button id id opts)) + ([text id opts] + (let [toast-opts (rf/sub [:toasts/toast id]) + dismiss! #(rf/dispatch [:toasts/close id]) + toast! #(rf/dispatch [:toasts/upsert id opts]) + dismissed? (not toast-opts)] + [rn/view {:style {:margin-bottom 10}} + [button/button + {:size 32 + :on-press #(if dismissed? (toast!) (dismiss!))} + (if dismissed? text (str "DISMISS " text))]]))) + +(defn toast-button-basic + [] + [toast-button + "Toast: basic" + {:icon :placeholder :icon-color "green" :text "This is an example toast"}]) + +(defn toast-button-with-undo-action + [] + [toast-button + "Toast: with undo action" + {:icon :info + :icon-color colors/danger-50-opa-40 + :text "This is an example toast" + :duration 4000 + :undo-duration 4 + :undo-on-press #(do + (rf/dispatch [:toasts/create + {:icon :placeholder + :icon-color "green" + :text "Undo pressed"}]) + (rf/dispatch [:toasts/close + "Toast: with undo action"]))}]) + +(defn toast-button-multiline + [] + [toast-button + "Toast: multiline" + {:icon :placeholder + :icon-color "green" + :text + "This is an example multiline toast This is an example multiline toast This is an example multiline toast" + :undo-duration 4 + :undo-on-press + #(do + (rf/dispatch + [:toasts/create + {:icon :placeholder :icon-color "green" :text "Undo pressed"}]) + (rf/dispatch [:toasts/close "Toast: with undo action"]))}]) + +(defn toast-button-30s-duration + [] + [toast-button + "Toast: 30s duration" + {:icon :placeholder + :icon-color "green" + :text "This is an example toast" + :duration 30000}]) + +(defn update-toast-button + [] + (let [suffix (reagent/atom 0)] + (fn [] + (let [toast-opts (rf/sub [:toasts/toast "Toast: 30s duration"])] + (when toast-opts + [rn/view {:style {:margin-bottom 10}} + [button/button + {:size 32 + :on-press + #(rf/dispatch + [:toasts/upsert + "Toast: 30s duration" + {:icon :placeholder + :icon-color "red" + :text (str "This is an updated example toast" " - " (swap! suffix inc)) + :duration 3000}])} + "update above toast"]]))))) + +(defn preview + [] + (fn [] + [rn/view + [rn/view + {:background-color "#508485" + :flex-direction :column + :justify-content :flex-start + :height 300}] + [into + [rn/view + {:flex 1 + :padding 16}] + [^{:key :basic} [toast-button-basic] + ^{:key :with-undo-action} [toast-button-with-undo-action] + ^{:key :with-multiline} [toast-button-multiline] + ^{:key :30s-duration} [toast-button-30s-duration] + ^{:key :upsert} + [update-toast-button]]]])) + +(defn preview-toasts + [] + [rn/view {:flex 1} + [rn/flat-list + {:flex 1 + :header [preview] + :key-fn str + :keyboardShouldPersistTaps :always}]]) diff --git a/src/status_im2/navigation/core.cljs b/src/status_im2/navigation/core.cljs index f55f89789c..e0927c9dab 100644 --- a/src/status_im2/navigation/core.cljs +++ b/src/status_im2/navigation/core.cljs @@ -132,6 +132,7 @@ (log/debug "screen-appear-reg" view-id) (when (get views/screens view-id) (when (and (not= view-id :bottom-sheet) + (not= view-id :toasts) (not= view-id :popover) (not= view-id :visibility-status-popover)) (set-view-id view-id) @@ -212,23 +213,30 @@ ;; OVERLAY (Popover and bottom sheets) (def dissmiss-overlay navigation/dissmiss-overlay) -(defn show-overlay [comp] - (dissmiss-overlay comp) - (navigation/show-overlay - {:component {:name comp - :id comp - :options (merge (cond-> (roots/status-bar-options) - (and platform/android? (not (colors/dark?))) - (assoc-in [:statusBar :backgroundColor] "#99999A")) - {:layout {:componentBackgroundColor (if platform/android? - colors/neutral-80-opa-20 ;; TODO adjust color - "transparent")} - :overlay {:interceptTouchOutside true}})}})) +(defn show-overlay + ([comp] (show-overlay comp {})) + ([comp opts] + (dissmiss-overlay comp) + (navigation/show-overlay + {:component {:name comp + :id comp + :options (merge (cond-> (roots/status-bar-options) + (and platform/android? (not (colors/dark?))) + (assoc-in [:statusBar :backgroundColor] "#99999A")) + {:layout {:componentBackgroundColor (if platform/android? + colors/neutral-80-opa-20 ;; TODO adjust color + "transparent")} + :overlay {:interceptTouchOutside true}} + opts)}}))) ;; POPOVER (re-frame/reg-fx :show-popover (fn [] (show-overlay "popover"))) (re-frame/reg-fx :hide-popover (fn [] (dissmiss-overlay "popover"))) +;; TOAST +(re-frame/reg-fx :show-toasts (fn [] (show-overlay "toasts" {:overlay {:interceptTouchOutside false} :layout {:componentBackgroundColor :transparent}}))) +(re-frame/reg-fx :hide-toasts (fn [] (dissmiss-overlay "toasts"))) + ;; VISIBILITY STATUS POPOVER (re-frame/reg-fx :show-visibility-status-popover (fn [] (show-overlay "visibility-status-popover"))) @@ -270,6 +278,11 @@ (fn [] (gesture/gesture-handler-root-hoc views/popover-comp)) (fn [] views/popover-comp)) + (navigation/register-component + "toasts" + (fn [] views/toasts-comp) + js/undefined) + (navigation/register-component "visibility-status-popover" (fn [] (gesture/gesture-handler-root-hoc views/visibility-status-popover-comp)) diff --git a/src/status_im2/navigation/view.cljs b/src/status_im2/navigation/view.cljs index d9c37d9ace..805b57315e 100644 --- a/src/status_im2/navigation/view.cljs +++ b/src/status_im2/navigation/view.cljs @@ -7,6 +7,7 @@ [status-im2.navigation.screens :as screens] [status-im2.setup.config :as config] [status-im2.setup.hot-reload :as reloader] + [status-im2.common.toasts.view :as toasts] ;; TODO (14/11/22 flexsurfer) move to status-im2 namespace [status-im.ui.screens.popover.views :as popover] @@ -92,6 +93,13 @@ (when js/goog.DEBUG [reloader/reload-view])]))) +(def toasts-comp + (reagent/reactify-component + (fn [] + ;; DON'T wrap this in safe-area-provider, it makes it unable to click through toasts + ^{:key (str "toasts" @reloader/cnt)} + [toasts/toasts]))) + (def visibility-status-popover-comp (reagent/reactify-component (fn [] diff --git a/src/status_im2/setup/events.cljs b/src/status_im2/setup/events.cljs index f35fdfb9a3..cd691d1a2d 100644 --- a/src/status_im2/setup/events.cljs +++ b/src/status_im2/setup/events.cljs @@ -1,17 +1,18 @@ (ns status-im2.setup.events (:require [clojure.string :as string] - [re-frame.core :as re-frame] - [status-im2.setup.db :as db] - [status-im2.common.theme.core :as theme] [quo2.theme :as quo2.theme] - [utils.re-frame :as rf] - + [re-frame.core :as re-frame] ;; TODO (14/11/22 flexsurfer move to status-im2 namespace [quo.theme :as quo.theme] [status-im.native-module.core :as status] [status-im.multiaccounts.login.core :as multiaccounts.login] [status-im.utils.keychain.core :as keychain] - [status-im2.navigation.events :as navigation])) + + [status-im2.common.theme.core :as theme] + [status-im2.common.toasts.events] + [status-im2.navigation.events :as navigation] + [status-im2.setup.db :as db] + [utils.re-frame :as rf])) (re-frame/reg-fx :setup/open-multiaccounts diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index c93498c534..fb6fafe783 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -3,10 +3,10 @@ status-im2.subs.activity-center status-im2.subs.bootnodes status-im2.subs.browser - status-im2.subs.communities - status-im2.subs.contact status-im2.subs.chat.chats status-im2.subs.chat.messages + status-im2.subs.communities + status-im2.subs.contact status-im2.subs.ens status-im2.subs.general status-im2.subs.home @@ -19,6 +19,7 @@ status-im2.subs.search status-im2.subs.stickers status-im2.subs.shell + status-im2.subs.toasts status-im2.subs.wallet.signing status-im2.subs.wallet.transactions status-im2.subs.wallet.wallet)) @@ -196,6 +197,8 @@ ;;intro-wizard (reg-root-key-sub :intro-wizard-state :intro-wizard) +(reg-root-key-sub :toasts/toasts :toasts/toasts) +(reg-root-key-sub :toasts/index :toasts/index) (reg-root-key-sub :popover/popover :popover/popover) (reg-root-key-sub :visibility-status-popover/popover :visibility-status-popover/popover) (reg-root-key-sub :add-account :add-account) diff --git a/src/status_im2/subs/toasts.cljs b/src/status_im2/subs/toasts.cljs new file mode 100644 index 0000000000..d553680774 --- /dev/null +++ b/src/status_im2/subs/toasts.cljs @@ -0,0 +1,9 @@ +(ns status-im2.subs.toasts + (:require + [re-frame.core :as re-frame])) + +(re-frame/reg-sub + :toasts/toast + :<- [:toasts/toasts] + (fn [toasts [_ toast-id]] + (get toasts toast-id)))