Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
579fde7f56 | ||
|
|
5ff1c8f1e4 | ||
|
|
7289940653 | ||
|
|
a253f8cedb | ||
|
|
daf52102ce | ||
|
|
fa20d4d9c3 | ||
|
|
dd26fcf42c | ||
|
|
8ac9cc08e1 |
@@ -204,7 +204,7 @@
|
||||
(.startSearchForLocalPairingPeers ^js (network) callback))
|
||||
|
||||
(defn local-pairing-preflight-outbound-check
|
||||
"Checks whether the device has allowed connecting to the local server"
|
||||
"Checks whether the device has allows connecting to the local server"
|
||||
[callback]
|
||||
(log/info "[native-module] Performing local pairing preflight check")
|
||||
(when platform/ios?
|
||||
@@ -253,15 +253,19 @@
|
||||
|
||||
(defn hash-typed-data
|
||||
"used for keycard"
|
||||
[data callback]
|
||||
(log/debug "[native-module] hash-typed-data")
|
||||
(.hashTypedData ^js (encryption) data callback))
|
||||
([data]
|
||||
(native-utils/promisify-native-module-call hash-typed-data data))
|
||||
([data callback]
|
||||
(log/debug "[native-module] hash-typed-data")
|
||||
(.hashTypedData ^js (encryption) data callback)))
|
||||
|
||||
(defn hash-typed-data-v4
|
||||
"used for keycard"
|
||||
[data callback]
|
||||
(log/debug "[native-module] hash-typed-data-v4")
|
||||
(.hashTypedDataV4 ^js (encryption) data callback))
|
||||
([data]
|
||||
(native-utils/promisify-native-module-call hash-typed-data-v4 data))
|
||||
([data callback]
|
||||
(log/debug "[native-module] hash-typed-data-v4")
|
||||
(.hashTypedDataV4 ^js (encryption) data callback)))
|
||||
|
||||
(defn send-transaction-with-signature
|
||||
"used for keycard"
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
selected? (assoc :border-top-color (colors/alpha color 0.4)
|
||||
:border-end-color (colors/alpha color 0.4)
|
||||
:border-bottom-color (colors/alpha color 0.2)
|
||||
:border-start-color (colors/alpha color 0.2))
|
||||
:border-start-color (colors/alpha color 0.2)
|
||||
:border-color nil)
|
||||
(zero? idx) (assoc :margin-left -4))))
|
||||
|
||||
(defn color-circle
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
[:scroll? {:optional true} [:maybe :boolean]]
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:buttons-container-style {:optional true} [:maybe :map]]]]]
|
||||
[:buttons-container-style {:optional true} [:maybe :map]]
|
||||
[:buttons-style {:optional true} [:maybe :map]]]]]
|
||||
:any])
|
||||
|
||||
(def ^:private role-icon
|
||||
@@ -43,10 +44,9 @@
|
||||
(defn- view-internal
|
||||
[{:keys [actions description description-text description-top-text error-message role button-one-label
|
||||
button-two-label blur? button-one-props button-two-props scroll? container-style
|
||||
buttons-container-style context-tag-props]}]
|
||||
buttons-container-style buttons-style context-tag-props]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[rn/view
|
||||
{:style (merge (style/container scroll? blur? theme) container-style)}
|
||||
[rn/view {:style (merge (style/container scroll? blur? theme) container-style)}
|
||||
(when (= description :top-error)
|
||||
[rn/view {:style style/error-message}
|
||||
[icon/icon
|
||||
@@ -82,7 +82,7 @@
|
||||
[button/button
|
||||
(merge
|
||||
{:size 40
|
||||
:container-style style/button-container
|
||||
:container-style (merge style/button-container buttons-style)
|
||||
:background (when (or blur? scroll?) :blur)
|
||||
:theme theme
|
||||
:accessibility-label :button-two}
|
||||
@@ -91,7 +91,7 @@
|
||||
[button/button
|
||||
(merge
|
||||
{:size 40
|
||||
:container-style style/button-container
|
||||
:container-style (merge style/button-container buttons-style)
|
||||
:background (when (or blur? scroll?) :blur)
|
||||
:theme theme
|
||||
:accessibility-label :button-one}
|
||||
|
||||
@@ -50,8 +50,7 @@
|
||||
[quo/text
|
||||
{:weight :semi-bold
|
||||
:size :heading-2
|
||||
:style style/header}
|
||||
(i18n/label :t/check-before-syncing)]
|
||||
:style style/header} (i18n/label :t/check-before-syncing)]
|
||||
[quo/text {:style style/description} (i18n/label :t/check-before-syncing-doc-description)]
|
||||
|
||||
(into
|
||||
@@ -70,5 +69,6 @@
|
||||
:disabled? (not= checked-count (count checks))
|
||||
:customization-color (or customization-color :blue)
|
||||
:accessibility-label :continue-button
|
||||
|
||||
:on-press on-continue}
|
||||
(i18n/label :t/continue)]]]))
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns status-im.common.enable-access-to-local-network.style)
|
||||
|
||||
(def header {:margin-bottom 8})
|
||||
|
||||
(def description {:padding-bottom 8
|
||||
:padding-horizontal 20})
|
||||
|
||||
(def list-container {:padding-horizontal 20
|
||||
:padding-bottom 12})
|
||||
@@ -1,66 +0,0 @@
|
||||
(ns status-im.common.enable-access-to-local-network.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.enable-access-to-local-network.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- step [{:keys [number text]} button]
|
||||
[rn/view {:style {:flex-direction :row
|
||||
:align-items :center
|
||||
:column-gap 8
|
||||
:padding-vertical 4}}
|
||||
[quo/step {:in-blur-view? true} number]
|
||||
[rn/view {:style {:flex-direction :row
|
||||
:align-items :center
|
||||
:column-gap 4}}
|
||||
[quo/text {:weight :regular
|
||||
:size :paragraph-2}
|
||||
text]
|
||||
button]])
|
||||
|
||||
(defn- hide-bottom-sheet
|
||||
[]
|
||||
(rf/dispatch [:hide-bottom-sheet]))
|
||||
|
||||
(defn view
|
||||
"This implementation must be only used on iOS since we need to request the networking
|
||||
permission, on Android we already have the permission granted."
|
||||
[]
|
||||
(let [[permission-granted?
|
||||
set-permission-granted] (rn/use-state false)
|
||||
verify-permission (rn/use-callback
|
||||
#(rf/dispatch
|
||||
[:syncing/preflight-outbound-check set-permission-granted])
|
||||
[])
|
||||
verifying-permission? nil]
|
||||
(prn "permission-granted?:" permission-granted?)
|
||||
[:<>
|
||||
[quo/drawer-top {:container-style {:padding-bottom 8}
|
||||
:title (i18n/label :t/enable-access-to-local-network)
|
||||
:type :default
|
||||
:blur? true}]
|
||||
[quo/text {:style style/description
|
||||
:weight :regular
|
||||
:size :paragraph-1}
|
||||
(i18n/label :t/enable-access-to-local-network-description)]
|
||||
[rn/view {:style style/list-container}
|
||||
[step {:number 1 :text "Open"}
|
||||
[quo/button {:type :primary
|
||||
:size 24
|
||||
:on-press (fn [] (js/alert "a"))}
|
||||
"Settings / Privacy / Local Network"]]
|
||||
[step {:number 2 :text "Find"}
|
||||
[quo/button {:type :grey
|
||||
:size 24
|
||||
:blur? true}
|
||||
"Status"]]
|
||||
[step {:number 3 :text "Toggle the switch to grant access"}]
|
||||
[step {:number 4 :text "Tap"}
|
||||
[quo/button {:type (if verifying-permission? :grey :primary)
|
||||
:size 24
|
||||
:on-press verify-permission}
|
||||
(if verifying-permission?
|
||||
"Verifying local network access..."
|
||||
"Verify local network access")]]]]))
|
||||
@@ -73,10 +73,10 @@
|
||||
[{:keys [scroll-ref tabs selected-tab on-tab-change scroll-shared-value content customization-color]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[:<>
|
||||
[:f> banner-card-blur-layer scroll-shared-value
|
||||
[:f> banner-card-hiding-layer
|
||||
[banner-card-blur-layer scroll-shared-value
|
||||
[banner-card-hiding-layer
|
||||
(assoc content :scroll-shared-value scroll-shared-value :theme theme)]]
|
||||
[:f> banner-card-tabs-layer
|
||||
[banner-card-tabs-layer
|
||||
{:scroll-shared-value scroll-shared-value
|
||||
:selected-tab selected-tab
|
||||
:tabs tabs
|
||||
|
||||
@@ -221,10 +221,7 @@
|
||||
(def ^:const wallet-connect-eth-sign-typed-v4-method "eth_signTypedData_v4")
|
||||
(def ^:const wallet-connect-supported-methods
|
||||
#{wallet-connect-personal-sign-method
|
||||
wallet-connect-eth-sign-method
|
||||
wallet-connect-eth-send-transaction-method
|
||||
;; NOTE: disabled, as we have no clear use cases for it and other wallets don't support it
|
||||
;; wallet-connect-eth-sign-transaction-method
|
||||
wallet-connect-eth-sign-typed-method
|
||||
wallet-connect-eth-sign-typed-v4-method})
|
||||
(def ^:const wallet-connect-supported-events #{"accountsChanged" "chainChanged"})
|
||||
@@ -237,11 +234,9 @@
|
||||
(def ^:const wallet-connect-message-signing-methods
|
||||
#{wallet-connect-personal-sign-method
|
||||
wallet-connect-eth-sign-typed-method
|
||||
wallet-connect-eth-sign-method
|
||||
wallet-connect-eth-sign-typed-v4-method})
|
||||
(def ^:const wallet-connect-transaction-methods
|
||||
#{wallet-connect-eth-send-transaction-method
|
||||
wallet-connect-eth-sign-transaction-method})
|
||||
#{wallet-connect-eth-send-transaction-method})
|
||||
|
||||
(def ^:const transaction-pending-type-wallet-connect-transfer "WalletConnectTransfer")
|
||||
|
||||
|
||||
@@ -114,13 +114,17 @@
|
||||
{:scroll-input (oops/oget % "nativeEvent.contentOffset.y")
|
||||
:shared-value scroll-shared-value})}])))
|
||||
|
||||
(defn- on-new-message-press
|
||||
[]
|
||||
(let [main-event [:show-bottom-sheet {:content chat.actions.view/new-chat}]]
|
||||
(rf/dispatch [:profile/check-profile-update-prompt main-event])))
|
||||
|
||||
(defn- banner-data
|
||||
[profile-link]
|
||||
{:title-props
|
||||
{:beta? true
|
||||
:label (i18n/label :t/messages)
|
||||
:handler #(rf/dispatch
|
||||
[:show-bottom-sheet {:content chat.actions.view/new-chat}])
|
||||
:handler on-new-message-press
|
||||
:accessibility-label :new-chat-button}
|
||||
:card-props
|
||||
{:on-press #(rf/dispatch [:open-share {:options {:url profile-link}}])
|
||||
|
||||
@@ -3,16 +3,39 @@
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.constants :as constants]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- keycard-documentation
|
||||
[]
|
||||
[quo/documentation-drawers
|
||||
{:title (i18n/label :t/keycard)
|
||||
:shell? true}
|
||||
[rn/view
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/keycard-documentation)]
|
||||
[quo/button
|
||||
{:size 40
|
||||
:type :primary
|
||||
:container-style {:margin-top 24 :margin-bottom 12}
|
||||
:background :blur
|
||||
:icon-right :i/external
|
||||
:on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])
|
||||
:accessibility-label :get-keycard}
|
||||
(i18n/label :t/buy-keycard)]]])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [on-press]} (rf/sub [:get-screen-params])]
|
||||
[:<>
|
||||
[quo/page-nav
|
||||
{:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back}]
|
||||
{:icon-name :i/arrow-left
|
||||
:on-press events-helper/navigate-back
|
||||
:right-side [{:icon-name :i/info
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content keycard-documentation
|
||||
:shell? true}])}]}]
|
||||
[quo/page-top
|
||||
{:title (i18n/label :t/check-keycard)
|
||||
:description :text
|
||||
|
||||
@@ -76,3 +76,16 @@
|
||||
args
|
||||
:on-success (get-on-success args)
|
||||
:on-failure (get-on-failure args)))
|
||||
|
||||
(defn keycard-address?
|
||||
[keypairs address]
|
||||
(let [find-keycard-keypair (fn [kps] (some #(when-not (empty? (:keycards %)) %) kps))
|
||||
keypair-addresses (fn [kp]
|
||||
(->> (:accounts kp)
|
||||
(map :address)
|
||||
set))]
|
||||
(-> keypairs
|
||||
vals
|
||||
find-keycard-keypair
|
||||
keypair-addresses
|
||||
(contains? (string/lower-case address)))))
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.check-before-syncing.view :as check-before-syncing]
|
||||
[status-im.common.enable-access-to-local-network.view :as enable-access-to-local-network]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.config :as config]
|
||||
@@ -25,10 +24,8 @@
|
||||
[]
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[enable-access-to-local-network/view]
|
||||
;[check-before-syncing/view {:on-submit navigate-to-sign-in-by-syncing}]
|
||||
)
|
||||
{:content (fn [] [check-before-syncing/view
|
||||
{:on-submit navigate-to-sign-in-by-syncing}])
|
||||
:shell? true}]))
|
||||
|
||||
(defn- navigate-to-sign-in-by-recovery-phrase
|
||||
|
||||
@@ -14,15 +14,14 @@
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn on-contact-request
|
||||
(defn on-send-contact-request
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-request/view])}]))
|
||||
(let [main-event [:show-bottom-sheet {:content contact-request/view}]]
|
||||
(rf/dispatch [:profile/check-profile-update-prompt main-event])))
|
||||
|
||||
(defn on-contact-review
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-review/view])}]))
|
||||
(rf/dispatch [:show-bottom-sheet {:content contact-review/view}]))
|
||||
|
||||
(defn view
|
||||
[{:keys [scroll-y]}]
|
||||
@@ -34,27 +33,27 @@
|
||||
profile-picture (profile.utils/photo contact)
|
||||
online? (rf/sub [:visibility-status-updates/online? public-key])
|
||||
theme (quo.theme/use-theme)
|
||||
contact-status (rn/use-memo (fn []
|
||||
(cond
|
||||
(= contact-request-state
|
||||
constants/contact-request-state-mutual) :contact
|
||||
blocked? :blocked
|
||||
:else nil))
|
||||
[blocked? contact-request-state])
|
||||
on-start-chat (rn/use-callback #(rf/dispatch [:chat.ui/start-chat
|
||||
public-key
|
||||
ens-name])
|
||||
[ens-name public-key])
|
||||
on-unblock-press (rn/use-callback (fn []
|
||||
(rf/dispatch [:contact/unblock-contact
|
||||
public-key])
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :user-unblocked
|
||||
:type :positive
|
||||
:text (i18n/label :t/user-unblocked
|
||||
{:username
|
||||
full-name})}]))
|
||||
[public-key full-name])]
|
||||
contact-status (rn/use-memo
|
||||
(fn []
|
||||
(cond
|
||||
(= contact-request-state
|
||||
constants/contact-request-state-mutual) :contact
|
||||
blocked? :blocked
|
||||
:else nil))
|
||||
[blocked? contact-request-state])
|
||||
on-start-chat (rn/use-callback
|
||||
#(rf/dispatch [:chat.ui/start-chat public-key ens-name])
|
||||
[ens-name public-key])
|
||||
on-unblock-press (rn/use-callback
|
||||
(fn []
|
||||
(rf/dispatch [:contact/unblock-contact
|
||||
public-key])
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :user-unblocked
|
||||
:type :positive
|
||||
:text (i18n/label :t/user-unblocked
|
||||
{:username full-name})}]))
|
||||
[public-key full-name])]
|
||||
[rn/view {:style style/header-container}
|
||||
[rn/view {:style style/header-top-wrapper}
|
||||
[rn/view {:style style/avatar-wrapper}
|
||||
@@ -93,14 +92,12 @@
|
||||
|
||||
(cond
|
||||
(and (not blocked?)
|
||||
(or
|
||||
(not contact-request-state)
|
||||
(= contact-request-state constants/contact-request-state-none)
|
||||
(= contact-request-state constants/contact-request-state-dismissed)))
|
||||
|
||||
(or (not contact-request-state)
|
||||
(= contact-request-state constants/contact-request-state-none)
|
||||
(= contact-request-state constants/contact-request-state-dismissed)))
|
||||
[quo/button
|
||||
{:container-style style/button-wrapper
|
||||
:on-press on-contact-request
|
||||
:on-press on-send-contact-request
|
||||
:customization-color customization-color
|
||||
:icon-left :i/add-user}
|
||||
(i18n/label :t/send-contact-request)]
|
||||
|
||||
@@ -4,27 +4,35 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx :profile/edit-accent-colour-success
|
||||
(fn [_ [customization-color]]
|
||||
(fn [_ [{:keys [customization-color navigate-back? show-toast?]}]]
|
||||
{:fx [[:dispatch [:profile/save-local-accent-color customization-color]]
|
||||
[:dispatch [:navigate-back]]
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/accent-colour-updated)}]]]}))
|
||||
(when navigate-back?
|
||||
[:dispatch [:navigate-back]])
|
||||
(when show-toast?
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/accent-colour-updated)}]])]}))
|
||||
|
||||
(rf/reg-event-fx :profile/save-local-accent-color
|
||||
(fn [{:keys [db]} [customization-color]]
|
||||
{:db (assoc-in db [:profile/profile :customization-color] customization-color)}))
|
||||
|
||||
(defn edit-accent-colour
|
||||
[{:keys [db]} [customization-color]]
|
||||
[{:keys [db]}
|
||||
[{:keys [color navigate-back? show-toast?]
|
||||
:or {navigate-back? true
|
||||
show-toast? true}}]]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_setCustomizationColor"
|
||||
:params [{:customizationColor customization-color
|
||||
:params [{:customizationColor color
|
||||
:keyUid key-uid}]
|
||||
:on-success [:profile/edit-accent-colour-success customization-color]
|
||||
:on-success [:profile/edit-accent-colour-success
|
||||
{:customization-color color
|
||||
:navigate-back? navigate-back?
|
||||
:show-toast? show-toast?}]
|
||||
:on-error #(log/error "failed to edit accent color." {:error %})}]]]}))
|
||||
|
||||
(rf/reg-event-fx :profile/edit-accent-colour edit-accent-colour)
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
[{:method "wakuext_setCustomizationColor"
|
||||
:params [{:customizationColor new-color
|
||||
:keyUid key-uid}]
|
||||
:on-success [:profile/edit-accent-colour-success new-color]
|
||||
:on-success [:profile/edit-accent-colour-success
|
||||
{:customization-color new-color
|
||||
:navigate-back? true
|
||||
:show-toast? true}]
|
||||
:on-error fn?}]]]}]
|
||||
(is (match? expected
|
||||
(sut/edit-accent-colour cofx [new-color])))))
|
||||
(sut/edit-accent-colour cofx [{:color new-color}])))))
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
{:type :primary
|
||||
:customization-color @unsaved-custom-color
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:profile/edit-accent-colour @unsaved-custom-color]))}
|
||||
(rf/dispatch [:profile/edit-accent-colour
|
||||
{:color @unsaved-custom-color}]))}
|
||||
(i18n/label :t/save-colour)]]]]))))
|
||||
|
||||
(defn view
|
||||
|
||||
@@ -11,19 +11,22 @@
|
||||
(update db :profile/profile dissoc :images))}))
|
||||
|
||||
(rf/reg-event-fx :profile/edit-profile-picture-success
|
||||
(fn [_ [images]]
|
||||
(let [has-picture? (rf/sub [:profile/has-picture])]
|
||||
(fn [{db :db} [{:keys [show-toast?]} images]]
|
||||
(let [has-picture? (-> db :profile/profile :images count pos?)]
|
||||
{:fx [[:dispatch [:profile/update-local-picture (reverse images)]]
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label (if has-picture?
|
||||
:t/profile-picture-updated
|
||||
:t/profile-picture-added))}]]]})))
|
||||
(when show-toast?
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label (if has-picture?
|
||||
:t/profile-picture-updated
|
||||
:t/profile-picture-added))}]])]})))
|
||||
|
||||
(defn edit-profile-picture
|
||||
[{:keys [db]} [picture crop-width crop-height]]
|
||||
[{:keys [db]}
|
||||
[{:keys [picture crop-width crop-height show-toast?]
|
||||
:or {show-toast? true}}]]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])
|
||||
crop-width (or crop-width profile-picture-picker/crop-size)
|
||||
crop-height (or crop-height profile-picture-picker/crop-size)
|
||||
@@ -31,25 +34,28 @@
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "multiaccounts_storeIdentityImage"
|
||||
:params [key-uid path 0 0 crop-width crop-height]
|
||||
:on-success [:profile/edit-profile-picture-success]}]]]}))
|
||||
:on-success [:profile/edit-profile-picture-success {:show-toast? show-toast?}]}]]]}))
|
||||
|
||||
(rf/reg-event-fx :profile/edit-picture edit-profile-picture)
|
||||
|
||||
(rf/reg-event-fx :profile/delete-profile-picture-success
|
||||
(fn [_]
|
||||
(fn [_ [{:keys [show-toast?]}]]
|
||||
{:fx [[:dispatch [:profile/update-local-picture nil]]
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/profile-picture-removed)}]]]}))
|
||||
(when show-toast?
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/profile-picture-removed)}]])]}))
|
||||
|
||||
(defn delete-profile-picture
|
||||
[{:keys [db]}]
|
||||
[{:keys [db]}
|
||||
[{:keys [show-toast?]
|
||||
:or {show-toast? true}}]]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "multiaccounts_deleteIdentityImage"
|
||||
:params [key-uid]
|
||||
:on-success [:profile/delete-profile-picture-success]}]]]}))
|
||||
:on-success [:profile/delete-profile-picture-success {:show-toast? show-toast?}]}]]]}))
|
||||
|
||||
(rf/reg-event-fx :profile/delete-picture delete-profile-picture)
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
[{:method "multiaccounts_storeIdentityImage"
|
||||
:params [key-uid picture 0 0 profile-picture-picker/crop-size
|
||||
profile-picture-picker/crop-size]
|
||||
:on-success [:profile/edit-profile-picture-success]}]]]}]
|
||||
:on-success [:profile/edit-profile-picture-success {:show-toast? true}]}]]]}]
|
||||
(is (match? expected
|
||||
(sut/edit-profile-picture cofx [picture])))))
|
||||
(sut/edit-profile-picture cofx [{:picture picture}])))))
|
||||
|
||||
(deftest delete-picture-test
|
||||
(let [key-uid "key-uid"
|
||||
@@ -22,6 +22,6 @@
|
||||
expected {:fx [[:json-rpc/call
|
||||
[{:method "multiaccounts_deleteIdentityImage"
|
||||
:params [key-uid]
|
||||
:on-success [:profile/delete-profile-picture-success]}]]]}]
|
||||
:on-success [:profile/delete-profile-picture-success {:show-toast? true}]}]]]}]
|
||||
(is (match? expected
|
||||
(sut/delete-profile-picture cofx)))))
|
||||
(sut/delete-profile-picture cofx [{}])))))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
has-picture? (rf/sub [:profile/has-picture])
|
||||
on-change-profile-pic (fn [picture]
|
||||
(if picture
|
||||
(rf/dispatch [:profile/edit-picture picture])
|
||||
(rf/dispatch [:profile/edit-picture {:picture picture}])
|
||||
(rf/dispatch [:profile/delete-picture])))]
|
||||
[rn/view
|
||||
{:key :edit-profile
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
(ns status-im.contexts.profile.edit.introduce-yourself.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn sheet
|
||||
[{:keys [pending-event]}]
|
||||
(let [on-accept (fn []
|
||||
(rf/dispatch [:open-modal :screen/profile.edit-profile-modal
|
||||
{:pending-event pending-event}]))
|
||||
on-reject #(rf/dispatch pending-event)]
|
||||
[:<>
|
||||
[rn/view {:style {:padding-horizontal 20}}
|
||||
[quo/text
|
||||
{:style {:padding-bottom 4}
|
||||
:size :heading-2
|
||||
:weight :semi-bold}
|
||||
(i18n/label :t/just-introduce-yourself)]
|
||||
[quo/text
|
||||
{:style {:padding-top 4 :padding-bottom 12}
|
||||
:size :paragraph-1
|
||||
:weight :regular}
|
||||
(i18n/label :t/add-display-name-and-picture)]]
|
||||
[quo/bottom-actions
|
||||
{:actions :two-actions
|
||||
:buttons-style {:flex-shrink 1 :flex-basis 0}
|
||||
:button-one-label (i18n/label :t/edit-profile)
|
||||
:button-one-props {:type :primary
|
||||
:on-press on-accept}
|
||||
:button-two-label (i18n/label :t/skip)
|
||||
:button-two-props {:type :grey
|
||||
:on-press on-reject}}]]))
|
||||
@@ -0,0 +1,62 @@
|
||||
(ns status-im.contexts.profile.edit.modal.events
|
||||
(:require [status-im.contexts.profile.edit.introduce-yourself.view :as introduce-yourself]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:profile/edit-profile
|
||||
(fn [_ [{:keys [display-name picture color on-success]}]]
|
||||
{:fx (cond-> []
|
||||
display-name
|
||||
(conj [:dispatch
|
||||
[:profile/edit-name
|
||||
{:display-name display-name
|
||||
:navigate-back? false
|
||||
:show-toast? false}]])
|
||||
color
|
||||
(conj [:dispatch
|
||||
[:profile/edit-accent-colour
|
||||
{:color color
|
||||
:navigate-back? false
|
||||
:show-toast? false}]])
|
||||
picture
|
||||
(conj [:dispatch
|
||||
[:profile/edit-picture
|
||||
{:picture picture
|
||||
:show-toast? false}]])
|
||||
|
||||
(nil? picture)
|
||||
(conj [:dispatch [:profile/delete-picture {:show-toast? false}]])
|
||||
|
||||
:always
|
||||
(conj [:dispatch [:navigate-back]]
|
||||
[:dispatch on-success]))}))
|
||||
|
||||
(defn- profile-update-asked-storage-key
|
||||
[key-uid]
|
||||
(keyword :update-profile-asked key-uid))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:profile/set-profile-update-as-asked
|
||||
(fn [{db :db}]
|
||||
(let [storage-key (-> db :profile/profile :key-uid profile-update-asked-storage-key)]
|
||||
{:fx [[:effects.async-storage/set {storage-key true}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:profile/ask-profile-update
|
||||
(fn [_ [pending-event]]
|
||||
{:fx [[:dispatch [:profile/set-profile-update-as-asked]]
|
||||
[:dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[introduce-yourself/sheet {:pending-event pending-event}])}]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:profile/check-profile-update-prompt
|
||||
(fn [{db :db} [pending-event]]
|
||||
(let [storage-key (-> db :profile/profile :key-uid profile-update-asked-storage-key)]
|
||||
{:fx [[:effects.async-storage/get
|
||||
{:keys [storage-key]
|
||||
:cb (fn [profile-updated-data]
|
||||
(rf/dispatch (if (storage-key profile-updated-data)
|
||||
pending-event
|
||||
[:profile/ask-profile-update pending-event])))}]]})))
|
||||
@@ -0,0 +1,55 @@
|
||||
(ns status-im.contexts.profile.edit.modal.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]))
|
||||
|
||||
(def continue-button
|
||||
{:width "100%"
|
||||
:margin-left :auto
|
||||
:margin-top (if platform/android? :auto 0)
|
||||
:margin-right :auto})
|
||||
|
||||
(def button-container
|
||||
{:width "100%"
|
||||
:padding-left 20
|
||||
:padding-right 20
|
||||
:padding-top 12
|
||||
:align-self :flex-end
|
||||
:height 64})
|
||||
|
||||
(defn view-button-container
|
||||
[keyboard-shown?]
|
||||
(merge button-container
|
||||
(if platform/ios?
|
||||
{:margin-bottom (if keyboard-shown? 0 34)}
|
||||
{:margin-bottom (if keyboard-shown? 12 34)})))
|
||||
|
||||
(def blur-button-container
|
||||
(merge button-container
|
||||
(when platform/android? {:padding-bottom 12})))
|
||||
|
||||
(def page-container
|
||||
{:flex 1})
|
||||
|
||||
(def info-message
|
||||
{:margin-top 8})
|
||||
|
||||
(def title
|
||||
{:color colors/white
|
||||
:margin-top 12
|
||||
:margin-bottom 18})
|
||||
|
||||
(def color-title
|
||||
{:color colors/white-70-blur
|
||||
:margin-top 20
|
||||
:margin-bottom 16})
|
||||
|
||||
(def content-container
|
||||
{:padding-horizontal 20})
|
||||
|
||||
(def input-container
|
||||
{:align-items :flex-start})
|
||||
|
||||
(def profile-input-container
|
||||
{:flex-direction :row
|
||||
:justify-content :center})
|
||||
@@ -0,0 +1,266 @@
|
||||
(ns status-im.contexts.profile.edit.modal.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[oops.core :as oops]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.hooks :as hooks]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.common.avatar-picture-picker.view :as profile-picture-picker]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.validation.profile :as profile-validator]
|
||||
[status-im.constants :as c]
|
||||
[status-im.contexts.profile.edit.modal.events]
|
||||
[status-im.contexts.profile.edit.modal.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.responsiveness :as responsiveness]))
|
||||
|
||||
(def scroll-view-height (reagent/atom 0))
|
||||
(def content-container-height (reagent/atom 0))
|
||||
(def set-scroll-view-height #(reset! scroll-view-height %))
|
||||
|
||||
(defn set-content-container-height
|
||||
[e]
|
||||
(reset! content-container-height (oops/oget e "nativeEvent.layout.height")))
|
||||
|
||||
(defn show-button-background?
|
||||
[keyboard-height keyboard-shown content-scroll-y]
|
||||
(let [button-container-height 64
|
||||
keyboard-view-height (+ keyboard-height button-container-height)]
|
||||
(when keyboard-shown
|
||||
(cond
|
||||
platform/android?
|
||||
(< (- @scroll-view-height button-container-height) @content-container-height)
|
||||
|
||||
platform/ios?
|
||||
(< (- @scroll-view-height keyboard-view-height) (- @content-container-height content-scroll-y))
|
||||
|
||||
:else
|
||||
false))))
|
||||
|
||||
(defn button-container
|
||||
[show-keyboard? keyboard-shown show-background? keyboard-height children]
|
||||
(let [height (reagent/atom 0)]
|
||||
(reset! height (if show-keyboard? (if keyboard-shown keyboard-height 0) 0))
|
||||
[rn/view {:style {:margin-top :auto}}
|
||||
(cond
|
||||
(and (> @height 0) show-background?)
|
||||
[quo/blur
|
||||
(when keyboard-shown
|
||||
{:blur-amount 34
|
||||
:blur-type :transparent
|
||||
:overlay-color :transparent
|
||||
:background-color (if platform/android?
|
||||
colors/neutral-100
|
||||
colors/neutral-80-opa-1-blur)
|
||||
:style style/blur-button-container})
|
||||
children]
|
||||
|
||||
(and (> @height 0) (not show-background?))
|
||||
[rn/view {:style (style/view-button-container true)}
|
||||
children]
|
||||
|
||||
(not show-keyboard?)
|
||||
[rn/view {:style (style/view-button-container false)}
|
||||
children])]))
|
||||
|
||||
(defn- add-keyboard-listener
|
||||
[e callback]
|
||||
(oops/ocall rn/keyboard "addListener" e callback))
|
||||
|
||||
(defn- on-keyboard-show
|
||||
[f]
|
||||
(add-keyboard-listener (if platform/android? "keyboardDidShow" "keyboardWillShow") f))
|
||||
|
||||
(defn- on-keyboard-hide
|
||||
[f]
|
||||
(add-keyboard-listener (if platform/android? "keyboardDidHide" "keyboardWillHide") f))
|
||||
|
||||
(defn floating-button
|
||||
[{:keys [custom-color scroll-y on-submit disabled?]}]
|
||||
(reagent/with-let [show-keyboard? (reagent/atom false)
|
||||
show-listener (on-keyboard-show #(reset! show-keyboard? true))
|
||||
hide-listener (on-keyboard-hide #(reset! show-keyboard? false))]
|
||||
(let [{:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard)
|
||||
show-background? (show-button-background? keyboard-height
|
||||
keyboard-shown
|
||||
scroll-y)]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style {:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0}
|
||||
:pointer-events :box-none}
|
||||
[button-container @show-keyboard? keyboard-shown show-background? keyboard-height
|
||||
[quo/button
|
||||
{:accessibility-label :submit-create-profile-button
|
||||
:type :primary
|
||||
:customization-color custom-color
|
||||
:on-press on-submit
|
||||
:container-style style/continue-button
|
||||
:disabled? disabled?}
|
||||
(i18n/label :t/continue)]]])
|
||||
(finally
|
||||
(oops/ocall show-listener "remove")
|
||||
(oops/ocall hide-listener "remove"))))
|
||||
|
||||
(defn- content
|
||||
[{:keys [set-display-name set-validation-msg picture-picker]}]
|
||||
(let [open-picture-picker (fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content picture-picker
|
||||
:shell? true
|
||||
:theme :dark}]))
|
||||
on-change-text (fn [s]
|
||||
(set-validation-msg s)
|
||||
(set-display-name s))]
|
||||
(fn [{:keys [profile-image custom-color display-name validation-msg valid-name?
|
||||
name-too-short? initial-display-name set-scroll set-scroll-height
|
||||
set-custom-color]}]
|
||||
(let [{window-width :width} (rn/get-window)
|
||||
info-type (cond
|
||||
validation-msg :error
|
||||
name-too-short? :default
|
||||
:else :success)
|
||||
info-message (or validation-msg
|
||||
(i18n/label :t/minimum-characters
|
||||
{:min-chars profile-validator/min-length}))
|
||||
full-name (if (seq display-name)
|
||||
display-name
|
||||
initial-display-name)]
|
||||
[rn/scroll-view
|
||||
{:on-layout set-scroll-height
|
||||
:on-scroll set-scroll
|
||||
:scroll-event-throttle 64
|
||||
:content-container-style {:flex-grow 1}}
|
||||
[rn/view {:on-layout set-content-container-height}
|
||||
[rn/view {:style style/content-container}
|
||||
[quo/text
|
||||
{:style style/title
|
||||
:size :heading-1
|
||||
:weight :semi-bold}
|
||||
(i18n/label :t/edit-profile)]
|
||||
[rn/view {:style style/input-container}
|
||||
[rn/view {:style style/profile-input-container}
|
||||
[quo/profile-input
|
||||
{:customization-color custom-color
|
||||
:placeholder initial-display-name
|
||||
:on-press open-picture-picker
|
||||
:image-picker-props {:profile-picture profile-image
|
||||
:full-name full-name
|
||||
:customization-color custom-color}
|
||||
:title-input-props {:default-value ""
|
||||
:auto-focus true
|
||||
:max-length c/profile-name-max-length
|
||||
:on-change-text on-change-text}}]]
|
||||
[quo/info-message
|
||||
{:status info-type
|
||||
:size :default
|
||||
:icon (if valid-name? :i/positive-state :i/info)
|
||||
:color (when (= :default info-type) colors/white-70-blur)
|
||||
:container-style style/info-message}
|
||||
info-message]
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style style/color-title}
|
||||
(i18n/label :t/colour)]]]
|
||||
[quo/color-picker
|
||||
{:blur? true
|
||||
:default-selected custom-color
|
||||
:on-change set-custom-color
|
||||
:window-width window-width
|
||||
:container-style {:padding-left (responsiveness/iphone-11-Pro-20-pixel-from-width
|
||||
window-width)}}]]]))))
|
||||
|
||||
(defn- submittable-state?
|
||||
[{:keys [new-color? new-picture? new-name? valid-name?]}]
|
||||
(let [acceptable-name? (or (not new-name?) valid-name?)]
|
||||
(or (and new-color? acceptable-name?)
|
||||
(and new-picture? acceptable-name?)
|
||||
(and new-name? valid-name?))))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [pending-event]} (rf/sub [:get-screen-params])
|
||||
{initial-display-name :display-name
|
||||
initial-color :customization-color
|
||||
[initial-image] :images} (rf/sub [:profile/profile])
|
||||
top (safe-area/get-top)
|
||||
scroll-y (reagent/atom 0)
|
||||
display-name (reagent/atom "")
|
||||
validation-msg (reagent/atom (profile-validator/validation-name @display-name))
|
||||
custom-color (reagent/atom (or initial-color c/profile-default-color))
|
||||
profile-image (reagent/atom initial-image)
|
||||
set-display-name #(reset! validation-msg (profile-validator/validation-name %))
|
||||
set-validation-msg #(reset! display-name (string/trim %))
|
||||
set-custom-color #(reset! custom-color %)
|
||||
set-scroll (fn [e]
|
||||
(reset! scroll-y (oops/oget e "nativeEvent.contentOffset.y")))
|
||||
set-scroll-height (fn [e]
|
||||
(reset! scroll-y 0)
|
||||
(-> e
|
||||
(oops/oget "nativeEvent.layout.height")
|
||||
set-scroll-view-height))
|
||||
picture-picker (fn []
|
||||
[profile-picture-picker/view
|
||||
{:on-result #(reset! profile-image %)
|
||||
:has-picture? (some? @profile-image)}])
|
||||
update-profile (fn []
|
||||
(rf/dispatch
|
||||
[:profile/edit-profile
|
||||
(cond-> {:on-success pending-event}
|
||||
(and (seq @display-name)
|
||||
(not= initial-display-name @display-name))
|
||||
(assoc :display-name @display-name)
|
||||
|
||||
(and (not= initial-image @profile-image))
|
||||
(assoc :picture @profile-image)
|
||||
|
||||
(not= initial-color @custom-color)
|
||||
(assoc :color @custom-color))]))
|
||||
on-close (fn []
|
||||
(events-helper/navigate-back)
|
||||
(rf/dispatch pending-event))]
|
||||
(fn []
|
||||
(let [name-too-short? (profile-validator/name-too-short? @display-name)
|
||||
valid-name? (and (not @validation-msg) (not name-too-short?))
|
||||
disabled? (not
|
||||
(submittable-state?
|
||||
{:new-color? (not= initial-color @custom-color)
|
||||
:new-picture? (not= @profile-image initial-image)
|
||||
:new-name? (and (not= initial-display-name @display-name)
|
||||
(seq @display-name))
|
||||
:valid-name? valid-name?}))]
|
||||
[quo/overlay {:type :shell}
|
||||
[rn/view {:style style/page-container}
|
||||
[quo/page-nav
|
||||
{:margin-top top
|
||||
:background :blur
|
||||
:icon-name :i/close
|
||||
:on-press on-close}]
|
||||
[content
|
||||
{:profile-image @profile-image
|
||||
:custom-color @custom-color
|
||||
:display-name @display-name
|
||||
:validation-msg @validation-msg
|
||||
:valid-name? valid-name?
|
||||
:name-too-short? name-too-short?
|
||||
:picture-picker picture-picker
|
||||
:initial-display-name initial-display-name
|
||||
:set-scroll set-scroll
|
||||
:set-scroll-height set-scroll-height
|
||||
:set-custom-color set-custom-color
|
||||
:set-display-name set-display-name
|
||||
:set-validation-msg set-validation-msg}]
|
||||
[floating-button
|
||||
{:custom-color @custom-color
|
||||
:scroll-y @scroll-y
|
||||
:on-submit update-profile
|
||||
:disabled? disabled?}]]]))))
|
||||
@@ -5,21 +5,26 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx :profile/edit-profile-name-success
|
||||
(fn [_]
|
||||
{:fx [[:dispatch [:navigate-back]]
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/name-updated)}]]]}))
|
||||
(fn [{:keys [db]} [display-name navigate-back? show-toast?]]
|
||||
{:db (assoc-in db [:profile/profile :display-name] display-name)
|
||||
:fx [(when navigate-back?
|
||||
[:dispatch [:navigate-back]])
|
||||
(when show-toast?
|
||||
[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text (i18n/label :t/name-updated)}]])]}))
|
||||
|
||||
(defn edit-profile-name
|
||||
[{:keys [db]} [name]]
|
||||
{:db (assoc-in db [:profile/profile :display-name] name)
|
||||
:fx [[:json-rpc/call
|
||||
[_
|
||||
[{:keys [display-name navigate-back? show-toast?]
|
||||
:or {navigate-back? true
|
||||
show-toast? true}}]]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_setDisplayName"
|
||||
:params [name]
|
||||
:on-success [:profile/edit-profile-name-success]}]]]})
|
||||
:params [display-name]
|
||||
:on-success [:profile/edit-profile-name-success display-name navigate-back? show-toast?]}]]]})
|
||||
|
||||
(rf/reg-event-fx :profile/edit-name edit-profile-name)
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
(deftest edit-name-test
|
||||
(let [new-name "John Doe"
|
||||
cofx {:db {:profile/profile {:display-name "Old name"}}}
|
||||
expected {:db {:profile/profile {:display-name new-name}}
|
||||
:fx [[:json-rpc/call
|
||||
expected {:fx [[:json-rpc/call
|
||||
[{:method "wakuext_setDisplayName"
|
||||
:params [name]
|
||||
:on-success [:profile/edit-profile-name-success]}]]]}]
|
||||
:on-success [:profile/edit-profile-name-success new-name true true]}]]]}]
|
||||
(is (match? expected
|
||||
(sut/edit-profile-name cofx [new-name])))))
|
||||
(sut/edit-profile-name cofx [{:display-name new-name}])))))
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
{:type :primary
|
||||
:customization-color customization-color
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:profile/edit-name @full-name]))
|
||||
(rf/dispatch [:profile/edit-name {:display-name @full-name}])
|
||||
(rf/dispatch [:profile/set-profile-update-as-asked]))
|
||||
:disabled? (boolean (or @typing?
|
||||
(string/blank? @full-name)
|
||||
(not (string/blank? @error-msg))))}
|
||||
|
||||
@@ -53,3 +53,4 @@
|
||||
|
||||
(def login-profile-card
|
||||
{:margin-bottom 20})
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
:z-index (get shared-values
|
||||
(get shell.constants/stacks-z-index-keywords stack-id))})}
|
||||
(case stack-id
|
||||
:communities-stack [:f> communities/view]
|
||||
:chats-stack [:f> chat/view]
|
||||
:communities-stack [communities/view]
|
||||
:chats-stack [chat/view]
|
||||
:wallet-stack [wallet/view]
|
||||
:browser-stack [browser.stack/browser-stack]
|
||||
[:<>])])
|
||||
@@ -38,7 +38,7 @@
|
||||
(defn lazy-screen
|
||||
[stack-id shared-values]
|
||||
(when (load-stack? stack-id)
|
||||
[:f> f-stack-view stack-id shared-values]))
|
||||
[f-stack-view stack-id shared-values]))
|
||||
|
||||
(defn view
|
||||
[shared-values]
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
[status-im.contexts.profile.config :as profile.config]
|
||||
[status-im.contexts.syncing.utils :as sync-utils]
|
||||
[taoensso.timbre :as log]
|
||||
[oops.core :as oops]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
@@ -68,48 +67,19 @@
|
||||
|
||||
(re-frame/reg-event-fx :syncing/clear-syncing-fallback-flow clear-syncing-fallback-flow)
|
||||
|
||||
#_(rf/defn preflight-outbound-check-for-local-pairing
|
||||
(rf/defn preflight-outbound-check-for-local-pairing
|
||||
{:events [:syncing/preflight-outbound-check]}
|
||||
[_ set-checks-passed]
|
||||
(let [callback (fn [raw-response]
|
||||
(log/info "Local pairing preflight check"
|
||||
{:response raw-response
|
||||
:event :syncing/preflight-outbound-check})
|
||||
(let [^js response-js (transforms/json->js raw-response)
|
||||
error (transforms/js->clj (.-error response-js))]
|
||||
(set-checks-passed (empty? error))))]
|
||||
(let [callback
|
||||
(fn [raw-response]
|
||||
(log/info "Local pairing preflight check"
|
||||
{:response raw-response
|
||||
:event :syncing/preflight-outbound-check})
|
||||
(let [^js response-js (transforms/json->js raw-response)
|
||||
error (transforms/js->clj (.-error response-js))]
|
||||
(set-checks-passed (empty? error))))]
|
||||
(native-module/local-pairing-preflight-outbound-check callback)))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects/ios-local-pairing-preflight-outbound-check
|
||||
native-module/local-pairing-preflight-outbound-check)
|
||||
|
||||
(defn- outbound-check [set-permission-granted raw-response]
|
||||
(log/debug "Local pairing preflight check"
|
||||
{:response raw-response
|
||||
:event :syncing/preflight-outbound-check})
|
||||
(def --x raw-response)
|
||||
(let [error? (-> raw-response transforms/json->js (oops/oget :error) seq)]
|
||||
(if error?
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:type :negative
|
||||
:theme :dark
|
||||
:text "Failed to verify local network access"}])
|
||||
(set-permission-granted true))
|
||||
(rf/dispatch [:syncing/set-preflight-outbound-checked])))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:syncing/preflight-outbound-check
|
||||
(fn [{db :db} [set-permission-granted]]
|
||||
{:db (assoc db :syncing/checking-preflight-outbound? true)
|
||||
:fx [[:effects/ios-local-pairing-preflight-outbound-check
|
||||
(partial outbound-check set-permission-granted)]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:syncing/set-preflight-outbound-checked
|
||||
(fn [{db :db}]
|
||||
{:db (dissoc db :syncing/checking-preflight-outbound?)}))
|
||||
|
||||
(rf/defn initiate-local-pairing-with-connection-string
|
||||
{:events [:syncing/input-connection-string-for-bootstrapping]}
|
||||
[_ connection-string]
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
|
||||
(defn- scan-qr-code-tab
|
||||
[qr-view-finder]
|
||||
(if (and ;@preflight-check-passed?
|
||||
(if (and @preflight-check-passed?
|
||||
@camera-permission-granted?
|
||||
(boolean (not-empty qr-view-finder)))
|
||||
[viewfinder qr-view-finder]
|
||||
@@ -276,7 +276,7 @@
|
||||
app-state-listener (.addEventListener rn/app-state "change" set-torch-off-fn)]
|
||||
#(.remove app-state-listener)))
|
||||
|
||||
(defn view
|
||||
(defn f-view
|
||||
[_]
|
||||
(let [insets (safe-area/get-insets)
|
||||
qr-code-succeed? (reagent/atom false)
|
||||
@@ -358,7 +358,7 @@
|
||||
(set-rescan-timeout))
|
||||
:set-rescan-timeout set-rescan-timeout}])
|
||||
[rn/view {:style (style/root-container (:top insets))}
|
||||
[header
|
||||
[:f> header
|
||||
{:active-tab active-tab
|
||||
:title title
|
||||
:title-opacity title-opacity
|
||||
@@ -392,3 +392,7 @@
|
||||
:container-style (style/camera-flash-button @qr-view-finder)
|
||||
:on-press #(swap! torch? not)}
|
||||
flashlight-icon])]]))))
|
||||
|
||||
(defn view
|
||||
[props]
|
||||
[:f> f-view props])
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
type :no-title}}]
|
||||
(let [{:keys [color emoji watch-only?]} (rf/sub [:wallet/current-viewing-account])
|
||||
networks (rf/sub [:wallet/selected-network-details])
|
||||
sending-collectible? (rf/sub [:wallet/sending-collectible?])
|
||||
keycard? (rf/sub [:wallet/selected-keypair-keycard?])]
|
||||
sending-collectible? (rf/sub [:wallet/sending-collectible?])]
|
||||
[quo/page-nav
|
||||
{:type type
|
||||
:icon-name icon-name
|
||||
@@ -46,11 +45,7 @@
|
||||
(not watch-only?)
|
||||
show-dapps-button?)
|
||||
{:icon-name :i/dapps
|
||||
:on-press #(rf/dispatch
|
||||
(if keycard?
|
||||
[:keycard/feature-unavailable-show
|
||||
{:feature-name :wallet.show-connected-dapps}]
|
||||
[:navigate-to :screen/wallet.connected-dapps]))})
|
||||
:on-press #(rf/dispatch [:navigate-to :screen/wallet.connected-dapps])})
|
||||
(when-not sending-collectible?
|
||||
{:content-type :account-switcher
|
||||
:customization-color color
|
||||
|
||||
@@ -123,3 +123,10 @@
|
||||
(-> (sign-transaction-hashes hashes address password)
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet/sign-message
|
||||
(fn [{:keys [message address password on-success on-error]}]
|
||||
(-> (wallet-rpc/sign-message message address (security/safe-unmask-data password))
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
chain-id
|
||||
constants/transaction-pending-type-wallet-connect-transfer
|
||||
(transforms/js-stringify tx-args 0)
|
||||
signature))
|
||||
(utils.hex/normalize-hex signature)))
|
||||
|
||||
(defn sign-message
|
||||
[message address password]
|
||||
|
||||
@@ -202,18 +202,17 @@
|
||||
:network-image (:source network)}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle (if (and estimated-time approval-fees-formatted)
|
||||
:subtitle (if approval-fees-formatted
|
||||
approval-fees-formatted
|
||||
(i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (if estimated-time
|
||||
(i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
(i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}]]]))
|
||||
(when estimated-time
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}])]]))
|
||||
|
||||
(defn- slide-button
|
||||
[]
|
||||
|
||||
@@ -148,15 +148,14 @@
|
||||
max-slippage (rf/sub [:wallet/swap-max-slippage])]
|
||||
[rn/view {:style style/details-container}
|
||||
[:<>
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (if estimated-time
|
||||
(i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
(i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?}]
|
||||
(when estimated-time
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
:loading? loading-swap-proposal?}])
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle (if (and estimated-time max-fees-formatted) max-fees-formatted (i18n/label :t/unknown))
|
||||
:subtitle (if max-fees-formatted max-fees-formatted (i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-slippage)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.effects
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.rpc :as wallet-rpc]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.sessions :as sessions]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/init
|
||||
@@ -69,20 +69,21 @@
|
||||
(promesa/catch (partial rf/call-continuation on-fail)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/sign-message
|
||||
(fn [{:keys [password address data rpc-method on-success on-error]}]
|
||||
(let [password (security/safe-unmask-data password)]
|
||||
(-> (condp =
|
||||
rpc-method
|
||||
:personal-sign
|
||||
(signing/personal-sign password address data)
|
||||
:effects.wallet-connect/hash-message
|
||||
(fn [{:keys [message on-success on-fail]}]
|
||||
(-> (wallet-rpc/hash-message-eip-191 message)
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-fail)))))
|
||||
|
||||
:eth-sign
|
||||
(signing/eth-sign password address data)
|
||||
|
||||
(signing/personal-sign password address data))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error))))))
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/hash-typed-data
|
||||
(fn [{:keys [message legacy? on-success on-fail]}]
|
||||
(-> (if legacy?
|
||||
(native-module/hash-typed-data message)
|
||||
(native-module/hash-typed-data-v4 message))
|
||||
(promesa/then (fn [response] (-> response transforms/json->clj :result)))
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-fail)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/prepare-transaction
|
||||
@@ -93,36 +94,12 @@
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-error))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/sign-transaction
|
||||
(fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}]
|
||||
(-> (transactions/sign-transaction (security/safe-unmask-data password)
|
||||
address
|
||||
tx-hash
|
||||
tx-args
|
||||
chain-id)
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/send-transaction
|
||||
(fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}]
|
||||
(-> (transactions/send-transaction (security/safe-unmask-data password)
|
||||
address
|
||||
tx-hash
|
||||
tx-args
|
||||
chain-id)
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet-connect/sign-typed-data
|
||||
(fn [{:keys [password address data version chain-id on-success on-error]}]
|
||||
(-> (typed-data/sign (security/safe-unmask-data password)
|
||||
address
|
||||
data
|
||||
chain-id
|
||||
version)
|
||||
(fn [{:keys [chain-id signature tx-args on-success on-error]}]
|
||||
(-> (wallet-rpc/send-transaction-with-signature chain-id
|
||||
tx-args
|
||||
signature)
|
||||
(promesa/then (partial rf/call-continuation on-success))
|
||||
(promesa/catch (partial rf/call-continuation on-error)))))
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.contexts.wallet.data-store :as wallet-data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
||||
@@ -46,15 +45,8 @@
|
||||
expired? (-> parsed-uri
|
||||
:expiryTimestamp
|
||||
uri/timestamp-expired?)
|
||||
version-supported? (uri/version-supported? version)
|
||||
keycard? (wallet-data-store/selected-keypair-keycard? db)]
|
||||
version-supported? (uri/version-supported? version)]
|
||||
(cond
|
||||
|
||||
keycard?
|
||||
{:fx [[:dispatch
|
||||
[:keycard/feature-unavailable-show
|
||||
{:feature-name :wallet.scan-dapp-connection}]]]}
|
||||
|
||||
(or (not valid-wc-uri?)
|
||||
(not version-supported?)
|
||||
(= network-status :offline)
|
||||
|
||||
@@ -41,12 +41,6 @@
|
||||
[:wallet-connect/process-eth-send-transaction
|
||||
{:on-success (fn [] (rf/dispatch [:wallet-connect/show-request-modal]))}]]
|
||||
|
||||
constants/wallet-connect-eth-sign-method
|
||||
[:dispatch [:wallet-connect/process-eth-sign]]
|
||||
|
||||
constants/wallet-connect-eth-sign-transaction-method
|
||||
[:dispatch [:wallet-connect/process-eth-sign-transaction]]
|
||||
|
||||
constants/wallet-connect-eth-sign-typed-method
|
||||
[:dispatch [:wallet-connect/process-sign-typed]]
|
||||
|
||||
@@ -56,6 +50,13 @@
|
||||
constants/wallet-connect-personal-sign-method
|
||||
[:dispatch [:wallet-connect/process-personal-sign]])]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/store-prepared-hash
|
||||
(fn [{:keys [db]} [prepared-hash]]
|
||||
{:db (assoc-in db
|
||||
[:wallet-connect/current-request :prepared-hash]
|
||||
prepared-hash)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-personal-sign
|
||||
(fn [{:keys [db]}]
|
||||
@@ -65,30 +66,20 @@
|
||||
[:wallet-connect/current-request]
|
||||
assoc
|
||||
:address (string/lower-case address)
|
||||
:raw-data raw-data
|
||||
:display-data (or parsed-data raw-data))
|
||||
:fx [[:dispatch [:wallet-connect/show-request-modal]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-sign
|
||||
(fn [{:keys [db]}]
|
||||
(let [[address raw-data] (data-store/get-db-current-request-params db)
|
||||
parsed-data (native-module/hex-to-utf8 raw-data)]
|
||||
{:db (update-in db
|
||||
[:wallet-connect/current-request]
|
||||
assoc
|
||||
:address (string/lower-case address)
|
||||
:raw-data raw-data
|
||||
:display-data (or parsed-data raw-data))
|
||||
:fx [[:dispatch [:wallet-connect/show-request-modal]]]})))
|
||||
:fx [[:effects.wallet-connect/hash-message
|
||||
{:message raw-data
|
||||
:on-success #(rf/dispatch [:wallet-connect/store-prepared-hash %])
|
||||
:on-fail #(rf/dispatch [:wallet-connect/on-processing-error %])}]
|
||||
[:dispatch [:wallet-connect/show-request-modal]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/prepare-transaction-success
|
||||
(fn [{:keys [db]} [prepared-tx chain-id]]
|
||||
(let [{:keys [tx-args]} prepared-tx
|
||||
tx (bean/->clj tx-args)
|
||||
address (-> tx :from string/lower-case)
|
||||
display-data (transactions/beautify-transaction tx)]
|
||||
(let [{:keys [tx-args tx-hash]} prepared-tx
|
||||
tx (bean/->clj tx-args)
|
||||
address (-> tx :from string/lower-case)
|
||||
display-data (transactions/beautify-transaction tx)]
|
||||
{:db (update-in db
|
||||
[:wallet-connect/current-request]
|
||||
assoc
|
||||
@@ -96,7 +87,8 @@
|
||||
:raw-data prepared-tx
|
||||
:transaction tx
|
||||
:chain-id chain-id
|
||||
:display-data display-data)})))
|
||||
:display-data display-data)
|
||||
:fx [[:dispatch [:wallet-connect/store-prepared-hash tx-hash]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-send-transaction
|
||||
@@ -116,25 +108,14 @@
|
||||
(rf/call-continuation on-success)))
|
||||
:on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-sign-transaction
|
||||
(fn [{:keys [db]}]
|
||||
(let [event (data-store/get-db-current-request-event db)
|
||||
tx (-> event data-store/get-request-params first)
|
||||
chain-id (-> event
|
||||
(get-in [:params :chainId])
|
||||
networks/eip155->chain-id)]
|
||||
{:fx [[:effects.wallet-connect/prepare-transaction
|
||||
{:tx tx
|
||||
:chain-id chain-id
|
||||
:on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id])
|
||||
:on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-sign-typed
|
||||
(fn [{:keys [db]}]
|
||||
(try
|
||||
(let [[address raw-data] (data-store/get-db-current-request-params db)
|
||||
method (-> db
|
||||
data-store/get-db-current-request-event
|
||||
data-store/get-request-method)
|
||||
session-chain-id (-> (data-store/get-db-current-request-event db)
|
||||
(get-in [:params :chainId])
|
||||
networks/eip155->chain-id)
|
||||
@@ -154,7 +135,13 @@
|
||||
:address (string/lower-case address)
|
||||
:display-data (typed-data/flatten-typed-data typed-data)
|
||||
:raw-data raw-data)
|
||||
:fx [[:dispatch [:wallet-connect/show-request-modal]]]}))
|
||||
:fx [[:effects.wallet-connect/hash-typed-data
|
||||
{:message raw-data
|
||||
:legacy? (not= constants/wallet-connect-eth-sign-typed-v4-method
|
||||
method)
|
||||
:on-success #(rf/dispatch [:wallet-connect/store-prepared-hash %])
|
||||
:on-fail #(rf/dispatch [:wallet-connect/on-processing-error %])}]
|
||||
[:dispatch [:wallet-connect/show-request-modal]]]}))
|
||||
(catch js/Error err
|
||||
{:fx [[:dispatch
|
||||
[:wallet-connect/on-processing-error
|
||||
|
||||
@@ -2,16 +2,47 @@
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.keycard.utils :as keycard]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.uri :as uri]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.hex :as hex]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-current-session
|
||||
:wallet-connect/authorized-signing
|
||||
(fn [{:keys [db]} [password]]
|
||||
(let [prepared-hash (get-in db [:wallet-connect/current-request :prepared-hash])
|
||||
address (get-in db [:wallet-connect/current-request :address])
|
||||
keycard-sign? (-> (get-in db [:wallet :keypairs])
|
||||
(keycard/keycard-address? address))
|
||||
on-success #(rf/dispatch [:wallet-connect/respond (hex/prefix-hex %)])
|
||||
on-fail #(rf/dispatch [:wallet-connect/on-sign-error %])]
|
||||
(if keycard-sign?
|
||||
{:fx [[:dispatch
|
||||
[:standard-auth/authorize-with-keycard
|
||||
{:on-complete (fn [pin]
|
||||
(rf/dispatch [:keycard/connect-and-sign-hashes
|
||||
{:keycard-pin pin
|
||||
:address address
|
||||
:hashes [prepared-hash]
|
||||
:on-success (fn [signatures]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(on-success (:signature (first
|
||||
signatures))))
|
||||
:on-failure on-fail}]))}]]]}
|
||||
{:fx [[:effects.wallet/sign-message
|
||||
{:message prepared-hash
|
||||
:address address
|
||||
:password password
|
||||
:on-success on-success
|
||||
:on-error on-fail}]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond
|
||||
(fn [{:keys [db]} [signature]]
|
||||
(let [event (get-in db [:wallet-connect/current-request :event])
|
||||
method (data-store/get-request-method event)
|
||||
screen (data-store/method-to-screen method)
|
||||
@@ -19,83 +50,30 @@
|
||||
(if (uri/timestamp-expired? expiry)
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:id :new-wallet-account-created
|
||||
:type :negative
|
||||
{:type :negative
|
||||
:text (i18n/label :t/wallet-connect-request-expired)}]]
|
||||
[:dispatch [:dismiss-modal screen]]]}
|
||||
{:fx [(condp = method
|
||||
constants/wallet-connect-personal-sign-method
|
||||
[:dispatch [:wallet-connect/respond-sign-message password :personal-sign]]
|
||||
{:fx [(condp contains? method
|
||||
#{constants/wallet-connect-personal-sign-method
|
||||
constants/wallet-connect-eth-sign-typed-method
|
||||
constants/wallet-connect-eth-sign-typed-v4-method}
|
||||
[:dispatch [:wallet-connect/finish-session-request signature]]
|
||||
|
||||
constants/wallet-connect-eth-sign-method
|
||||
[:dispatch [:wallet-connect/respond-sign-message password :eth-sign]]
|
||||
|
||||
constants/wallet-connect-eth-send-transaction-method
|
||||
[:dispatch [:wallet-connect/respond-send-transaction-data password]]
|
||||
|
||||
constants/wallet-connect-eth-sign-transaction-method
|
||||
[:dispatch [:wallet-connect/respond-sign-transaction-data password]]
|
||||
|
||||
constants/wallet-connect-eth-sign-typed-method
|
||||
[:dispatch [:wallet-connect/respond-sign-typed-data password :v1]]
|
||||
|
||||
constants/wallet-connect-eth-sign-typed-v4-method
|
||||
[:dispatch [:wallet-connect/respond-sign-typed-data password :v4]])]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-sign-message
|
||||
(fn [{:keys [db]} [password rpc-method]]
|
||||
(let [{:keys [address raw-data]} (get db :wallet-connect/current-request)]
|
||||
{:fx [[:effects.wallet-connect/sign-message
|
||||
{:password password
|
||||
:address address
|
||||
:data raw-data
|
||||
:rpc-method rpc-method
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-sign-typed-data
|
||||
(fn [{:keys [db]} [password typed-data-version]]
|
||||
(let [{:keys [address raw-data event]} (get db :wallet-connect/current-request)
|
||||
chain-id (get-in event [:params :chainId])]
|
||||
{:fx [[:effects.wallet-connect/sign-typed-data
|
||||
{:password password
|
||||
:address address
|
||||
:data raw-data
|
||||
:chain-id chain-id
|
||||
:version typed-data-version
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
#{constants/wallet-connect-eth-send-transaction-method}
|
||||
[:dispatch [:wallet-connect/respond-send-transaction-data signature]])]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-send-transaction-data
|
||||
(fn [{:keys [db]} [password]]
|
||||
(let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)
|
||||
{:keys [tx-hash tx-args]} raw-data]
|
||||
(fn [{:keys [db]} [signature]]
|
||||
(let [{:keys [chain-id raw-data]} (get db :wallet-connect/current-request)
|
||||
{:keys [tx-args]} raw-data]
|
||||
{:fx [[:effects.wallet-connect/send-transaction
|
||||
{:password password
|
||||
:address address
|
||||
{:signature signature
|
||||
:chain-id chain-id
|
||||
:tx-hash tx-hash
|
||||
:tx-args tx-args
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/respond-sign-transaction-data
|
||||
(fn [{:keys [db]} [password]]
|
||||
(let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)
|
||||
{:keys [tx-hash tx-args]} raw-data]
|
||||
{:fx [[:effects.wallet-connect/sign-transaction
|
||||
{:password password
|
||||
:address address
|
||||
:chain-id chain-id
|
||||
:tx-hash tx-hash
|
||||
:tx-params tx-args
|
||||
:on-error [:wallet-connect/on-sign-error]
|
||||
:on-success [:wallet-connect/finish-session-request]}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-sign-error
|
||||
(fn [{:keys [db]} [error]]
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
(defn- on-auth-success
|
||||
[password]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:wallet-connect/respond-current-session password]))
|
||||
(rf/dispatch [:wallet-connect/authorized-signing password]))
|
||||
|
||||
(defn view
|
||||
[{:keys [warning-label slide-button-text error-state]} & children]
|
||||
(let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])
|
||||
offline? (rf/sub [:network/offline?])
|
||||
theme (quo.theme/use-theme)]
|
||||
theme (quo.theme/use-theme)
|
||||
sign-on-keycard? (rf/sub [:wallet-connect/sign-on-keycard?])]
|
||||
[:<>
|
||||
(when (or offline? error-state)
|
||||
[quo/alert-banner
|
||||
@@ -48,6 +49,8 @@
|
||||
:disabled? (or offline? error-state)
|
||||
:customization-color customization-color
|
||||
:on-auth-success on-auth-success
|
||||
:on-complete (when sign-on-keycard?
|
||||
#(on-auth-success ""))
|
||||
:auth-button-label (i18n/label :t/confirm)}]]
|
||||
[rn/view {:style style/warning-container}
|
||||
[quo/text
|
||||
|
||||
@@ -30,10 +30,8 @@
|
||||
(def method-to-screen
|
||||
{constants/wallet-connect-personal-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-v4-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction
|
||||
constants/wallet-connect-eth-sign-transaction-method :screen/wallet-connect.sign-transaction})
|
||||
constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction})
|
||||
|
||||
(defn extract-native-call-signature
|
||||
[data]
|
||||
@@ -59,11 +57,11 @@
|
||||
|
||||
(defn get-current-request-dapp
|
||||
[request sessions]
|
||||
(let [dapp-url (get-in request [:event :verifyContext :verified :origin])]
|
||||
(let [request-topic (get-in request [:event :topic])]
|
||||
(->> sessions
|
||||
(filter (fn [session]
|
||||
(= (utils.string/remove-trailing-slash dapp-url)
|
||||
(utils.string/remove-trailing-slash (get session :url)))))
|
||||
(= (:topic session)
|
||||
request-topic)))
|
||||
first)))
|
||||
|
||||
(defn get-dapp-redirect-url
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
|
||||
(deftest get-current-request-dapp-test
|
||||
(testing "returns the correct dapp based on the request's origin"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://dapp.com"}
|
||||
{:url "https://anotherdapp.com"}]]
|
||||
(is (= {:url "https://dapp.com"}
|
||||
(let [request {:event {:topic "123"}}
|
||||
sessions [{:topic "123"}
|
||||
{:topic "456"}]]
|
||||
(is (= {:topic "123"}
|
||||
(sut/get-current-request-dapp request sessions)))))
|
||||
|
||||
(testing "returns nil if no matching dapp is found"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://anotherdapp.com"}]]
|
||||
(let [request {:event {:topic "123"}}
|
||||
sessions [{:topic "456"}]]
|
||||
(is (nil? (sut/get-current-request-dapp request sessions))))))
|
||||
|
||||
(deftest get-dapp-redirect-url-test
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.signing
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[status-im.contexts.wallet.rpc :as wallet-rpc]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[utils.hex :as hex]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn eth-sign
|
||||
[password address data]
|
||||
(-> {:data data
|
||||
:account address
|
||||
:password password}
|
||||
transforms/clj->json
|
||||
native-module/sign-message
|
||||
(promesa/then data-store/extract-native-call-signature)))
|
||||
|
||||
(defn personal-sign
|
||||
[password address data]
|
||||
(-> (wallet-rpc/hash-message-eip-191 data)
|
||||
(promesa/then #(wallet-rpc/sign-message % address password))
|
||||
(promesa/then hex/prefix-hex)))
|
||||
@@ -14,8 +14,7 @@
|
||||
(defn transaction-request?
|
||||
[event]
|
||||
(->> (data-store/get-request-method event)
|
||||
(contains? #{constants/wallet-connect-eth-send-transaction-method
|
||||
constants/wallet-connect-eth-sign-transaction-method})))
|
||||
(contains? constants/wallet-connect-transaction-methods)))
|
||||
|
||||
;; NOTE: Currently we don't allow the user to configure the tx priority as we don't
|
||||
;; show the estimated time, but when we implement it, we should allow to change it
|
||||
@@ -136,22 +135,6 @@
|
||||
:suggested-fees suggested-fees
|
||||
:estimated-time estimated-time}))
|
||||
|
||||
(defn sign-transaction
|
||||
[password address tx-hash tx-args chain-id]
|
||||
(promesa/let
|
||||
[signature (wallet-rpc/sign-message tx-hash address password)
|
||||
raw-tx (wallet-rpc/build-raw-transaction chain-id tx-args signature)]
|
||||
raw-tx))
|
||||
|
||||
(defn send-transaction
|
||||
[password address tx-hash tx-args chain-id]
|
||||
(promesa/let
|
||||
[signature (wallet-rpc/sign-message tx-hash address password)
|
||||
tx (wallet-rpc/send-transaction-with-signature chain-id
|
||||
tx-args
|
||||
signature)]
|
||||
tx))
|
||||
|
||||
(defn transactions->display-array
|
||||
[data]
|
||||
(remove (fn [[k v]]
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
[status-im.contexts.profile.contact.view :as contact-profile]
|
||||
[status-im.contexts.profile.edit.accent-colour.view :as edit-accent-colour]
|
||||
[status-im.contexts.profile.edit.bio.view :as edit-bio]
|
||||
[status-im.contexts.profile.edit.modal.view :as edit-profile-modal]
|
||||
[status-im.contexts.profile.edit.name.view :as edit-name]
|
||||
[status-im.contexts.profile.edit.view :as edit-profile]
|
||||
[status-im.contexts.profile.profiles.view :as profiles]
|
||||
@@ -1127,7 +1128,12 @@
|
||||
:options {:theme :dark
|
||||
:layout options/onboarding-layout}
|
||||
:on-focus [:onboarding/overlay-dismiss]
|
||||
:component profiles/view}]
|
||||
:component profiles/view}
|
||||
|
||||
{:name :screen/profile.edit-profile-modal
|
||||
:metrics {:track? true}
|
||||
:options options/transparent-screen-options
|
||||
:component edit-profile-modal/view}]
|
||||
|
||||
[{:name :shell
|
||||
:metrics {:track? true}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns status-im.subs.wallet.dapps.requests
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.contexts.keycard.utils :as keycard]
|
||||
[status-im.contexts.wallet.common.utils :as wallet-utils]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
@@ -59,3 +60,10 @@
|
||||
:wallet-connect/typed-data-request?
|
||||
:<- [:wallet-connect/current-request-method]
|
||||
typed-data/typed-data-request?)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/sign-on-keycard?
|
||||
:<- [:wallet/keypairs]
|
||||
:<- [:wallet-connect/current-request-address]
|
||||
(fn [[keypairs address]]
|
||||
(keycard/keycard-address? keypairs address)))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
(fn []
|
||||
(let [new-name "John Doe"]
|
||||
(promesa/do
|
||||
(rf/dispatch [:profile/edit-name new-name])
|
||||
(rf/dispatch [:profile/edit-name {:display-name new-name}])
|
||||
(h/wait-for [:navigate-back :toasts/upsert])
|
||||
(let [profile (rf/sub [:profile/profile])
|
||||
display-name (profile.utils/displayed-name profile)]
|
||||
@@ -26,7 +26,10 @@
|
||||
(let [mock-image "resources/images/mock2/monkey.png"
|
||||
absolute-path (.resolve test-utils/path mock-image)]
|
||||
(promesa/do
|
||||
(rf/dispatch [:profile/edit-picture absolute-path 80 80])
|
||||
(rf/dispatch [:profile/edit-picture
|
||||
{:picture absolute-path
|
||||
:crop-width 80
|
||||
:crop-height 80}])
|
||||
(h/wait-for [:profile/update-local-picture :toasts/upsert])
|
||||
(let [profile (rf/sub [:profile/profile])]
|
||||
(is (not (nil? (:images profile))))))))))
|
||||
|
||||
@@ -32,7 +32,8 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
for home in (self.home_1, self.home_2):
|
||||
home.navigate_back_to_home_view()
|
||||
home.chats_tab.click()
|
||||
self.home_1.add_contact(self.public_key_2)
|
||||
self.username_1 = "user_1"
|
||||
self.home_1.add_contact(public_key=self.public_key_2, username=self.username_1)
|
||||
|
||||
self.home_2.just_fyi("Accepting contact request from activity centre")
|
||||
self.home_2.handle_contact_request(self.username_1)
|
||||
@@ -243,7 +244,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
unpin_element.click_until_absense_of_element(desired_element=unpin_element)
|
||||
self.chat_1.pin_message(self.message_4, 'pin-to-chat')
|
||||
for chat in self.chat_1, self.chat_2:
|
||||
if chat.chat_element_by_text(self.message_4).pinned_by_label.is_element_displayed(30):
|
||||
if not chat.chat_element_by_text(self.message_4).pinned_by_label.is_element_displayed(30):
|
||||
self.errors.append(chat, "Message 4 is not pinned in chat after unpinning previous one")
|
||||
|
||||
self.home_1.just_fyi("Check pinned messages are visible in Pinned panel for both users")
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from _pytest.outcomes import Failed
|
||||
from selenium.common import NoSuchElementException
|
||||
from selenium.common import NoSuchElementException, TimeoutException
|
||||
|
||||
from base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers
|
||||
from support.api.network_api import NetworkApi
|
||||
@@ -20,6 +21,7 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
def prepare_devices(self):
|
||||
self.network_api = NetworkApi()
|
||||
print('1')
|
||||
self.drivers, self.loop = create_shared_drivers(2)
|
||||
self.sign_in_1, self.sign_in_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
|
||||
self.sender, self.receiver = transaction_senders['ETH_1'], transaction_senders['ETH_2']
|
||||
@@ -144,7 +146,7 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
device_time_before_sending = self.wallet_1.driver.device_time
|
||||
self.wallet_1.send_asset(address='arb1:' + self.receiver['wallet_address'],
|
||||
asset_name='Ether',
|
||||
amount=amount_to_send,
|
||||
amount=f"{amount_to_send:.4f}",
|
||||
network_name=self.network)
|
||||
# ToDo: Arbiscan API is down, looking for analogue
|
||||
# self.network_api.wait_for_confirmation_of_transaction(address=self.sender['wallet_address'],
|
||||
@@ -180,7 +182,7 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
device_time_before_sending = self.wallet_1.driver.device_time
|
||||
self.wallet_1.send_asset_from_drawer(address='arb1:' + self.receiver['wallet_address'],
|
||||
asset_name='Ether',
|
||||
amount=amount_to_send,
|
||||
amount=f"{amount_to_send:.4f}",
|
||||
network_name=self.network)
|
||||
# ToDo: Arbiscan API is down, looking for analogue
|
||||
# self.network_api.wait_for_confirmation_of_transaction(address=self.sender['wallet_address'],
|
||||
@@ -210,29 +212,28 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
|
||||
class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
def prepare_devices(self):
|
||||
self.network_api = NetworkApi()
|
||||
self.drivers, self.loop = create_shared_drivers(1)
|
||||
self.sign_in_view = SignInView(self.drivers[0])
|
||||
self.sender, self.receiver = transaction_senders['ETH_1'], transaction_senders['ETH_2']
|
||||
self.total_balance = {'Ether': 0.0052, 'USDCoin': 5.0, 'Status': 10.0, 'Uniswap': 0.627, 'Dai Stablecoin': 0.0}
|
||||
self.total_balance = {'Ether': 0.0262, 'USDCoin': 5.0, 'Status': 13.0, 'Uniswap': 0.627, 'Dai Stablecoin': 0.0}
|
||||
self.mainnet_balance = {'Ether': 0.005, 'USDCoin': 0.0, 'Status': 10.0, 'Uniswap': 0.127, 'Dai Stablecoin': 0.0}
|
||||
self.optimizm_balance = {'Ether': 0.0001, 'USDCoin': 5.0, 'Status': 0, 'Uniswap': 0, 'Dai Stablecoin': 0.0}
|
||||
self.arb_balance = {'Ether': 0.0001, 'USDCoin': 0.0, 'Status': 0.0, 'Uniswap': 0.5, 'Dai Stablecoin': 0.0}
|
||||
self.optimism_balance = {'Ether': 0.0011, 'USDCoin': 5.0, 'Status': 3.0, 'Uniswap': 0, 'Dai Stablecoin': 0.0}
|
||||
self.arb_balance = {'Ether': 0.0051, 'USDCoin': 0.0, 'Status': 0.0, 'Uniswap': 0.5, 'Dai Stablecoin': 0.0}
|
||||
self.base_balance = {'Ether': 0.015, 'USDCoin': 0.0, 'Status': 0.0, 'Uniswap': 0.0, 'Dai Stablecoin': 0.0}
|
||||
self.sender['wallet_address'] = '0x' + self.sender['address']
|
||||
self.receiver['wallet_address'] = '0x' + self.receiver['address']
|
||||
self.sign_in_view.recover_access(passphrase=self.sender['passphrase'])
|
||||
|
||||
self.home_view = self.sign_in_view.get_home_view()
|
||||
self.sender_username = self.home_view.get_username()
|
||||
self.wallet_view = self.home_view.wallet_tab.click()
|
||||
|
||||
@marks.testrail_id(740490)
|
||||
def test_wallet_balance_mainnet(self):
|
||||
self.profile_view = self.home_view.profile_button.click()
|
||||
self.profile_view.switch_network()
|
||||
self.sign_in_view.sign_in(user_name=self.sender_username)
|
||||
self.sign_in_view.wallet_tab.click()
|
||||
self.wallet_view = self.home_view.wallet_tab.click()
|
||||
self.account_name = 'Account 1'
|
||||
|
||||
@marks.testrail_id(740490)
|
||||
def test_wallet_balance_mainnet(self):
|
||||
self.wallet_view.just_fyi("Checking total balance")
|
||||
real_balance = {}
|
||||
for asset in self.total_balance:
|
||||
@@ -245,7 +246,8 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
expected_balances = {
|
||||
'Mainnet': self.mainnet_balance,
|
||||
'Arbitrum': self.arb_balance,
|
||||
'Optimism': self.optimizm_balance
|
||||
'Optimism': self.optimism_balance,
|
||||
'Base': self.base_balance
|
||||
}
|
||||
|
||||
for network in expected_balances:
|
||||
@@ -262,8 +264,290 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(741554)
|
||||
def test_wallet_send_flow_mainnet(self):
|
||||
self.wallet_view.get_account_element().click()
|
||||
self.wallet_view.send_button.click()
|
||||
self.wallet_view.address_text_input.send_keys(self.receiver['wallet_address'])
|
||||
self.wallet_view.continue_button.click()
|
||||
|
||||
asset_data = {
|
||||
'Ether': {'networks': ['Mainnet', 'Arbitrum'], 'amount': '0.00001'},
|
||||
'Status': {'networks': ['Mainnet', 'Optimism'], 'amount': '1'}
|
||||
}
|
||||
for asset, data in asset_data.items():
|
||||
for network in data['networks']:
|
||||
self.wallet_view.just_fyi("Checking the send flow for %s on %s" % (asset, network))
|
||||
self.wallet_view.select_asset(asset)
|
||||
self.wallet_view.select_network(network)
|
||||
self.wallet_view.set_amount(data['amount'])
|
||||
try:
|
||||
max_fees_text = self.wallet_view.get_data_item_element_text(data_item_name='Max fees')
|
||||
if not re.findall(r"[$]\d+.\d+", max_fees_text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s on %s: max fee is not a number - %s" % (asset, network, max_fees_text))
|
||||
except TimeoutException:
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s on %s: max fees is not shown before Review Send button is clicked" % (asset, network))
|
||||
|
||||
self.wallet_view.confirm_button.click()
|
||||
self.wallet_view.just_fyi("Checking Review Send page for %s on %s" % (asset, network))
|
||||
|
||||
expected_amount = "%s %s" % (data['amount'], 'ETH' if asset == 'Ether' else 'SNT')
|
||||
sender_short_address = self.sender['wallet_address'].replace(self.sender['wallet_address'][6:-3],
|
||||
'…').lower()
|
||||
receiver_short_address = self.receiver['wallet_address'].replace(self.receiver['wallet_address'][6:-3],
|
||||
'…').lower()
|
||||
for text in [self.account_name, sender_short_address, expected_amount]:
|
||||
if not self.wallet_view.from_data_container.get_child_element_by_text(text).is_element_displayed():
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s on %s: text %s is not shown in 'From' container on the Review Send page" % (
|
||||
asset, network, text))
|
||||
for text in [receiver_short_address, expected_amount]:
|
||||
if not self.wallet_view.to_data_container.get_child_element_by_text(text).is_element_displayed():
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s on %s: text %s is not shown in 'To' container on the Review Send page" % (
|
||||
asset, network, text))
|
||||
|
||||
data_to_check = {
|
||||
'Est. time': ' min',
|
||||
'Max fees': r"[$]\d+.\d+",
|
||||
'Recipient gets': expected_amount
|
||||
}
|
||||
for key, expected_value in data_to_check.items():
|
||||
try:
|
||||
text = self.wallet_view.get_data_item_element_text(data_item_name=key)
|
||||
if key == 'Max fees':
|
||||
if not re.findall(expected_value, text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s on %s: max fee is not a number - %s on the Review Send page" % (
|
||||
asset, network, text))
|
||||
else:
|
||||
if text != expected_value:
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s on %s: %s text %s doesn't match expected %s on the Review Send page" % (
|
||||
asset, network, key, text, expected_value))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s on %s: %s is not shown on the Review Send page" % (asset, network, key))
|
||||
|
||||
self.wallet_view.slide_button_track.slide()
|
||||
if not self.wallet_view.password_input.is_element_displayed():
|
||||
self.errors.append("%s on %s: can't confirm transaction" % (asset, network))
|
||||
self.wallet_view.click_system_back_button_until_presence_of_element(
|
||||
element=self.wallet_view.element_by_text('Select token'), attempts=4)
|
||||
self.wallet_view.click_system_back_button_until_presence_of_element(
|
||||
element=self.wallet_view.add_account_button, attempts=6)
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(741555)
|
||||
def test_wallet_swap_flow_mainnet(self):
|
||||
self.wallet_view.navigate_back_to_wallet_view()
|
||||
self.wallet_view.get_account_element().click()
|
||||
self.wallet_view.swap_button.click()
|
||||
for network in ['Mainnet', 'Optimism']:
|
||||
self.wallet_view.just_fyi("Checking the Swap flow for SNT on %s" % network)
|
||||
self.wallet_view.select_asset('Status')
|
||||
self.wallet_view.select_network(network)
|
||||
self.wallet_view.set_amount('1')
|
||||
data_to_check = {
|
||||
'Max fees': r"[$]\d+.\d+",
|
||||
'Max slippage': r"\d+.\d+[%]"
|
||||
}
|
||||
for key, expected_value in data_to_check.items():
|
||||
try:
|
||||
text = self.wallet_view.get_data_item_element_text(data_item_name=key)
|
||||
if not re.findall(expected_value, text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: %s is not a number - %s before pressing Review Swap button" % (
|
||||
network, key, text))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: %s is not shown before pressing Review Swap button" % (network, key))
|
||||
|
||||
try:
|
||||
self.wallet_view.wait_for_swap_input_to_be_shown()
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view, "%s: ETH amount is not displayed")
|
||||
continue
|
||||
self.wallet_view.approve_swap_button.click()
|
||||
self.wallet_view.just_fyi("Checking 'Set Spending Cap' screen for %s" % network)
|
||||
if not self.wallet_view.spending_cap_approval_info_container.get_child_element_by_text(
|
||||
'1 SNT').is_element_displayed():
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: Spending cap is not shown on the 'Set Spending Cap' screen" % network)
|
||||
for text in [self.account_name,
|
||||
self.sender['wallet_address'].replace(self.sender['wallet_address'][5:-3], '...').lower()]:
|
||||
if not self.wallet_view.account_approval_info_container.get_child_element_by_text(
|
||||
text).is_element_displayed():
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s: Text %s is not shown for the account on the 'Set Spending Cap' screen" % (network, text))
|
||||
if not self.wallet_view.token_approval_info_container.get_child_element_by_text(
|
||||
'SNT').is_element_displayed():
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: Token is not shown on the 'Set Spending Cap' screen" % network)
|
||||
if not self.wallet_view.spender_contract_approval_info_container.is_element_displayed():
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: Spender contract info is missing on the 'Set Spending Cap' screen" % network)
|
||||
|
||||
data_to_check = {
|
||||
'Network': network,
|
||||
# 'Max fees': r"[$]\d+.\d+", # ToDO: enable when https://github.com/status-im/status-mobile/issues/21948 is fixed
|
||||
# 'Est. time': ' min',
|
||||
}
|
||||
for key, expected_value in data_to_check.items():
|
||||
try:
|
||||
text = self.wallet_view.get_data_item_element_text(data_item_name=key)
|
||||
if key == 'Max fees':
|
||||
if not re.findall(expected_value, text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: max fee is not a number - %s on the 'Set Spending Cap' screen" % (
|
||||
network, text))
|
||||
else:
|
||||
if text != expected_value:
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s: %s text %s doesn't match expected %s on the 'Set Spending Cap' screen" % (
|
||||
network, key, text, expected_value))
|
||||
except (TimeoutException, NoSuchElementException):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s: %s is not shown on the 'Set Spending Cap' screen" % (network, key))
|
||||
try:
|
||||
self.wallet_view.slide_button_track.slide()
|
||||
if not self.wallet_view.password_input.is_element_displayed():
|
||||
self.errors.append(self.wallet_view, "%s: can't sign swap" % network)
|
||||
except NoSuchElementException:
|
||||
self.errors.append(self.wallet_view, "%s: can't sign swap" % network)
|
||||
|
||||
self.wallet_view.click_system_back_button_until_presence_of_element(
|
||||
element=self.wallet_view.element_by_text('Select asset to pay'), attempts=4)
|
||||
self.wallet_view.click_system_back_button_until_presence_of_element(
|
||||
element=self.wallet_view.add_account_button, attempts=6)
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(741612)
|
||||
def test_wallet_bridge_flow_mainnet(self):
|
||||
self.wallet_view.navigate_back_to_wallet_view()
|
||||
self.wallet_view.get_account_element().click()
|
||||
self.wallet_view.bridge_button.click()
|
||||
networks = {'Optimism': 'Arbitrum', 'Arbitrum': 'Base', 'Base': 'Optimism'}
|
||||
amount = '0.001'
|
||||
for network_from, network_to in networks.items():
|
||||
self.wallet_view.just_fyi("Checking bridge from %s to %s" % (network_from, network_to))
|
||||
self.wallet_view.select_asset('Ether')
|
||||
self.wallet_view.select_network(network_from)
|
||||
self.wallet_view.select_network(network_to)
|
||||
self.wallet_view.set_amount(amount)
|
||||
data_to_check = {
|
||||
'Max fees': r"[$]\d+.\d+",
|
||||
'Bridged to %s' % network_to: r"0.000\d+ ETH"
|
||||
}
|
||||
for key, expected_value in data_to_check.items():
|
||||
try:
|
||||
text = self.wallet_view.get_data_item_element_text(data_item_name=key)
|
||||
if not re.findall(expected_value, text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s to %s: %s is not a number - %s before pressing Review Bridge button" % (
|
||||
network_from, network_to, key, text))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s to %s: %s is not shown before pressing Review Bridge button" % (
|
||||
network_from, network_to, key))
|
||||
self.wallet_view.just_fyi("Checking routes from %s to %s" % (network_from, network_to))
|
||||
try:
|
||||
element = self.wallet_view.get_route_element('from')
|
||||
element.wait_for_element()
|
||||
shown_amount = element.amount_text
|
||||
if shown_amount != amount + ' ETH':
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'From' route amount %s doesn't match expected %s" %
|
||||
(network_from, network_to, shown_amount, amount + ' ETH'))
|
||||
shown_network = element.network_text
|
||||
if shown_network != network_from:
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'From' route network %s doesn't match expected %s" %
|
||||
(network_from, network_to, shown_network, network_from))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'From' route is not shown" % (network_from, network_to))
|
||||
|
||||
try:
|
||||
element = self.wallet_view.get_route_element('to')
|
||||
element.wait_for_element()
|
||||
shown_amount = element.amount_text
|
||||
if not re.findall(r"0.000\d+ ETH", shown_amount):
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'To' route amount %s is not a number" %
|
||||
(network_from, network_to, shown_amount))
|
||||
shown_network = element.network_text
|
||||
if shown_network != network_to:
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'To' route network %s doesn't match expected %s" %
|
||||
(network_from, network_to, shown_network, network_to))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view, "%s to %s: 'To' route is not shown" % (network_from, network_to))
|
||||
|
||||
self.wallet_view.confirm_button.click()
|
||||
self.wallet_view.just_fyi("Checking Bridge screen from %s to %s" % (network_from, network_to))
|
||||
containers = {'from': self.wallet_view.from_data_container, 'to': self.wallet_view.to_data_container}
|
||||
for name, container in containers.items():
|
||||
try:
|
||||
container.wait_for_element()
|
||||
for text in [self.account_name,
|
||||
self.sender['wallet_address'].replace(self.sender['wallet_address'][6:-3],
|
||||
'…').lower()]:
|
||||
if not container.get_child_element_by_text(text).is_element_displayed():
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s to %s: Text %s is not shown in the '%s' data container on the Review Bridge screen"
|
||||
% (network_from, network_to, text, name))
|
||||
amount_text = container.amount_text
|
||||
if name == 'from' and amount_text != amount + ' ETH':
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s to %s: amount %s in the 'from' data container doesn't match expected %s ETH"
|
||||
% (network_from, network_to, amount_text, amount))
|
||||
if name == 'to' and not re.findall(r"0.000\d+ ETH", amount_text):
|
||||
self.errors.append(
|
||||
self.wallet_view,
|
||||
"%s to %s: amount %s in the 'to' data container is not a number"
|
||||
% (network_from, network_to, amount_text))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view, "%s to %s: data '%s' is not shown in Review Bridge screen" %
|
||||
(network_from, network_to, name))
|
||||
if network_to == 'Arbitrum':
|
||||
network_to_short_name = 'Arb1.'
|
||||
elif network_to == 'Optimism':
|
||||
network_to_short_name = 'Oeth.'
|
||||
else:
|
||||
network_to_short_name = network_to
|
||||
data_to_check = {
|
||||
'Est. time': ' min',
|
||||
'Max fees': r"[$]\d+.\d+",
|
||||
'Bridged to %s' % network_to_short_name: r"0.000\d+ ETH"
|
||||
}
|
||||
for key, expected_value in data_to_check.items():
|
||||
try:
|
||||
text = self.wallet_view.get_data_item_element_text(data_item_name=key)
|
||||
if not re.findall(expected_value, text):
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s to %s: %s has incorrect value - %s on the Review Bridge screen" % (
|
||||
network_from, network_to, key, text))
|
||||
except TimeoutException:
|
||||
self.errors.append(self.wallet_view,
|
||||
"%s to %s: %s is not shown on the Review Bridge screen" % (
|
||||
network_from, network_to, key))
|
||||
self.wallet_view.slide_button_track.slide()
|
||||
if not self.wallet_view.password_input.is_element_displayed():
|
||||
self.errors.append("%s to %s: can't confirm bridge" % (network_from, network_to))
|
||||
self.wallet_view.click_system_back_button(times=5)
|
||||
self.wallet_view.click_system_back_button_until_presence_of_element(
|
||||
element=self.wallet_view.add_account_button, attempts=6)
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(727231)
|
||||
def test_wallet_add_remove_regular_account(self):
|
||||
self.wallet_view.navigate_back_to_wallet_view()
|
||||
self.wallet_view.just_fyi("Adding new regular account")
|
||||
new_account_name = "New Account"
|
||||
self.wallet_view.add_regular_account(account_name=new_account_name)
|
||||
@@ -276,7 +560,7 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
self.wallet_view.just_fyi("Checking that the new wallet is added to the Share QR Code menu")
|
||||
self.home_view.show_qr_code_button.click()
|
||||
self.home_view.share_wallet_tab_button.click()
|
||||
if self.home_view.account_name_text.text != 'Account 1':
|
||||
if self.home_view.account_name_text.text != self.account_name:
|
||||
self.errors.append(self.home_view, "Incorrect first account is shown on Share QR Code menu")
|
||||
self.home_view.qr_code_image_element.swipe_left_on_element()
|
||||
try:
|
||||
@@ -319,7 +603,7 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
|
||||
self.wallet_view.just_fyi("Checking that the new wallet is added to the Share QR Code menu")
|
||||
self.home_view.show_qr_code_button.click()
|
||||
self.home_view.share_wallet_tab_button.click()
|
||||
if self.home_view.account_name_text.text != 'Account 1':
|
||||
if self.home_view.account_name_text.text != self.account_name:
|
||||
self.errors.append(self.home_view, "Incorrect first account is shown on Share QR Code menu")
|
||||
self.home_view.qr_code_image_element.swipe_left_on_element()
|
||||
try:
|
||||
|
||||
@@ -201,7 +201,7 @@ class BaseView(object):
|
||||
for _ in range(times):
|
||||
self.driver.press_keycode(4)
|
||||
|
||||
def _navigate_back_to_view(self, element, attempts=3):
|
||||
def click_system_back_button_until_presence_of_element(self, element, attempts=3):
|
||||
counter = 0
|
||||
while not element.is_element_displayed(1) and counter <= attempts:
|
||||
self.driver.press_keycode(4)
|
||||
@@ -218,14 +218,14 @@ class BaseView(object):
|
||||
or not self.community_floating_screen.is_element_disappeared(1) \
|
||||
or not self.discover_communities_floating_screen.is_element_disappeared(1):
|
||||
self.driver.press_keycode(4)
|
||||
self._navigate_back_to_view(self.chats_tab)
|
||||
self.click_system_back_button_until_presence_of_element(self.chats_tab)
|
||||
|
||||
def navigate_back_to_chat_view(self):
|
||||
self._navigate_back_to_view(self.get_chat_view().chat_message_input)
|
||||
self.click_system_back_button_until_presence_of_element(self.get_chat_view().chat_message_input)
|
||||
|
||||
def navigate_back_to_wallet_view(self, attempts=3):
|
||||
element = self.get_wallet_view().network_drop_down
|
||||
self._navigate_back_to_view(element)
|
||||
self.click_system_back_button_until_presence_of_element(element)
|
||||
|
||||
def click_system_home_button(self):
|
||||
self.driver.info('Press system Home button')
|
||||
|
||||
@@ -247,7 +247,9 @@ class HomeView(BaseView):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver)
|
||||
|
||||
self.plus_button = Button(self.driver, accessibility_id="new-chat-button")
|
||||
self.skip_editing_profile_button = Button(self.driver, xpath="//*[@content-desc='button-two']//*[@text='Skip']")
|
||||
self.edit_profile_button = Button(self.driver, xpath="//*[@content-desc='button-one']//*[@text='Edit Profile']")
|
||||
self.submit_create_profile_button = Button(self.driver, accessibility_id="submit-create-profile-button")
|
||||
self.plus_community_button = Button(self.driver, accessibility_id="new-communities-button")
|
||||
self.chat_name_text = Text(self.driver, accessibility_id="chat-name-text")
|
||||
self.start_new_chat_button = ChatButton(self.driver, accessibility_id="start-1-1-chat-button")
|
||||
@@ -400,9 +402,15 @@ class HomeView(BaseView):
|
||||
self.close_activity_centre.wait_for_rendering_ended_and_click()
|
||||
self.chats_tab.wait_for_visibility_of_element()
|
||||
|
||||
def add_contact(self, public_key, nickname=''):
|
||||
def add_contact(self, public_key, username='', nickname=''):
|
||||
self.driver.info("Adding user to Contacts via chats > add new contact")
|
||||
self.new_chat_button.click_until_presence_of_element(self.add_a_contact_chat_bottom_sheet_button)
|
||||
self.new_chat_button.click()
|
||||
if username:
|
||||
self.edit_profile_button.click()
|
||||
self.get_sign_in_view().profile_title_input.send_keys(username)
|
||||
self.submit_create_profile_button.click()
|
||||
else:
|
||||
self.skip_editing_profile_button.click_if_shown()
|
||||
self.add_a_contact_chat_bottom_sheet_button.click()
|
||||
|
||||
chat = self.get_chat_view()
|
||||
|
||||
@@ -59,6 +59,17 @@ class ActivityElement(BaseElement):
|
||||
xpath="//*[@content-desc='context-tag'][3]/android.widget.TextView").text
|
||||
|
||||
|
||||
class ConfirmationViewInfoContainer(BaseElement):
|
||||
|
||||
def __init__(self, driver, label_name: str):
|
||||
self.locator = "//*[@content-desc='summary-%s-label']/following-sibling::android.view.ViewGroup[1]" % label_name
|
||||
super().__init__(driver, xpath=self.locator)
|
||||
|
||||
@property
|
||||
def amount_text(self):
|
||||
return Text(self.driver, xpath=self.locator + "/*[@content-desc='networks']/android.widget.TextView").text
|
||||
|
||||
|
||||
class WalletView(BaseView):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver)
|
||||
@@ -84,6 +95,8 @@ class WalletView(BaseView):
|
||||
self.driver, xpath="//*[@content-desc='account-avatar']/../following-sibling::android.widget.TextView[1]")
|
||||
self.account_emoji_button = Button(self.driver, accessibility_id='account-emoji')
|
||||
self.send_button = Button(self.driver, accessibility_id='send')
|
||||
self.swap_button = Button(self.driver, accessibility_id='swap')
|
||||
self.bridge_button = Button(self.driver, accessibility_id='bridge')
|
||||
self.send_from_drawer_button = Button(
|
||||
self.driver, xpath="//*[@content-desc='send']/*[@content-desc='left-icon-for-action']")
|
||||
self.copy_address_button = Button(self.driver, accessibility_id='copy-address')
|
||||
@@ -104,6 +117,25 @@ class WalletView(BaseView):
|
||||
self.confirm_button = Button(self.driver, accessibility_id='button-one')
|
||||
self.done_button = Button(self.driver, accessibility_id='done')
|
||||
|
||||
# Review Send and Review Bridge screens
|
||||
self.from_data_container = ConfirmationViewInfoContainer(self.driver, label_name='from')
|
||||
self.to_data_container = ConfirmationViewInfoContainer(self.driver, label_name='to')
|
||||
|
||||
# Swap flow
|
||||
self.approve_swap_button = Button(self.driver, accessibility_id='Approve')
|
||||
self.spending_cap_approval_info_container = BaseElement(
|
||||
self.driver,
|
||||
xpath="//*[@content-desc='spending-cap-label']/following-sibling::*[@content-desc='approval-info'][1]")
|
||||
self.account_approval_info_container = BaseElement(
|
||||
self.driver,
|
||||
xpath="//*[@content-desc='account-label']/following-sibling::*[@content-desc='approval-info'][1]")
|
||||
self.token_approval_info_container = BaseElement(
|
||||
self.driver,
|
||||
xpath="//*[@content-desc='token-label']/following-sibling::*[@content-desc='approval-info'][1]")
|
||||
self.spender_contract_approval_info_container = BaseElement(
|
||||
self.driver,
|
||||
xpath="//*[@content-desc='spender-contract-label']/following-sibling::*[@content-desc='approval-info'][1]")
|
||||
|
||||
# Edit key pair
|
||||
self.edit_key_pair_button = Button(self.driver, accessibility_id="Edit")
|
||||
self.key_pairs_plus_button = Button(self.driver, accessibility_id="standard-title-action")
|
||||
@@ -156,11 +188,11 @@ class WalletView(BaseView):
|
||||
self.confirm_button.click()
|
||||
self.slide_and_confirm_with_password()
|
||||
|
||||
def set_amount(self, amount: float):
|
||||
for i in '{:f}'.format(amount).rstrip('0'):
|
||||
def set_amount(self, amount: str):
|
||||
for i in amount:
|
||||
Button(self.driver, accessibility_id='keyboard-key-%s' % i).click()
|
||||
|
||||
def send_asset(self, address: str, asset_name: str, amount: float, network_name: str):
|
||||
def send_asset(self, address: str, asset_name: str, amount: str, network_name: str):
|
||||
self.send_button.click()
|
||||
self.address_text_input.send_keys(address)
|
||||
self.continue_button.click()
|
||||
@@ -169,7 +201,7 @@ class WalletView(BaseView):
|
||||
self.set_amount(amount)
|
||||
self.confirm_transaction()
|
||||
|
||||
def send_asset_from_drawer(self, address: str, asset_name: str, amount: float, network_name: str):
|
||||
def send_asset_from_drawer(self, address: str, asset_name: str, amount: str, network_name: str):
|
||||
asset_element = self.get_asset(asset_name)
|
||||
asset_element.long_press_without_release()
|
||||
self.send_from_drawer_button.double_click()
|
||||
@@ -250,3 +282,30 @@ class WalletView(BaseView):
|
||||
wallet_address = self.sharing_text_native.text
|
||||
self.click_system_back_button()
|
||||
return wallet_address
|
||||
|
||||
def get_data_item_element_text(self, data_item_name: str):
|
||||
element = Text(self.driver, xpath="//*[@text='%s']/following-sibling::android.widget.TextView" % data_item_name)
|
||||
element.wait_for_element(15)
|
||||
return element.text
|
||||
|
||||
def wait_for_swap_input_to_be_shown(self):
|
||||
locator = "//*[@content-desc='swap-input'][2]//*[@content-desc='token-avatar']" \
|
||||
"/following-sibling::*//*[starts-with(@text,'0.000')]"
|
||||
BaseElement(self.driver, xpath=locator).wait_for_visibility_of_element()
|
||||
|
||||
def get_route_element(self, route_name: str):
|
||||
class RouteElement(BaseElement):
|
||||
def __init__(self, driver, route_name):
|
||||
self.locator = "//*[@text='%s']/following-sibling::*[@content-desc='container'][%s]" % (
|
||||
route_name.capitalize(), 1 if route_name == 'from' else 2)
|
||||
super().__init__(driver, xpath=self.locator)
|
||||
|
||||
@property
|
||||
def amount_text(self):
|
||||
return Text(self.driver, xpath="(%s//android.widget.TextView)[1]" % self.locator).text
|
||||
|
||||
@property
|
||||
def network_text(self):
|
||||
return Text(self.driver, xpath="(%s//android.widget.TextView)[2]" % self.locator).text
|
||||
|
||||
return RouteElement(self.driver, route_name)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"add-bootnode": "Add bootnode",
|
||||
"add-contact": "Add contact",
|
||||
"add-custom-token": "Add custom token",
|
||||
"add-display-name-and-picture": "Add an optional display name and profile picture so others can easily recognize you",
|
||||
"add-eth": "Add ETH",
|
||||
"add-favourite": "Add favourite",
|
||||
"add-mailserver": "Add Status node",
|
||||
@@ -884,7 +885,6 @@
|
||||
"enable": "Enable",
|
||||
"enable-access-to-camera": "Enable access to camera",
|
||||
"enable-access-to-local-network": "Enable access to local network",
|
||||
"enable-access-to-local-network-description": "To pair with your other device in the network",
|
||||
"enable-all": "Enable all",
|
||||
"enable-biometrics": "Enable biometrics",
|
||||
"enable-camera": "Enable camera",
|
||||
@@ -1349,6 +1349,7 @@
|
||||
"joined-group-chat-description": "You've joined {{group-name}} from invitation by {{username}}",
|
||||
"jul": "Jul",
|
||||
"jun": "Jun",
|
||||
"just-introduce-yourself": "Introduce yourself",
|
||||
"K": "K",
|
||||
"keep-card-steady": "Keep the card steady under the phone",
|
||||
"keep-key": "KeepKey",
|
||||
@@ -1382,6 +1383,7 @@
|
||||
"keycard-connected-title": "Connected",
|
||||
"keycard-contains-key-pair": "Keycard contains your profile key pair",
|
||||
"keycard-desc": "Own a Keycard? Store your keys on it; you’ll need it for transactions",
|
||||
"keycard-documentation": "Keycard is a hardware wallet in a credit-card sized smartcard that works with the Status app. You can store your Status keys and sign transactions securely with it.\n\nKeycard is designed to be mobile-friendly and uses contactless NFC technology to communicate with your phone, so you can sign transactions with just a tap of the card. It is lightweight, convenient, and can fit in your wallet, pocket, or phone case.\n\nProtect your assets and accounts with the Keycard, a cold storage hardware wallet that provides a contactless and convenient experience.",
|
||||
"keycard-dont-ask-card": "Don't ask for card to sign in",
|
||||
"keycard-empty": "Keycard is empty",
|
||||
"keycard-empty-ready": "Keycard is empty and ready to be used",
|
||||
|
||||
Reference in New Issue
Block a user