feat: scan account from QR (#17464)
Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
parent
6acae424d7
commit
5829eaf77b
|
@ -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)))))
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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})
|
|
@ -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))
|
|
@ -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")
|
||||
|
|
|
@ -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)))})]])))
|
||||
|
|
|
@ -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)})
|
|
@ -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))))))
|
|
@ -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)}])
|
|
@ -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))
|
||||
|
|
|
@ -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]))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue