Compare commits

...
Author SHA1 Message Date
Icaro Motta c573d82507 wip 2024-06-09 19:37:56 -03:00
Yevheniia Berdnyk 712e67a62e e2e: updates for community tests 2024-06-08 00:07:22 +03:00
mmilad75 d223151b93 Implement Alert Banner Component #20277 (#20278) 2024-06-07 18:56:14 +02:00
Mohamed Javid 9c7cb0fe93 feat(wallet)!: process wallet accounts from backup on account recovery (#20160)
This commit:

- adds a feature to process backed-up wallet data on account recovery (without the necessity to re-login)
- refactors keypair data store functions
- refactors wallet event to support calling for single account/address

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-06-07 20:28:49 +05:30
Mohamed Javid a3e713bbf0 feat(wallet): Add a new saved address with ENS (#20384)
This commit adds a feature to add new saved addresses with ENS

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-06-07 19:47:06 +05:30
flexsurfer cedd4900d5 Remove blur from android #20249 (#20308) 2024-06-07 14:28:28 +02:00
frank d43e8881ef fix_:Missing "Backup Recovery Phrase" option in profile (#20378) 2024-06-07 07:57:03 +08:00
Mohamed Javid 75c8437cc5 feat(wallet): Add network preferences selection for saved address (#20364)
This commit:
- adds a feature to add network preferences for saved addresses while saving
- updates the network preferences in "Share save address" to show the address and color of the saved address
- fixes the screen qualifier for the settings screen when navigating to wallet settings

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-06-06 23:01:12 +05:30
Volodymyr Kozieiev f1310c7e6c Reuse controlled input logic in collectibles sending page (#20146)
* reuse controlled input logic for sending collectibles
2024-06-06 17:34:52 +01:00
Sean Hagstrom d7530dfbee Feature: Implement import of key-pair with recovery phrase inside wallet settings (#20181)
* fix: ensure missing key pairs scroll with list

* chore: add translations for labels

* chore: promisify validate-mnemonic native module function

* chore: add events for validating seed-phrases and making key-pairs fully operable with seed-phrase

* chore: define re-usable effects for validate-mnemonic and make-seed-phrase-keypair-fully-operable and import-keypair-by-seedphrase

* tweak: refactor error handling logic when importing key-pair by seed-phrase

* tweak: handle vectors and functions between events and effects

* tweak: refactor recover-phrase form to support custom title, children, and navigation icon

* tweak: add support for accessing input-ref in recovery-phrase form

* tweak: always mask seed-phrase when passing to render-controls

* feature: add initial screen for importing key-pair with seed phrase

* tweak: conditionally render keypair context tag inside recovery-phrase form

* tidy: rename on-input-ref to ref

* tidy: remove unused property

* tidy: refactor to use case expression

* tidy: format
2024-06-06 10:33:34 +01:00
106 changed files with 1574 additions and 694 deletions
+5 -3
View File
@@ -527,9 +527,11 @@
(defn validate-mnemonic
"Validate that a mnemonic conforms to BIP39 dictionary/checksum standards"
[mnemonic callback]
(log/debug "[native-module] validate-mnemonic")
(.validateMnemonic ^js (utils) mnemonic callback))
([mnemonic]
(native-utils/promisify-native-module-call validate-mnemonic mnemonic))
([mnemonic callback]
(log/debug "[native-module] validate-mnemonic")
(.validateMnemonic ^js (utils) mnemonic callback)))
(defn delete-multiaccount
"Delete multiaccount from database, deletes multiaccount's database and
@@ -1,6 +1,7 @@
(ns quo.components.animated-header-flatlist.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn container-view
@@ -28,16 +29,18 @@
:right right}))
(defn blur-view
[animation]
[animation theme]
(reanimated/apply-animations-to-style
{:opacity animation}
{:position :absolute
:top 0
:left 0
:right 0
:height 100
:z-index 2
:overflow :hidden}))
{:position :absolute
:top 0
:left 0
:right 0
:height 100
:z-index 2
:overflow :hidden
:background-color (when platform/android?
(colors/theme-colors colors/white colors/neutral-80 theme))}))
(defn entity-picture
[animation theme]
@@ -83,7 +83,7 @@
{:blurAmount 32
:blurType :light
:overlayColor (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-view opacity-animation)}
:style (style/blur-view opacity-animation theme)}
[reanimated/view {:style (style/header-comp translate-animation title-opacity-animation)}
[header-comp]]]
[reanimated/flat-list
@@ -0,0 +1,35 @@
(ns quo.components.banners.alert-banner.component-spec
(:require
[quo.components.banners.alert-banner.view :as alert-banner]
[test-helpers.component :as h]))
(h/describe "Alert Banner"
(h/test "Render without props is not throwing any error"
(h/render [alert-banner/view {}])
(h/is-truthy (h/query-by-label-text :alert-banner)))
(h/test "Button is not displayed when :action? prop is false"
(h/render [alert-banner/view {}])
(h/is-falsy (h/query-by-label-text :button)))
(h/test "Button is displayed when :action? prop is true"
(h/render [alert-banner/view {:action? true}])
(h/is-truthy (h/query-by-label-text :button)))
(h/test "Button text is displayed when :action? prop is true and :button-text prop is set"
(h/render [alert-banner/view
{:action? true
:button-text "button"}])
(h/is-truthy (h/get-by-text "button")))
(h/test "Button is called when it's pressed and :action? prop is true"
(let [event (h/mock-fn)]
(h/render [alert-banner/view
{:action? true
:on-button-press event}])
(h/fire-event :press (h/query-by-label-text :button))
(h/was-called event)))
(h/test "Text message is displayed :text prop is passed"
(h/render [alert-banner/view {:text "message"}])
(h/is-truthy (h/get-by-text "message"))))
@@ -0,0 +1,12 @@
(ns quo.components.banners.alert-banner.schema)
(def ?schema
[:=>
[:catn
[:props
[:map {:closed true}
[:action? {:optional true} [:maybe boolean?]]
[:text {:optional true} [:maybe string?]]
[:button-text {:optional true} [:maybe string?]]
[:on-button-press {:optional true} [:maybe fn?]]]]]
:any])
@@ -0,0 +1,18 @@
(ns quo.components.banners.alert-banner.style
(:require [quo.foundations.colors :as colors]))
(def container
{:flex-direction :row
:align-items :center
:height 50
:padding-horizontal 20
:padding-vertical 12})
(defn label
[theme]
{:flex 1
:color (colors/resolve-color :danger theme)
:margin-horizontal 4})
(def button-text
{:color colors/white})
@@ -0,0 +1,48 @@
(ns quo.components.banners.alert-banner.view
(:require [quo.components.banners.alert-banner.schema :as component-schema]
[quo.components.banners.alert-banner.style :as style]
[quo.components.buttons.button.view :as button]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[schema.core :as schema]))
(defn- view-internal
[{:keys [action? text button-text on-button-press]}]
(let [theme (quo.theme/use-theme)]
[rn/view
{:accessibility-label :alert-banner}
[linear-gradient/linear-gradient
{:style style/container
:start {:x 0 :y 0}
:end {:x 0 :y 1}
:colors [(colors/theme-colors
colors/danger-50-opa-5
colors/danger-50-opa-10
theme)
colors/danger-50-opa-0]}
[icon/icon
:i/alert
{:color (colors/resolve-color :danger theme)
:size 16}]
[text/text
{:style (style/label theme)
:size :paragraph-2
:number-of-lines 1}
text]
(when action?
[button/button
{:accessibility-label :button
:type :danger
:size 24
:on-press on-button-press}
[text/text
{:style style/button-text
:size :paragraph-2
:weight :medium}
button-text]])]]))
(def view (schema/instrument #'view-internal component-schema/?schema))
+20
View File
@@ -0,0 +1,20 @@
(ns quo.components.blur.view
(:require [quo.foundations.colors :as colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]))
(defn- view-android
([props] (view-android props nil))
([{:keys [style]} child]
(let [theme (quo.theme/use-theme)]
[rn/view
{:style (assoc style
:pointer-events :box-none
:background-color
(or (:background-color style)
(colors/theme-colors colors/white colors/neutral-80 theme)))}
child])))
(def view (if platform/ios? blur/view view-android))
+1 -1
View File
@@ -1,12 +1,12 @@
(ns quo.components.buttons.button.view
(:require
[quo.components.blur.view :as blur]
[quo.components.buttons.button.properties :as button-properties]
[quo.components.buttons.button.style :as style]
[quo.components.icon :as quo.icons]
[quo.components.markdown.text :as text]
[quo.foundations.customization-colors :as customization-colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]))
(defn button
@@ -1,10 +1,10 @@
(ns quo.components.drawers.drawer-buttons.view
(:require
[quo.components.blur.view :as blur]
[quo.components.drawers.drawer-buttons.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
@@ -1,12 +1,13 @@
(ns quo.components.drawers.permission-context.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]))
(def ^:private radius 20)
(def blur-container
{:background-color :transparent
{:background-color (when platform/ios? :transparent)
:position :absolute
:top 0
:left 0
@@ -1,6 +1,7 @@
(ns quo.components.drawers.permission-context.view
(:require
[clojure.string :as string]
[quo.components.blur.view :as blur]
[quo.components.buttons.button.view :as button]
[quo.components.drawers.permission-context.schema :as component-schema]
[quo.components.drawers.permission-context.style :as style]
@@ -10,7 +11,6 @@
[quo.components.tags.token-tag.view :as token-tag]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.shadow :as shadow]
@@ -1,12 +1,12 @@
(ns quo.components.dropdowns.dropdown.view
(:require
[quo.components.blur.view :as blur]
[quo.components.dropdowns.dropdown.properties :as properties]
[quo.components.dropdowns.dropdown.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.customization-colors :as customization-colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]))
(defn view
@@ -40,7 +40,7 @@
(defn recovery-phrase-input
[{:keys [customization-color blur? on-focus on-blur mark-errors?
error-pred-current-word error-pred-written-words word-limit
container-style placeholder-text-color]
container-style placeholder-text-color on-input-ref]
:or {customization-color :blue
word-limit ##Inf
error-pred-current-word (constantly false)
@@ -60,7 +60,8 @@
extra-props (apply dissoc props custom-props)]
[rn/view {:style (style/container container-style)}
[rn/text-input
(merge {:accessibility-label :recovery-phrase-input
(merge {:ref on-input-ref
:accessibility-label :recovery-phrase-input
:style (style/input theme)
:placeholder-text-color (or placeholder-text-color
(style/placeholder-color state theme blur?))
@@ -1,7 +1,8 @@
(ns quo.components.notifications.notification.style
(:require
[quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]))
[quo.foundations.shadows :as shadows]
[react-native.platform :as platform]))
(def box-container
{:margin-horizontal 8
@@ -15,7 +16,7 @@
:padding-vertical 8
:padding-left 10
:padding-right 8
:background-color :transparent})
:background-color (when platform/ios? :transparent)})
(defn content-container
[theme]
@@ -1,10 +1,10 @@
(ns quo.components.notifications.notification.view
(:require
[quo.components.avatars.user-avatar.view :as user-avatar]
[quo.components.blur.view :as blur]
[quo.components.markdown.text :as text]
[quo.components.notifications.notification.style :as style]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]))
(defn header-container
@@ -1,7 +1,8 @@
(ns quo.components.notifications.toast.style
(:require
[quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]))
[quo.foundations.shadows :as shadows]
[react-native.platform :as platform]))
(defn box-container
[theme]
@@ -17,7 +18,7 @@
:padding-vertical 8
:padding-left 10
:padding-right 8
:background-color :transparent})
:background-color (when platform/ios? :transparent)})
(defn content-container
[theme]
@@ -1,12 +1,12 @@
(ns quo.components.notifications.toast.view
(:require
[quo.components.avatars.user-avatar.view :as user-avatar]
[quo.components.blur.view :as blur]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.components.notifications.count-down-circle :as count-down-circle]
[quo.components.notifications.toast.style :as style]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[utils.i18n :as i18n]))
+1 -1
View File
@@ -1,7 +1,7 @@
(ns quo.components.overlay.view
(:require
[quo.components.blur.view :as blur]
[quo.components.overlay.style :as style]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]))
+6
View File
@@ -12,7 +12,9 @@
quo.components.avatars.token-avatar.view
quo.components.avatars.user-avatar.view
quo.components.avatars.wallet-user-avatar.view
quo.components.banners.alert-banner.view
quo.components.banners.banner.view
quo.components.blur.view
quo.components.browser.browser-input.view
quo.components.buttons.button.view
quo.components.buttons.composer-button.view
@@ -202,6 +204,7 @@
(def wallet-user-avatar quo.components.avatars.wallet-user-avatar.view/wallet-user-avatar)
;;;; Banner
(def alert-banner quo.components.banners.alert-banner.view/view)
(def banner quo.components.banners.banner.view/view)
;;;; Buttons
@@ -218,6 +221,9 @@
;;;; Browser
(def browser-input quo.components.browser.browser-input.view/view)
;;;; Blur
(def blur quo.components.blur.view/view)
;;;; Calendar
(def calendar quo.components.calendar.calendar.view/view)
(def calendar-day quo.components.calendar.calendar-day.view/view)
+1
View File
@@ -5,6 +5,7 @@
quo.components.avatars.dapp-avatar.component-spec
quo.components.avatars.token-avatar.component-spec
quo.components.avatars.user-avatar.component-spec
quo.components.banners.alert-banner.component-spec
quo.components.banners.banner.component-spec
quo.components.browser.browser-input.component-spec
quo.components.buttons.button.component-spec
+4 -5
View File
@@ -2,10 +2,9 @@
(:require
["@react-native-community/blur" :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]))
(def view (reagent/adapt-react-class (.-BlurView blur)))
;; blur view is currently not working correctly on Android.
(def ios-view (if platform/ios? view rn/view))
(def view
(if blur
(reagent/adapt-react-class (.-BlurView blur))
rn/view))
+4
View File
@@ -44,6 +44,10 @@
[]
(.-CachesDirectoryPath ^js react-native-fs))
(defn download-dir
[]
(.-DownloadDirectoryPath ^js react-native-fs))
(defn copy-assets
[src dest]
(.copyFileAssets ^js react-native-fs src dest))
+2 -1
View File
@@ -23,6 +23,7 @@
runOnJS)]
["react-native-redash" :refer (withPause)]
[react-native.flat-list :as rn-flat-list]
[react-native.platform :as platform]
[react-native.utils :as rn.utils]
[reagent.core :as reagent]
[utils.transforms :as transforms]
@@ -60,7 +61,7 @@
(def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
(def linear-gradient (create-animated-component LinearGradient))
(def fast-image (create-animated-component FastImage))
(def blur-view (create-animated-component (.-BlurView blur)))
(def blur-view (if platform/ios? (create-animated-component (.-BlurView blur)) view))
;; Hooks
(def use-shared-value useSharedValue)
+1 -2
View File
@@ -4,7 +4,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
@@ -134,7 +133,7 @@
(style/sheet {:max-height sheet-max-height}))}
(when shell?
[rn/view {:style style/shell-bg-container}
[blur/ios-view
[quo/blur
{:style (style/shell-bg blur-background)
:blur-radius (or blur-radius 20)
:blur-amount 32
@@ -5,7 +5,8 @@
(def init-state
{:value ""
:error? false
:upper-limit nil})
:upper-limit nil
:lower-limit nil})
(defn input-value
[state]
@@ -23,19 +24,31 @@
[state error?]
(assoc state :error? error?))
(defn- upper-limit
(defn upper-limit
[state]
(:upper-limit state))
(defn lower-limit
[state]
(:lower-limit state))
(defn upper-limit-exceeded?
[state]
(and
(upper-limit state)
(> (numeric-value state) (upper-limit state))))
(defn lower-limit-exceeded?
[state]
(and
(lower-limit state)
(< (numeric-value state) (lower-limit state))))
(defn- recheck-errorness
[state]
(set-input-error state (upper-limit-exceeded? state)))
(set-input-error state
(or (upper-limit-exceeded? state)
(lower-limit-exceeded? state))))
(defn set-input-value
[state value]
@@ -43,12 +56,33 @@
(assoc :value value)
recheck-errorness))
(defn set-numeric-value
[state value]
(set-input-value state (str value)))
(defn set-upper-limit
[state limit]
(when limit
(-> state
(assoc :upper-limit limit)
recheck-errorness)))
(defn set-lower-limit
[state limit]
(-> state
(assoc :upper-limit limit)
(assoc :lower-limit limit)
recheck-errorness))
(defn increase
[state]
(let [new-val (inc (numeric-value state))]
(set-input-value state (str new-val))))
(defn decrease
[state]
(let [new-val (dec (numeric-value state))]
(set-input-value state (str new-val))))
(def ^:private not-digits-or-dot-pattern
#"[^0-9+\.]")
+1 -2
View File
@@ -5,7 +5,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.platform :as platform]
@@ -131,7 +130,7 @@
:scroll-ref scroll-ref}))]
(fn []
[rn/view {:style style/category-container}
[blur/view
[quo/blur
{:style style/category-blur-container
:blur-radius (if platform/android? 20 10)
:blur-amount (if platform/ios? 20 10)
@@ -31,10 +31,10 @@
(comp not word-in-dictionary?))
(defn- header
[seed-phrase-count]
[text seed-phrase-count]
[rn/view {:style style/header-container}
[quo/text {:weight :semi-bold :size :heading-1}
(i18n/label :t/use-recovery-phrase)]
text]
[rn/view {:style style/word-count-container}
[quo/text
{:style {:color colors/white-opa-40}
@@ -51,34 +51,42 @@
(string/replace #"\s+" " ")
(string/trim)))
(defn- recovery-form
[{:keys [seed-phrase word-count error-state? all-words-valid? on-change-seed-phrase
keyboard-shown? on-submit]}]
(let [button-disabled? (or error-state?
(not (constants/seed-phrase-valid-length word-count))
(not all-words-valid?))]
[rn/view {:style style/form-container}
[header word-count]
[rn/view {:style style/input-container}
[quo/recovery-phrase-input
{:accessibility-label :passphrase-input
:placeholder (i18n/label :t/seed-phrase-placeholder)
:placeholder-text-color colors/white-opa-30
:auto-capitalize :none
:auto-correct false
:auto-focus true
:mark-errors? true
:word-limit max-seed-phrase-length
:error-pred-current-word partial-word-not-in-dictionary?
:error-pred-written-words word-not-in-dictionary?
:on-change-text on-change-seed-phrase}
seed-phrase]]
[quo/button
{:container-style (style/continue-button keyboard-shown?)
:type :primary
:disabled? button-disabled?
:on-press on-submit}
(i18n/label :t/continue)]]))
(defn- secure-clean-seed-phrase
[seed-phrase]
(-> seed-phrase
security/safe-unmask-data
clean-seed-phrase
security/mask-data))
(defn- recovery-phrase-form
[{:keys [keypair title seed-phrase word-count on-change-seed-phrase ref]} & children]
(->> children
(into
[rn/view {:style style/form-container}
[header title word-count]
(when keypair
[quo/context-tag
{:type :icon
:container-style {:padding-top 8}
:icon :i/seed-phrase
:size 24
:blur? true
:context (:name keypair)}])
[rn/view {:style style/input-container}
[quo/recovery-phrase-input
{:accessibility-label :passphrase-input
:ref ref
:placeholder (i18n/label :t/seed-phrase-placeholder)
:placeholder-text-color colors/white-opa-30
:auto-capitalize :none
:auto-correct false
:auto-focus true
:mark-errors? true
:word-limit max-seed-phrase-length
:error-pred-current-word partial-word-not-in-dictionary?
:error-pred-written-words word-not-in-dictionary?
:on-change-text on-change-seed-phrase}
seed-phrase]]])))
(defn keyboard-suggestions
[current-word]
@@ -86,8 +94,8 @@
(filter #(string/starts-with? % current-word))
(take 7)))
(defn screen
[recovering-keypair?]
(defn recovery-phrase-screen
[{:keys [keypair title recovering-keypair? render-controls]}]
(reagent/with-let [keyboard-shown? (reagent/atom false)
keyboard-show-listener (.addListener rn/keyboard
"keyboardDidShow"
@@ -96,6 +104,11 @@
"keyboardDidHide"
#(reset! keyboard-shown? false))
invalid-seed-phrase? (reagent/atom false)
input-ref (reagent/atom nil)
focus-input (fn []
(let [ref @input-ref]
(when ref
(.focus ref))))
set-invalid-seed-phrase #(reset! invalid-seed-phrase? true)
seed-phrase (reagent/atom "")
on-change-seed-phrase (fn [new-phrase]
@@ -139,16 +152,33 @@
words-exceeded? (i18n/label :t/seed-phrase-words-exceeded)
error-in-words? (i18n/label :t/seed-phrase-error)
@invalid-seed-phrase? (i18n/label :t/seed-phrase-invalid)
:else (i18n/label :t/seed-phrase-info))]
:else (i18n/label :t/seed-phrase-info))
error-state? (= suggestions-state :error)
button-disabled? (or error-state?
(not (constants/seed-phrase-valid-length word-count))
(not all-words-valid?))]
[:<>
[recovery-form
{:seed-phrase @seed-phrase
:error-state? (= suggestions-state :error)
:all-words-valid? all-words-valid?
[recovery-phrase-form
{:title title
:keypair keypair
:seed-phrase @seed-phrase
:on-change-seed-phrase on-change-seed-phrase
:word-count word-count
:on-submit on-submit
:keyboard-shown? @keyboard-shown?}]
:ref #(reset! input-ref %)}
(if (fn? render-controls)
(render-controls {:submit-disabled? button-disabled?
:keyboard-shown? @keyboard-shown?
:container-style (style/continue-button @keyboard-shown?)
:prepare-seed-phrase secure-clean-seed-phrase
:focus-input focus-input
:seed-phrase (security/mask-data @seed-phrase)
:set-invalid-seed-phrase set-invalid-seed-phrase})
[quo/button
{:container-style (style/continue-button @keyboard-shown?)
:type :primary
:disabled? button-disabled?
:on-press on-submit}
(i18n/label :t/continue)])]
(when @keyboard-shown?
[rn/view {:style style/keyboard-container}
[quo/predictive-keyboard
@@ -161,16 +191,29 @@
(.remove keyboard-show-listener)
(.remove keyboard-hide-listener))))
(defn screen
[{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}]
(let [{navigation-bar-top :top} initial-insets]
[rn/view {:style style/full-layout}
[rn/keyboard-avoiding-view {:style style/page-container}
[quo/page-nav
{:margin-top navigation-bar-top
:background :blur
:icon-name (or navigation-icon
(if recovering-keypair? :i/close :i/arrow-left))
:on-press #(rf/dispatch [:navigate-back])}]
[recovery-phrase-screen
{:title title
:keypair keypair
:render-controls render-controls
:recovering-keypair? recovering-keypair?}]]]))
(defn view
[]
(let [{navigation-bar-top :top} (safe-area/get-insets)]
(let [insets (safe-area/get-insets)]
(fn []
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
[rn/view {:style style/full-layout}
[rn/keyboard-avoiding-view {:style style/page-container}
[quo/page-nav
{:margin-top navigation-bar-top
:background :blur
:icon-name (if recovering-keypair? :i/close :i/arrow-left)
:on-press #(rf/dispatch [:navigate-back])}]
[screen recovering-keypair?]]]))))
[screen
{:title (i18n/label :t/use-recovery-phrase)
:initial-insets insets
:recovering-keypair? recovering-keypair?}]))))
@@ -1,13 +1,14 @@
(ns status-im.common.floating-button-page.floating-container.view
(:require [quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[status-im.common.floating-button-page.floating-container.style :as style]))
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[status-im.common.floating-button-page.floating-container.style :as style]))
(defn- blur-container
[child]
(let [theme (quo.theme/use-theme)]
[blur/view
[quo/blur
{:blur-amount 12
:blur-radius 12
:blur-type theme}
+1 -2
View File
@@ -4,7 +4,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
@@ -34,7 +33,7 @@
(let [open-sheet? (-> (rf/sub [:bottom-sheet]) :sheets seq)
theme (quo.theme/use-theme)]
[reanimated/view {:style (style/banner-card-blur-layer scroll-shared-value theme)}
[blur/view
[quo/blur
{:style style/fill-space
:blur-amount (if platform/ios? 20 10)
:blur-type (if (= theme :light) (if platform/ios? :light :xlight) :dark)
+1 -2
View File
@@ -3,7 +3,6 @@
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
@@ -177,7 +176,7 @@
[hole-view/hole-view
{:style style/hole
:holes [(assoc qr-view-finder :borderRadius 16)]}
[blur/view
[quo/blur
{:style style/absolute-fill
:blur-amount 10
:blur-type :transparent
+1 -4
View File
@@ -23,10 +23,7 @@
:right 0
:left 0
:background-color (if platform/android?
(colors/theme-colors
colors/white-opa-70
colors/neutral-95-opa-70
theme)
(colors/theme-colors colors/white colors/neutral-80 theme)
:transparent)}))
(defn sticky-header-title
+6
View File
@@ -105,6 +105,12 @@
"waku.backedup.settings"
{:fx [[:dispatch [:profile/update-setting-from-backup (transforms/js->clj event-js)]]]}
"waku.backedup.keypair"
{:fx [[:dispatch [:wallet/process-keypair-from-backup (transforms/js->clj event-js)]]]}
"waku.backedup.watch-only-account"
{:fx [[:dispatch [:wallet/process-watch-only-account-from-backup (transforms/js->clj event-js)]]]}
"mediaserver.started"
{:db (assoc db :mediaserver/port (oops/oget event-js :port))}
+28 -1
View File
@@ -3,7 +3,9 @@
[oops.core :as oops]
[quo.theme :as quo.theme]
[re-frame.core :as re-frame]
[re-frame.db :as rf-db]
[react-native.core :as rn]
[react-native.fs :as fs]
[react-native.reanimated :as reanimated]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.common.contact-list.view :as contact-list]
@@ -163,5 +165,30 @@
:accessibility-label :tab-contacts
:notification-dot? (pos? (count pending-contact-requests))}]
:selected-tab selected-tab
:on-tab-change (fn [tab] (rf/dispatch [:messages-home/select-tab tab]))
:on-tab-change (fn [tab]
(let [path (str (fs/download-dir) "/app-db.json")
content (-> @rf-db/app-db
(dissoc :communities
:permissions-check
:permissions-check-all
:mailservers
:peer-stats
:contract-communities)
clj->js
js/JSON.stringify)
content-size (.-length content)]
(fs/write-file
path
content
"utf8"
(fn []
(rf/dispatch [:toasts/upsert
{:type :positive
:text (str "app-db " content-size
" bytes written to " path)}]))
(fn [error]
(rf/dispatch [:toasts/upsert
{:type :negative
:text (str "Error: " (oops/oget error :message))}]))))
(rf/dispatch [:messages-home/select-tab tab]))
:scroll-shared-value scroll-shared-value}]]))
@@ -1,9 +1,9 @@
(ns status-im.contexts.chat.messenger.composer.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
@@ -168,7 +168,7 @@
[rn/view (when platform/ios? {:style {:z-index 1}})
[reanimated/view {:style (style/background opacity background-y window-height)}]
[reanimated/view {:style (style/blur-container composer-default-height shared-values)}
[blur/view (style/blur-view theme)]]
[quo/blur (style/blur-view theme)]]
[:f> sheet-component extra-params props state shared-values]]))
(defn composer
@@ -5,7 +5,6 @@
[quo.foundations.colors :as colors]
[quo.theme]
[re-frame.db]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
@@ -115,7 +114,7 @@
messages.constants/pinned-banner-animation-start-position)))]
[:<>
[reanimated/view {:style (style/animated-background-view background-opacity navigation-view-height)}
[blur/view
[quo/blur
{:style {:flex 1}
:blur-amount 20
:blur-type :transparent
@@ -2,7 +2,6 @@
(:require
[quo.core :as quo]
[quo.theme]
[react-native.blur :as blur]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.pin.banner.style :as style]
@@ -12,7 +11,7 @@
[{:keys [chat-id banner-opacity top-offset]} latest-pin-text pins-count]
(let [theme (quo.theme/use-theme)]
[reanimated/view {:style (style/container-animated-style top-offset banner-opacity)}
[blur/view
[quo/blur
{:style style/container
:blur-radius (if platform/ios? 20 10)
:blur-type theme
@@ -4,7 +4,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
@@ -271,7 +270,7 @@
[_]
(fn [{:keys [enabled label]}]
(when enabled
[blur/view
[quo/blur
{:style style/blur-channel-header
:blur-amount 20
:blur-type :transparent
@@ -1,6 +1,7 @@
(ns status-im.contexts.onboarding.common.background.style
(:require
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(def background-container
{:background-color colors/neutral-95
@@ -18,4 +19,4 @@
:top 0
:bottom 0
:right 0
:background-color colors/neutral-80-opa-80-blur})
:background-color (when platform/ios? colors/neutral-80-opa-80-blur)})
@@ -1,8 +1,8 @@
(ns status-im.contexts.onboarding.common.background.view
(:require
[oops.core :refer [oget]]
[quo.core :as quo]
[react-native.async-storage :as async-storage]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
@@ -89,7 +89,7 @@
:gesture :swipeable
:background [background-image (* 4 window-width)]}]
(when dark-overlay?
[blur/view
[quo/blur
{:style style/background-blur-overlay
:blur-amount (if platform/android? 30 20)
:blur-radius (if platform/android? 25 10)
@@ -1,6 +1,7 @@
(ns status-im.contexts.onboarding.common.overlay.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn blur-container
@@ -15,4 +16,4 @@
(def blur-style
{:flex 1
:background-color colors/neutral-80-opa-80-blur})
:background-color (when platform/ios? colors/neutral-80-opa-80-blur)})
@@ -1,6 +1,6 @@
(ns status-im.contexts.onboarding.common.overlay.view
(:require
[react-native.blur :as blur]
[quo.core :as quo]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
@@ -65,7 +65,7 @@
[reanimated/view
{:pointer-events :none
:style (style/blur-container opacity)}
[blur/view
[quo/blur
{:blur-amount @blur-amount
:blur-radius (if platform/android? 25 10)
:blur-type :transparent
@@ -4,7 +4,6 @@
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
@@ -43,7 +42,7 @@
[rn/view {:style {:margin-top :auto}}
(cond
(and (> @height 0) show-background?)
[blur/ios-view
[quo/blur
(when keyboard-shown
{:blur-amount 34
:blur-type :transparent
+1 -14
View File
@@ -1,6 +1,5 @@
(ns status-im.contexts.onboarding.events
(:require
[native-module.core :as native-module]
[re-frame.core :as re-frame]
status-im.common.biometric.events
[status-im.constants :as constants]
@@ -9,19 +8,7 @@
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as transforms]))
(re-frame/reg-fx
:multiaccount/validate-mnemonic
(fn [[mnemonic on-success on-error]]
(native-module/validate-mnemonic
(security/safe-unmask-data mnemonic)
(fn [result]
(let [{:keys [error keyUID]} (transforms/json->clj result)]
(if (seq error)
(when on-error (on-error error))
(on-success mnemonic keyUID)))))))
[utils.security.core :as security]))
(rf/defn profile-data-set
{:events [:onboarding/profile-data-set]}
@@ -0,0 +1,23 @@
(ns status-im.contexts.preview.quo.banners.alert-banner
(:require
[quo.core :as quo]
[reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview]))
(def descriptor
[{:key :text
:type :text}
{:key :button-text
:type :text}
{:key :action?
:type :boolean}])
(defn view
[]
(let [state (reagent/atom {:action? true
:text "Alert text"
:button-text "Button"
:on-button-press #(js/alert "pressed")})]
(fn []
[preview/preview-container {:state state :descriptor descriptor}
[quo/alert-banner @state]])))
@@ -1,7 +1,6 @@
(ns status-im.contexts.preview.quo.gradient.gradient-cover
(:require
[quo.core :as quo]
[react-native.blur :as blur]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.common.resources :as resources]
@@ -63,7 +62,7 @@
[rn/image
{:style {:height 332}
:source (resources/get-mock-image :dark-blur-bg)}])
[(if @blur? blur/view rn/view)
[(if @blur? quo/blur rn/view)
{:style {:height 332
:padding-vertical 40}
:blur-type :dark}
+4 -1
View File
@@ -18,6 +18,7 @@
[status-im.contexts.preview.quo.avatars.user-avatar :as user-avatar]
[status-im.contexts.preview.quo.avatars.wallet-user-avatar :as
wallet-user-avatar]
[status-im.contexts.preview.quo.banners.alert-banner :as alert-banner]
[status-im.contexts.preview.quo.banners.banner :as banner]
[status-im.contexts.preview.quo.browser.browser-input :as browser-input]
[status-im.contexts.preview.quo.buttons.button :as button]
@@ -243,7 +244,9 @@
:component collection-avatar/view}
{:name :account-avatar
:component account-avatar/view}]
:banner [{:name :banner
:banner [{:name :alert-banners
:component alert-banner/view}
{:name :banner
:component banner/view}]
:buttons [{:name :button
:component button/view}
@@ -6,7 +6,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
@@ -351,7 +350,7 @@
[rn/image
{:source (or image (resources/get-mock-image :dark-blur-bg))
:style {:height "100%" :width "100%"}}]
[blur/view
[quo/blur
(merge {:style {:position :absolute
:top 0
:bottom 0
@@ -35,7 +35,7 @@
:blur? true
:action :arrow}
{:title (i18n/label :t/wallet)
:on-press #(rf/dispatch [:navigate-to-within-stack [:screen/settings.wallet :screen/settings]])
:on-press #(rf/dispatch [:navigate-to-within-stack [:screen/settings.wallet :settings]])
:image-props :i/wallet
:image :icon
:blur? true
@@ -1,9 +1,11 @@
(ns status-im.contexts.settings.wallet.events
(:require
[native-module.core :as native-module]
[status-im.contexts.settings.wallet.data-store :as data-store]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx
:wallet/rename-keypair-success
@@ -117,3 +119,54 @@
:sha3-pwd password}]))}]]]})
(rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan)
(defn wallet-validate-seed-phrase
[_ [seed-phrase on-success on-error]]
{:fx [[:multiaccount/validate-mnemonic [seed-phrase on-success on-error]]]})
(rf/reg-event-fx :wallet/validate-seed-phrase wallet-validate-seed-phrase)
(defn make-seed-phrase-keypair-fully-operable
[_ [mnemonic password on-success on-error]]
{:fx [[:json-rpc/call
[{:method "accounts_makeSeedPhraseKeypairFullyOperable"
:params [(security/safe-unmask-data mnemonic)
(-> password security/safe-unmask-data native-module/sha3)]
:on-success on-success
:on-error on-error}]]]})
(rf/reg-event-fx :wallet/make-seed-phrase-keypair-fully-operable make-seed-phrase-keypair-fully-operable)
(defn import-keypair-by-seed-phrase
[_ [{:keys [keypair-key-uid seed-phrase password on-success on-error]}]]
{:fx [[:import-keypair-by-seed-phrase
{:keypair-key-uid keypair-key-uid
:seed-phrase seed-phrase
:password password
:on-success (fn []
(rf/dispatch [:wallet/make-keypairs-accounts-fully-operable
#{keypair-key-uid}])
(cond
(vector? on-success) (rf/dispatch (conj on-success))
(fn? on-success) (on-success)))
:on-error (fn [error]
(rf/dispatch [:wallet/import-keypair-by-seed-phrase-failed error])
(cond
(vector? on-error) (rf/dispatch (conj on-error error))
(fn? on-error) (on-error error)))}]]})
(rf/reg-event-fx :wallet/import-keypair-by-seed-phrase import-keypair-by-seed-phrase)
(defn import-keypair-by-seed-phrase-failed
[_ [error]]
(let [error-type (-> error ex-message keyword)
error-data (ex-data error)]
(when-not (and (= error-type :import-keypair-by-seed-phrase/import-error)
(= (:hint error-data) :incorrect-seed-phrase-for-keypair))
{:fx [[:dispatch
[:toasts/upsert
{:type :negative
:theme :dark
:text (:error error-data)}]]]})))
(rf/reg-event-fx :wallet/import-keypair-by-seed-phrase-failed import-keypair-by-seed-phrase-failed)
@@ -2,7 +2,9 @@
(:require
[cljs.test :refer-macros [deftest is testing]]
matcher-combinators.test
[status-im.contexts.settings.wallet.events :as sut]))
[native-module.core :as native-module]
[status-im.contexts.settings.wallet.events :as sut]
[utils.security.core :as security]))
(def mock-key-uid "key-1")
(defn mock-db
@@ -95,3 +97,33 @@
(let [effects (sut/success-keypair-qr-scan nil [connection-string keypairs-key-uids])
fx (:fx effects)]
(is (some? fx))))))
(deftest wallet-validate-seed-phrase-test
(let [cofx {:db {}}
seed-phrase-masked (security/mask-data "seed phrase")
on-success #(prn "success")
on-error #(prn "error")
expected {:fx [[:multiaccount/validate-mnemonic
[seed-phrase-masked on-success on-error]]]}]
(is (= expected
(sut/wallet-validate-seed-phrase
cofx
[seed-phrase-masked on-success on-error])))))
(deftest make-seed-phrase-keypair-fully-operable-test
(let [cofx {:db {}}
mnemonic "seed phrase"
password "password"
mnemonic-masked (security/mask-data mnemonic)
password-masked (security/mask-data password)
on-success #(prn "success")
on-error #(prn "error")
expected {:fx [[:json-rpc/call
[{:method "accounts_makeSeedPhraseKeypairFullyOperable"
:params [mnemonic (native-module/sha3 password)]
:on-success fn?
:on-error fn?}]]]}]
(is (match? expected
(sut/make-seed-phrase-keypair-fully-operable
cofx
[mnemonic-masked password-masked on-success on-error])))))
@@ -6,27 +6,31 @@
[utils.re-frame :as rf]))
(defn view
[props keypair]
(let [has-paired-device (rf/sub [:pairing/has-paired-devices])
missing-keypair? (= (:stored props) :missing)
on-scan-qr (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.scan-keypair-qr
[(:key-uid keypair)]])
[keypair])
on-show-qr (rn/use-callback #(rf/dispatch [:open-modal
:screen/settings.encrypted-key-pair-qr
keypair])
[keypair])
on-remove-keypair (rn/use-callback #(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:content (fn []
[remove-key-pair/view keypair])}])
[keypair])
on-rename-keypair (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.rename-keypair
keypair])
[keypair])]
[{:keys [drawer-props keypair]}]
(let [has-paired-device (rf/sub [:pairing/has-paired-devices])
missing-keypair? (= (:stored drawer-props) :missing)
on-scan-qr (rn/use-callback #(rf/dispatch [:open-modal
:screen/settings.scan-keypair-qr
[(:key-uid keypair)]])
[keypair])
on-show-qr (rn/use-callback #(rf/dispatch [:open-modal
:screen/settings.encrypted-key-pair-qr
keypair])
[keypair])
on-remove-keypair (rn/use-callback #(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:content (fn []
[remove-key-pair/view keypair])}])
[keypair])
on-rename-keypair (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.rename-keypair
keypair])
[keypair])
on-import-seed-phrase (rn/use-callback
#(rf/dispatch [:open-modal :screen/settings.import-seed-phrase keypair])
[keypair])]
[:<>
[quo/drawer-top props]
[quo/drawer-top drawer-props]
[quo/action-drawer
[(when has-paired-device
(if-not missing-keypair?
@@ -38,8 +42,15 @@
:accessibility-label :import-by-scan-qr
:label (i18n/label :t/import-by-scanning-encrypted-qr)
:on-press on-scan-qr}]))
(when (= (:type props) :keypair)
[{:icon :i/edit
(when (= (:type drawer-props) :keypair)
[(when missing-keypair?
(case (:type keypair)
:seed {:icon :i/seed
:accessibility-label :import-seed-phrase
:label (i18n/label :t/import-by-entering-recovery-phrase)
:on-press #(on-import-seed-phrase keypair)}
nil))
{:icon :i/edit
:accessibility-label :rename-key-pair
:label (i18n/label :t/rename-key-pair)
:on-press on-rename-keypair}
@@ -0,0 +1,65 @@
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.import-seed-phrase.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.enter-seed-phrase.view :as enter-seed-phrase]
[status-im.common.standard-authentication.core :as standard-auth]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn import-seed-phrase-controls
[{:keys [submit-disabled?
container-style
prepare-seed-phrase
seed-phrase
set-invalid-seed-phrase
focus-input]}]
(let [keypair (rf/sub [:get-screen-params])
customization-color (rf/sub [:profile/customization-color])
show-errors (rn/use-callback
#(js/setTimeout
(fn []
(focus-input)
(reagent/next-tick set-invalid-seed-phrase))
600))
on-import-error (rn/use-callback
(fn [_error]
(rf/dispatch [:hide-bottom-sheet])
(show-errors)))
on-import-success (rn/use-callback
(fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:navigate-back])))
on-auth-success (rn/use-callback
(fn [password]
(rf/dispatch [:wallet/import-keypair-by-seed-phrase
{:keypair-key-uid (:key-uid keypair)
:seed-phrase (prepare-seed-phrase seed-phrase)
:password password
:on-success on-import-success
:on-error on-import-error}]))
[keypair seed-phrase on-import-success on-import-error])]
[standard-auth/slide-button
{:blur? true
:size :size-48
:customization-color customization-color
:track-text (i18n/label :t/slide-to-import)
:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/import-key-pair)
:auth-button-icon-left :i/seed
:container-style container-style
:disabled? submit-disabled?
:dependencies [on-auth-success]}]))
(defn view
[]
(let [keypair (rf/sub [:get-screen-params])]
[quo/overlay {:type :shell}
[enter-seed-phrase/screen
{:keypair keypair
:navigation-icon :i/close
:render-controls import-seed-phrase-controls
:title (i18n/label :t/enter-recovery-phrase)
:initial-insets (safe-area/get-insets)}]]))
@@ -14,11 +14,12 @@
(rf/dispatch [:navigate-back]))
(defn on-options-press
[{:keys [theme]
:as props} keypair]
[{:keys [drawer-props keypair]}]
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/view props keypair])
:theme theme}]))
{:content (fn [] [actions/view
{:drawer-props drawer-props
:keypair keypair}])
:theme (:theme drawer-props)}]))
(defn options-drawer-props
[{{:keys [name]} :keypair
@@ -48,15 +49,17 @@
on-press (rn/use-callback
(fn []
(on-options-press
(options-drawer-props
{:theme theme
:keypair item
:type (if default-keypair? :default-keypair :keypair)
:stored :on-device
:shortened-key shortened-key
:customization-color customization-color
:profile-picture profile-picture})
item))
{:keypair item
:drawer-props (options-drawer-props
{:theme theme
:keypair item
:type (if default-keypair?
:default-keypair
:keypair)
:stored :on-device
:shortened-key shortened-key
:customization-color customization-color
:profile-picture profile-picture})}))
[customization-color default-keypair? item
profile-picture shortened-key theme])]
[quo/keypair
@@ -78,13 +81,13 @@
(rf/dispatch [:show-bottom-sheet
{:theme :dark
:content (fn [] [actions/view
(options-drawer-props
{:theme :dark
:type :keypair
:stored :missing
:blur? true
:keypair keypair-data})
keypair-data])}]))
{:keypair keypair-data
:drawer-props (options-drawer-props
{:theme :dark
:type :keypair
:stored :missing
:blur? true
:keypair keypair-data})}])}]))
(defn view
[]
@@ -112,16 +115,16 @@
:accessibility-label :keypairs-and-accounts-header
:customization-color customization-color}]
[rn/view {:style style/settings-keypairs-container}
(when (seq missing-keypairs)
[quo/missing-keypairs
{:blur? true
:keypairs missing-keypairs
:on-import-press on-import-press
:container-style style/missing-keypairs-container-style
:on-options-press on-missing-keypair-options-press}])
[rn/flat-list
{:data operable-keypairs
:render-fn keypair
:header (when (seq missing-keypairs)
[quo/missing-keypairs
{:blur? true
:keypairs missing-keypairs
:on-import-press on-import-press
:container-style style/missing-keypairs-container-style
:on-options-press on-missing-keypair-options-press}])
:render-data {:profile-picture profile-picture
:compressed-key compressed-key
:customization-color customization-color}
@@ -8,6 +8,7 @@
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.contexts.settings.wallet.saved-addresses.add-address-to-save.style :as style]
[status-im.contexts.wallet.common.validation :as validation]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -18,7 +19,7 @@
(defn- validate-input
[account-addresses saved-addresses user-input]
(cond
(or (nil? user-input) (= user-input ""))
(string/blank? user-input)
nil
(contains? saved-addresses user-input)
@@ -89,7 +90,7 @@
(defn- existing-saved-address
[{:keys [address]}]
(let [{:keys [name customization-color chain-short-names ens has-ens?]}
(let [{:keys [name customization-color chain-short-names ens ens?]}
(rf/sub [:wallet/saved-address-by-address
address])]
[rn/view {:style style/existing-saved-address-container}
@@ -103,43 +104,65 @@
:active-state? true
:user-props {:name name
:address (str chain-short-names address)
:ens (when has-ens? ens)
:ens (when ens? ens)
:customization-color customization-color
:blur? true}
:container-style style/saved-address-item}]]))
(defn view
[]
(let [profile-color (rf/sub [:profile/customization-color])
accounts-addresses (rf/sub [:wallet/addresses])
saved-addresses-addresses (rf/sub [:wallet/saved-addresses-addresses])
[input-value set-input-value] (rn/use-state nil)
[error set-error] (rn/use-state nil)
error? (some? error)
validate #(validate-input accounts-addresses
saved-addresses-addresses
%)
clear-input (rn/use-callback
(fn []
(set-input-value nil)
(set-error nil)))
on-change-text (rn/use-callback
(fn [new-value]
(let [lowercase-value (string/lower-case new-value)]
(set-error (validate lowercase-value))
(set-input-value lowercase-value))))
paste-into-input (rn/use-callback #(clipboard/get-string
(fn [clipboard-text]
(on-change-text clipboard-text))))
on-press-continue (rn/use-callback
(fn []
(rf/dispatch [:wallet/set-address-to-save
{:address input-value}])
(rf/dispatch
[:navigate-to-within-stack
[:screen/settings.save-address
:screen/settings.add-address-to-save]]))
[input-value])]
(let [profile-color (rf/sub [:profile/customization-color])
accounts-addresses (rf/sub [:wallet/addresses])
saved-addresses-addresses (rf/sub [:wallet/saved-addresses-addresses])
[address-or-ens set-address-or-ens] (rn/use-state "")
[ens-address set-ens-address] (rn/use-state "")
[error set-error] (rn/use-state nil)
error? (some? error)
ens-name? (validation/ens-name? address-or-ens)
address (if ens-name? ens-address address-or-ens)
button-disabled? (or (string/blank? address-or-ens)
(and ens-name? (string/blank? ens-address))
error?)
validate #(validate-input accounts-addresses
saved-addresses-addresses
%)
clear-input (rn/use-callback
(fn []
(set-address-or-ens "")
(set-ens-address "")
(set-error nil)))
on-ens-resolve (rn/use-callback
(fn [resolved-address]
(set-error (validate resolved-address))
(set-ens-address resolved-address)))
on-change-text (rn/use-callback
(fn [new-value]
(let [trimmed-value (string/trim new-value)]
(set-error (validate (string/lower-case trimmed-value)))
(set-address-or-ens trimmed-value)
(set-ens-address "")
(when (validation/ens-name? trimmed-value)
(debounce/debounce-and-dispatch
[:wallet/resolve-ens
{:ens new-value
:on-success on-ens-resolve
:on-error #(set-error :ens-not-registered)}]
300)))))
paste-into-input (rn/use-callback #(clipboard/get-string
(fn [clipboard-text]
(on-change-text clipboard-text))))
on-press-continue (rn/use-callback
(fn []
(rf/dispatch
[:wallet/set-address-to-save
{:address address
:ens address-or-ens
:ens? ens-name?}])
(rf/dispatch
[:navigate-to-within-stack
[:screen/settings.save-address
:screen/settings.add-address-to-save]]))
[address ens-name? address-or-ens])]
(rn/use-unmount #(rf/dispatch [:wallet/clear-address-to-save]))
[quo/overlay {:type :shell}
[floating-button-page/view
@@ -153,8 +176,7 @@
:accessibility-label :add-address-to-save-page-nav}]
:footer [quo/button
{:customization-color profile-color
:disabled? (or (string/blank? input-value)
error?)
:disabled? button-disabled?
:on-press on-press-continue}
(i18n/label :t/continue)]}
[quo/page-top
@@ -164,7 +186,7 @@
:description :text
:description-text (i18n/label :t/add-address-to-save-description)}]
[address-input
{:input-value input-value
{:input-value address-or-ens
:on-change-text on-change-text
:paste-into-input paste-into-input
:clear-input clear-input}]
@@ -172,4 +194,4 @@
[:<>
[error-view {:error error}]
(when (= error :existing-saved-address)
[existing-saved-address {:address input-value}])])]]))
[existing-saved-address {:address address}])])]]))
@@ -55,10 +55,8 @@
:address "0x2"
:mixedcase-address "0x2"
:chain-short-names "eth:arb1:oeth:"
:has-ens? false
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
:ens? false
:network-preferences-names `(:mainnet :arbitrum :optimism)
:name "Bob"
:created-at 1716826714
:ens ""
@@ -76,10 +74,8 @@
:address "0x2"
:mixedcase-address "0x2"
:chain-short-names "eth:arb1:oeth:"
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
:has-ens? false
:network-preferences-names `(:mainnet :arbitrum :optimism)
:ens? false
:name "Bob"
:created-at 1716826714
:ens ""
@@ -89,10 +85,8 @@
:address "0x1"
:mixedcase-address "0x1"
:chain-short-names "eth:arb1:oeth:"
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
:has-ens? false
:network-preferences-names `(:mainnet :arbitrum :optimism)
:ens? false
:name "Amy"
:created-at 1716826806
:ens ""
@@ -7,9 +7,10 @@
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.not-implemented :as not-implemented]
[status-im.constants :as constants]
[status-im.contexts.settings.wallet.saved-addresses.save-address.style :as style]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.sheets.network-preferences.view :as network-preferences]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -17,29 +18,86 @@
[]
(rf/dispatch [:navigate-back]))
(defn- extract-address
[address]
(re-find constants/regx-address-contains address))
(defn- network-preferences-sheet
[{:keys [address color selected-networks set-selected-networks]}]
(fn []
[network-preferences/view
{:title (i18n/label :t/add-network-preferences)
:description (i18n/label :t/saved-address-network-preference-selection-description)
:button-label (i18n/label :t/add-preferences)
:blur? true
:selected-networks (set selected-networks)
:account {:address address
:color color}
:on-save (fn [chain-ids]
(set-selected-networks (map network-utils/id->network chain-ids))
(rf/dispatch [:hide-bottom-sheet]))}]))
(defn view
[]
(let [{:keys [address]} (rf/sub [:wallet/saved-address])
(let [{:keys [address ens ens?]} (rf/sub [:wallet/saved-address])
[network-prefixes address-without-prefix] (utils/split-prefix-and-address address)
[address-label set-address-label] (rn/use-state "")
[address-color set-address-color] (rn/use-state (rand-nth colors/account-colors))
placeholder (i18n/label :t/address-name)
on-press-save (rn/use-callback
(fn []
(let [address-without-prefix (extract-address address)]
(rf/dispatch [:wallet/save-address
{:on-success
[:wallet/add-saved-address-success
(i18n/label :t/address-saved)]
:on-error
[:wallet/add-saved-address-failed]
:name address-label
:address address-without-prefix
:customization-color address-color}])))
[address address-label address-color])]
[selected-networks set-selected-networks]
(rn/use-state (network-utils/network-preference-prefix->network-names network-prefixes))
chain-short-names (rn/use-memo
#(network-utils/network-names->network-preference-prefix
selected-networks)
[selected-networks])
placeholder (i18n/label :t/address-name)
address-text (rn/use-callback
(fn []
[quo/address-text
{:full-address? true
:address (str chain-short-names address-without-prefix)
:format :long}])
[address-without-prefix chain-short-names])
open-network-preferences (rn/use-callback
(fn []
(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:shell? true
:content (network-preferences-sheet
{:address address-without-prefix
:color address-color
:selected-networks selected-networks
:set-selected-networks
set-selected-networks})}]))
[address selected-networks address-color])
on-press-save (rn/use-callback
(fn []
(rf/dispatch [:wallet/save-address
{:on-success
[:wallet/add-saved-address-success
(i18n/label :t/address-saved)]
:on-error
[:wallet/add-saved-address-failed]
:name address-label
:ens ens
:address address-without-prefix
:customization-color address-color
:chain-short-names chain-short-names}]))
[address-without-prefix chain-short-names address-label
address-color])
data-item-props (rn/use-memo
#(cond-> {:status :default
:size :default
:subtitle-type :default
:label :none
:blur? true
:icon-right? true
:right-icon :i/advanced
:card? true
:title (i18n/label :t/address)
:subtitle ens
:custom-subtitle address-text
:on-press open-network-preferences
:container-style style/data-item}
ens?
(dissoc :custom-subtitle))
[ens ens? open-network-preferences address-text])]
[quo/overlay {:type :shell}
[floating-button-page/view
{:footer-container-padding 0
@@ -92,22 +150,4 @@
[quo/divider-line
{:blur? true
:container-style style/color-picker-bottom-divider}]
[quo/data-item
{:status :default
:size :default
:subtitle-type :default
:label :none
:blur? true
:icon-right? true
:right-icon :i/advanced
:card? true
:title (i18n/label :t/address)
:custom-subtitle (rn/use-callback
(fn []
[quo/address-text
{:full-address? true
:address address
:format :long}])
[address])
:on-press not-implemented/alert
:container-style style/data-item}]]]))
[quo/data-item data-item-props]]]))
@@ -1,4 +1,4 @@
(ns status-im.contexts.settings.wallet.saved-addresses.sheets.share-address.style)
(ns status-im.contexts.settings.wallet.saved-addresses.share-address.style)
(def screen-container
{:flex 1})
@@ -1,9 +1,9 @@
(ns status-im.contexts.settings.wallet.saved-addresses.sheets.share-address.view
(ns status-im.contexts.settings.wallet.saved-addresses.share-address.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.platform :as platform]
[status-im.contexts.settings.wallet.saved-addresses.sheets.share-address.style :as style]
[status-im.contexts.settings.wallet.saved-addresses.share-address.style :as style]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.sheets.network-preferences.view :as network-preferences]
@@ -33,16 +33,20 @@
:isNewTask true})}]))
(defn- open-preferences
[selected-networks set-selected-networks]
[{:keys [address color selected-networks set-selected-networks]}]
(let [on-save (fn [chain-ids]
(rf/dispatch [:hide-bottom-sheet])
(set-selected-networks (map network-utils/id->network chain-ids)))
sheet-content (fn []
[network-preferences/view
{:blur? true
{:description (i18n/label
:t/saved-address-network-preference-selection-description)
:button-label (i18n/label :t/display)
:blur? true
:selected-networks (set selected-networks)
:on-save on-save
:button-label (i18n/label :t/display)}])]
:account {:address address
:color color}
:on-save on-save}])]
(rf/dispatch [:show-bottom-sheet
{:theme :dark
:shell? true
@@ -54,10 +58,13 @@
network-preferences-names]} (rf/sub [:get-screen-params])
[wallet-type set-wallet-type] (rn/use-state :legacy)
[selected-networks set-selected-networks] (rn/use-state network-preferences-names)
on-settings-press (rn/use-callback #(open-preferences
selected-networks
set-selected-networks)
[selected-networks])
on-settings-press (rn/use-callback
#(open-preferences
{:selected-networks selected-networks
:set-selected-networks set-selected-networks
:address address
:color customization-color})
[address customization-color selected-networks])
on-legacy-press (rn/use-callback #(set-wallet-type :legacy))
on-multichain-press (rn/use-callback #(set-wallet-type :multichain))
share-title (str name " " (i18n/label :t/address))
@@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.platform :as platform]
[status-im.common.not-implemented :as not-implemented]
@@ -52,10 +53,11 @@
open-remove-confirmation-sheet (rn/use-callback
#(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:shell? true
:content (fn []
[remove-address/view opts])}])
{:theme :dark
:shell? true
:blur-background colors/bottom-sheet-background-blur
:content (fn []
[remove-address/view opts])}])
[opts])
open-show-address-qr (rn/use-callback
#(rf/dispatch [:open-modal
@@ -1,6 +1,7 @@
(ns status-im.contexts.settings.wallet.saved-addresses.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
@@ -20,27 +21,29 @@
:container-style style/empty-container-style}]))
(defn- saved-address
[{:keys [name address chain-short-names customization-color has-ens? ens network-preferences-names]}]
[{:keys [name address chain-short-names customization-color ens? ens network-preferences-names]}]
(let [full-address (str chain-short-names address)
on-press-saved-address (rn/use-callback
#(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:shell? true
:content (fn []
[address-options/view
{:address address
:chain-short-names chain-short-names
:full-address full-address
:name name
:network-preferences-names network-preferences-names
:customization-color customization-color}])}])
{:theme :dark
:shell? true
:blur-background colors/bottom-sheet-background-blur
:content (fn []
[address-options/view
{:address address
:chain-short-names chain-short-names
:full-address full-address
:name name
:network-preferences-names
network-preferences-names
:customization-color customization-color}])}])
[address chain-short-names full-address name customization-color])]
[quo/saved-address
{:blur? true
:user-props {:name name
:address full-address
:ens (when has-ens? ens)
:ens (when ens? ens)
:customization-color customization-color
:blur? true}
:container-style {:margin-horizontal 8}
@@ -88,12 +91,13 @@
:icon :i/add
:container-style style/title-container}]
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:render-section-header-fn header
:render-section-footer-fn footer
:sections saved-addresses
:render-fn saved-address
:separator [rn/view {:style {:height 4}}]
:content-container-style {:flex-grow 1}
:empty-component [empty-state]}]]))
{:key-fn :title
:shows-vertical-scroll-indicator false
:sticky-section-headers-enabled false
:render-section-header-fn header
:render-section-footer-fn footer
:sections saved-addresses
:render-fn saved-address
:separator [rn/view {:style {:height 4}}]
:content-container-style {:flex-grow 1}
:empty-component [empty-state]}]]))
@@ -8,11 +8,11 @@
(defn open-saved-addresses-settings-modal
[]
(rf/dispatch [:navigate-to-within-stack [:screen/settings.saved-addresses :screen/settings]]))
(rf/dispatch [:navigate-to-within-stack [:screen/settings.saved-addresses :settings]]))
(defn open-keypairs-and-accounts-settings-modal
[]
(rf/dispatch [:navigate-to-within-stack [:screen/settings.keypairs-and-accounts :screen/settings]]))
(rf/dispatch [:navigate-to-within-stack [:screen/settings.keypairs-and-accounts :settings]]))
(defn basic-settings-options
[]
@@ -38,7 +38,7 @@
(defn open-network-settings-modal
[]
(rf/dispatch [:navigate-to-within-stack [:screen/settings.network-settings :screen/settings]]))
(rf/dispatch [:navigate-to-within-stack [:screen/settings.network-settings :settings]]))
(defn advanced-settings-options
[]
@@ -8,7 +8,9 @@
(defn bottom-tabs-container
[pass-through? height]
[{:height height}
{:background-color (if pass-through? :transparent colors/neutral-100)
{:background-color (if (and platform/ios? pass-through?)
:transparent
colors/neutral-100)
:flex 1
:align-items :center
:height (utils/bottom-tabs-container-height)
@@ -1,6 +1,7 @@
(ns status-im.contexts.shell.jump-to.components.jump-to-screen.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[status-im.contexts.shell.jump-to.utils :as utils]))
;;;; Placeholder
@@ -63,4 +64,4 @@
:left 0
:right 0
:top 0
:background-color colors/neutral-100-opa-70-blur})
:background-color (when platform/ios? colors/neutral-100-opa-70-blur)})
@@ -3,7 +3,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[react-native.safe-area :as safe-area]
@@ -84,7 +83,7 @@
(let [pass-through? (rf/sub [:shell/shell-pass-through?])]
[rn/view {:style (style/top-nav-blur-overlay-container (+ 56 top) pass-through?)}
(when pass-through?
[blur/view (bottom-tabs/blur-overlay-params style/top-nav-blur-overlay)])]))
[quo/blur (bottom-tabs/blur-overlay-params style/top-nav-blur-overlay)])]))
(defn view
[]
@@ -4,7 +4,6 @@
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
@@ -254,7 +253,7 @@
[hole-view/hole-view
{:style style/hole
:holes [(assoc qr-view-finder :borderRadius 16)]}
[blur/view
[quo/blur
{:style style/absolute-fill
:blur-amount 10
:blur-type :transparent
@@ -8,7 +8,7 @@
(defn view
[]
(let [tokens-loading? (rf/sub [:wallet/tokens-loading?])
(let [tokens-loading? (rf/sub [:wallet/current-viewing-account-tokens-loading?])
tokens (rf/sub [:wallet/current-viewing-account-token-values])
{:keys [watch-only?]} (rf/sub [:wallet/current-viewing-account])]
(if tokens-loading?
@@ -11,8 +11,8 @@
(defn get-keypairs-success
[{:keys [db]} [keypairs]]
(let [parsed-keypairs (data-store/parse-keypairs keypairs)
default-key-uid (:key-uid (first parsed-keypairs))]
(let [parsed-keypairs (data-store/rpc->keypairs keypairs)
default-key-uid (:key-uid (some #(when (= (:type %) :profile) %) parsed-keypairs))]
{:db (-> db
(assoc-in [:wallet :keypairs] parsed-keypairs)
(assoc-in [:wallet :ui :create-account :selected-keypair-uid] default-key-uid))}))
@@ -4,7 +4,6 @@
[native-module.core :as native-module]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.wallet.add-account.create-account.new-keypair.backup-recovery-phrase.style :as
@@ -86,7 +85,7 @@
:first-half? false}]])
(when-not @revealed?
[rn/view {:style style/blur-container}
[blur/view (style/blur theme)]])]
[quo/blur (style/blur theme)]])]
(when-not @revealed?
[rn/view
{:style {:padding-horizontal 20
@@ -34,8 +34,8 @@
(->> given-accounts
(filter (fn [{:keys [path]}]
(not (string/starts-with? path constants/path-eip1581))))
(map (fn [{:keys [customization-color emoji name address]}]
{:account-props {:customization-color customization-color
(map (fn [{:keys [color emoji name address]}]
{:account-props {:customization-color color
:size 32
:emoji emoji
:type :default
@@ -48,20 +48,21 @@
:action :none}))))
(defn- keypair
[item index _
[item _ _
{:keys [profile-picture compressed-key selected-key-uid set-selected-key-uid customization-color]}]
(let [accounts (parse-accounts (:accounts item))]
(let [profile-keypair? (= (:type item) :profile)
accounts (parse-accounts (:accounts item))]
[quo/keypair
{:customization-color customization-color
:profile-picture (when (zero? index) profile-picture)
:profile-picture (when profile-keypair? profile-picture)
:status-indicator false
:type (if (zero? index) :default-keypair :other)
:type (if profile-keypair? :default-keypair :other)
:stored :on-device
:on-options-press #(js/alert "Options pressed")
:action :selector
:blur? false
:details {:full-name (:name item)
:address (when (zero? index)
:address (when profile-keypair?
(utils/get-shortened-compressed-key compressed-key))}
:on-press #(set-selected-key-uid (:key-uid item))
:accounts accounts
@@ -110,6 +110,20 @@
new-request? (update-in [:wallet :accounts] update-vals #(dissoc % :collectibles)))
:fx collectible-requests})))
(defn request-new-collectibles-for-account-from-signal
[{:keys [db]} [address]]
(let [pending-requests (get-in db [:wallet :ui :collectibles :pending-requests] 0)
[request-id] (get-unique-collectible-request-id 1)]
{:db (assoc-in db [:wallet :ui :collectibles :pending-requests] (inc pending-requests))
:fx [[:dispatch
[:wallet/request-new-collectibles-for-account
{:request-id request-id
:account address
:amount collectibles-request-batch-size}]]]}))
(rf/reg-event-fx :wallet/request-new-collectibles-for-account-from-signal
request-new-collectibles-for-account-from-signal)
(rf/reg-event-fx
:wallet/request-collectibles-for-current-viewing-account
(fn [{:keys [db]} _]
@@ -0,0 +1,70 @@
(ns status-im.contexts.wallet.collectible.events-test
(:require
[cljs.test :refer-macros [deftest is testing]]
matcher-combinators.test
[status-im.contexts.wallet.collectible.events :as events]))
(deftest store-collectibles-test
(testing "flush-collectibles"
(let [collectible-1 {:collectible-data {:image-url "https://..." :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-2 {:collectible-data {:image-url "" :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-3 {:collectible-data {:image-url "" :animation-url nil}
:ownership [{:address "0x2"
:balance "1"}]}
db {:wallet {:ui {:collectibles {:pending-requests 0
:fetched {"0x1" [collectible-1
collectible-2]
"0x2" [collectible-3]}}}
:accounts {"0x1" {}
"0x3" {}}}}
expected-db {:wallet {:ui {:collectibles {}}
:accounts {"0x1" {:collectibles (list collectible-1 collectible-2)}
"0x2" {:collectibles (list collectible-3)}
"0x3" {}}}}
result-db (:db (events/flush-collectibles {:db db}))]
(is (match? result-db expected-db)))))
(deftest clear-stored-collectibles-test
(let [db {:wallet {:accounts {"0x1" {:collectibles [{:id 1} {:id 2}]}
"0x2" {"some other stuff" "with any value"
:collectibles [{:id 3}]}
"0x3" {}}}}]
(testing "clear-stored-collectibles"
(let [expected-db {:wallet {:accounts {"0x1" {}
"0x2" {"some other stuff" "with any value"}
"0x3" {}}}}
effects (events/clear-stored-collectibles {:db db})
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-last-collectible-details-test
(testing "store-last-collectible-details"
(let [db {:wallet {}}
last-collectible {:description "Pandaria"
:image-url "https://..."}
expected-db {:wallet {:last-collectible-details {:description "Pandaria"
:image-url "https://..."}}}
effects (events/store-last-collectible-details {:db db}
[last-collectible])
result-db (:db effects)]
(is (match? result-db expected-db)))))
(deftest request-new-collectibles-for-account-from-signal-test
(testing "request new collectibles for account from signal"
(let [db {:wallet {}}
address "0x1"
expected {:db {:wallet {:ui {:collectibles {:pending-requests 1}}}}
:fx [[:dispatch
[:wallet/request-new-collectibles-for-account
{:request-id 0
:account address
:amount events/collectibles-request-batch-size}]]]}
effects (events/request-new-collectibles-for-account-from-signal {:db db}
[address])]
(is (match? expected effects)))))
@@ -17,7 +17,8 @@
"image/gif"
"image/bmp"
"image/png"
"image/webp"})
"image/webp"
"image/avif"})
(defn supported-file?
[collectible-type]
@@ -83,7 +83,9 @@
(colors/theme-colors colors/white-opa-50 colors/neutral-95-opa-70-blur theme))]
[rn/view {:style style/animated-header}
[reanimated/blur-view
{:style {:flex 1}
{:style {:flex 1
:background-color (when platform/android?
(colors/theme-colors colors/white colors/neutral-80 theme))}
:blur-type :transparent
:overlay-color :transparent
:blur-amount blur-amount
@@ -114,7 +114,17 @@
[prefix]
(as-> prefix $
(string/split $ ":")
(map short-name->network $)))
(map short-name->network $)
(remove nil? $)))
(defn network-names->network-preference-prefix
[network-names]
(if (empty? network-names)
""
(->> network-names
(map network->short-name)
(remove nil?)
short-names->network-preference-prefix)))
(defn network-ids->formatted-text
[network-ids]
@@ -33,7 +33,18 @@
(= expected (utils/network-preference-prefix->network-names short-names))
(seq [:mainnet]) "eth"
(seq [:mainnet :optimism]) "eth:oeth"
(seq [:mainnet :optimism :arbitrum]) "eth:oeth:arb1"))
(seq [:mainnet :optimism :arbitrum]) "eth:oeth:arb1"
(seq [:mainnet :arbitrum]) "eth:sol:arb1"))
(deftest network-names->network-preference-prefix-test
(are [expected network-names]
(= expected (utils/network-names->network-preference-prefix network-names))
"eth:" [:mainnet]
"eth:oeth:" [:mainnet :optimism]
"eth:oeth:arb1:" [:mainnet :optimism :arbitrum]
"eth:arb1:" [:mainnet :polygon :arbitrum]
"" []
"" nil))
(deftest network-ids->formatted-text-test
(testing "Empty network-ids should return an empty string"
@@ -1,6 +1,6 @@
(ns status-im.contexts.wallet.common.validation
(:require [status-im.constants :as constants]))
(defn ens-name? [s] (re-find constants/regx-ens s))
(defn ens-name? [s] (boolean (re-find constants/regx-ens s)))
(defn eth-address? [s] (re-find constants/regx-multichain-address s))
(defn private-key? [s] (re-find constants/regx-private-key s))
+33 -40
View File
@@ -1,6 +1,5 @@
(ns status-im.contexts.wallet.data-store
(:require
[camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske]
[clojure.set :as set]
[clojure.string :as string]
@@ -22,7 +21,9 @@
(defn add-keys-to-account
[account]
(assoc account :watch-only? (= (:type account) :watch)))
(-> account
(assoc :watch-only? (= (:type account) :watch))
(assoc :default-account? (:wallet account))))
(defn- sanitize-emoji
"As Desktop uses Twemoji, the emoji received can be an img tag
@@ -44,9 +45,11 @@
(update :test-preferred-chain-ids chain-ids-string->set)
(update :type keyword)
(update :operable keyword)
(update :color #(if (seq %) (keyword %) constants/account-default-customization-color))
(update :color
#(if (and (not (keyword? %)) (string/blank? %))
constants/account-default-customization-color
(keyword %)))
(update :emoji sanitize-emoji)
(assoc :default-account? (:wallet account))
add-keys-to-account))
(defn rpc->accounts
@@ -107,50 +110,40 @@
:nativeCurrencySymbol :native-currency-symbol
:nativeCurrencyName :native-currency-symbol})))
(defn sort-keypairs
[keypairs]
(sort-by #(if (some (fn [account]
(string/starts-with? (:path account) constants/path-eip1581))
(:accounts %))
0
1)
keypairs))
(defn get-keypair-lowest-operability
[{:keys [accounts]}]
(cond
(some #(= (:operable %) :no) accounts)
:no
(defn sort-and-rename-keypairs
[keypairs]
(let [sorted-keypairs (sort-keypairs keypairs)]
(map (fn [item]
(update item
:accounts
(fn [accounts]
(map
(fn [{:keys [colorId] :as account}]
(assoc account
:customization-color
(if (seq colorId)
(keyword colorId)
:blue)))
accounts))))
sorted-keypairs)))
(some #(= (:operable %) :partially) accounts)
:partially
(defn parse-keypairs
[keypairs]
(let [renamed-data (sort-and-rename-keypairs keypairs)]
(cske/transform-keys csk/->kebab-case-keyword renamed-data)))
:else
:fully))
(defn- network-short-names->full-names
[short-names-string]
(->> (string/split short-names-string constants/chain-id-separator)
(map network-utils/short-name->network)
(remove nil?)
set))
(defn- add-keys-to-keypair
[keypair]
(assoc keypair :lowest-operability (get-keypair-lowest-operability keypair)))
(defn rpc->keypair
[keypair]
(-> keypair
(update :type keyword)
(update :accounts #(map rpc->account %))
add-keys-to-keypair))
(defn rpc->keypairs
[keypairs]
(->> (map rpc->keypair keypairs)
(sort-by #(if (= (:type %) :profile) 0 1))))
(defn- add-keys-to-saved-address
[saved-address]
(-> saved-address
(assoc :network-preferences-names
(network-short-names->full-names (:chain-short-names saved-address)))
(assoc :has-ens? (not (string/blank? (:ens saved-address))))))
(network-utils/network-preference-prefix->network-names (:chain-short-names saved-address)))
(assoc :ens? (not (string/blank? (:ens saved-address))))))
(defn rpc->saved-address
[saved-address]
+1 -2
View File
@@ -6,5 +6,4 @@
:selected-networks (set constants/default-network-names)})
(def defaults
{:ui {:network-filter network-filter-defaults
:tokens-loading? true}})
{:ui {:network-filter network-filter-defaults}})
+72 -1
View File
@@ -2,8 +2,16 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[promesa.core :as promesa]
[re-frame.core :as rf]
[taoensso.timbre :as log]))
[status-im.common.json-rpc.events :as json-rpc]
[taoensso.timbre :as log]
[utils.security.core :as security]
[utils.transforms :as transforms]))
(defn- error-message
[kw]
(-> kw symbol str))
(rf/reg-fx
:effects.wallet/create-account-from-mnemonic
@@ -17,3 +25,66 @@
{:MnemonicPhrase phrase
:paths paths}
on-success))))
(defn validate-mnemonic
[mnemonic]
(-> mnemonic
(security/safe-unmask-data)
(native-module/validate-mnemonic)
(promesa/then (fn [result]
(let [{:keys [keyUID]} (transforms/json->clj result)]
{:key-uid keyUID})))))
(rf/reg-fx
:multiaccount/validate-mnemonic
(fn [[mnemonic on-success on-error]]
(-> (validate-mnemonic mnemonic)
(promesa/then (fn [{:keys [key-uid]}]
(when (fn? on-success)
(on-success mnemonic key-uid))))
(promesa/catch (fn [error]
(when (and error (fn? on-error))
(on-error error)))))))
(defn make-seed-phrase-fully-operable
[mnemonic password]
(promesa/create
(fn [resolver rejecter]
(json-rpc/call {:method "accounts_makeSeedPhraseKeypairFullyOperable"
:params [(security/safe-unmask-data mnemonic)
(-> password security/safe-unmask-data native-module/sha3)]
:on-error (fn [error]
(rejecter (ex-info (str error) {:error error})))
:on-success (fn [value]
(resolver {:value value}))}))))
(defn import-keypair-by-seed-phrase
[keypair-key-uid seed-phrase password]
(-> (validate-mnemonic seed-phrase)
(promesa/then
(fn [{:keys [key-uid]}]
(if (not= keypair-key-uid key-uid)
(promesa/rejected
(ex-info
(error-message :import-keypair-by-seed-phrase/import-error)
{:hint :incorrect-seed-phrase-for-keypair}))
(make-seed-phrase-fully-operable seed-phrase password))))
(promesa/catch
(fn [error]
(promesa/rejected
(ex-info
(error-message :import-keypair-by-seed-phrase/import-error)
(ex-data error)))))))
(rf/reg-fx
:import-keypair-by-seed-phrase
(fn [{:keys [keypair-key-uid seed-phrase password on-success on-error]}]
(-> (import-keypair-by-seed-phrase keypair-key-uid seed-phrase password)
(promesa/then (fn [_result]
(cond
(vector? on-success) (rf/dispatch on-success)
(fn? on-success) (on-success))))
(promesa/catch (fn [error]
(cond
(vector? on-error) (rf/dispatch (conj on-error error))
(fn? on-error) (on-error error)))))))
+114 -56
View File
@@ -57,19 +57,27 @@
{:fx [[:dispatch [:wallet/clean-current-viewing-account]]
[:dispatch [:pop-to-root :shell-stack]]]}))
(defn log-rpc-error
[_ [{:keys [event params]} error]]
(log/warn (str "[wallet] Failed to " event)
{:params params
:error error}))
(rf/reg-event-fx :wallet/log-rpc-error log-rpc-error)
(rf/reg-event-fx
:wallet/get-accounts-success
(fn [{:keys [db]} [accounts]]
(let [wallet-accounts (filter #(not (:chat %)) accounts)
(let [wallet-accounts (data-store/rpc->accounts accounts)
wallet-db (get db :wallet)
new-account? (:new-account? wallet-db)
navigate-to-account (:navigate-to-account wallet-db)]
{:db (assoc-in db
[:wallet :accounts]
(utils.collection/index-by :address (data-store/rpc->accounts wallet-accounts)))
:fx [[:dispatch [:wallet/get-wallet-token]]
(utils.collection/index-by :address wallet-accounts))
:fx [[:dispatch [:wallet/get-wallet-token-for-all-accounts]]
[:dispatch [:wallet/request-collectibles-for-all-accounts {:new-request? true}]]
[:dispatch [:wallet/check-recent-history]]
[:dispatch [:wallet/check-recent-history-for-all-accounts]]
(when new-account?
[:dispatch [:wallet/navigate-to-new-account navigate-to-account]])]})))
@@ -79,9 +87,16 @@
{:fx [[:json-rpc/call
[{:method "accounts_getAccounts"
:on-success [:wallet/get-accounts-success]
:on-error #(log/info "failed to get accounts "
{:error %
:event :wallet/get-accounts})}]]]}))
:on-error [:wallet/log-rpc-error {:event :wallet/get-accounts}]}]]]}))
(defn process-account-from-signal
[{:keys [db]} [{:keys [address] :as account}]]
{:db (assoc-in db [:wallet :accounts address] (data-store/rpc->account account))
:fx [[:dispatch [:wallet/get-wallet-token-for-account address]]
[:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]]
[:dispatch [:wallet/check-recent-history-for-account address]]]})
(rf/reg-event-fx :wallet/process-account-from-signal process-account-from-signal)
(rf/reg-event-fx
:wallet/save-account
@@ -93,9 +108,7 @@
(rf/dispatch [:wallet/get-accounts])
(when (fn? on-success)
(on-success)))
:on-error #(log/info "failed to save account "
{:error %
:event :wallet/save-account})}]]]}))
:on-error [:wallet/log-rpc-error {:event :wallet/save-account}]}]]]}))
(rf/reg-event-fx
:wallet/show-account-deleted-toast
@@ -125,35 +138,41 @@
[{:method "accounts_deleteAccount"
:params [address]
:on-success [:wallet/remove-account-success toast-message]
:on-error #(log/info "failed to remove account "
{:error %
:event :wallet/remove-account})}]]]}))
:on-error [:wallet/log-rpc-error {:event :wallet/remove-account}]}]]]}))
(defn get-wallet-token-for-all-accounts
[{:keys [db]}]
{:fx (->> (get-in db [:wallet :accounts])
vals
(mapv
(fn [{:keys [address]}]
[:dispatch [:wallet/get-wallet-token-for-account address]])))})
(rf/reg-event-fx :wallet/get-wallet-token-for-all-accounts get-wallet-token-for-all-accounts)
(defn get-wallet-token-for-account
[{:keys [db]} [address]]
{:db (assoc-in db [:wallet :ui :tokens-loading address] true)
:fx [[:json-rpc/call
[{:method "wallet_getWalletToken"
:params [[address]]
:on-success [:wallet/store-wallet-token address]
:on-error [:wallet/get-wallet-token-for-account-failed address]}]]]})
(rf/reg-event-fx :wallet/get-wallet-token-for-account get-wallet-token-for-account)
(rf/reg-event-fx
:wallet/get-wallet-token
(fn [{:keys [db]}]
(let [addresses (->> (get-in db [:wallet :accounts])
vals
(map :address))]
{:db (assoc-in db [:wallet :ui :tokens-loading?] true)
:fx [[:json-rpc/call
[{:method "wallet_getWalletToken"
:params [addresses]
:on-success [:wallet/store-wallet-token]
:on-error [:wallet/get-wallet-token-failed addresses]}]]]})))
(rf/reg-event-fx
:wallet/get-wallet-token-failed
(fn [{:keys [db]} [params error]]
:wallet/get-wallet-token-for-account-failed
(fn [{:keys [db]} [address error]]
(log/info "failed to get wallet token "
{:error error
:event :wallet/get-wallet-token
:params params})
{:db (assoc-in db [:wallet :ui :tokens-loading?] false)}))
:event :wallet/get-wallet-token-for-account
:params address})
{:db (assoc-in db [:wallet :ui :tokens-loading address] false)}))
(rf/reg-event-fx
:wallet/store-wallet-token
(fn [{:keys [db]} [raw-tokens-data]]
(fn [{:keys [db]} [address raw-tokens-data]]
(let [tokens (data-store/rpc->tokens raw-tokens-data)
add-tokens (fn [stored-accounts tokens-per-account]
(reduce-kv (fn [accounts address tokens-data]
@@ -164,7 +183,7 @@
tokens-per-account))]
{:db (-> db
(update-in [:wallet :accounts] add-tokens tokens)
(assoc-in [:wallet :ui :tokens-loading?] false))})))
(assoc-in [:wallet :ui :tokens-loading address] false))})))
(rf/defn scan-address-success
{:events [:wallet/scan-address-success]}
@@ -209,7 +228,9 @@
(security/safe-unmask-data password))
account-config]
:on-success [:wallet/add-account-success lowercase-address]
:on-error #(log/info "failed to create account " % account-config)}]]]})))
:on-error [:wallet/log-rpc-error
{:event :wallet/add-account
:params account-config}]}]]]})))
(defn get-keypairs
[_]
@@ -217,7 +238,7 @@
[{:method "accounts_getKeypairs"
:params []
:on-success [:wallet/get-keypairs-success]
:on-error #(log/info "failed to get keypairs " %)}]]]})
:on-error [:wallet/log-rpc-error {:event :wallet/get-keypairs}]}]]]})
(rf/reg-event-fx :wallet/get-keypairs get-keypairs)
@@ -254,7 +275,7 @@
[{:method "wallet_getEthereumChains"
:params []
:on-success [:wallet/get-ethereum-chains-success]
:on-error #(log/info "failed to get networks " %)}]}))
:on-error [:wallet/log-rpc-error {:event :wallet/get-ethereum-chains}]}]}))
(rf/reg-event-fx
:wallet/get-ethereum-chains-success
@@ -354,29 +375,34 @@
(rf/reg-event-fx :wallet/reload
(fn [_]
{:fx [[:dispatch-n [[:wallet/get-wallet-token]]]]}))
{:fx [[:dispatch [:wallet/get-wallet-token-for-all-accounts]]]}))
(rf/reg-event-fx :wallet/start-wallet
(fn [_]
{:fx [[:json-rpc/call
[{:method "wallet_startWallet"
:on-error #(log/info "failed to start wallet"
{:error %
:event :wallet/start-wallet})}]]]}))
:on-error [:wallet/log-rpc-error {:event :wallet/start-wallet}]}]]]}))
(defn check-recent-history-for-all-accounts
[{:keys [db]}]
{:fx (->> (get-in db [:wallet :accounts])
vals
(mapv (fn [{:keys [address]}]
[:dispatch [:wallet/check-recent-history-for-account address]])))})
(rf/reg-event-fx :wallet/check-recent-history-for-all-accounts check-recent-history-for-all-accounts)
(rf/reg-event-fx
:wallet/check-recent-history
(fn [{:keys [db]}]
(let [addresses (->> (get-in db [:wallet :accounts])
vals
(map :address))
chain-ids (chain/chain-ids db)]
:wallet/check-recent-history-for-account
(fn [{:keys [db]} [address]]
(let [chain-ids (chain/chain-ids db)
params [chain-ids [address]]]
{:fx [[:json-rpc/call
[{:method "wallet_checkRecentHistoryForChainIDs"
:params [chain-ids addresses]
:on-error #(log/info "failed to check recent history"
{:error %
:event :wallet/check-recent-history})}]]]})))
:params params
:on-error [:wallet/log-rpc-error
{:event :wallet/check-recent-history-for-account
:params params}]}]]]})))
(rf/reg-event-fx :wallet/initialize
(fn []
@@ -496,9 +522,9 @@
;; https://github.com/status-im/status-mobile/issues/19864
:method "wallet_filterActivityAsync"
:params request-params
:on-error #(log/info "failed to fetch activities"
{:error %
:event :wallet/fetch-activities})}]]]})))
:on-error [:wallet/log-rpc-error
{:event :wallet/fetch-activities
:params request-params}]}]]]})))
(rf/reg-event-fx
:wallet/activity-filtering-done
@@ -523,6 +549,38 @@
{:fx [[:json-rpc/call
[{:method "wallet_getCryptoOnRamps"
:on-success [:wallet/get-crypto-on-ramps-success]
:on-error #(log/info "failed to fetch crypto on ramps"
{:error %
:event :wallet/get-crypto-on-ramps})}]]]}))
:on-error [:wallet/log-rpc-error {:event :wallet/get-crypto-on-ramps}]}]]]}))
(rf/reg-event-fx
:wallet/resolve-ens
(fn [{db :db} [{:keys [ens on-success on-error]}]]
(let [chain-id (network-utils/network->chain-id db constants/mainnet-network-name)]
{:fx [[:json-rpc/call
[{:method "ens_addressOf"
:params [chain-id ens]
:on-success on-success
:on-error on-error}]]]})))
(rf/reg-event-fx
:wallet/process-keypair-from-backup
(fn [{:keys [db]} [{:keys [backedUpKeypair]}]]
(let [{:keys [key-uid accounts]} backedUpKeypair
keypairs-db (get-in db [:wallet :keypairs])
updated-keypairs (-> (filter #(not= (:key-uid %) key-uid) keypairs-db)
(conj backedUpKeypair)
data-store/rpc->keypairs)
accounts-fx (mapv (fn [{:keys [chat] :as account}]
;; We exclude the chat account from the profile keypair
;; for fetching the assets
(when-not chat
[:dispatch
[:wallet/process-account-from-signal
account]]))
accounts)]
{:db (assoc-in db [:wallet :keypairs] updated-keypairs)
:fx accounts-fx})))
(rf/reg-event-fx
:wallet/process-watch-only-account-from-backup
(fn [_ [{:keys [backedUpWatchOnlyAccount]}]]
{:fx [[:dispatch [:wallet/process-account-from-signal backedUpWatchOnlyAccount]]]}))
+95 -53
View File
@@ -3,10 +3,55 @@
[cljs.test :refer-macros [deftest is testing]]
matcher-combinators.test
[status-im.constants :as constants]
[status-im.contexts.wallet.collectible.events :as collectible-events]
[status-im.contexts.wallet.events :as events]))
(def address "0x2f88d65f3cb52605a54a833ae118fb1363acccd2")
(def address "0x2ee6138eb9344a8b76eca3cf7554a06c82a1e2d8")
(def raw-account
{:path "m/44'/60'/0'/0/0"
:emoji "🛃"
:key-uid "0xf9b4dc40911638052ef9cbed6e8ac689198d8f11d2235c5d62e2457c1503dc4f"
:address address
:wallet true
:name "Ethereum account"
:createdAt 1716548742000
:type "generated"
:chat false
:prodPreferredChainIds "1:42161"
:hidden false
:position 0
:clock 1712315009484
:testPreferredChainIds "11155111:421614"
:colorId "purple"
:operable "fully"
:mixedcase-address "0x2Ee6138eb9344a8b76Eca3cf7554A06C82A1e2D8"
:public-key
"0x04ee7c47e4b68cc05dcd3377cbd5cde6be3c89fcf20a981e55e0285ed63a50f51f8b423465eee134c51bb0255e6041e9e5b006054b0fa72a7c76942a5a1a3f4e7e"
:removed false})
(def account
{:path "m/44'/60'/0'/0/0"
:emoji "🛃"
:key-uid "0xf9b4dc40911638052ef9cbed6e8ac689198d8f11d2235c5d62e2457c1503dc4f"
:address address
:color :purple
:wallet true
:default-account? true
:name "Ethereum account"
:type :generated
:chat false
:test-preferred-chain-ids #{11155111 421614}
:watch-only? false
:hidden false
:prod-preferred-chain-ids #{1 42161}
:position 0
:clock 1712315009484
:created-at 1716548742000
:operable :fully
:mixedcase-address "0x2Ee6138eb9344a8b76Eca3cf7554A06C82A1e2D8"
:public-key
"0x04ee7c47e4b68cc05dcd3377cbd5cde6be3c89fcf20a981e55e0285ed63a50f51f8b423465eee134c51bb0255e6041e9e5b006054b0fa72a7c76942a5a1a3f4e7e"
:removed false})
(deftest scan-address-success-test
(let [db {}]
@@ -25,57 +70,6 @@
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-collectibles-test
(testing "flush-collectibles"
(let [collectible-1 {:collectible-data {:image-url "https://..." :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-2 {:collectible-data {:image-url "" :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-3 {:collectible-data {:image-url "" :animation-url nil}
:ownership [{:address "0x2"
:balance "1"}]}
db {:wallet {:ui {:collectibles {:pending-requests 0
:fetched {"0x1" [collectible-1
collectible-2]
"0x2" [collectible-3]}}}
:accounts {"0x1" {}
"0x3" {}}}}
expected-db {:wallet {:ui {:collectibles {}}
:accounts {"0x1" {:collectibles (list collectible-1 collectible-2)}
"0x2" {:collectibles (list collectible-3)}
"0x3" {}}}}
result-db (:db (collectible-events/flush-collectibles {:db db}))]
(is (match? result-db expected-db)))))
(deftest clear-stored-collectibles-test
(let [db {:wallet {:accounts {"0x1" {:collectibles [{:id 1} {:id 2}]}
"0x2" {"some other stuff" "with any value"
:collectibles [{:id 3}]}
"0x3" {}}}}]
(testing "clear-stored-collectibles"
(let [expected-db {:wallet {:accounts {"0x1" {}
"0x2" {"some other stuff" "with any value"}
"0x3" {}}}}
effects (collectible-events/clear-stored-collectibles {:db db})
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-last-collectible-details-test
(testing "store-last-collectible-details"
(let [db {:wallet {}}
last-collectible {:description "Pandaria"
:image-url "https://..."}
expected-db {:wallet {:last-collectible-details {:description "Pandaria"
:image-url "https://..."}}}
effects (collectible-events/store-last-collectible-details {:db db}
[last-collectible])
result-db (:db effects)]
(is (match? result-db expected-db)))))
(deftest reset-selected-networks-test
(testing "reset-selected-networks"
(let [db {:wallet {}}
@@ -122,3 +116,51 @@
effects (events/update-selected-networks {:db db} props)
result-fx (:fx effects)]
(is (match? result-fx expected-fx)))))
(deftest get-wallet-token-for-all-accounts-test
(testing "get wallet token for all accounts"
(let [address-1 "0x1"
address-2 "0x2"
cofx {:db {:wallet {:accounts {address-1 {:address address-1}
address-2 {:address address-2}}}}}
effects (events/get-wallet-token-for-all-accounts cofx)
result-fx (:fx effects)
expected-fx [[:dispatch [:wallet/get-wallet-token-for-account address-1]]
[:dispatch [:wallet/get-wallet-token-for-account address-2]]]]
(is (match? expected-fx result-fx)))))
(deftest get-wallet-token-for-account-test
(testing "get wallet token for account"
(let [cofx {:db {}}
effects (events/get-wallet-token-for-account cofx [address])
expected-effects {:db {:wallet {:ui {:tokens-loading {address true}}}}
:fx [[:json-rpc/call
[{:method "wallet_getWalletToken"
:params [[address]]
:on-success [:wallet/store-wallet-token address]
:on-error [:wallet/get-wallet-token-for-account-failed
address]}]]]}]
(is (match? expected-effects effects)))))
(deftest check-recent-history-for-all-accounts-test
(testing "check recent history for all accounts"
(let [address-1 "0x1"
address-2 "0x2"
cofx {:db {:wallet {:accounts {address-1 {:address address-1}
address-2 {:address address-2}}}}}
effects (events/check-recent-history-for-all-accounts cofx)
result-fx (:fx effects)
expected-fx [[:dispatch [:wallet/check-recent-history-for-account address-1]]
[:dispatch [:wallet/check-recent-history-for-account address-2]]]]
(is (match? expected-fx result-fx)))))
(deftest process-account-from-signal-test
(testing "process account from signal"
(let [cofx {:db {:wallet {:accounts {}}}}
effects (events/process-account-from-signal cofx [raw-account])
expected-effects {:db {:wallet {:accounts {address account}}}
:fx [[:dispatch [:wallet/get-wallet-token-for-account address]]
[:dispatch
[:wallet/request-new-collectibles-for-account-from-signal address]]
[:dispatch [:wallet/check-recent-history-for-account address]]]}]
(is (match? expected-effects effects)))))
@@ -8,7 +8,7 @@
(defn view
[]
(let [tokens-loading? (rf/sub [:wallet/tokens-loading?])
(let [tokens-loading? (rf/sub [:wallet/home-tokens-loading?])
{:keys [tokens]} (rf/sub [:wallet/aggregated-token-values-and-balance])]
(if tokens-loading?
[quo/skeleton-list
+1 -1
View File
@@ -65,7 +65,7 @@
[]
(let [[selected-tab set-selected-tab] (rn/use-state (:id (first tabs-data)))
account-list-ref (rn/use-ref-atom nil)
tokens-loading? (rf/sub [:wallet/tokens-loading?])
tokens-loading? (rf/sub [:wallet/home-tokens-loading?])
networks (rf/sub [:wallet/selected-network-details])
account-cards-data (rf/sub [:wallet/account-cards-data])
cards (conj account-cards-data (new-account-card-data))
@@ -2,6 +2,7 @@
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.controlled-input.utils :as controlled-input]
[status-im.contexts.wallet.collectible.utils :as utils]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.send.select-collectible-amount.style :as style]
@@ -11,24 +12,23 @@
(defn view
[]
(let [on-close (rn/use-callback #(rf/dispatch [:navigate-back]))
[value set-value] (rn/use-state 1)
inc-value (rn/use-callback (fn [] (set-value (inc value)))
[value])
dec-value (rn/use-callback (fn [] (set-value (dec value)))
[value])
add-digit (rn/use-callback (fn [digit]
(set-value (+ (js/parseInt digit)
(* value 10))))
[value])
delete-digit (rn/use-callback (fn []
(set-value (Math/floor (/ value 10))))
[value])
send-transaction-data (rf/sub [:wallet/wallet-send])
collectible (:collectible send-transaction-data)
balance (utils/collectible-balance collectible)
[value set-value] (rn/use-state (-> controlled-input/init-state
(controlled-input/set-numeric-value 1)
(controlled-input/set-lower-limit 1)))
preview-uri (get-in collectible [:preview-url :uri])
incorrect-value? (or (< value 1) (> value balance))]
incorrect-value? (controlled-input/input-error value)
increase-value (rn/use-callback #(set-value controlled-input/increase))
decrease-value (rn/use-callback #(set-value controlled-input/decrease))
delete-character (rn/use-callback #(set-value controlled-input/delete-last))
add-character (rn/use-callback (fn [c]
(set-value #(controlled-input/add-character % c))))]
(rn/use-effect
(fn []
(set-value #(controlled-input/set-upper-limit % balance)))
[balance])
[rn/view
[account-switcher/view
{:icon-name :i/arrow-left
@@ -44,13 +44,12 @@
{:title (i18n/label :t/max {:number balance})
:status (if incorrect-value? :error :default)
:container-style style/network-tags-container}]
[quo/amount-input
{:max-value (if (integer? balance) balance 0)
:min-value 1
:value value
:on-inc-press inc-value
:on-dec-press dec-value
{:max-value (controlled-input/upper-limit value)
:min-value (controlled-input/lower-limit value)
:value (controlled-input/numeric-value value)
:on-inc-press increase-value
:on-dec-press decrease-value
:container-style style/amount-input-container
:status (if incorrect-value? :error :default)}]
[quo/bottom-actions
@@ -58,12 +57,11 @@
:button-one-props {:on-press #(rf/dispatch
[:wallet/set-collectible-amount-to-send
{:stack-id :screen/wallet.select-collectible-amount
:amount value}])
:amount (controlled-input/numeric-value value)}])
:disabled? incorrect-value?}
:button-one-label (i18n/label :t/confirm)}]
[quo/numbered-keyboard
{:left-action :none
:delete-key? true
:on-press add-digit
:on-delete delete-digit}]]))
:on-press add-character
:on-delete delete-character}]]))
@@ -3,7 +3,6 @@
[quo.core :as quo]
[quo.foundations.colors :as colors]
quo.theme
[react-native.blur :as blur]
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[react-native.gesture :as gesture]
@@ -43,7 +42,7 @@
{:on-layout #(reset! options-height (oops/oget % "nativeEvent.layout.height"))
:style (when show-account-selector? style/options-container)}
(when show-account-selector?
[blur/view
[quo/blur
{:style (style/blur-container @options-height)
:blur-radius (if platform/android? 20 10)
:blur-amount (if platform/ios? 20 10)
@@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.sheets.network-preferences.style
(:require [quo.foundations.colors :as colors]))
(:require [quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(def blur
{:position :absolute
@@ -7,7 +8,7 @@
:left 0
:right 0
:bottom 0
:overlay-color colors/neutral-100-opa-70-blur})
:overlay-color (when platform/ios? colors/neutral-100-opa-70-blur)})
(def data-item
{:margin-horizontal 20
@@ -1,18 +1,18 @@
(ns status-im.contexts.wallet.sheets.network-preferences.view
(:require [quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.sheets.network-preferences.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.sheets.network-preferences.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[{:keys [title first-section-label second-section-label selected-networks
[{:keys [first-section-label second-section-label selected-networks
receiver-preferred-networks account watch-only?]}]
(let [state (reagent/atom :default)
{:keys [color address
@@ -38,7 +38,7 @@
initial-network-preferences-names
@network-preferences-names-state))]
(fn [{:keys [on-save on-change blur? button-label first-section-warning-label
second-section-warning-label]}]
second-section-warning-label title description]}]
(let [theme (quo.theme/use-theme)
network-details (rf/sub [:wallet/network-details])
first-section-networks (filter (fn [network]
@@ -64,16 +64,17 @@
[:<>
;; quo/overlay isn't compatible with sheets
(when blur?
[blur/view
[quo/blur
{:style style/blur
:blur-amount 20
:blur-radius 25}])
[quo/drawer-top
{:title (or title (i18n/label :t/network-preferences))
:description (when-not receiver?
(if watch-only?
(i18n/label :t/network-preferences-desc-1)
(i18n/label :t/network-preferences-desc-2)))
(or description
(if watch-only?
(i18n/label :t/network-preferences-desc-1)
(i18n/label :t/network-preferences-desc-2))))
:blur? blur?}]
(when-not receiver?
[quo/data-item
+1 -3
View File
@@ -40,6 +40,4 @@
"wallet-activity-filtering-done" {:fx [[:dispatch
[:wallet/activity-filtering-done
(transforms/js->clj event-js)]]]}
(log/debug ::unknown-wallet-event
:type event-type
:event (transforms/js->clj event-js))))))
(log/debug ::unknown-wallet-event :type event-type)))))
+7 -1
View File
@@ -59,6 +59,8 @@
[status-im.contexts.profile.settings.view :as settings]
[status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.view :as
encrypted-key-pair-qr]
[status-im.contexts.settings.wallet.keypairs-and-accounts.import-seed-phrase.view :as
import-seed-phrase]
[status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename]
[status-im.contexts.settings.wallet.keypairs-and-accounts.scan-qr.view :as scan-keypair-qr]
[status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts]
@@ -66,7 +68,7 @@
[status-im.contexts.settings.wallet.saved-addresses.add-address-to-save.view :as
wallet-add-address-to-save]
[status-im.contexts.settings.wallet.saved-addresses.save-address.view :as wallet-save-address]
[status-im.contexts.settings.wallet.saved-addresses.sheets.share-address.view :as
[status-im.contexts.settings.wallet.saved-addresses.share-address.view :as
share-saved-address]
[status-im.contexts.settings.wallet.saved-addresses.view :as saved-addresses-settings]
[status-im.contexts.settings.wallet.wallet-options.view :as wallet-options]
@@ -547,6 +549,10 @@
:options options/transparent-modal-screen-options
:component scan-keypair-qr/view}
{:name :screen/settings.import-seed-phrase
:options options/transparent-screen-options
:component import-seed-phrase/view}
{:name :screen/settings.network-settings
:options options/transparent-modal-screen-options
:component network-settings/view}
@@ -9,7 +9,7 @@
:address "0x1"
:mixedcase-address "0x1"
:chain-short-names "eth:arb1:oeth:"
:has-ens? false
:ens? false
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
@@ -22,7 +22,7 @@
:address "0x2"
:mixedcase-address "0x2"
:chain-short-names "eth:"
:has-ens? false
:ens? false
:network-preferences-names #{:mainnet}
:name "Bob"
:created-at 1716828825
@@ -33,7 +33,7 @@
:address "0x1"
:mixedcase-address "0x1"
:chain-short-names "eth:"
:has-ens? false
:ens? false
:network-preferences-names #{:mainnet}
:name "Alice"
:created-at 1716826745
@@ -44,7 +44,7 @@
:address "0x2"
:mixedcase-address "0x2"
:chain-short-names "eth:arb1:oeth:"
:has-ens? false
:ens? false
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
@@ -61,7 +61,7 @@
:address "0x1"
:mixedcase-address "0x1"
:chain-short-names "eth:"
:has-ens? false
:ens? false
:network-preferences-names #{:mainnet}
:name "Alice"
:created-at 1716826745
@@ -74,7 +74,7 @@
:address "0x2"
:mixedcase-address "0x2"
:chain-short-names "eth:arb1:oeth:"
:has-ens? false
:ens? false
:network-preferences-names #{:arbitrum
:optimism
:mainnet}
+25 -9
View File
@@ -39,9 +39,25 @@
:-> :scanned-address)
(rf/reg-sub
:wallet/tokens-loading?
:wallet/tokens-loading
:<- [:wallet/ui]
:-> :tokens-loading?)
:-> :tokens-loading)
(rf/reg-sub
:wallet/home-tokens-loading?
:<- [:wallet/tokens-loading]
(fn [tokens-loading]
(->> tokens-loading
vals
(some true?)
boolean)))
(rf/reg-sub
:wallet/current-viewing-account-tokens-loading?
:<- [:wallet/tokens-loading]
:<- [:wallet/current-viewing-account-address]
(fn [[tokens-loading current-viewing-account-address]]
(get tokens-loading current-viewing-account-address)))
(rf/reg-sub
:wallet/create-account
@@ -218,9 +234,9 @@
:or {networks []
size 32}}]
(->> accounts
(keep (fn [{:keys [path customization-color emoji name address]}]
(keep (fn [{:keys [path color emoji name address]}]
(when-not (string/starts-with? path constants/path-eip1581)
{:account-props {:customization-color customization-color
{:account-props {:customization-color color
:size size
:emoji emoji
:type :default
@@ -233,8 +249,8 @@
(defn- format-settings-missing-keypair-accounts
[accounts]
(->> accounts
(map (fn [{:keys [customization-color emoji]}]
{:customization-color customization-color
(map (fn [{:keys [color emoji]}]
{:customization-color color
:emoji emoji
:type :default}))))
@@ -311,15 +327,15 @@
:wallet/account-cards-data
:<- [:wallet/accounts]
:<- [:wallet/balances-in-selected-networks]
:<- [:wallet/tokens-loading?]
:<- [:wallet/tokens-loading]
:<- [:profile/currency-symbol]
(fn [[accounts balances tokens-loading? currency-symbol]]
(fn [[accounts balances tokens-loading currency-symbol]]
(mapv (fn [{:keys [color address watch-only?] :as account}]
(assoc account
:customization-color color
:type (if watch-only? :watch-only :empty)
:on-press #(rf/dispatch [:wallet/navigate-to-account address])
:loading? tokens-loading?
:loading? (get tokens-loading address)
:balance (utils/prettify-balance currency-symbol (get balances address))))
accounts)))
+41 -43
View File
@@ -609,46 +609,46 @@
(rf/sub [sub-name])))))
(def chat-account
{:path "m/43'/60'/1581'/0'/0"
:emoji ""
:key-uid "abc"
:address "address-1"
:color-id ""
:wallet false
:name "My Profile"
:type "generated"
:chat true
:customization-color :blue
:hidden false
:removed false})
{:path "m/43'/60'/1581'/0'/0"
:emoji ""
:key-uid "abc"
:address "address-1"
:color-id ""
:wallet false
:name "My Profile"
:type "generated"
:chat true
:color :blue
:hidden false
:removed false})
(def operable-wallet-account
{:path "m/44'/60'/0'/0/0"
:emoji "🤡"
:key-uid "abc"
:address "address-2"
:wallet true
:name "My Account"
:type "generated"
:chat false
:customization-color :primary
:hidden false
:operable :fully
:removed false})
{:path "m/44'/60'/0'/0/0"
:emoji "🤡"
:key-uid "abc"
:address "address-2"
:wallet true
:name "My Account"
:type "generated"
:chat false
:color :primary
:hidden false
:operable :fully
:removed false})
(def inoperable-wallet-account
{:path "m/44'/60'/0'/0/0"
:emoji "🧠"
:key-uid "def"
:address "address-3"
:wallet true
:name "My Other Account"
:type "generated"
:chat false
:customization-color :primary
:hidden false
:operable :no
:removed false})
{:path "m/44'/60'/0'/0/0"
:emoji "🧠"
:key-uid "def"
:address "address-3"
:wallet true
:name "My Other Account"
:type "generated"
:chat false
:color :primary
:hidden false
:operable :no
:removed false})
(def default-keypair-accounts
{:key-uid "abc"
@@ -686,14 +686,13 @@
{:missing [{:name (:name seed-phrase-keypair-accounts)
:key-uid (:key-uid seed-phrase-keypair-accounts)
:type (keyword (:type seed-phrase-keypair-accounts))
:accounts [{:customization-color (:customization-color inoperable-wallet-account)
:accounts [{:customization-color (:color inoperable-wallet-account)
:emoji (:emoji inoperable-wallet-account)
:type :default}]}]
:operable [{:name (:name default-keypair-accounts)
:key-uid (:key-uid default-keypair-accounts)
:type (keyword (:type default-keypair-accounts))
:accounts [{:account-props {:customization-color (:customization-color
operable-wallet-account)
:accounts [{:account-props {:customization-color (:color operable-wallet-account)
:size 32
:emoji (:emoji operable-wallet-account)
:type :default
@@ -717,7 +716,7 @@
[:wallet :accounts]
{(:address operable-wallet-account) operable-wallet-account}))))
(let [{:keys [customization-color
(let [{:keys [color
name
address
emoji]} operable-wallet-account
@@ -730,7 +729,7 @@
:operable [{:name (:name default-keypair-accounts)
:key-uid (:key-uid default-keypair-accounts)
:type (keyword (:type default-keypair-accounts))
:accounts [{:account-props {:customization-color customization-color
:accounts [{:account-props {:customization-color color
:size size-option
:emoji emoji
:type :default
@@ -763,8 +762,7 @@
:operable [{:name (:name default-keypair-accounts)
:key-uid (:key-uid default-keypair-accounts)
:type (keyword (:type default-keypair-accounts))
:accounts [{:account-props {:customization-color (:customization-color
operable-wallet-account)
:accounts [{:account-props {:customization-color (:color operable-wallet-account)
:size 32
:emoji (:emoji operable-wallet-account)
:type :default
+1 -1
View File
@@ -68,7 +68,7 @@
(defn wallet-loaded?
[]
(not @(rf/subscribe [:wallet/tokens-loading?])))
(not @(rf/subscribe [:wallet/home-tokens-loading?])))
(defn assert-messenger-started
[]
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.179.27",
"commit-sha1": "9e5386955c4ae271ae8794a561fd953c0407f8e7",
"src-sha256": "13aw37bj3xsl0glqlk7ir9kxrdkw0qn5c0fly8qac73z81y6xj0g"
"version": "v0.179.28",
"commit-sha1": "33ccf784c52e56d98f2928b4618db5490a1e1f95",
"src-sha256": "0cz8cbzqbp6i3p9la0iibk73s2fmlk449zd9fznms8b02g4n3xic"
}
@@ -170,7 +170,7 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
self.home_2.just_fyi("Device 2 sign in, user name is " + self.username_2)
self.home_2.reopen_app(sign_in=False)
self.device_2.show_profiles_button.wait_and_click()
self.device_2.get_user(username=self.username_2).click()
self.device_2.get_user_profile_by_name(username=self.username_2).click()
self.device_2.sign_in()
self.loop.run_until_complete(run_in_parallel(((_device_1_creates_user, {}),
@@ -115,12 +115,18 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[2].add_remove_same_reaction(message=message, emoji="thumbs-up")
self.chats[2].set_reaction(message=message, emoji="laugh")
for i in range(3):
self.chats[i].just_fyi("Checking reactions count for each group member and admin")
message_element = self.chats[i].chat_element_by_text(message)
message_element.emojis_below_message(emoji="thumbs-up").wait_for_element_text(2)
message_element.emojis_below_message(emoji="love").wait_for_element_text(1)
message_element.emojis_below_message(emoji="laugh").wait_for_element_text(1)
def _check_reactions_count(chat_view_index):
self.chats[chat_view_index].just_fyi("Checking reactions count for each group member and admin")
chat_element = self.chats[chat_view_index].chat_element_by_text(message)
chat_element.emojis_below_message(emoji="thumbs-up").wait_for_element_text(2)
chat_element.emojis_below_message(emoji="love").wait_for_element_text(1)
chat_element.emojis_below_message(emoji="laugh").wait_for_element_text(1)
self.loop.run_until_complete(run_in_parallel((
(_check_reactions_count, {'chat_view_index': 0}),
(_check_reactions_count, {'chat_view_index': 1}),
(_check_reactions_count, {'chat_view_index': 2})
)))
self.chats[0].just_fyi("Admin checks info about voted users")
self.chats[0].chat_element_by_text(message).emojis_below_message(
@@ -163,15 +169,23 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[2].add_remove_same_reaction(message=message, emoji="laugh")
self.chats[2].add_remove_same_reaction(message=message, emoji="sad")
for i in range(3):
self.chats[i].just_fyi("Checking reactions count for each group member and admin after they were changed")
message_element = self.chats[i].chat_element_by_text(message)
def _check_reactions_count_after_change(chat_view_index):
self.chats[chat_view_index].just_fyi(
"Checking reactions count for each group member and admin after they were changed")
chat_element = self.chats[chat_view_index].chat_element_by_text(message)
try:
message_element.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1)
message_element.emojis_below_message(emoji="love").wait_for_element_text(1)
message_element.emojis_below_message(emoji="sad").wait_for_element_text(2)
chat_element.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1)
chat_element.emojis_below_message(emoji="love").wait_for_element_text(1)
chat_element.emojis_below_message(emoji="sad").wait_for_element_text(2)
except (Failed, NoSuchElementException):
self.errors.append("Incorrect reactions count for %s after changing the reactions" % self.usernames[i])
self.errors.append(
"Incorrect reactions count for %s after changing the reactions" % self.usernames[chat_view_index])
self.loop.run_until_complete(run_in_parallel((
(_check_reactions_count_after_change, {'chat_view_index': 0}),
(_check_reactions_count_after_change, {'chat_view_index': 1}),
(_check_reactions_count_after_change, {'chat_view_index': 2})
)))
self.chats[0].just_fyi("Admin relogins")
self.chats[0].reopen_app()
@@ -286,11 +300,16 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
@marks.testrail_id(702808)
def test_group_chat_offline_pn(self):
for i in range(1, 3):
self.homes[i].navigate_back_to_home_view()
self.homes[i].chats_tab.click()
self.homes[i].groups_tab.click()
self.homes[i].get_chat(self.chat_name).click()
def _proceed_to_chat(index):
self.homes[index].navigate_back_to_home_view()
self.homes[index].chats_tab.click()
self.homes[index].groups_tab.click()
self.homes[index].get_chat(self.chat_name).click()
self.loop.run_until_complete(run_in_parallel((
(_proceed_to_chat, {'index': 1}),
(_proceed_to_chat, {'index': 2})
)))
message_1, message_2 = 'message from old member', 'message from new member'
@@ -318,11 +337,18 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.homes[0].chats_tab.click()
self.homes[0].get_chat(self.chat_name).click()
self.homes[0].just_fyi("check that messages are shown for every member")
for i in range(3):
for message in (message_1, message_2):
if not self.chats[i].chat_element_by_text(message).is_element_displayed(30):
self.errors.append('%s if not shown for device %s' % (message, str(i)))
def _check_messages(index):
self.chats[index].just_fyi("Check that messages are shown for user %s" % self.usernames[index])
for message_text in (message_1, message_2):
if not self.chats[index].chat_element_by_text(message_text).is_element_displayed(30):
self.errors.append('%s if not shown for device %s' % (message_text, index))
self.loop.run_until_complete(run_in_parallel((
(_check_messages, {'index': 0}),
(_check_messages, {'index': 1}),
(_check_messages, {'index': 2})
)))
self.errors.verify_no_errors()
@marks.testrail_id(702732)
@@ -381,28 +407,33 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[1].chat_element_by_text(self.message_4).pinned_by_label.is_element_displayed(30)):
self.errors.append("Message 4 is not pinned in group chat after unpinning previous one")
self.chats[0].just_fyi("Check pinned messages count and content")
for chat_number, group_chat in enumerate([self.chats[0], self.chats[1]]):
count = group_chat.pinned_messages_count.text
def _check_pinned_messages(index):
self.chats[index].just_fyi("Check pinned messages count and content for user %s" % self.usernames[index])
count = self.chats[index].pinned_messages_count.text
if count != '3':
self.errors.append(
"Pinned messages count %s doesn't match expected 3 for user %s" % (count, chat_number + 1))
group_chat.pinned_messages_count.click()
for message in self.message_1, self.message_3, self.message_4:
pinned_by = group_chat.pinned_messages_list.get_message_pinned_by_text(message)
"Pinned messages count %s doesn't match expected 3 for user %s" % (count, self.usernames[index]))
self.chats[index].pinned_messages_count.click()
for message_text in self.message_1, self.message_3, self.message_4:
pinned_by = self.chats[index].pinned_messages_list.get_message_pinned_by_text(message_text)
if pinned_by.is_element_displayed():
text = pinned_by.text.strip()
expected_text = "You" if chat_number == 0 else self.usernames[0]
expected_text = "You" if index == 0 else self.usernames[0]
if text != expected_text:
self.errors.append(
"Pinned by '%s' doesn't match expected '%s' for user %s" % (
text, expected_text, chat_number + 1)
text, expected_text, self.usernames[index])
)
else:
self.errors.append(
"Message '%s' is missed on Pinned messages list for user %s" % (message, chat_number + 1)
"Message '%s' is missed on Pinned messages list for user %s" % (message, self.usernames[index])
)
self.loop.run_until_complete(run_in_parallel((
(_check_pinned_messages, {'index': 0}),
(_check_pinned_messages, {'index': 1})
)))
self.errors.verify_no_errors()
@marks.testrail_id(703495)
@@ -33,43 +33,6 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.community_view = self.home.get_community_view()
self.channel = self.community_view.get_channel(self.channel_name).click()
@marks.testrail_id(703503)
@marks.xfail(reason="Curated communities not loading, https://github.com/status-im/status-mobile/issues/17852",
run=False)
def test_community_discovery(self):
self.home.navigate_back_to_home_view()
self.home.communities_tab.click()
self.home.discover_communities_button.click()
self.home.community_card_item.wait_for_visibility_of_element(30)
if len(self.home.community_card_item.find_elements()) > 1:
contributors_test_community_attributes = "Test Community", 'Open for anyone', 'Web3', 'Software dev'
for text in contributors_test_community_attributes:
if not self.home.element_by_text(text).is_element_displayed(10):
self.errors.append("'%s' text is not in Discovery!" % text)
self.home.element_by_text(contributors_test_community_attributes[0]).click()
element_templates = {
self.community_view.join_button: 'discovery_join_button.png',
self.community_view.get_channel_avatar(): 'discovery_general_channel.png',
}
for element, template in element_templates.items():
if element.is_element_differs_from_template(template):
element.save_new_screenshot_of_element('%s_different.png' % template.split('.')[0])
self.errors.append(
"Element %s is different from expected template %s!" % (element.locator, template))
self.community_view.navigate_back_to_home_view()
self.home.communities_tab.click()
self.home.discover_communities_button.click()
self.home.community_card_item.wait_for_visibility_of_element(30)
self.home.swipe_up()
status_ccs_community_attributes = '(old) Status CCs', 'Community for Status CCs', 'Ethereum', \
'Software dev', 'Web3'
for text in status_ccs_community_attributes:
if not self.community_view.element_by_text(text).is_element_displayed(10):
self.errors.append("'%s' text is not shown for (old) Status CCs!" % text)
self.errors.verify_no_errors()
@marks.testrail_id(702846)
def test_community_navigate_to_channel_when_relaunch(self):
text_message = 'some_text'
@@ -261,7 +224,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that can remove user from logged out state")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.wait_and_click()
user_card = self.sign_in.get_user(username=self.username)
user_card = self.sign_in.get_user_profile_by_name(username=self.username)
user_card.open_user_options()
self.sign_in.remove_profile_button.click()
if not self.sign_in.element_by_translation_id("remove-profile-confirm-message").is_element_displayed(30):
@@ -276,6 +239,67 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(703503)
def test_community_discovery(self):
try:
# workaround for case if a user is logged out in the previous test
self.sign_in.get_user_profile_by_index(index=1).click()
self.sign_in.sign_in()
except NoSuchElementException:
pass
self.home.navigate_back_to_home_view()
self.home.just_fyi("Turn off testnet in the profile settings")
profile = self.home.profile_button.click()
profile.advanced_button.scroll_and_click()
profile.testnet_mode_toggle.click()
profile.ok_button.click()
self.sign_in.sign_in()
self.home.just_fyi("Check Discover Communities content")
self.home.communities_tab.click()
self.home.discover_communities_button.click()
self.home.community_card_item.wait_for_elements(seconds=120)
expected_communities = {
' 0xUX': ['Design', 'Ethereum', 'Collaboration'],
'Status': ['Web3', 'Blockchain', 'Ethereum'],
'Status Inu': ['News', 'Social', 'Web3'],
}
for community_name, tags in expected_communities.items():
self.home.just_fyi("Check %s community tags in the Discover communities screen" % community_name)
card = self.home.get_discover_community_card_by_name(community_name=community_name)
try:
card.wait_for_visibility_of_element(30)
if community_name == 'Status':
card.swipe_to_web_element()
missing_tags = list()
for text in tags:
try:
card.get_child_element_by_text(text=text).wait_for_element()
except TimeoutException:
missing_tags.append(text)
if missing_tags:
self.errors.append("Community '%s' is missing tag(s) %s." % (community_name, ','.join(tags)))
if community_name == 'Status':
self.home.just_fyi("Check Status community screen")
card.click()
if self.community_view.join_button.is_element_differs_from_template(
'status_community_join_button.png'):
self.errors.append("Status community Join button is different from expected template.")
if self.community_view.community_logo.is_element_differs_from_template('status_community_logo.png'):
self.errors.append("Status community logo is different from expected template.")
self.community_view.close_community_view_button.click()
self.home.discover_communities_button.click()
self.home.swipe_up()
except TimeoutException:
self.errors.append("Community '%s' is not in the Discover Communities list." % community_name)
self.errors.verify_no_errors()
# Note: this test should always be the LAST ONE in the group because it turns on mainnet in the app!
@pytest.mark.xdist_group(name="new_three_2")
@marks.new_ui_critical
@@ -998,6 +1022,8 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(702948)
@marks.xfail(reason="Can't enter channel after community is fetched for the first time, " \
"https://github.com/status-im/status-mobile/issues/20395")
def test_community_hashtag_links_to_community_channels(self):
for home in self.homes:
home.navigate_back_to_home_view()
@@ -1098,6 +1124,8 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(703629)
@marks.xfail(reason="Can't enter channel after community is fetched for the first time, " \
"https://github.com/status-im/status-mobile/issues/20395")
def test_community_join_when_node_owner_offline(self):
for home in self.homes:
home.navigate_back_to_home_view()
@@ -1143,8 +1171,8 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
general_channel.click()
if not self.channel_2.chat_element_by_text(control_message_general_chat).is_element_displayed(30):
self.errors.append(
"Message in community channel is not visible for user before join, it was indicated as" \
"%s sent for the sender before he went offline" % "" if message_sent else "not")
"Message in community channel is not visible for user before join, it was indicated as " \
"%s sent for the sender before he went offline" % ("" if message_sent else "not"))
else:
self.errors.append("Community channel is not displayed for user before join")
self.community_2.toast_content_element.wait_for_invisibility_of_element(30)
+3
View File
@@ -367,6 +367,9 @@ class BaseElement(object):
def exclude_emoji(value):
return 'emoji' if value in emoji.UNICODE_EMOJI else value
def get_child_element_by_text(self, text: str):
return BaseElement(self.driver, prefix=self.locator, xpath="//*[@text='%s']" % text)
class EditBox(BaseElement):
+3 -3
View File
@@ -406,10 +406,10 @@ class CommunityView(HomeView):
#### NEW UI
# Communities initial page
self.close_community_view_button = Button(
self.driver,
xpath="//*[@content-desc='community-options-for-community']/../*[1]//android.widget.ImageView")
self.close_community_view_button = Button(self.driver, accessibility_id="back-button")
self.community_title = Text(self.driver, accessibility_id="community-title")
self.community_logo = BaseElement(
self.driver, xpath="//*[@content-desc='community-title']/preceding-sibling::*/android.widget.ImageView")
self.community_description_text = Text(self.driver, accessibility_id="community-description-text")
self.community_status_joined = Text(self.driver, accessibility_id="status-tag-positive")
self.community_status_pending = Text(self.driver, accessibility_id="status-tag-pending")

Some files were not shown because too many files have changed in this diff Show More