Implement alert banner (#19011)
This commit is contained in:
parent
e4997a2088
commit
604e232bda
|
@ -46,6 +46,7 @@
|
|||
[_]
|
||||
;; we need to disable notifications before starting the logout process
|
||||
{:effects/push-notifications-disable nil
|
||||
:dispatch [:alert-banners/remove-all]
|
||||
:dispatch-later [{:ms 100
|
||||
:dispatch [::logout-method
|
||||
{:auth-method keychain/auth-method-none
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
(reg-root-key-sub :peers-count :peers-count)
|
||||
(reg-root-key-sub :peers-summary :peers-summary)
|
||||
(reg-root-key-sub :web3-node-version :web3-node-version)
|
||||
(reg-root-key-sub :alert-banners :alert-banners)
|
||||
|
||||
;;keycard
|
||||
(reg-root-key-sub :keycard :keycard)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
(ns status-im.common.alert-banner.events
|
||||
(:require [re-frame.core :as re-frame]))
|
||||
|
||||
(defn add-alert-banner
|
||||
[{:keys [db]} [banner]]
|
||||
(let [current-banners-count (count (get db :alert-banners))
|
||||
db (assoc-in db [:alert-banners (:type banner)] banner)]
|
||||
(cond-> {:db db}
|
||||
(zero? current-banners-count)
|
||||
(assoc :show-alert-banner nil))))
|
||||
|
||||
(defn remove-alert-banner
|
||||
[{:keys [db]} [banner-type]]
|
||||
(let [db (update-in db [:alert-banners] dissoc banner-type)
|
||||
new-count (count (get db :alert-banners))]
|
||||
(cond-> {:db db}
|
||||
(zero? new-count)
|
||||
(assoc :hide-alert-banner nil))))
|
||||
|
||||
(defn remove-all-alert-banners
|
||||
[{:keys [db]}]
|
||||
{:db (dissoc db :alert-banners)
|
||||
:hide-alert-banner nil})
|
||||
|
||||
(re-frame/reg-event-fx :alert-banners/add add-alert-banner)
|
||||
(re-frame/reg-event-fx :alert-banners/remove remove-alert-banner)
|
||||
(re-frame/reg-event-fx :alert-banners/remove-all remove-all-alert-banners)
|
|
@ -0,0 +1,42 @@
|
|||
(ns status-im.common.alert-banner.events-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is testing]]
|
||||
matcher-combinators.test
|
||||
[status-im.common.alert-banner.events :as events]))
|
||||
|
||||
(deftest add-alert-banner
|
||||
(testing "Alert banner is added"
|
||||
(is (match? {:db {:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}}}
|
||||
:show-alert-banner nil}
|
||||
(events/add-alert-banner {:db {}}
|
||||
[{:text "Alert"
|
||||
:type :alert}])))))
|
||||
|
||||
(deftest remove-alert-banner
|
||||
(testing "Alert banner is removed"
|
||||
(is (match? {:db {}
|
||||
:hide-alert-banner nil}
|
||||
(events/remove-alert-banner {:db {:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}}}}
|
||||
[:alert]))))
|
||||
(testing "Alert banner is not removed"
|
||||
(is (match? {:db {:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}}}}
|
||||
(events/remove-alert-banner {:db {:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}}}}
|
||||
[:error])))))
|
||||
|
||||
(deftest remove-all-alert-banners
|
||||
(testing "All Alert banners are removed"
|
||||
(is (match? {:db {}
|
||||
:hide-alert-banner nil}
|
||||
(events/remove-all-alert-banners {:db {:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}
|
||||
:error {:text "Error"
|
||||
:type :error}}}})))))
|
|
@ -0,0 +1,20 @@
|
|||
(ns status-im.common.alert-banner.style)
|
||||
|
||||
(def border-radius 20)
|
||||
|
||||
(defn container
|
||||
[background-color]
|
||||
{:background-color background-color})
|
||||
|
||||
(def second-banner-wrapper
|
||||
{:margin-top (- border-radius)
|
||||
:overflow :hidden
|
||||
:border-top-left-radius border-radius
|
||||
:border-top-right-radius border-radius})
|
||||
|
||||
(defn hole-view
|
||||
[background-color]
|
||||
{:padding-top 11
|
||||
:align-items :center
|
||||
:height 60
|
||||
:background-color background-color})
|
|
@ -0,0 +1,68 @@
|
|||
(ns status-im.common.alert-banner.view
|
||||
(:require [quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
quo.theme
|
||||
[react-native.core :as rn]
|
||||
[react-native.hole-view :as hole-view]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.alert-banner.style :as style]
|
||||
[status-im.constants :as constants]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn get-colors-map
|
||||
[theme]
|
||||
{:alert
|
||||
{:background-color (colors/resolve-color :warning theme 20)
|
||||
:text-color (colors/resolve-color :warning theme)}
|
||||
:error
|
||||
{:background-color (colors/resolve-color :danger theme 20)
|
||||
:text-color (colors/resolve-color :danger theme)}})
|
||||
|
||||
(defn- banner
|
||||
[{:keys [text type second-banner? colors-map]}]
|
||||
[rn/view (when second-banner? {:style style/second-banner-wrapper})
|
||||
[hole-view/hole-view
|
||||
{:style (style/hole-view (get-in colors-map [type :background-color]))
|
||||
:holes (if second-banner?
|
||||
[]
|
||||
[{:x 0
|
||||
:y constants/alert-banner-height
|
||||
:width (:width (rn/get-window))
|
||||
:height constants/alert-banner-height
|
||||
:borderRadius style/border-radius}])}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style {:color (get-in colors-map [type :text-color])}}
|
||||
text]]])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [banners (rf/sub [:alert-banners])
|
||||
theme (quo.theme/use-theme-value)
|
||||
banners-count (count banners)
|
||||
alert-banner (:alert banners)
|
||||
error-banner (:error banners)
|
||||
safe-area-top (safe-area/get-top)
|
||||
colors-map (get-colors-map theme)]
|
||||
[hole-view/hole-view
|
||||
{:style {:background-color colors/neutral-100}
|
||||
:holes [{:x 0
|
||||
:y (+ safe-area-top (* constants/alert-banner-height banners-count))
|
||||
:width (:width (rn/get-window))
|
||||
:height constants/alert-banner-height
|
||||
:borderRadius style/border-radius}]}
|
||||
[rn/view
|
||||
{:style {:height safe-area-top
|
||||
:background-color (get-in colors-map
|
||||
[(if error-banner :error :alert) :background-color])}}]
|
||||
(when error-banner
|
||||
[banner
|
||||
(assoc error-banner
|
||||
:colors-map colors-map
|
||||
:second-banner? false)])
|
||||
(when alert-banner
|
||||
[banner
|
||||
(assoc alert-banner
|
||||
:colors-map colors-map
|
||||
:second-banner? (= 2 banners-count))])]))
|
|
@ -2,14 +2,12 @@
|
|||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defn container
|
||||
[{:keys [top] :as _insets}]
|
||||
(let [padding-top (if platform/ios? top (+ top 10))]
|
||||
{:flex 1
|
||||
:padding-top padding-top}))
|
||||
[padding-top]
|
||||
{:flex 1
|
||||
:padding-top padding-top})
|
||||
|
||||
(defn background
|
||||
[opacity]
|
||||
|
|
|
@ -49,21 +49,24 @@
|
|||
set-animating-false (fn [ms]
|
||||
(js/setTimeout #(reset! animating? false) ms))]
|
||||
(fn [{:keys [content skip-background? theme]}]
|
||||
(let [insets (safe-area/get-insets)
|
||||
{:keys [height]} (rn/get-window)
|
||||
opacity (reanimated/use-shared-value 0)
|
||||
translate-y (reanimated/use-shared-value height)
|
||||
close (fn []
|
||||
(set-animating-true)
|
||||
(reanimated/animate translate-y height 300)
|
||||
(reanimated/animate opacity 0 300)
|
||||
(rf/dispatch [:navigate-back])
|
||||
true)
|
||||
reset-open-sheet (fn []
|
||||
(reanimated/animate translate-y 0 300)
|
||||
(reanimated/animate opacity 1 300)
|
||||
(set-animating-false 300)
|
||||
(reset! scroll-enabled? true))]
|
||||
(let [{:keys [top] :as insets} (safe-area/get-insets)
|
||||
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
|
||||
padding-top (+ alert-banners-top-margin
|
||||
(if platform/ios? top (+ top 10)))
|
||||
{:keys [height]} (rn/get-window)
|
||||
opacity (reanimated/use-shared-value 0)
|
||||
translate-y (reanimated/use-shared-value height)
|
||||
close (fn []
|
||||
(set-animating-true)
|
||||
(reanimated/animate translate-y height 300)
|
||||
(reanimated/animate opacity 0 300)
|
||||
(rf/dispatch [:navigate-back])
|
||||
true)
|
||||
reset-open-sheet (fn []
|
||||
(reanimated/animate translate-y 0 300)
|
||||
(reanimated/animate opacity 1 300)
|
||||
(set-animating-false 300)
|
||||
(reset! scroll-enabled? true))]
|
||||
(rn/use-mount
|
||||
(fn []
|
||||
(rn/hw-back-add-listener close)
|
||||
|
@ -71,7 +74,7 @@
|
|||
(reanimated/animate opacity 1 300)
|
||||
(set-animating-false 300)
|
||||
#(rn/hw-back-remove-listener close)))
|
||||
[rn/view {:style (style/container insets)}
|
||||
[rn/view {:style (style/container padding-top)}
|
||||
(when-not skip-background?
|
||||
[reanimated/view {:style (style/background opacity)}])
|
||||
[gesture/gesture-detector
|
||||
|
|
|
@ -465,3 +465,5 @@
|
|||
|
||||
(def ^:const bridge-name-transfer "Transfer")
|
||||
(def ^:const bridge-name-erc-721-transfer "ERC721Transfer")
|
||||
|
||||
(def ^:const alert-banner-height 40)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
(ns status-im.contexts.preview.status-im.banners.alert-banner
|
||||
(:require
|
||||
[reagent.core :as reagent]
|
||||
[status-im.contexts.preview.quo.preview :as preview]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def descriptor
|
||||
[{:key :alert-banner?
|
||||
:type :boolean}
|
||||
{:key :alert-message
|
||||
:type :text}
|
||||
{:key :error-banner?
|
||||
:type :boolean}
|
||||
{:key :error-message
|
||||
:type :text}])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:alert-banner? false
|
||||
:alert-message "Testnet mode enabled"
|
||||
:error-banner? false
|
||||
:error-message "Main and failover JSON RPC URLs offline"})
|
||||
alert-banner? (reagent/cursor state [:alert-banner?])
|
||||
alert-message (reagent/cursor state [:alert-message])
|
||||
error-banner? (reagent/cursor state [:error-banner?])
|
||||
error-message (reagent/cursor state [:error-message])]
|
||||
(fn []
|
||||
(if @alert-banner?
|
||||
(rf/dispatch [:alert-banners/add
|
||||
{:type :alert
|
||||
:text @alert-message}])
|
||||
(rf/dispatch [:alert-banners/remove :alert]))
|
||||
(if @error-banner?
|
||||
(rf/dispatch [:alert-banners/add
|
||||
{:type :error
|
||||
:text @error-message}])
|
||||
(rf/dispatch [:alert-banners/remove :error]))
|
||||
[preview/preview-container {:state state :descriptor descriptor}])))
|
|
@ -5,6 +5,7 @@
|
|||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.contexts.preview.quo.common :as common]
|
||||
[status-im.contexts.preview.status-im.banners.alert-banner :as alert-banner]
|
||||
[status-im.contexts.preview.status-im.common.floating-button-page.view :as
|
||||
floating-button-page]
|
||||
[status-im.contexts.preview.status-im.style :as style]
|
||||
|
@ -12,7 +13,10 @@
|
|||
|
||||
(def screens-categories
|
||||
{:common [{:name :floating-button-page
|
||||
:component floating-button-page/view}]})
|
||||
:component floating-button-page/view}]
|
||||
:banner
|
||||
[{:name :alert-banner-preview
|
||||
:component alert-banner/view}]})
|
||||
|
||||
(defn- category-view
|
||||
[]
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
[status-im.contexts.shell.jump-to.constants :as shell.constants]
|
||||
[status-im.contexts.shell.jump-to.state :as state]
|
||||
[status-im.contexts.shell.jump-to.utils :as utils]
|
||||
[status-im.contexts.wallet.home.view :as wallet-new]))
|
||||
[status-im.contexts.wallet.home.view :as wallet-new]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn load-stack?
|
||||
[stack-id]
|
||||
|
@ -42,9 +43,16 @@
|
|||
|
||||
(defn f-home-stack
|
||||
[]
|
||||
(let [shared-values @state/shared-values-atom
|
||||
theme (quo.theme/use-theme-value)]
|
||||
[reanimated/view {:style (style/home-stack shared-values (assoc (utils/dimensions) :theme theme))}
|
||||
(let [shared-values @state/shared-values-atom
|
||||
theme (quo.theme/use-theme-value)
|
||||
{:keys [width height]} (utils/dimensions)
|
||||
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])]
|
||||
[reanimated/view
|
||||
{:style (style/home-stack
|
||||
shared-values
|
||||
{:theme theme
|
||||
:width width
|
||||
:height (- height alert-banners-top-margin)})}
|
||||
[lazy-screen :communities-stack shared-values]
|
||||
[lazy-screen :chats-stack shared-values]
|
||||
[lazy-screen :browser-stack shared-values]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require
|
||||
[legacy.status-im.bottom-sheet.events]
|
||||
[legacy.status-im.keycard.core :as keycard]
|
||||
status-im.common.alert-banner.events
|
||||
status-im.common.alert.effects
|
||||
status-im.common.async-storage.effects
|
||||
status-im.common.emoji-picker.events
|
||||
|
|
|
@ -94,6 +94,13 @@
|
|||
(fn [] (gesture/gesture-handler-root-hoc views/bottom-sheet))
|
||||
(fn [] views/bottom-sheet)))
|
||||
|
||||
;;;; Alert Banner
|
||||
(navigation/register-component
|
||||
"alert-banner"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/alert-banner #js {:flex 0}))
|
||||
(fn [] views/alert-banner))
|
||||
|
||||
|
||||
;; LEGACY (should be removed in status 2.0)
|
||||
|
||||
(defonce
|
||||
|
|
|
@ -181,6 +181,13 @@
|
|||
(rf/reg-fx :hide-bottom-sheet
|
||||
(fn [] (navigation/dissmiss-overlay "bottom-sheet")))
|
||||
|
||||
;;;; Alert Banner
|
||||
(rf/reg-fx :show-alert-banner
|
||||
(fn [] (show-overlay "alert-banner" {:overlay {:interceptTouchOutside false}})))
|
||||
|
||||
(rf/reg-fx :hide-alert-banner
|
||||
(fn [] (navigation/dissmiss-overlay "alert-banner")))
|
||||
|
||||
;;;; Merge options
|
||||
|
||||
(rf/reg-fx :merge-options
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
schema.view
|
||||
[status-im.common.alert-banner.view :as alert-banner]
|
||||
[status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen]
|
||||
[status-im.common.bottom-sheet.view :as bottom-sheet]
|
||||
[status-im.common.toasts.view :as toasts]
|
||||
|
@ -43,9 +44,10 @@
|
|||
:z-index 999999999999999999}]))
|
||||
|
||||
(defn wrapped-screen-style
|
||||
[{:keys [top? bottom?]} background-color]
|
||||
[{:keys [top? bottom? background-color alert-banners-top-margin]}]
|
||||
(merge
|
||||
{:flex 1
|
||||
:margin-top alert-banners-top-margin
|
||||
:background-color (or background-color (colors/theme-colors colors/white colors/neutral-100))}
|
||||
(when bottom?
|
||||
{:padding-bottom (safe-area/get-bottom)})
|
||||
|
@ -62,11 +64,16 @@
|
|||
(keyword screen-key))
|
||||
{:keys [insets sheet? theme]} options
|
||||
user-theme (theme/get-theme)
|
||||
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
|
||||
background-color (or (get-in options [:layout :backgroundColor])
|
||||
(when sheet? :transparent))]
|
||||
^{:key (str "root" screen-key @reloader/cnt)}
|
||||
[theme/provider {:theme (or theme user-theme)}
|
||||
[rn/view {:style (wrapped-screen-style insets background-color)}
|
||||
[rn/view
|
||||
{:style (wrapped-screen-style (assoc
|
||||
insets
|
||||
:background-color background-color
|
||||
:alert-banners-top-margin alert-banners-top-margin))}
|
||||
[inactive]
|
||||
(if sheet?
|
||||
[bottom-sheet-screen/view {:content component}]
|
||||
|
@ -140,3 +147,11 @@
|
|||
(when js/goog.DEBUG
|
||||
[reloader/reload-view])])
|
||||
functional-compiler))
|
||||
|
||||
(def alert-banner
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "alert-banner" @reloader/cnt)}
|
||||
[theme/provider {:theme :dark}
|
||||
[alert-banner/view]])
|
||||
functional-compiler))
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
(ns status-im.subs.alert-banner
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:alert-banners/top-margin
|
||||
:<- [:alert-banners]
|
||||
(fn [banners]
|
||||
(let [banners-count (count banners)]
|
||||
(when (pos? banners-count)
|
||||
(+ (* constants/alert-banner-height banners-count) 8)))))
|
|
@ -0,0 +1,32 @@
|
|||
(ns status-im.subs.alert-banner-test
|
||||
(:require
|
||||
[cljs.test :refer [is testing]]
|
||||
[re-frame.db :as rf-db]
|
||||
status-im.subs.activity-center
|
||||
[test-helpers.unit :as h]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(h/deftest-sub :alert-banners/top-margin
|
||||
[sub-name]
|
||||
(testing "returns 48 when only alert banner"
|
||||
(swap! rf-db/app-db assoc
|
||||
:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}})
|
||||
(is (= (rf/sub [sub-name]) 48)))
|
||||
|
||||
(testing "returns 48 when only error banner"
|
||||
(swap! rf-db/app-db assoc
|
||||
:alert-banners
|
||||
{:error {:text "Error"
|
||||
:type :error}})
|
||||
(is (= (rf/sub [sub-name]) 48)))
|
||||
|
||||
(testing "returns 88 when both alert and error banner"
|
||||
(swap! rf-db/app-db assoc
|
||||
:alert-banners
|
||||
{:alert {:text "Alert"
|
||||
:type :alert}
|
||||
:error {:text "Error"
|
||||
:type :error}})
|
||||
(is (= (rf/sub [sub-name]) 88))))
|
|
@ -2,6 +2,7 @@
|
|||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
status-im.subs.activity-center
|
||||
status-im.subs.alert-banner
|
||||
status-im.subs.chats
|
||||
status-im.subs.communities
|
||||
status-im.subs.contact
|
||||
|
@ -62,6 +63,7 @@
|
|||
(reg-root-key-sub :waku/v2-peer-stats :peer-stats)
|
||||
(reg-root-key-sub :password-authentication :password-authentication)
|
||||
(reg-root-key-sub :initials-avatar-font-file :initials-avatar-font-file)
|
||||
(reg-root-key-sub :alert-banners :alert-banners)
|
||||
|
||||
;;onboarding
|
||||
(reg-root-key-sub :onboarding/generated-keys? :onboarding/generated-keys?)
|
||||
|
|
Loading…
Reference in New Issue