diff --git a/src/quo/components/inputs/address_input/component_spec.cljs b/src/quo/components/inputs/address_input/component_spec.cljs index 5b36044903..62b1d23260 100644 --- a/src/quo/components/inputs/address_input/component_spec.cljs +++ b/src/quo/components/inputs/address_input/component_spec.cljs @@ -6,11 +6,12 @@ [test-helpers.component :as h])) (def ens-regex #"^(?=.{5,255}$)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$") +(def address-regex #"^0x[a-fA-F0-9]{40}$") (h/describe "Address input" (h/test "default render" (with-redefs [clipboard/get-string #(% "")] - (h/render [address-input/address-input {:ens-regex ens-regex}]) + (h/render [address-input/address-input]) (h/is-truthy (h/get-by-label-text :address-text-input)))) (h/test "on focus with blur? false" @@ -152,11 +153,37 @@ on-detect-ens (h/mock-fn)] (with-redefs [clipboard/get-string #(% clipboard)] (h/render [address-input/address-input - {:on-detect-ens on-detect-ens - :valid-ens? true - :ens-regex ens-regex}]) + {:on-detect-ens on-detect-ens + :valid-ens-or-address? true + :ens-regex ens-regex}]) (h/wait-for #(h/is-truthy (h/get-by-label-text :paste-button))) (h/fire-event :press (h/get-by-label-text :paste-button)) (h/wait-for #(h/is-falsy (h/get-by-label-text :clear-button))) (h/wait-for #(h/is-truthy (h/get-by-label-text :positive-button-container))) - (h/was-called on-detect-ens))))) + (h/was-called on-detect-ens)))) + + (h/test "address loading state and call on-detect-address" + (let [clipboard "0x2f88d65f3cb52605a54a833ae118fb1363acccd2" + on-detect-address (h/mock-fn)] + (with-redefs [clipboard/get-string #(% clipboard)] + (h/render [address-input/address-input + {:on-detect-address on-detect-address + :address-regex address-regex}]) + (h/wait-for #(h/is-truthy (h/get-by-label-text :paste-button))) + (h/fire-event :press (h/get-by-label-text :paste-button)) + (h/wait-for #(h/is-falsy (h/get-by-label-text :clear-button))) + (h/wait-for #(h/is-truthy (h/get-by-label-text :loading-button-container))) + (h/was-called on-detect-address)))) + + (h/test "address valid state and call on-detect-address" + (let [clipboard "0x2f88d65f3cb52605a54a833ae118fb1363acccd2" + on-detect-address (h/mock-fn)] + (with-redefs [clipboard/get-string #(% clipboard)] + (h/render [address-input/address-input + {:on-detect-address on-detect-address + :address-regex address-regex}]) + (h/wait-for #(h/is-truthy (h/get-by-label-text :paste-button))) + (h/fire-event :press (h/get-by-label-text :paste-button)) + (h/wait-for #(h/is-falsy (h/get-by-label-text :clear-button))) + (h/wait-for #(h/is-truthy (h/get-by-label-text :positive-button-container))) + (h/was-called on-detect-address))))) diff --git a/src/quo/components/inputs/address_input/view.cljs b/src/quo/components/inputs/address_input/view.cljs index 1607c27c30..bcbab7151f 100644 --- a/src/quo/components/inputs/address_input/view.cljs +++ b/src/quo/components/inputs/address_input/view.cljs @@ -61,10 +61,14 @@ value (reagent/atom "") focused? (atom false)] (fn [{:keys [scanned-value theme blur? on-change-text on-blur on-focus on-clear on-scan on-detect-ens - ens-regex - valid-ens?]}] + on-detect-address + ens-regex address-regex + valid-ens-or-address?]}] (let [on-change (fn [text] - (let [ens? (boolean (re-matches ens-regex text))] + (let [ens? (when ens-regex + (boolean (re-matches ens-regex text))) + address? (when address-regex + (boolean (re-matches address-regex text)))] (if (> (count text) 0) (reset! status :typing) (reset! status :active)) @@ -73,7 +77,10 @@ (on-change-text text)) (when (and ens? on-detect-ens) (reset! status :loading) - (on-detect-ens text)))) + (on-detect-ens text)) + (when (and address? on-detect-address) + (reset! status :loading) + (on-detect-address text)))) on-paste (fn [] (clipboard/get-string (fn [clipboard] @@ -145,12 +152,12 @@ {:on-press on-clear :blur? blur? :theme theme}]]) - (when (and (= @status :loading) (not valid-ens?)) + (when (and (= @status :loading) (not valid-ens-or-address?)) [rn/view {:style style/buttons-container :accessibility-label :loading-button-container} [loading-icon blur? theme]]) - (when (and (= @status :loading) valid-ens?) + (when (and (= @status :loading) valid-ens-or-address?) [rn/view {:style style/buttons-container :accessibility-label :positive-button-container} diff --git a/src/quo/components/navigation/page_nav/view.cljs b/src/quo/components/navigation/page_nav/view.cljs index caaf1c3e78..19f94f643f 100644 --- a/src/quo/components/navigation/page_nav/view.cljs +++ b/src/quo/components/navigation/page_nav/view.cljs @@ -196,7 +196,11 @@ :no-title [page-nav-base props [right-content - {:background background :content right-side :max-actions 3 :behind-overlay? behind-overlay?}]] + {:background background + :content right-side + :max-actions 3 + :behind-overlay? behind-overlay? + :account-switcher account-switcher}]] :title (let [centered? (= text-align :center)] diff --git a/src/status_im/ui/screens/qr_scanner/views.cljs b/src/status_im/ui/screens/qr_scanner/views.cljs index 9125031b12..6ef22d464a 100644 --- a/src/status_im/ui/screens/qr_scanner/views.cljs +++ b/src/status_im/ui/screens/qr_scanner/views.cljs @@ -35,7 +35,7 @@ :number-of-lines 1 :align :center :size :large} - (or title (i18n/label :t/scan-qr))]}]) + (or title (i18n/label :t/scan-qr-code))]}]) (defn qr-test-view [opts] diff --git a/src/status_im2/common/scan_qr_code/style.cljs b/src/status_im2/common/scan_qr_code/style.cljs new file mode 100644 index 0000000000..c4eeed2327 --- /dev/null +++ b/src/status_im2/common/scan_qr_code/style.cljs @@ -0,0 +1,152 @@ +(ns status-im2.common.scan-qr-code.style + (:require [quo.foundations.colors :as colors])) + +(def background + {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0 + :background-color colors/neutral-95}) + +(def screen-padding 20) +(def flash-button-size 32) +(def flash-button-spacing 12) + +(def flex-spacer {:flex 1}) + +(def absolute-fill + {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0}) + +(def hole + (merge absolute-fill + {:z-index 2 :opacity 0.95})) + +(defn root-container + [padding-top] + {:z-index 5 + :flex 1 + :padding-top padding-top}) + +(def header-container + {:flex-direction :row + :justify-content :space-between + :padding-horizontal screen-padding + :margin-vertical 12}) + +(def header-text + {:padding-horizontal screen-padding + :padding-top 12 + :padding-bottom 8 + :color colors/white}) + +(def header-sub-text + {:padding-horizontal screen-padding + :color colors/white}) + +(def tabs-container + {:padding-horizontal screen-padding + :margin-top 20}) + +(def scan-qr-code-container + {:margin-top 19}) + +(def qr-view-finder + {:margin-horizontal screen-padding + :height 1 + :display :flex}) + +(defn qr-view-finder-container + [size] + {:width size + :height size + :justify-content :space-between + :margin-left -1 + :margin-top -1}) + +(defn viewfinder-container + [viewfinder] + {:position :absolute + :left (:x viewfinder) + :top (:y viewfinder)}) + +(def view-finder-border-container + {:flex-direction :row + :justify-content :space-between}) + +(defn camera-flash-button + [viewfinder] + {:position :absolute + :top (- (+ (:y viewfinder) (:height viewfinder)) flash-button-size flash-button-spacing) + :right (+ screen-padding flash-button-spacing)}) + +(defn- get-border + [border-vertical-width border-horizontal-width corner-radius] + {:border-color colors/white + :width 78 + :height 78 + border-vertical-width 2 + border-horizontal-width 2 + corner-radius 16}) + +(def white-border + (let [base-tip {:background-color colors/white + :position :absolute + :height 1.9 ; 1.9 instead of 2 to fix the tips protruding + :width 1.9 + :border-radius 1}] + {:top-left + {:border (get-border :border-top-width :border-left-width :border-top-left-radius) + :tip-1 (assoc base-tip :right -1 :top 0) + :tip-2 (assoc base-tip :left 0 :bottom -1)} + :top-right + {:border (get-border :border-top-width :border-right-width :border-top-right-radius) + :tip-1 (assoc base-tip :right 0 :bottom -1) + :tip-2 (assoc base-tip :left -1 :top 0)} + :bottom-left + {:border (get-border :border-bottom-width :border-left-width :border-bottom-left-radius) + :tip-1 (assoc base-tip :right -1 :bottom 0) + :tip-2 (assoc base-tip :left 0 :top -1)} + :bottom-right + {:border (get-border :border-bottom-width :border-right-width :border-bottom-right-radius) + :tip-1 (assoc base-tip :right 0 :top -1) + :tip-2 (assoc base-tip :left -1 :bottom 0)}})) + +(def viewfinder-text + {:color colors/white-opa-70 + :text-align :center + :padding-top 16}) + +(def camera-permission-container + {:height 335 + :margin-top 19 + :margin-horizontal screen-padding + :background-color colors/white-opa-5 + :border-color colors/white-opa-10 + :border-width 1 + :border-radius 12 + :border-style :dashed + :align-items :center + :justify-content :center}) + +(def enable-camera-access-header + {:color colors/white}) + +(def enable-camera-access-sub-text + {:color colors/white-opa-70 + :margin-bottom 16}) + +(def camera-style + {:flex 1}) + +(def camera-container + {:position :absolute + :top 0 + :left 0 + :right 0 + :bottom 0 + :border-radius 16}) diff --git a/src/status_im2/common/scan_qr_code/view.cljs b/src/status_im2/common/scan_qr_code/view.cljs new file mode 100644 index 0000000000..66658cac19 --- /dev/null +++ b/src/status_im2/common/scan_qr_code/view.cljs @@ -0,0 +1,244 @@ +(ns status-im2.common.scan-qr-code.view + (:require [clojure.string :as string] + [oops.core :as oops] + [quo.core :as quo] + [quo.foundations.colors :as colors] + [quo.theme :as quo.theme] + [react-native.blur :as blur] + [react-native.camera-kit :as camera-kit] + [react-native.core :as rn] + [react-native.hole-view :as hole-view] + [react-native.permissions :as permissions] + [react-native.safe-area :as safe-area] + [reagent.core :as reagent] + [status-im2.common.device-permissions :as device-permissions] + [status-im2.common.scan-qr-code.style :as style] + [utils.debounce :as debounce] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [utils.transforms :as transforms])) + +(defonce camera-permission-granted? (reagent/atom false)) + +(defn- header + [{:keys [title subtitle]}] + [:<> + [rn/view {:style style/header-container} + [quo/button + {:icon-only? true + :type :grey + :background :blur + :size 32 + :accessibility-label :close-scan-qr-code + :on-press #(rf/dispatch [:navigate-back])} + :i/arrow-left]] + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/header-text} + title] + [quo/text + {:size :paragraph-1 + :weight :regular + :style style/header-sub-text} + subtitle]]) + +(defn get-labels-and-on-press-method + [] + {:title-label-key :t/enable-access-to-camera + :description-label-key :t/to-scan-a-qr-enable-your-camera + :button-icon :i/camera + :button-label :t/enable-camera + :accessibility-label :request-camera-permission + :on-press (fn [] + (device-permissions/camera #(reset! camera-permission-granted? true)))}) + +(defn- camera-permission-view + [] + (let [{:keys [title-label-key + description-label-key + button-icon + button-label + accessibility-label + on-press]} (get-labels-and-on-press-method)] + [rn/view {:style style/camera-permission-container} + [quo/text + {:size :paragraph-1 + :weight :medium + :style style/enable-camera-access-header} + (i18n/label title-label-key)] + [quo/text + {:size :paragraph-2 + :weight :regular + :style style/enable-camera-access-sub-text} + (i18n/label description-label-key)] + [quo/button + {:icon-left button-icon + :type :primary + :size 32 + :accessibility-label accessibility-label + :customization-color :blue + :on-press on-press} + (i18n/label button-label)]])) + +(defn- qr-scan-hole-area + [qr-view-finder] + [rn/view + {:style style/qr-view-finder + :on-layout (fn [event] + (let [layout (transforms/js->clj (oops/oget event "nativeEvent.layout")) + view-finder (assoc layout :height (:width layout))] + (reset! qr-view-finder view-finder)))}]) + +(defn- white-border + [corner] + (let [border-styles (style/white-border corner)] + [rn/view + [rn/view {:style (border-styles :border)}] + [rn/view {:style (border-styles :tip-1)}] + [rn/view {:style (border-styles :tip-2)}]])) + +(defn- white-square + [layout-size] + [rn/view {:style (style/qr-view-finder-container layout-size)} + [rn/view {:style style/view-finder-border-container} + [white-border :top-left] + [white-border :top-right]] + [rn/view {:style style/view-finder-border-container} + [white-border :bottom-left] + [white-border :bottom-right]]]) + +(defn- viewfinder + [qr-view-finder] + (let [layout-size (+ (:width qr-view-finder) 2)] + [rn/view {:style (style/viewfinder-container qr-view-finder)} + [white-square layout-size] + [quo/text + {:size :paragraph-2 + :weight :regular + :style style/viewfinder-text} + (i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]])) + +(defn- scan-qr-code-tab + [qr-view-finder] + (if (and @camera-permission-granted? + (boolean (not-empty qr-view-finder))) + [viewfinder qr-view-finder] + [camera-permission-view])) + +(defn- check-qr-code-and-navigate + [{:keys [event error-message validate-fn on-success-scan on-failed-scan]}] + (let [scanned-value (string/trim (oops/oget event "nativeEvent.codeStringValue")) + validated? (if validate-fn (validate-fn scanned-value) true)] + (if validated? + (on-success-scan scanned-value) + (do + (on-failed-scan) + (debounce/debounce-and-dispatch + [:toasts/upsert + {:icon :i/info + :icon-color colors/danger-50 + :theme :dark + :text error-message}] + 300))))) + +(defn- render-camera + [{:keys [torch-mode qr-view-finder scan-code? validate-fn error-message set-qr-code-succeeded + set-rescan-timeout]}] + [:<> + [rn/view {:style style/camera-container} + [camera-kit/camera + {:style style/camera-style + :camera-type camera-kit/camera-type-back + :zoom-mode :off + :torch-mode torch-mode + :scan-barcode true + :on-read-code #(when scan-code? + (check-qr-code-and-navigate {:event % + :validate-fn validate-fn + :error-message error-message + :on-success-scan set-qr-code-succeeded + :on-failed-scan set-rescan-timeout}))}]] + [hole-view/hole-view + {:style style/hole + :holes [(assoc qr-view-finder :borderRadius 16)]} + [blur/view + {:style style/absolute-fill + :blur-amount 10 + :blur-type :transparent + :overlay-color colors/neutral-80-opa-80 + :background-color colors/neutral-80-opa-80}]]]) + +(defn- set-listener-torch-off-on-app-inactive + [torch-atm] + (let [set-torch-off-fn #(when (not= % "active") (reset! torch-atm false)) + app-state-listener (.addEventListener rn/app-state "change" set-torch-off-fn)] + #(.remove app-state-listener))) + +(defn f-view-internal + [{:keys [title subtitle validate-fn on-success-scan error-message]}] + (let [insets (safe-area/get-insets) + qr-code-succeed? (reagent/atom false) + qr-view-finder (reagent/atom {}) + torch? (reagent/atom false) + scan-code? (reagent/atom true) + set-rescan-timeout (fn [] + (reset! scan-code? false) + (js/setTimeout #(reset! scan-code? true) 3000))] + (fn [] + (let [torch-mode (if @torch? :on :off) + flashlight-icon (if @torch? :i/flashlight-on :i/flashlight-off) + show-camera? (and @camera-permission-granted? + (boolean (not-empty @qr-view-finder))) + camera-ready-to-scan? (and show-camera? + (not @qr-code-succeed?))] + (rn/use-effect + #(set-listener-torch-off-on-app-inactive torch?)) + + (rn/use-effect + (fn [] + (when-not @camera-permission-granted? + (permissions/permission-granted? :camera + #(reset! camera-permission-granted? %) + #(reset! camera-permission-granted? false))))) + [:<> + [rn/view {:style style/background}] + (when camera-ready-to-scan? + [render-camera + {:torch-mode torch-mode + :qr-view-finder @qr-view-finder + :scan-code? @scan-code? + :error-message error-message + :validate-fn validate-fn + :set-qr-code-succeeded (fn [value] + (when on-success-scan + (on-success-scan value)) + (rf/dispatch [:navigate-back])) + :set-rescan-timeout set-rescan-timeout}]) + [rn/view {:style (style/root-container (:top insets))} + [header + {:title title + :subtitle subtitle}] + (when (empty? @qr-view-finder) + [:<> + [rn/view {:style style/scan-qr-code-container}] + [qr-scan-hole-area qr-view-finder]]) + [scan-qr-code-tab @qr-view-finder] + [rn/view {:style style/flex-spacer}] + (when show-camera? + [quo.theme/provider {:theme :light} + [quo/button + {:icon-only? true + :type :grey + :background :photo + :size style/flash-button-size + :accessibility-label :camera-flash + :container-style (style/camera-flash-button @qr-view-finder) + :on-press #(swap! torch? not)} + flashlight-icon]])]])))) + +(defn view-internal + [props] + [:f> f-view-internal props]) + +(def view (quo.theme/with-theme view-internal)) diff --git a/src/status_im2/constants.cljs b/src/status_im2/constants.cljs index 4fbb2461e5..0dc08edf16 100644 --- a/src/status_im2/constants.cljs +++ b/src/status_im2/constants.cljs @@ -182,6 +182,8 @@ (def regx-community-universal-link #"((^https?://join.status.im/)|(^status-im://))c/([\x00-\x7F]+)$") (def regx-deep-link #"((^ethereum:.*)|(^status-im://[\x00-\x7F]+$))") (def regx-ens #"^(?=.{5,255}$)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$") +(def regx-address #"^0x[a-fA-F0-9]{40}$") +(def regx-address-contains #"(?i)0x[a-fA-F0-9]{40}") (def ^:const dapp-permission-contact-code "contact-code") (def ^:const dapp-permission-web3 "web3") diff --git a/src/status_im2/contexts/quo_preview/inputs/address_input.cljs b/src/status_im2/contexts/quo_preview/inputs/address_input.cljs index f16d23c32f..bad8699bcf 100644 --- a/src/status_im2/contexts/quo_preview/inputs/address_input.cljs +++ b/src/status_im2/contexts/quo_preview/inputs/address_input.cljs @@ -14,9 +14,9 @@ (defn view [] - (let [state (reagent/atom {:scanned-value "" - :blur? false - :valid-ens? false}) + (let [state (reagent/atom {:scanned-value "" + :blur? false + :valid-ens-or-address? false}) timer (atom nil)] (fn [] [preview/preview-container @@ -29,8 +29,9 @@ {:on-scan #(js/alert "Not implemented yet") :ens-regex constants/regx-ens :on-detect-ens (fn [_] - (swap! state assoc :valid-ens? false) + (swap! state assoc :valid-ens-or-address? false) (when @timer (js/clearTimeout @timer)) - (reset! timer (js/setTimeout #(swap! state assoc :valid-ens? true) - 2000)))})]]))) + (reset! timer (js/setTimeout + #(swap! state assoc :valid-ens-or-address? true) + 2000)))})]]))) diff --git a/src/status_im2/contexts/wallet/events.cljs b/src/status_im2/contexts/wallet/events.cljs new file mode 100644 index 0000000000..1d84fc70d5 --- /dev/null +++ b/src/status_im2/contexts/wallet/events.cljs @@ -0,0 +1,12 @@ +(ns status-im2.contexts.wallet.events + (:require [utils.re-frame :as rf])) + +(rf/defn scan-address-success + {:events [:wallet-2/scan-address-success]} + [{:keys [db]} address] + {:db (assoc db :wallet-2/scanned-address address)}) + +(rf/defn clean-scanned-address + {:events [:wallet-2/clean-scanned-address]} + [{:keys [db]}] + {:db (dissoc db :wallet-2/scanned-address)}) diff --git a/src/status_im2/contexts/wallet/events_test.cljs b/src/status_im2/contexts/wallet/events_test.cljs new file mode 100644 index 0000000000..4644045b92 --- /dev/null +++ b/src/status_im2/contexts/wallet/events_test.cljs @@ -0,0 +1,22 @@ +(ns status-im2.contexts.wallet.events-test + (:require + [cljs.test :refer-macros [deftest is testing]] + [status-im2.contexts.wallet.events :as events])) + +(def address "0x2f88d65f3cb52605a54a833ae118fb1363acccd2") + +(deftest scan-address-success + (let [db {}] + (testing "scan-address-success" + (let [expected-db {:wallet-2/scanned-address address} + effects (events/scan-address-success {:db db} address) + result-db (:db effects)] + (is (= result-db expected-db)))))) + +(deftest clean-scanned-address + (let [db {:wallet-2/scanned-address address}] + (testing "clean-scanned-address" + (let [expected-db {} + effects (events/clean-scanned-address {:db db}) + result-db (:db effects)] + (is (= result-db expected-db)))))) diff --git a/src/status_im2/contexts/wallet/scan_account/view.cljs b/src/status_im2/contexts/wallet/scan_account/view.cljs new file mode 100644 index 0000000000..ef88a3a785 --- /dev/null +++ b/src/status_im2/contexts/wallet/scan_account/view.cljs @@ -0,0 +1,24 @@ +(ns status-im2.contexts.wallet.scan-account.view + (:require [status-im2.common.scan-qr-code.view :as scan-qr-code] + [status-im2.constants :as constants] + [utils.debounce :as debounce] + [utils.i18n :as i18n])) + +(defn- contains-address? + [s] + (boolean (re-find constants/regx-address-contains s))) + +(defn- extract-address + [scanned-text] + (first (re-seq constants/regx-address-contains scanned-text))) + +(defn view + [] + [scan-qr-code/view + {:title (i18n/label :t/scan-qr) + :subtitle (i18n/label :t/scan-an-account-qr-code) + :error-message (i18n/label :t/oops-this-qr-does-not-contain-an-address) + :validate-fn #(contains-address? %) + :on-success-scan #(debounce/debounce-and-dispatch [:wallet-2/scan-address-success + (extract-address %)] + 300)}]) diff --git a/src/status_im2/contexts/wallet/send/select_address/view.cljs b/src/status_im2/contexts/wallet/send/select_address/view.cljs index a0fe9a86ca..d1f3ff023c 100644 --- a/src/status_im2/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im2/contexts/wallet/send/select_address/view.cljs @@ -42,27 +42,38 @@ (defn- address-input [] - (let [timer (atom nil) - valid-ens? (reagent/atom false) - input-value (atom "")] - [quo/address-input - {:on-scan #(js/alert "Not implemented yet") - :ens-regex constants/regx-ens - :on-detect-ens - (fn [_] - (reset! valid-ens? false) - (when @timer (js/clearTimeout @timer)) - (reset! timer (js/setTimeout #(reset! valid-ens? true) 2000))) - :on-change-text #(reset! input-value %) - :valid-ens? @valid-ens?}])) + (let [timer (atom nil) + valid-ens-or-address? (reagent/atom false) + input-value (atom "") + on-detect-address-or-ens (fn [_] + (reset! valid-ens-or-address? false) + (when @timer (js/clearTimeout @timer)) + (reset! timer (js/setTimeout #(reset! valid-ens-or-address? true) + 2000)))] + (fn [] + (let [scanned-address (rf/sub [:wallet-2/scanned-address])] + [quo/address-input + {:on-scan #(rf/dispatch [:open-modal :scan-address]) + :ens-regex constants/regx-ens + :address-regex constants/regx-address + :scanned-value scanned-address + :on-detect-ens on-detect-address-or-ens + :on-detect-address on-detect-address-or-ens + :on-change-text (fn [text] + (when-not (= scanned-address text) + (rf/dispatch [:wallet-2/clean-scanned-address])) + (reset! input-value text)) + :on-clear #(rf/dispatch [:wallet-2/clean-scanned-address]) + :valid-ens-or-address? @valid-ens-or-address?}])))) -(defn- view-internal +(defn- f-view-internal [] (let [margin-top (safe-area/get-top) selected-tab (reagent/atom (:id (first tabs-data))) - on-close #(rf/dispatch [:navigate-back]) + on-close #(rf/dispatch [:dismiss-modal :wallet-select-address]) on-change-tab #(reset! selected-tab %)] (fn [] + (rn/use-effect (fn [] #(rf/dispatch [:wallet-2/clean-scanned-address]))) [rn/scroll-view {:content-container-style (style/container margin-top) :keyboard-should-persist-taps :never @@ -71,7 +82,11 @@ {:icon-name :i/close :on-press on-close :accessibility-label :top-bar - :right-side :account-switcher}] + :right-side :account-switcher + :account-switcher {:customization-color :purple + :on-press #(js/alert "Not implemented yet") + :state :default + :emoji "🍑"}}] [quo/text-combinations {:title (i18n/label :t/send-to) :container-style style/title-container @@ -89,4 +104,8 @@ :on-change on-change-tab}] [tab-view @selected-tab]]))) +(defn view-internal + [] + [:f> f-view-internal]) + (def view (quo.theme/with-theme view-internal)) diff --git a/src/status_im2/events.cljs b/src/status_im2/events.cljs index 84a5690f32..095c9a5c40 100644 --- a/src/status_im2/events.cljs +++ b/src/status_im2/events.cljs @@ -18,6 +18,7 @@ status-im2.contexts.profile.events status-im2.contexts.shell.share.events status-im2.contexts.syncing.events + status-im2.contexts.wallet.events [status-im2.db :as db] [utils.re-frame :as rf])) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index 9d2709feda..ce226b9f19 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -42,6 +42,7 @@ [status-im2.contexts.wallet.create-account.view :as wallet-create-account] [status-im2.contexts.wallet.saved-address.view :as wallet-saved-address] [status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses] + [status-im2.contexts.wallet.scan-account.view :as scan-address] [status-im2.contexts.wallet.send.select-address.view :as wallet-select-address] [status-im2.navigation.options :as options] [status-im2.navigation.transitions :as transitions])) @@ -259,7 +260,13 @@ {:name :wallet-select-address :options {:modalPresentationStyle :overCurrentContext} - :component wallet-select-address/view}] + :component wallet-select-address/view} + + {:name :scan-address + :options (merge + options/dark-screen + {:modalPresentationStyle :overCurrentContext}) + :component scan-address/view}] (when config/quo-preview-enabled? quo.preview/screens) diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index 58da74d493..480472d333 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -130,6 +130,115 @@ ; Messages home view -> tabs (reg-root-key-sub :messages-home/selected-tab :messages-home/selected-tab) +;;browser +(reg-root-key-sub :browsers :browser/browsers) +(reg-root-key-sub :browser/options :browser/options) +(reg-root-key-sub :dapps/permissions :dapps/permissions) +(reg-root-key-sub :bookmarks :bookmarks/bookmarks) +(reg-root-key-sub :browser/screen-id :browser/screen-id) + +;;stickers +(reg-root-key-sub :stickers/selected-pack :stickers/selected-pack) +(reg-root-key-sub :stickers/packs :stickers/packs) +(reg-root-key-sub :stickers/recent-stickers :stickers/recent-stickers) + +;;mailserver +(reg-root-key-sub :mailserver/current-id :mailserver/current-id) +(reg-root-key-sub :mailserver/mailservers :mailserver/mailservers) +(reg-root-key-sub :mailserver.edit/mailserver :mailserver.edit/mailserver) +(reg-root-key-sub :mailserver/state :mailserver/state) +(reg-root-key-sub :mailserver/pending-requests :mailserver/pending-requests) +(reg-root-key-sub :mailserver/request-error? :mailserver/request-error) +(reg-root-key-sub :mailserver/fetching-gaps-in-progress :mailserver/fetching-gaps-in-progress) + +;;contacts +(reg-root-key-sub :contacts/contacts-raw :contacts/contacts) +(reg-root-key-sub :contacts/current-contact-identity :contacts/identity) +(reg-root-key-sub :contacts/current-contact-ens-name :contacts/ens-name) +(reg-root-key-sub :contacts/new-identity :contacts/new-identity) +(reg-root-key-sub :group/selected-contacts :group/selected-contacts) +(reg-root-key-sub :contacts/search-query :contacts/search-query) + +;;wallet +(reg-root-key-sub :wallet :wallet) +(reg-root-key-sub :prices :prices) +(reg-root-key-sub :prices-loading? :prices-loading?) +(reg-root-key-sub :wallet.transactions :wallet.transactions) +(reg-root-key-sub :wallet/custom-token-screen :wallet/custom-token-screen) +(reg-root-key-sub :wallet/prepare-transaction :wallet/prepare-transaction) +(reg-root-key-sub :wallet-service/manual-setting :wallet-service/manual-setting) +(reg-root-key-sub :wallet/recipient :wallet/recipient) +(reg-root-key-sub :wallet/favourites :wallet/favourites) +(reg-root-key-sub :wallet/refreshing-history? :wallet/refreshing-history?) +(reg-root-key-sub :wallet/fetching-error :wallet/fetching-error) +(reg-root-key-sub :wallet/non-archival-node :wallet/non-archival-node) +(reg-root-key-sub :wallet/current-base-fee :wallet/current-base-fee) +(reg-root-key-sub :wallet/slow-base-fee :wallet/slow-base-fee) +(reg-root-key-sub :wallet/normal-base-fee :wallet/normal-base-fee) +(reg-root-key-sub :wallet/fast-base-fee :wallet/fast-base-fee) +(reg-root-key-sub :wallet/current-priority-fee :wallet/current-priority-fee) +(reg-root-key-sub :wallet/transactions-management-enabled? :wallet/transactions-management-enabled?) +(reg-root-key-sub :wallet/all-tokens :wallet/all-tokens) +(reg-root-key-sub :wallet/collectible-collections :wallet/collectible-collections) +(reg-root-key-sub :wallet/fetching-collection-assets :wallet/fetching-collection-assets) +(reg-root-key-sub :wallet/collectible-assets :wallet/collectible-assets) +(reg-root-key-sub :wallet/selected-collectible :wallet/selected-collectible) +(reg-root-key-sub :wallet/modal-selecting-source-token? :wallet/modal-selecting-source-token?) +(reg-root-key-sub :wallet/swap-from-token :wallet/swap-from-token) +(reg-root-key-sub :wallet/swap-to-token :wallet/swap-to-token) +(reg-root-key-sub :wallet/swap-from-token-amount :wallet/swap-from-token-amount) +(reg-root-key-sub :wallet/swap-to-token-amount :wallet/swap-to-token-amount) +(reg-root-key-sub :wallet/swap-advanced-mode? :wallet/swap-advanced-mode?) + +;; Wallet 2 +(reg-root-key-sub :wallet-2/scanned-address :wallet-2/scanned-address) + +;;; Link previews + +(reg-root-key-sub :link-previews-whitelist :link-previews-whitelist) +(reg-root-key-sub :chat/link-previews :chat/link-previews) + +;;commands +(reg-root-key-sub :commands/select-account :commands/select-account) + +;;ethereum +(reg-root-key-sub :ethereum/current-block :ethereum/current-block) + +;;ens +(reg-root-key-sub :ens/registration :ens/registration) +(reg-root-key-sub :ens/registrations :ens/registrations) +(reg-root-key-sub :ens/names :ens/names) + +;;signing +(reg-root-key-sub :signing/sign :signing/sign) +(reg-root-key-sub :signing/tx :signing/tx) +(reg-root-key-sub :signing/edit-fee :signing/edit-fee) + +;;intro-wizard +(reg-root-key-sub :intro-wizard-state :intro-wizard) + +(reg-root-key-sub :toasts :toasts) +(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) + +(reg-root-key-sub :keycard :keycard) + +(reg-root-key-sub :auth-method :auth-method) + +;; keycard +(reg-root-key-sub :keycard/banner-hidden :keycard/banner-hidden) + +;; delete profile +(reg-root-key-sub :delete-profile/error :delete-profile/error) +(reg-root-key-sub :delete-profile/keep-keys-on-keycard? :delete-profile/keep-keys-on-keycard?) + +;; push notifications +(reg-root-key-sub :push-notifications/servers :push-notifications/servers) +(reg-root-key-sub :push-notifications/preferences :push-notifications/preferences) + +(reg-root-key-sub :buy-crypto/on-ramps :buy-crypto/on-ramps) + ;; communities (reg-root-key-sub :communities :communities) (reg-root-key-sub :communities/create :communities/create) diff --git a/translations/en.json b/translations/en.json index 763a50bf32..720539d496 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1226,8 +1226,8 @@ "save-password-unavailable": "Set device passcode to save password", "save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.", "scan-or-enter-sync-code": "Scan or enter sync code", - "scan-qr": "Scan QR code", - "scan-qr-code": "Scan a QR code with a wallet address", + "scan-qr": "Scan QR", + "scan-qr-code": "Scan QR code", "search": "Search", "search-discover-communities": "Search communities or categories", "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", @@ -2348,5 +2348,7 @@ "edit-account": "Edit account", "share-account": "Share account", "remove-account": "Remove account", - "select-another-account": "Select another account" + "select-another-account": "Select another account", + "oops-this-qr-does-not-contain-an-address": "Oops! This QR does not contain an address", + "scan-an-account-qr-code": "Scan an account QR code" }