From 604e232bda02ae688950864e311c2ed42642d864 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Tue, 12 Mar 2024 14:03:37 +0530 Subject: [PATCH] Implement alert banner (#19011) --- .../status_im/multiaccounts/logout/core.cljs | 1 + src/legacy/status_im/subs/root.cljs | 1 + src/status_im/common/alert_banner/events.cljs | 27 ++++++++ .../common/alert_banner/events_test.cljs | 42 ++++++++++++ src/status_im/common/alert_banner/style.cljs | 20 ++++++ src/status_im/common/alert_banner/view.cljs | 68 +++++++++++++++++++ .../common/bottom_sheet_screen/style.cljs | 8 +-- .../common/bottom_sheet_screen/view.cljs | 35 +++++----- src/status_im/constants.cljs | 2 + .../status_im/banners/alert_banner.cljs | 38 +++++++++++ .../contexts/preview/status_im/main.cljs | 6 +- .../jump_to/components/home_stack/view.cljs | 16 +++-- src/status_im/events.cljs | 1 + src/status_im/navigation/core.cljs | 7 ++ src/status_im/navigation/effects.cljs | 7 ++ src/status_im/navigation/view.cljs | 19 +++++- src/status_im/subs/alert_banner.cljs | 12 ++++ src/status_im/subs/alert_banner_test.cljs | 32 +++++++++ src/status_im/subs/root.cljs | 2 + 19 files changed, 316 insertions(+), 28 deletions(-) create mode 100644 src/status_im/common/alert_banner/events.cljs create mode 100644 src/status_im/common/alert_banner/events_test.cljs create mode 100644 src/status_im/common/alert_banner/style.cljs create mode 100644 src/status_im/common/alert_banner/view.cljs create mode 100644 src/status_im/contexts/preview/status_im/banners/alert_banner.cljs create mode 100644 src/status_im/subs/alert_banner.cljs create mode 100644 src/status_im/subs/alert_banner_test.cljs diff --git a/src/legacy/status_im/multiaccounts/logout/core.cljs b/src/legacy/status_im/multiaccounts/logout/core.cljs index 5310e9d4dc..e3cbe6cd98 100644 --- a/src/legacy/status_im/multiaccounts/logout/core.cljs +++ b/src/legacy/status_im/multiaccounts/logout/core.cljs @@ -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 diff --git a/src/legacy/status_im/subs/root.cljs b/src/legacy/status_im/subs/root.cljs index d6a4a7307e..a5705bb6a9 100644 --- a/src/legacy/status_im/subs/root.cljs +++ b/src/legacy/status_im/subs/root.cljs @@ -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) diff --git a/src/status_im/common/alert_banner/events.cljs b/src/status_im/common/alert_banner/events.cljs new file mode 100644 index 0000000000..ac815410d8 --- /dev/null +++ b/src/status_im/common/alert_banner/events.cljs @@ -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) diff --git a/src/status_im/common/alert_banner/events_test.cljs b/src/status_im/common/alert_banner/events_test.cljs new file mode 100644 index 0000000000..6244a483ef --- /dev/null +++ b/src/status_im/common/alert_banner/events_test.cljs @@ -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}}}}))))) diff --git a/src/status_im/common/alert_banner/style.cljs b/src/status_im/common/alert_banner/style.cljs new file mode 100644 index 0000000000..3594d37dcc --- /dev/null +++ b/src/status_im/common/alert_banner/style.cljs @@ -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}) diff --git a/src/status_im/common/alert_banner/view.cljs b/src/status_im/common/alert_banner/view.cljs new file mode 100644 index 0000000000..7363b400f1 --- /dev/null +++ b/src/status_im/common/alert_banner/view.cljs @@ -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))])])) diff --git a/src/status_im/common/bottom_sheet_screen/style.cljs b/src/status_im/common/bottom_sheet_screen/style.cljs index 01c35fcb3a..2d3e02d75a 100644 --- a/src/status_im/common/bottom_sheet_screen/style.cljs +++ b/src/status_im/common/bottom_sheet_screen/style.cljs @@ -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] diff --git a/src/status_im/common/bottom_sheet_screen/view.cljs b/src/status_im/common/bottom_sheet_screen/view.cljs index 40a65565cf..f3c08409d4 100644 --- a/src/status_im/common/bottom_sheet_screen/view.cljs +++ b/src/status_im/common/bottom_sheet_screen/view.cljs @@ -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 diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index dd2b52b432..975b8ded53 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -465,3 +465,5 @@ (def ^:const bridge-name-transfer "Transfer") (def ^:const bridge-name-erc-721-transfer "ERC721Transfer") + +(def ^:const alert-banner-height 40) diff --git a/src/status_im/contexts/preview/status_im/banners/alert_banner.cljs b/src/status_im/contexts/preview/status_im/banners/alert_banner.cljs new file mode 100644 index 0000000000..5fac9295bc --- /dev/null +++ b/src/status_im/contexts/preview/status_im/banners/alert_banner.cljs @@ -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}]))) diff --git a/src/status_im/contexts/preview/status_im/main.cljs b/src/status_im/contexts/preview/status_im/main.cljs index 643f82b434..9a850f046f 100644 --- a/src/status_im/contexts/preview/status_im/main.cljs +++ b/src/status_im/contexts/preview/status_im/main.cljs @@ -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 [] diff --git a/src/status_im/contexts/shell/jump_to/components/home_stack/view.cljs b/src/status_im/contexts/shell/jump_to/components/home_stack/view.cljs index 0706232088..1f5bd8d882 100644 --- a/src/status_im/contexts/shell/jump_to/components/home_stack/view.cljs +++ b/src/status_im/contexts/shell/jump_to/components/home_stack/view.cljs @@ -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] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 3b91531681..6320ffa7d5 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -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 diff --git a/src/status_im/navigation/core.cljs b/src/status_im/navigation/core.cljs index 868f6d27fe..757ce321c1 100644 --- a/src/status_im/navigation/core.cljs +++ b/src/status_im/navigation/core.cljs @@ -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 diff --git a/src/status_im/navigation/effects.cljs b/src/status_im/navigation/effects.cljs index 5b1bac456c..f4ba54b8f0 100644 --- a/src/status_im/navigation/effects.cljs +++ b/src/status_im/navigation/effects.cljs @@ -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 diff --git a/src/status_im/navigation/view.cljs b/src/status_im/navigation/view.cljs index c24bc27780..6c1921a1b1 100644 --- a/src/status_im/navigation/view.cljs +++ b/src/status_im/navigation/view.cljs @@ -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)) diff --git a/src/status_im/subs/alert_banner.cljs b/src/status_im/subs/alert_banner.cljs new file mode 100644 index 0000000000..0628242311 --- /dev/null +++ b/src/status_im/subs/alert_banner.cljs @@ -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))))) diff --git a/src/status_im/subs/alert_banner_test.cljs b/src/status_im/subs/alert_banner_test.cljs new file mode 100644 index 0000000000..0e213afeb5 --- /dev/null +++ b/src/status_im/subs/alert_banner_test.cljs @@ -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)))) diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index fa0293a2ba..91ecdf3011 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -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?)