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.utils.test :as utils.test])
|
||||||
(:require [status-im.chat.default-chats :refer (default-chats)]))
|
(: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 action-button #js {:default #js {:Item #js {}}})
|
||||||
(def config #js {:default #js {}})
|
(def config #js {:default #js {}})
|
||||||
(def camera #js {:RNCamera #js {:Constants #js {}}})
|
(def camera #js {:RNCamera #js {:Constants #js {}}})
|
||||||
|
@ -111,7 +125,9 @@
|
||||||
(def react-native-shake #js {})
|
(def react-native-shake #js {})
|
||||||
(def react-native-share #js {:default {}})
|
(def react-native-share #js {:default {}})
|
||||||
(def react-native-svg #js {:SvgUri #js {:render identity}
|
(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-webview #js {:default {}})
|
||||||
(def react-native-audio-toolkit #js {:MediaStates {}})
|
(def react-native-audio-toolkit #js {:MediaStates {}})
|
||||||
(def net-info #js {})
|
(def net-info #js {})
|
||||||
|
@ -207,7 +223,10 @@
|
||||||
:withTiming (fn [])
|
:withTiming (fn [])
|
||||||
:withDelay (fn [])
|
:withDelay (fn [])
|
||||||
:Easing #js {:bezier identity}
|
: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 {}
|
(def react-native-gesture-handler #js {:default #js {}
|
||||||
:State #js {:BEGAN nil
|
:State #js {:BEGAN nil
|
||||||
:ACTIVE 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.tags
|
||||||
quo2.components.tags.context-tags
|
quo2.components.tags.context-tags
|
||||||
quo2.components.tabs.tabs
|
quo2.components.tabs.tabs
|
||||||
|
quo2.components.notifications.toast
|
||||||
quo2.components.tabs.account-selector
|
quo2.components.tabs.account-selector
|
||||||
quo2.components.navigation.floating-shell-button
|
quo2.components.navigation.floating-shell-button
|
||||||
quo2.components.tags.status-tags
|
quo2.components.tags.status-tags
|
||||||
|
@ -43,8 +44,10 @@
|
||||||
quo2.components.selectors.disclaimer
|
quo2.components.selectors.disclaimer
|
||||||
quo2.components.selectors.selectors
|
quo2.components.selectors.selectors
|
||||||
quo2.components.settings.privacy-option
|
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 button quo2.components.buttons.button/button)
|
||||||
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
|
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
|
||||||
(def text quo2.components.markdown.text/text)
|
(def text quo2.components.markdown.text/text)
|
||||||
|
@ -107,6 +110,7 @@
|
||||||
(def activity-log quo2.components.notifications.activity-log.view/view)
|
(def activity-log quo2.components.notifications.activity-log.view/view)
|
||||||
(def info-count quo2.components.notifications.info-count/info-count)
|
(def info-count quo2.components.notifications.info-count/info-count)
|
||||||
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
|
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
|
||||||
|
(def count-down-circle quo2.components.notifications.count-down-circle/circle-timer)
|
||||||
|
|
||||||
;;;; SETTINGS
|
;;;; SETTINGS
|
||||||
(def privacy-option quo2.components.settings.privacy-option/card)
|
(def privacy-option quo2.components.settings.privacy-option/card)
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
(ns react-native.core
|
(ns react-native.core
|
||||||
(:require [reagent.core :as reagent]
|
(:require ["react" :as react]
|
||||||
["react-native" :as react-native]
|
["react-native" :as react-native]
|
||||||
|
[cljs-bean.core :as bean]
|
||||||
["@react-native-community/blur" :as blur]
|
["@react-native-community/blur" :as blur]
|
||||||
|
[oops.core :as oops]
|
||||||
[react-native.flat-list :as flat-list]
|
[react-native.flat-list :as flat-list]
|
||||||
[react-native.section-list :as section-list]
|
[react-native.section-list :as section-list]
|
||||||
|
[reagent.core :as reagent]
|
||||||
[react-native.platform :as platform]))
|
[react-native.platform :as platform]))
|
||||||
|
|
||||||
(def app-state ^js (.-AppState ^js react-native))
|
(def app-state ^js (.-AppState ^js react-native))
|
||||||
|
@ -73,3 +76,15 @@
|
||||||
(merge (when platform/ios? {:behavior :padding})
|
(merge (when platform/ios? {:behavior :padding})
|
||||||
props)]
|
props)]
|
||||||
children))
|
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
|
(ns react-native.reanimated
|
||||||
(:require ["react-native" :as rn]
|
(:require ["react-native" :as rn]
|
||||||
[reagent.core :as reagent]
|
|
||||||
[clojure.string :as string]
|
|
||||||
["react-native-linear-gradient" :default LinearGradient]
|
["react-native-linear-gradient" :default LinearGradient]
|
||||||
["react-native-reanimated" :default reanimated
|
["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
|
;; Animated Components
|
||||||
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
|
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
|
||||||
|
@ -95,3 +100,8 @@
|
||||||
#js {:duration duration
|
#js {:duration duration
|
||||||
:easing (get easings easing)})
|
:easing (get easings easing)})
|
||||||
number-of-repetitions reverse?)))))
|
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.wallet.network-amount :as network-amount]
|
||||||
[status-im2.contexts.quo-preview.navigation.page-nav :as page-nav]
|
[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.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]
|
[status-im2.contexts.quo-preview.community.token-gating :as token-gating]
|
||||||
[re-frame.core :as re-frame]))
|
[re-frame.core :as re-frame]))
|
||||||
|
|
||||||
|
@ -158,7 +159,10 @@
|
||||||
:component floating-shell-button/preview-floating-shell-button}]
|
:component floating-shell-button/preview-floating-shell-button}]
|
||||||
:notifications [{:name :activity-logs
|
:notifications [{:name :activity-logs
|
||||||
:insets {:top false}
|
: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
|
:posts-and-attachments [{:name :messages-skeleton
|
||||||
:insets {:top false}
|
:insets {:top false}
|
||||||
:component messages-skeleton/preview-messages-skeleton}]
|
: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)
|
(log/debug "screen-appear-reg" view-id)
|
||||||
(when (get views/screens view-id)
|
(when (get views/screens view-id)
|
||||||
(when (and (not= view-id :bottom-sheet)
|
(when (and (not= view-id :bottom-sheet)
|
||||||
|
(not= view-id :toasts)
|
||||||
(not= view-id :popover)
|
(not= view-id :popover)
|
||||||
(not= view-id :visibility-status-popover))
|
(not= view-id :visibility-status-popover))
|
||||||
(set-view-id view-id)
|
(set-view-id view-id)
|
||||||
|
@ -212,23 +213,30 @@
|
||||||
;; OVERLAY (Popover and bottom sheets)
|
;; OVERLAY (Popover and bottom sheets)
|
||||||
(def dissmiss-overlay navigation/dissmiss-overlay)
|
(def dissmiss-overlay navigation/dissmiss-overlay)
|
||||||
|
|
||||||
(defn show-overlay [comp]
|
(defn show-overlay
|
||||||
(dissmiss-overlay comp)
|
([comp] (show-overlay comp {}))
|
||||||
(navigation/show-overlay
|
([comp opts]
|
||||||
{:component {:name comp
|
(dissmiss-overlay comp)
|
||||||
:id comp
|
(navigation/show-overlay
|
||||||
:options (merge (cond-> (roots/status-bar-options)
|
{:component {:name comp
|
||||||
(and platform/android? (not (colors/dark?)))
|
:id comp
|
||||||
(assoc-in [:statusBar :backgroundColor] "#99999A"))
|
:options (merge (cond-> (roots/status-bar-options)
|
||||||
{:layout {:componentBackgroundColor (if platform/android?
|
(and platform/android? (not (colors/dark?)))
|
||||||
colors/neutral-80-opa-20 ;; TODO adjust color
|
(assoc-in [:statusBar :backgroundColor] "#99999A"))
|
||||||
"transparent")}
|
{:layout {:componentBackgroundColor (if platform/android?
|
||||||
:overlay {:interceptTouchOutside true}})}}))
|
colors/neutral-80-opa-20 ;; TODO adjust color
|
||||||
|
"transparent")}
|
||||||
|
:overlay {:interceptTouchOutside true}}
|
||||||
|
opts)}})))
|
||||||
|
|
||||||
;; POPOVER
|
;; POPOVER
|
||||||
(re-frame/reg-fx :show-popover (fn [] (show-overlay "popover")))
|
(re-frame/reg-fx :show-popover (fn [] (show-overlay "popover")))
|
||||||
(re-frame/reg-fx :hide-popover (fn [] (dissmiss-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
|
;; VISIBILITY STATUS POPOVER
|
||||||
(re-frame/reg-fx :show-visibility-status-popover
|
(re-frame/reg-fx :show-visibility-status-popover
|
||||||
(fn [] (show-overlay "visibility-status-popover")))
|
(fn [] (show-overlay "visibility-status-popover")))
|
||||||
|
@ -270,6 +278,11 @@
|
||||||
(fn [] (gesture/gesture-handler-root-hoc views/popover-comp))
|
(fn [] (gesture/gesture-handler-root-hoc views/popover-comp))
|
||||||
(fn [] views/popover-comp))
|
(fn [] views/popover-comp))
|
||||||
|
|
||||||
|
(navigation/register-component
|
||||||
|
"toasts"
|
||||||
|
(fn [] views/toasts-comp)
|
||||||
|
js/undefined)
|
||||||
|
|
||||||
(navigation/register-component
|
(navigation/register-component
|
||||||
"visibility-status-popover"
|
"visibility-status-popover"
|
||||||
(fn [] (gesture/gesture-handler-root-hoc views/visibility-status-popover-comp))
|
(fn [] (gesture/gesture-handler-root-hoc views/visibility-status-popover-comp))
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[status-im2.navigation.screens :as screens]
|
[status-im2.navigation.screens :as screens]
|
||||||
[status-im2.setup.config :as config]
|
[status-im2.setup.config :as config]
|
||||||
[status-im2.setup.hot-reload :as reloader]
|
[status-im2.setup.hot-reload :as reloader]
|
||||||
|
[status-im2.common.toasts.view :as toasts]
|
||||||
|
|
||||||
;; TODO (14/11/22 flexsurfer) move to status-im2 namespace
|
;; TODO (14/11/22 flexsurfer) move to status-im2 namespace
|
||||||
[status-im.ui.screens.popover.views :as popover]
|
[status-im.ui.screens.popover.views :as popover]
|
||||||
|
@ -92,6 +93,13 @@
|
||||||
(when js/goog.DEBUG
|
(when js/goog.DEBUG
|
||||||
[reloader/reload-view])])))
|
[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
|
(def visibility-status-popover-comp
|
||||||
(reagent/reactify-component
|
(reagent/reactify-component
|
||||||
(fn []
|
(fn []
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
(ns status-im2.setup.events
|
(ns status-im2.setup.events
|
||||||
(:require [clojure.string :as string]
|
(: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]
|
[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
|
;; TODO (14/11/22 flexsurfer move to status-im2 namespace
|
||||||
[quo.theme :as quo.theme]
|
[quo.theme :as quo.theme]
|
||||||
[status-im.native-module.core :as status]
|
[status-im.native-module.core :as status]
|
||||||
[status-im.multiaccounts.login.core :as multiaccounts.login]
|
[status-im.multiaccounts.login.core :as multiaccounts.login]
|
||||||
[status-im.utils.keychain.core :as keychain]
|
[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
|
(re-frame/reg-fx
|
||||||
:setup/open-multiaccounts
|
:setup/open-multiaccounts
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
status-im2.subs.activity-center
|
status-im2.subs.activity-center
|
||||||
status-im2.subs.bootnodes
|
status-im2.subs.bootnodes
|
||||||
status-im2.subs.browser
|
status-im2.subs.browser
|
||||||
status-im2.subs.communities
|
|
||||||
status-im2.subs.contact
|
|
||||||
status-im2.subs.chat.chats
|
status-im2.subs.chat.chats
|
||||||
status-im2.subs.chat.messages
|
status-im2.subs.chat.messages
|
||||||
|
status-im2.subs.communities
|
||||||
|
status-im2.subs.contact
|
||||||
status-im2.subs.ens
|
status-im2.subs.ens
|
||||||
status-im2.subs.general
|
status-im2.subs.general
|
||||||
status-im2.subs.home
|
status-im2.subs.home
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
status-im2.subs.search
|
status-im2.subs.search
|
||||||
status-im2.subs.stickers
|
status-im2.subs.stickers
|
||||||
status-im2.subs.shell
|
status-im2.subs.shell
|
||||||
|
status-im2.subs.toasts
|
||||||
status-im2.subs.wallet.signing
|
status-im2.subs.wallet.signing
|
||||||
status-im2.subs.wallet.transactions
|
status-im2.subs.wallet.transactions
|
||||||
status-im2.subs.wallet.wallet))
|
status-im2.subs.wallet.wallet))
|
||||||
|
@ -196,6 +197,8 @@
|
||||||
;;intro-wizard
|
;;intro-wizard
|
||||||
(reg-root-key-sub :intro-wizard-state :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 :popover/popover :popover/popover)
|
||||||
(reg-root-key-sub :visibility-status-popover/popover :visibility-status-popover/popover)
|
(reg-root-key-sub :visibility-status-popover/popover :visibility-status-popover/popover)
|
||||||
(reg-root-key-sub :add-account :add-account)
|
(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