feat: toast component (#14376)
This commit is contained in:
parent
01660765b7
commit
37909c2d81
|
@ -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
|
||||
|
|
|
@ -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)}])]])})))
|
|
@ -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)}])
|
|
@ -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)
|
||||
|
|
|
@ -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"))))))
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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))
|
|
@ -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]}])))))
|
|
@ -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])))]))
|
|
@ -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}]
|
||||
|
|
|
@ -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}]])
|
|
@ -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))
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
Loading…
Reference in New Issue