Compare commits

...
Author SHA1 Message Date
Alexander 37c0daec34 Merge branch 'develop' into bugfix/21636 2024-12-12 18:00:57 +01:00
flexsurfer 676998b83b [#21803] Cannot perform transactions/create derived account via keycard using NON default keypairs which existed before keycard migration (#21808) 2024-12-12 16:31:54 +01:00
Ulises Manuel 755ef19315 feat(profile): Make terms of use and privacy policy easily accessible (#21759)
* Fix `:true` keyword being used in navigation screens.

* Add about screen, terms of use and privacy policy links

* Remove old about app screen
2024-12-11 13:15:26 -06:00
Ulises Manuel bc9b0f8c87 feat(oboarding): Update ilustrations (#21781)
* Share usage data screen image done

* Update login by syncing and login using recovery phrase images

*  Small refactor: remove `merge` and add a `let` binding

* Fix modal entering expected animation
2024-12-11 11:05:23 -06:00
Yevheniia Berdnyk c8b063493b e2e: False failures fixes 2024-12-11 00:38:34 +02:00
Alexander 84a3fffe9f Assets not sorted by fiat balance on 'Assets to pay' and 'Assets to receive' screens (#21636) 2024-12-10 18:23:57 +01:00
flexsurfer 949e78a17e [#21577] Keycard [Wallet] - Derives next account for profile key pair (#21753) 2024-12-10 18:07:45 +01:00
Mohsen a821ec5a14 [#21763] feat: add feature unavailable for dapps interaction (#21771) 2024-12-10 11:35:44 +01:00
cuiyourong 4648066ec7 chore: fix some typos in comment (#21775) 2024-12-10 09:27:32 +01:00
Ulises ManuelandYevheniia Berdnyk e85bc539d3 feat(onboarding): Remove color and username steps (#21715)
* Remove password disclaimer, update copy and simplify implementation

* Remove non-existing wallet event

* Skip profile configuration for the "create profile" onboarding flow

* Include flow for new profiles when another profile already exists

* Add explanation about temp username

* Point 20 to a constant

* Simplify create profile password screen

* Fix password creation screen jump on mount

* e2e: updated tests

* Completely remove the "create profile" onboarding screen

* e2e: updated tests with recovering user

---------

Co-authored-by: Yevheniia Berdnyk <ie.berdnyk@gmail.com>
2024-12-09 13:55:47 -06:00
Mohamed Javid c26d298573 fix_: slider reset in standard authentication (#21779)
This commit resets the slider-button after the completion of slider in standard authentication.


Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-12-10 00:56:57 +05:30
Mohamed Javid cdde36ca28 fix(wallet)_: logic for calculating zero balance (#21777)
This commit fixes logic to calculate whether the user has zero balance in all non-watched accounts.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-12-10 00:21:00 +05:30
77 changed files with 1174 additions and 953 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ This one is straightforward, just evaluate a navigation dispatch form from anywh
#### REPL-ing a component
This is also straighforward, but there are some small differences. Just like above, we only have to evaluate a dispatch form, as follows:
This is also straightforward, but there are some small differences. Just like above, we only have to evaluate a dispatch form, as follows:
```cljs
;; example debugging a quo component, although it could be any other component e.g. from the `status-im2` ns.
+1 -1
View File
@@ -26,4 +26,4 @@ These log files are uploaded to Jenkins as CI job artifacts on failure using `ar
* `set_xcode_version.log` - Output from `node_modules/react-native/scripts/react-native-xcode.sh`.
- Created by redirecting output of `shellScript` in `ios/StatusIm.xcodeproj/project.pbxproj` for "Run Script" build Phase.
* `Status PR-StatusImPR.log - Created by [Fastlane Gym](https://docs.fastlane.tools/actions/gym/).
- Configred via [`Fastfile`](../Fastfile) using `buildlog_path` argument for `build_ios_app`.
- Configured via [`Fastfile`](../Fastfile) using `buildlog_path` argument for `build_ios_app`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

@@ -1,67 +0,0 @@
(ns legacy.status-im.ui.screens.about-app.views
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.copyable-text :as copyable-text]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.react :as react]
[quo.core :as quo]
[utils.i18n :as i18n]
[utils.re-frame :as rf])
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview about-app
[]
(views/letsubs [app-version [:get-app-short-version]
commit-hash [:get-commit-hash]
node-version [:get-app-node-version]]
[:<>
[quo/page-nav
{:type :title
:title (i18n/label :t/about-app)
:background :blur
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
[react/scroll-view
[copyable-text/copyable-text-view
{:copied-text app-version}
[list.item/list-item
{:size :small
:accessibility-label :app-version
:title (i18n/label :t/version)
:accessory :text
:accessory-text app-version}]]
[copyable-text/copyable-text-view
{:copied-text commit-hash}
[list.item/list-item
{:size :small
:accessibility-label :commit-hash
:title (i18n/label :t/app-commit)
:accessory :text
:accessory-text commit-hash}]]
[copyable-text/copyable-text-view
{:copied-text node-version}
[list.item/list-item
{:size :small
:accessibility-label :node-version
:title (i18n/label :t/node-version)
:accessory :text
:accessory-text node-version}]]]]))
(views/defview learn-more-sheet
[]
(views/letsubs [{:keys [title content]} [:bottom-sheet/options]]
[react/view
{:style {:padding-left 16
:padding-top 16
:padding-right 34
:padding-bottom 0}}
[react/view {:style {:align-items :center :flex-direction :row :margin-bottom 16}}
[icons/icon :main-icons/info
{:color colors/blue
:container-style {:margin-right 13}}]
[react/text {:style {:typography :title-bold}} title]]
[react/text {:style {:color colors/gray}} content]]))
(def learn-more
{:content learn-more-sheet})
@@ -1,7 +1,6 @@
(ns legacy.status-im.ui.screens.screens
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.screens.about-app.views :as about-app]
[legacy.status-im.ui.screens.advanced-settings.views :as advanced-settings]
[legacy.status-im.ui.screens.appearance.views :as appearance]
[legacy.status-im.ui.screens.backup-settings.view :as backup-settings]
@@ -93,12 +92,6 @@
:insets {:top? platform/android?}}
:component sync-settings/sync-settings}
;; ABOUT
{:name :about-app
:options {:topBar {:visible false}
:insets {:top? platform/android?}}
:component about-app/about-app}
;; STATUS HELP
{:name :help-center
:options {:topBar {:visible false}
@@ -16,7 +16,8 @@
[rn/view {:style (style/loading-container size blur? theme)}]))
(defn- left-subtitle
[{:keys [size subtitle-type subtitle-color icon icon-color blur? subtitle customization-color emoji
[{:keys [size subtitle-text-props subtitle-type subtitle-color icon icon-color blur? subtitle
customization-color emoji
network-image]
:or {subtitle-type :default}}]
(let [theme (quo.theme/use-theme)]
@@ -39,9 +40,10 @@
:style style/image}]
nil)])
[text/text
{:weight :medium
:size :paragraph-2
:style (style/description subtitle-color blur? theme)}
(merge {:weight :medium
:size :paragraph-2
:style (style/description subtitle-color blur? theme)}
subtitle-text-props)
subtitle]
(when (= subtitle-type :editable)
[icons/icon :i/edit
@@ -75,7 +77,8 @@
(defn- left-side
"The description can either be given as a string `subtitle-type` or a component `custom-subtitle`"
[{:keys [title status size blur? custom-subtitle icon subtitle subtitle-type subtitle-color icon-color
[{:keys [title subtitle-text-props status size blur? custom-subtitle icon subtitle subtitle-type
subtitle-color icon-color
customization-color network-image emoji title-icon]
:as props}]
(let [theme (quo.theme/use-theme)]
@@ -94,6 +97,7 @@
[custom-subtitle props]
[left-subtitle
{:theme theme
:subtitle-text-props subtitle-text-props
:size size
:subtitle-type subtitle-type
:subtitle-color subtitle-color
@@ -140,6 +144,7 @@
[:size {:optional true} [:maybe [:enum :default :small :large]]]
[:title :string]
[:subtitle {:optional true} [:maybe [:or :string :double]]]
[:subtitle-text-props {:optional true} [:maybe :map]]
[:custom-subtitle {:optional true} [:maybe fn?]]
[:icon {:optional true} [:maybe :keyword]]
[:title-icon {:optional true} [:maybe :keyword]]
@@ -79,11 +79,11 @@
[rn/view {:style style/community-logo-ring}]])
(defn- description-container
[{:keys [description description-text collection-text community-text
collection-image community-image blur?]
[{:keys [description description-text collection-text community-text blur?
collection-image community-image description-accessibility-label]
context-tag-props :context-tag
summary-props :summary}]
[rn/view
[rn/view {:accessibility-label description-accessibility-label}
(cond
(and (= description :text) (not (string/blank? description-text)))
[text/text
@@ -61,14 +61,17 @@
(defn view
[{:keys [header footer customization-color footer-container-padding header-container-style
content-container-style gradient-cover? keyboard-should-persist-taps shell-overlay?
blur-options content-avoid-keyboard? automatically-adjust-keyboard-insets]
blur-options content-avoid-keyboard? automatically-adjust-keyboard-insets
;; Note: Provide `initial-header-height` to avoid a jump due to the on-layout 1-frame
;; delay. Revisit this on RN 0.76 since the on-layout delay has been fixed.
initial-header-height]
:or {footer-container-padding (safe-area/get-top)}}
& children]
(reagent/with-let [scroll-view-ref (atom nil)
set-scroll-ref #(reset! scroll-view-ref %)
window-height (:height (rn/get-window))
footer-container-height (reagent/atom 0)
header-height (reagent/atom 0)
header-height (reagent/atom nil)
content-container-height (reagent/atom 0)
content-scroll-y (reagent/atom 0)
keyboard-height (reagent/atom 0)
@@ -112,7 +115,7 @@
{:style
(if content-avoid-keyboard?
(style/content-keyboard-avoiding-view
{:top @header-height
{:top (or @header-height initial-header-height)
:bottom (if keyboard-shown?
@footer-container-height
(+ bottom-safe-area @footer-container-height))})
+11 -2
View File
@@ -1,11 +1,20 @@
(ns status-im.common.privacy.view
(:require-macros [legacy.status-im.utils.slurp :refer [slurp]])
(:require [quo.core :as quo]
[react-native.gesture :as gesture]))
[react-native.gesture :as gesture]
[status-im.contexts.settings.common.header :as header]
[utils.i18n :as i18n]))
(def privacy-statement-text (slurp "resources/privacy.mdwn"))
(defn privacy-statement
[]
[gesture/scroll-view {:margin 20}
[gesture/scroll-view {:style {:margin 20}}
[quo/text privacy-statement-text]])
(defn view
[]
[quo/overlay {:type :shell}
[header/view {:title (i18n/label :t/privacy-policy)}]
[gesture/scroll-view {:style {:padding-horizontal 20}}
[quo/text privacy-statement-text]]])
+2
View File
@@ -3,6 +3,8 @@
(def ui
{:add-new-contact (js/require "../resources/images/ui2/add-contact.png")
:biometrics (js/require "../resources/images/ui2/biometrics.png")
:usage-data (js/require "../resources/images/ui2/usage-data.png")
:login-syncing (js/require "../resources/images/ui2/login-syncing.png")
:chat-privately-with-friends (js/require "../resources/images/ui2/chat-privately-with-friends.png")
:desktop-how-to-pair-sign-in (js/require "../resources/images/ui2/desktop-how-to-pair-sign-in.png")
:desktop-how-to-pair-logged-in (js/require
@@ -10,24 +10,43 @@
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-fx :effects.keycard/call-on-auth-success
(fn [on-auth-success]
(when on-auth-success (on-auth-success ""))))
(defn- handle-password-success
[has-partially-operable-accounts? on-auth-success masked-password]
(let [on-auth-success-callback #(on-auth-success masked-password)]
(rf/dispatch [:standard-auth/set-success true])
(rf/dispatch [:standard-auth/reset-login-password])
(if has-partially-operable-accounts?
(rf/dispatch [:wallet/make-partially-operable-accounts-fully-operable
{:password masked-password
:on-success on-auth-success-callback
:on-error on-auth-success-callback}])
(on-auth-success-callback))))
(defn authorize
[{:keys [db]} [{:keys [on-auth-success keycard-supported?] :as args}]]
(let [key-uid (get-in db [:profile/profile :key-uid])
keycard? (get-in db [:profile/profile :keycard-pairing])]
[{:keys [db]} [{:keys [on-auth-success] :as args}]]
(let [key-uid (get-in db [:profile/profile :key-uid])
keycard-profile? (get-in db [:profile/profile :keycard-pairing])]
{:fx
[(if keycard?
(if keycard-supported?
[:effects.keycard/call-on-auth-success on-auth-success]
[:effects.utils/show-popup
{:title "This feature is not supported yet "
:content
"Keycard support is limited to logging in
and signing the sending transaction.
Use Status Desktop to access all functions."}])
[(if keycard-profile?
[:dispatch
[:standard-auth/authorize-with-keycard
{:on-complete
(fn [pin]
(rf/dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/get-keys
{:pin pin
:on-success (fn [{:keys [encryption-public-key]}]
(rf/dispatch [:keycard/disconnect])
(handle-password-success false
on-auth-success
(security/mask-data encryption-public-key)))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]))}]))}]]
[:effects.biometric/check-if-available
{:key-uid key-uid
:on-success #(rf/dispatch [:standard-auth/authorize-with-biometric args])
@@ -89,21 +108,12 @@
(defn- bottom-sheet-password-view
[{:keys [on-press-biometric on-auth-success auth-button-icon-left auth-button-label]}]
(fn []
(let [has-partially-operable-accounts? (rf/sub [:wallet/has-partially-operable-accounts?])
handle-password-success
(fn [password]
(let [sha3-pwd (security/hash-masked-password password)
on-auth-success-callback #(on-auth-success sha3-pwd)]
(rf/dispatch [:standard-auth/set-success true])
(rf/dispatch [:standard-auth/reset-login-password])
(if has-partially-operable-accounts?
(rf/dispatch [:wallet/make-partially-operable-accounts-fully-operable
{:password sha3-pwd
:on-success on-auth-success-callback
:on-error on-auth-success-callback}])
(on-auth-success-callback))))]
(let [has-partially-operable-accounts? (rf/sub [:wallet/has-partially-operable-accounts?])]
[enter-password/view
{:on-enter-password handle-password-success
{:on-enter-password #(handle-password-success
has-partially-operable-accounts?
on-auth-success
(security/hash-masked-password %))
:on-press-biometrics on-press-biometric
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])))
@@ -9,26 +9,25 @@
(defn view
[{:keys [track-text customization-color auth-button-label on-auth-success on-auth-fail
auth-button-icon-left size blur? container-style disabled? dependencies keycard-supported?]
auth-button-icon-left size blur? container-style disabled? dependencies on-complete]
:or {container-style {:flex 1}}}]
(let [theme (quo.theme/use-theme)
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method constants/auth-method-biometric)
on-complete (rn/use-callback
(fn [reset]
(rf/dispatch [:standard-auth/authorize
{:on-close (fn [success?]
(js/setTimeout #(reset success?) 200))
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:keycard-supported? keycard-supported?
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}]))
(vec (conj dependencies on-auth-success on-auth-fail)))
biometric-type (rf/sub [:biometrics/supported-type])]
(let [theme (quo.theme/use-theme)
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method constants/auth-method-biometric)
on-complete-callback (rn/use-callback
(fn [reset-slider-fn]
(js/setTimeout #(reset-slider-fn false) 500)
(rf/dispatch [:standard-auth/authorize
{:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}]))
(vec (conj dependencies on-auth-success on-auth-fail)))
on-complete (or on-complete on-complete-callback)
biometric-type (rf/sub [:biometrics/supported-type])]
[quo/slide-button
{:container-style container-style
:size size
+11 -2
View File
@@ -1,11 +1,20 @@
(ns status-im.common.terms.view
(:require-macros [legacy.status-im.utils.slurp :refer [slurp]])
(:require [quo.core :as quo]
[react-native.gesture :as gesture]))
[react-native.gesture :as gesture]
[status-im.contexts.settings.common.header :as header]
[utils.i18n :as i18n]))
(def terms-of-use-text (slurp "resources/terms-of-use.mdwn"))
(defn terms-of-use
[]
[gesture/scroll-view {:margin 20}
[gesture/scroll-view {:style {:margin 20}}
[quo/text terms-of-use-text]])
(defn view
[]
[quo/overlay {:type :shell}
[header/view {:title (i18n/label :t/terms-of-service)}]
[gesture/scroll-view {:style {:padding-horizontal 20}}
[quo/text terms-of-use-text]]])
@@ -28,9 +28,10 @@
:long-enough?
:short-enough?)))))
(def password-tip? (set constants/password-tips))
(defn strength
[validations]
(->> (select-keys validations constants/password-tips)
(vals)
(filter true?)
count))
(->> validations
(filter #(and (password-tip? (key %)) (val %)))
(count)))
+7
View File
@@ -334,6 +334,7 @@
(def ^:const privacy-policy-link "https://status.im/privacy-policy/")
(def ^:const terms-of-service-link "https://status.im/terms-of-use")
(def ^:const create-account-link "https://status.app/help/wallet/create-wallet-accounts")
(def ^:const mobile-upvote-link "https://status-mobile.featureupvote.com")
(def ^:const visibility-status-unknown 0)
(def ^:const visibility-status-automatic 1)
@@ -641,3 +642,9 @@
(def ^:const wallet-connect-transaction-refresh-interval-ms 10000)
(def ^:const native-token-symbol "ETH")
;; About app links
(def ^:const status-app-url "http://status.app")
(def ^:const go-waku-url "https://github.com/waku-org/go-waku")
(def ^:const status-go-url "https://github.com/status-im/status-go")
(def ^:const status-mobile-url "https://github.com/status-im/status-mobile")
@@ -52,8 +52,8 @@
{:eventName "navigation"
:platform platform-os
:appVersion app-version
:eventValue {:viewId "onboarding.create-profile-info"}}}]
(tracking/track-view-id-event :screen/onboarding.create-profile)))
:eventValue {:viewId "onboarding.create-profile-password"}}}]
(tracking/track-view-id-event :screen/onboarding.create-profile-password)))
(is (= [] (tracking/track-view-id-event :unknown-stack)))))
(deftest tracked-event-test
@@ -11,30 +11,35 @@
[{:keys [contact-id]}]
(let [{:keys [contact-request-state
community-id
chat-name]} (rf/sub [:chats/current-chat-chat-view])
chat-type (rf/sub [:chats/chat-type])
[primary-name _] (if (= chat-type :public-chat)
[(str "#" chat-name) ""]
(rf/sub [:contacts/contact-two-names-by-identity contact-id]))
community-chat? (= chat-type :community-chat)
joined (when community-chat?
(rf/sub [:communities/community-joined community-id]))
pending? (when community-chat?
(rf/sub [:communities/my-pending-request-to-join community-id]))
contact-request-send? (or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)]
chat-name]} (rf/sub [:chats/current-chat-chat-view])
chat-type (rf/sub [:chats/chat-type])
[primary-name _] (if (= chat-type :public-chat)
[(str "#" chat-name) ""]
(rf/sub [:contacts/contact-two-names-by-identity contact-id]))
community-chat? (= chat-type :community-chat)
joined (when community-chat?
(rf/sub [:communities/community-joined community-id]))
pending? (when community-chat?
(rf/sub [:communities/my-pending-request-to-join community-id]))
contact-request-send? (or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
[rn/view {:style style/container}
[quo/permission-context
{:blur? true
:on-press (cond
(and community-chat? (not pending?) (not joined))
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id community-id}])
(if keycard?
keycard-feature-unavailable
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id community-id}]))
(not community-chat?)
#(rf/dispatch [:chat.ui/show-profile contact-id]))
@@ -27,7 +27,10 @@
(fn []
(let [theme (quo.theme/use-theme)
{:keys [id]} (rf/sub [:get-screen-params])
{:keys [color name images]} (rf/sub [:communities/community id])]
{:keys [color name images]} (rf/sub [:communities/community id])
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
[rn/safe-area-view {:flex 1}
[gesture/scroll-view {:style style/container}
[rn/view style/page-container
@@ -59,7 +62,9 @@
(i18n/label :t/cancel)]
[quo/button
{:accessibility-label :join-community-button
:on-press #(join-community-and-navigate-back id)
:on-press (if keycard?
keycard-feature-unavailable
#(join-community-and-navigate-back id))
:container-style {:flex 1}
:inner-style {:background-color (colors/resolve-color color theme)}}
(i18n/label :t/request-to-join)]]
@@ -106,12 +106,14 @@
[quo/text (i18n/label :t/network-not-supported)])
(defn- request-access-button
[id color]
[id color keycard? keycard-feature-unavailable]
[quo/button
{:on-press (if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
{:on-press (if keycard?
keycard-feature-unavailable
(if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}])))
:accessibility-label :show-request-to-join-screen-button
:customization-color color
:container-style {:margin-bottom 12}
@@ -126,27 +128,33 @@
(defn- token-requirements
[{:keys [id color role-permissions?]}]
(let [{:keys [can-request-access? no-member-permission? networks-not-supported?
highest-permission-role
tokens]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
on-press (rn/use-callback
(fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id id}])))
[id])
on-press-info #(rf/dispatch
[:show-bottom-sheet {:content token-gated-communities-info}])]
(let
[{:keys [can-request-access? no-member-permission? networks-not-supported?
highest-permission-role
tokens]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label
(communities.utils/role->translation-key highest-permission-role
:t/member))
on-press (rn/use-callback
(fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id id}])))
[id])
on-press-info #(rf/dispatch
[:show-bottom-sheet {:content token-gated-communities-info}])
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
(cond
networks-not-supported?
[network-not-supported]
(or (not role-permissions?) no-member-permission?)
[request-access-button id color]
[request-access-button id color keycard? keycard-feature-unavailable]
:else
[quo/community-token-gating
@@ -154,7 +162,7 @@
:tokens tokens
:community-color color
:satisfied? can-request-access?
:on-press on-press
:on-press (if keycard? keycard-feature-unavailable on-press)
:on-press-info on-press-info}])))
(defn- join-community
+27 -1
View File
@@ -9,7 +9,9 @@
status-im.contexts.keycard.pin.events
status-im.contexts.keycard.sign.events
[status-im.contexts.keycard.utils :as keycard.utils]
utils.datetime))
[utils.address :as address]
utils.datetime
[utils.ethereum.eip.eip55 :as eip55]))
(rf/reg-event-fx :keycard/on-card-connected
(fn [{:keys [db]} _]
@@ -65,6 +67,30 @@
(fn [_ [data]]
{:effects.keycard/export-key data}))
(rf/reg-event-fx :keycard/connect-derive-address-and-add-account
(fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]]
{:fx [[:dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/export-key
{:pin pin
:path derivation-path
:on-success (fn [public-key]
(let [derived-account {:public-key (str "0x" public-key)
:address (eip55/address->checksum
(str "0x"
(address/public-key->address
(subs public-key 2))))
:path derivation-path}]
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:wallet/add-account
(assoc account-preferences :key-uid key-uid)
derived-account])))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]]]}))
(rf/reg-event-fx :keycard/cancel-connection
(fn [{:keys [db]}]
{:db (update db
@@ -0,0 +1,12 @@
(ns status-im.contexts.keycard.feature-unavailable.events
(:require
[status-im.contexts.keycard.feature-unavailable.view :as feature-unavailable]
[utils.re-frame :as rf]))
(rf/reg-event-fx
:keycard/feature-unavailable-show
(fn [_ [options]]
{:fx [[:dispatch
[:show-bottom-sheet
{:theme (:theme options)
:content feature-unavailable/view}]]]}))
@@ -0,0 +1,28 @@
(ns status-im.contexts.keycard.feature-unavailable.view
(:require
[quo.core :as quo]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn on-upvote
[]
(rf/dispatch [:open-url constants/mobile-upvote-link]))
(defn view
[]
[:<>
[quo/drawer-top
{:title (i18n/label :t/feature-unavailable)
:description (i18n/label :t/feature-unavailable-for-keycard-description)}]
[quo/information-box
{:type :default
:icon :i/info
:style {:margin-top 8 :margin-horizontal 20}}
[:<>
(i18n/label :t/feature-unavailable-info)
[quo/text
{:style {:text-decoration-line :underline}
:size :paragraph-2
:on-press on-upvote}
(i18n/label :t/upvote-it)]]]])
@@ -91,7 +91,7 @@
:title (i18n/label :t/log-in-by-syncing)
:subtitle (i18n/label :t/log-in-by-syncing-subtitle)
:accessibility-label :log-in-by-syncing-icon-card
:image (resources/get-image :ethereum-address)
:image (resources/get-image :login-syncing)
:on-press show-check-before-syncing}])
(defn- use-empty-keycard-icon-card
@@ -1,6 +1,7 @@
(ns status-im.contexts.onboarding.create-password.style
(:require
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(def heading {:margin-bottom 20})
(def heading-subtitle {:color colors/white})
@@ -21,6 +22,18 @@
:padding-top 12
:padding-horizontal 20})
(def disclaimer-container {:padding-horizontal 20})
(def footer-container {:padding-bottom 12})
(def footer-button-container {:margin-top 20 :padding-horizontal 20})
(def blur-options
{:blur-amount 34
:blur-radius 20
:blur-type :transparent
:overlay-color :transparent
:background-color (if platform/android?
colors/neutral-100
colors/neutral-80-opa-1-blur)
:padding-vertical 0
:padding-horizontal 0})
(def page-nav-height 56)
@@ -1,9 +1,7 @@
(ns status-im.contexts.onboarding.create-password.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button]
[status-im.common.password-with-hint.view :as password-with-hint]
@@ -29,22 +27,37 @@
(i18n/label :t/password-creation-subtitle)]])
(defn password-inputs
[{:keys [passwords-match? on-change-password on-change-repeat-password on-input-focus
password-long-enough? password-short-enough? empty-password? show-password-validation?
on-blur-repeat-password]}]
(let [hint-1-status (if password-long-enough? :success :default)
hint-2-status (if passwords-match? :success :error)
hint-2-text (if passwords-match?
(i18n/label :t/password-creation-match)
(i18n/label :t/password-creation-dont-match))
error? (and show-password-validation?
(not passwords-match?)
(not empty-password?))]
[{:keys [set-password set-repeat-password same-password-length? same-passwords?
password-long-enough? password-short-enough? non-empty-password?]}]
(let [[show-validation?
set-show-validation?] (rn/use-state false)
on-change-password (rn/use-callback
(fn [new-value]
(set-password new-value)
(when same-password-length?
(set-show-validation? true)))
[same-password-length?])
on-change-repeat-password (rn/use-callback
(fn [new-value]
(set-repeat-password new-value)
(when same-password-length?
(set-show-validation? true)))
[same-password-length?])
on-blur-repeat-password (rn/use-callback
#(set-show-validation? non-empty-password?)
[non-empty-password?])
hint-1-status (if password-long-enough? :success :default)
hint-2-status (if same-passwords? :success :error)
hint-2-text (if same-passwords?
(i18n/label :t/password-creation-match)
(i18n/label :t/password-creation-dont-match))
error? (and show-validation?
(not same-passwords?)
non-empty-password?)]
[:<>
[password-with-hint/view
{:hint (if (not password-short-enough?)
{:text (i18n/label
:t/password-creation-max-length-hint)
{:text (i18n/label :t/password-creation-max-length-hint)
:status :error
:shown? true}
{:text (i18n/label :t/password-creation-hint)
@@ -52,55 +65,50 @@
:shown? true})
:placeholder (i18n/label :t/password-creation-placeholder-1)
:on-change-text on-change-password
:on-focus on-input-focus
:auto-focus true}]
[rn/view {:style style/space-between-inputs}]
[password-with-hint/view
{:hint {:text hint-2-text
:status hint-2-status
:shown? (and (not empty-password?)
show-password-validation?)}
:shown? (and non-empty-password? show-validation?)}
:error? error?
:placeholder (i18n/label :t/password-creation-placeholder-2)
:on-change-text on-change-repeat-password
:on-focus on-input-focus
:on-blur on-blur-repeat-password}]]))
(defn help
[{{:keys [lower-case? upper-case? numbers? symbols?]} :validations
password-strength :password-strength}]
[rn/view
[quo/strength-divider {:type (constants/strength-status password-strength :info)}
(i18n/label :t/password-creation-tips-title)]
[rn/view {:style style/password-tips}
[quo/tips {:completed? lower-case?}
(i18n/label :t/password-creation-tips-1)]
[quo/tips {:completed? upper-case?}
(i18n/label :t/password-creation-tips-2)]
[quo/tips {:completed? numbers?}
(i18n/label :t/password-creation-tips-3)]
[quo/tips {:completed? symbols?}
(i18n/label :t/password-creation-tips-4)]]])
[{:keys [lower-case? upper-case? numbers? symbols?] :as validations}]
(let [password-strength (constants/strength-status (password/strength validations) :info)]
[rn/view
[quo/strength-divider {:type password-strength}
(i18n/label :t/password-creation-tips-title)]
[rn/view {:style style/password-tips}
[quo/tips {:completed? lower-case?}
(i18n/label :t/password-creation-tips-1)]
[quo/tips {:completed? upper-case?}
(i18n/label :t/password-creation-tips-2)]
[quo/tips {:completed? numbers?}
(i18n/label :t/password-creation-tips-3)]
[quo/tips {:completed? symbols?}
(i18n/label :t/password-creation-tips-4)]]]))
(defn- use-password-checks
[password]
(rn/use-memo
(fn []
(let [{:keys [long-enough? short-enough?]
:as validations} (password/validate password)]
{:password-long-enough? long-enough?
:password-short-enough? short-enough?
:password-validations validations
:password-strength (password/strength validations)
:empty-password? (empty? password)}))
(-> password
(password/validate)
(assoc :non-empty? (seq password))))
[password]))
(defn- use-repeat-password-checks
[password repeat-password]
(rn/use-memo
(fn []
{:same-password-length? (and (seq password) (= (count password) (count repeat-password)))
:same-passwords? (and (seq password) (= password repeat-password))})
{:same-password-length? (and (seq password)
(= (count password) (count repeat-password)))
:same-passwords? (and (seq password)
(= password repeat-password))})
[password repeat-password]))
(defn create-password-doc
@@ -119,102 +127,70 @@
{:content create-password-doc
:shell? true}]))
(defn- navigate-back
[]
(rf/dispatch [:navigate-back]))
(defn- navigate-back [] (rf/dispatch [:navigate-back]))
(defn- page-nav
[]
(let [{:keys [top]} (safe-area/get-insets)]
[quo/page-nav
{:margin-top top
:background :blur
:icon-name :i/arrow-left
:on-press navigate-back
:right-side [{:icon-name :i/info
:on-press on-press-info}]}]))
[top]
[quo/page-nav
{:margin-top top
:background :blur
:icon-name :i/arrow-left
:on-press navigate-back
:right-side [{:icon-name :i/info
:on-press on-press-info}]}])
(defn- help-and-confirm-button
[{:keys [password-validations same-passwords? on-submit]}]
(let [{customization-color :color} (rf/sub [:onboarding/profile])
all-requirements-met? (and (:non-empty? password-validations)
(:long-enough? password-validations)
(:short-enough? password-validations)
same-passwords?)]
[rn/view {:style style/footer-container}
[help password-validations]
[quo/button
{:container-style style/footer-button-container
:disabled? (not all-requirements-met?)
:customization-color customization-color
:on-press on-submit}
(i18n/label :t/password-creation-confirm)]]))
(defn- on-confirm-password
[password]
(rf/dispatch [:onboarding/password-set (security/mask-data password)]))
(defn create-password
[]
(let [[password set-password] (rn/use-state "")
[repeat-password set-repeat-password] (rn/use-state "")
[accepts-disclaimer? set-accepts-disclaimer?] (rn/use-state false)
[focused-input set-focused-input] (rn/use-state nil)
[show-password-validation?
set-show-password-validation?] (rn/use-state false)
{user-color :color} (rf/sub [:onboarding/profile])
{:keys [password-long-enough?
password-short-enough?
password-validations password-strength
empty-password?]} (use-password-checks password)
{:keys [same-password-length? same-passwords?]} (use-repeat-password-checks password
repeat-password)
meet-requirements? (and (not empty-password?)
password-long-enough?
password-short-enough?
same-passwords?
accepts-disclaimer?)]
(let [[password set-password] (rn/use-state "")
[repeat-password
set-repeat-password] (rn/use-state "")
{:keys [long-enough? short-enough? non-empty?]
:as password-validations} (use-password-checks password)
{:keys [same-password-length?
same-passwords?]} (use-repeat-password-checks password repeat-password)
on-submit (rn/use-callback
#(on-confirm-password password)
[password])
top (safe-area/get-top)]
[floating-button/view
{:header [page-nav]
:keyboard-should-persist-taps :handled
:content-avoid-keyboard? true
{:header [page-nav top]
:initial-header-height (+ style/page-nav-height top)
:keyboard-should-persist-taps :handled
:content-avoid-keyboard? true
:automatically-adjust-keyboard-insets true
:blur-options
{:blur-amount 34
:blur-radius 20
:blur-type :transparent
:overlay-color :transparent
:background-color (if platform/android? colors/neutral-100 colors/neutral-80-opa-1-blur)
:padding-vertical 0
:padding-horizontal 0}
:footer-container-padding 0
:footer
[rn/view
{:style style/footer-container}
(when same-passwords?
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?
:customization-color user-color}
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= focused-input :password) (not same-passwords?))
[help
{:validations password-validations
:password-strength password-strength}])
[quo/button
{:container-style style/footer-button-container
:disabled? (not meet-requirements?)
:customization-color user-color
:on-press #(rf/dispatch
[:onboarding/password-set
(security/mask-data password)])}
(i18n/label :t/password-creation-confirm)]]}
:blur-options style/blur-options
:footer-container-padding 0
:footer [help-and-confirm-button
{:password-validations password-validations
:same-passwords? same-passwords?
:on-submit on-submit}]}
[rn/view {:style style/form-container}
[header]
[password-inputs
{:password-long-enough? password-long-enough?
:password-short-enough? password-short-enough?
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? show-password-validation?
:on-input-focus #(set-focused-input :password)
:on-change-password (fn [new-value]
(set-password new-value)
(when same-password-length?
(set-show-password-validation? true)))
:on-change-repeat-password (fn [new-value]
(set-repeat-password new-value)
(when same-password-length?
(set-show-password-validation? true)))
:on-blur-repeat-password #(if empty-password?
(set-show-password-validation? false)
(set-show-password-validation? true))}]]]))
{:password-long-enough? long-enough?
:password-short-enough? short-enough?
:non-empty-password? non-empty?
:same-passwords? same-passwords?
:same-password-length? same-password-length?
:set-password set-password
:set-repeat-password set-repeat-password}]]]))
@@ -1,60 +0,0 @@
(ns status-im.contexts.onboarding.create-profile.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(def continue-button
{:width "100%"
:margin-left :auto
:margin-top (if platform/android? :auto 0)
:margin-right :auto})
(def button-container
{:width "100%"
:padding-left 20
:padding-right 20
:padding-top 12
:align-self :flex-end
:height 64})
(defn view-button-container
[keyboard-shown?]
(merge button-container
(if platform/ios?
{:margin-bottom (if keyboard-shown? 0 34)}
{:margin-bottom (if keyboard-shown? 12 34)})))
(def blur-button-container
(merge button-container
(when platform/android? {:padding-bottom 12})))
(def page-container
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:z-index 100})
(def info-message
{:margin-top 8})
(def title
{:color colors/white
:margin-top 12
:margin-bottom 18})
(def color-title
{:color colors/white-70-blur
:margin-top 20
:margin-bottom 16})
(def content-container
{:padding-horizontal 20})
(def input-container
{:align-items :flex-start})
(def profile-input-container
{:flex-direction :row
:justify-content :center})
@@ -1,210 +0,0 @@
(ns status-im.contexts.onboarding.create-profile.view
(:require
[clojure.string :as string]
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.avatar-picture-picker.view :as profile-picture-picker]
[status-im.common.validation.profile :as profile-validator]
[status-im.constants :as c]
[status-im.contexts.onboarding.create-profile.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.responsiveness :as responsiveness]))
(def scroll-view-height (reagent/atom 0))
(def content-container-height (reagent/atom 0))
(defn show-button-background
[keyboard-height keyboard-shown content-scroll-y]
(let [button-container-height 64
keyboard-view-height (+ keyboard-height button-container-height)]
(when keyboard-shown
(cond
platform/android?
(< (- @scroll-view-height button-container-height) @content-container-height)
platform/ios?
(< (- @scroll-view-height keyboard-view-height) (- @content-container-height content-scroll-y))
:else
false))))
(defn button-container
[show-keyboard? keyboard-shown show-background? keyboard-height children]
(let [height (reagent/atom 0)]
(reset! height (if show-keyboard? (if keyboard-shown keyboard-height 0) 0))
[rn/view {:style {:margin-top :auto}}
(cond
(and (> @height 0) show-background?)
[quo/blur
(when keyboard-shown
{:blur-amount 34
:blur-type :transparent
:overlay-color :transparent
:background-color (if platform/android? colors/neutral-100 colors/neutral-80-opa-1-blur)
:style style/blur-button-container})
children]
(and (> @height 0) (not show-background?))
[rn/view {:style (style/view-button-container true)}
children]
(not show-keyboard?)
[rn/view {:style (style/view-button-container false)}
children])]))
(defn- page
[{:keys [onboarding-profile-data navigation-bar-top]}]
(reagent/with-let [show-keyboard? (reagent/atom false)
content-scroll-y (reagent/atom 0)
show-listener (oops/ocall rn/keyboard
"addListener"
(if platform/android?
"keyboardDidShow"
"keyboardWillShow")
#(reset! show-keyboard? true))
hide-listener (oops/ocall rn/keyboard
"addListener"
(if platform/android?
"keyboardDidHide"
"keyboardWillHide")
#(reset! show-keyboard? false))
{:keys [image-path display-name color]} onboarding-profile-data
full-name (reagent/atom display-name)
validation-msg (reagent/atom
(profile-validator/validation-name
@full-name))
on-change-text (fn [s]
(reset! validation-msg
(profile-validator/validation-name
s))
(reset! full-name (string/trim s)))
custom-color (reagent/atom (or color
c/profile-default-color))
profile-pic (reagent/atom image-path)
on-change-profile-pic #(reset! profile-pic %)
on-change #(reset! custom-color %)]
(let [name-too-short? (profile-validator/name-too-short? @full-name)
valid-name? (and (not @validation-msg) (not name-too-short?))
info-message (if @validation-msg
@validation-msg
(i18n/label :t/minimum-characters
{:min-chars
profile-validator/min-length}))
info-type (cond @validation-msg :error
name-too-short? :default
:else :success)
{:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard)
show-background? (show-button-background keyboard-height
keyboard-shown
@content-scroll-y)
{window-width :width} (rn/get-window)]
[rn/view {:style style/page-container}
[quo/page-nav
{:margin-top navigation-bar-top
:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])}]
[rn/scroll-view
{:on-layout (fn [event]
(let [height (oops/oget event "nativeEvent.layout.height")]
(reset! scroll-view-height height)
(reset! content-scroll-y 0)))
:on-scroll (fn [event]
(let [y (oops/oget event "nativeEvent.contentOffset.y")]
(reset! content-scroll-y y)))
:scroll-event-throttle 64
:content-container-style {:flexGrow 1}}
[rn/view
{:on-layout (fn [event]
(let [height (oops/oget event "nativeEvent.layout.height")]
(reset! content-container-height height)))}
[rn/view
{:style style/content-container}
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/title} (i18n/label :t/create-profile)]
[rn/view
{:style style/input-container}
[rn/view
{:style style/profile-input-container}
[quo/profile-input
{:customization-color @custom-color
:placeholder (i18n/label :t/your-name)
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[profile-picture-picker/view
{:on-result on-change-profile-pic
:has-picture? false}])
:shell? true}]))
:image-picker-props {:profile-picture @profile-pic
:full-name (if (seq @full-name)
@full-name
(i18n/label :t/your-name))
:customization-color @custom-color}
:title-input-props {:default-value display-name
:auto-focus true
:max-length c/profile-name-max-length
:on-change-text on-change-text}}]]
[quo/info-message
{:status info-type
:size :default
:icon (if valid-name? :i/positive-state :i/info)
:color (when (= :default info-type) colors/white-70-blur)
:container-style style/info-message}
info-message]
[quo/text
{:size :paragraph-2
:weight :medium
:style style/color-title}
(i18n/label :t/accent-colour)]]]
[quo/color-picker
{:blur? true
:default-selected :blue
:on-change on-change
:window-width window-width
:container-style {:padding-left (responsiveness/iphone-11-Pro-20-pixel-from-width
window-width)}}]]]
[rn/keyboard-avoiding-view
{:style {:position :absolute
:top 0
:bottom 0
:left 0
:right 0}
:pointer-events :box-none}
[button-container @show-keyboard? keyboard-shown show-background? keyboard-height
[quo/button
{:accessibility-label :submit-create-profile-button
:type :primary
:customization-color @custom-color
:on-press (fn []
(rf/dispatch [:onboarding/profile-data-set
{:image-path @profile-pic
:display-name @full-name
:color @custom-color}]))
:container-style style/continue-button
:disabled? (or (not valid-name?) (not (seq @full-name)))}
(i18n/label :t/continue)]]]])
(finally
(oops/ocall show-listener "remove")
(oops/ocall hide-listener "remove"))))
(defn create-profile
[]
(let [{:keys [top]} (safe-area/get-insets)
onboarding-profile-data (rf/sub [:onboarding/profile])]
[page
{:navigation-bar-top top
:onboarding-profile-data onboarding-profile-data}]))
+83 -71
View File
@@ -1,5 +1,6 @@
(ns status-im.contexts.onboarding.events
(:require
[quo.foundations.colors :as colors]
[re-frame.core :as re-frame]
status-im.common.biometric.events
[status-im.constants :as constants]
@@ -111,63 +112,57 @@
(rf/reg-event-fx
:onboarding/password-set
(fn [{:keys [db]} [masked-password]]
(let [biometric-supported-type (get-in db [:biometrics :supported-type])]
(let [biometric-supported-type (get-in db [:biometrics :supported-type])
from-screen (get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]
{:db (-> db
(assoc-in [:onboarding/profile :password] masked-password)
(assoc-in [:onboarding/profile :auth-method] constants/auth-method-password))
:fx [[:dispatch
(if biometric-supported-type
[:navigate-to-within-stack
[:screen/onboarding.enable-biometrics
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]
[:navigate-to-within-stack [:screen/onboarding.enable-biometrics from-screen]]
[:onboarding/create-account-and-login])]]})))
(rf/defn navigate-to-enable-biometrics
{:events [:onboarding/navigate-to-enable-biometrics]}
[{:keys [db]}]
(let [supported-type (get-in db [:biometrics :supported-type])]
{:dispatch (if supported-type
[:open-modal :screen/onboarding.enable-biometrics]
[:open-modal :screen/onboarding.enable-notifications])}))
(rf/reg-event-fx
:onboarding/navigate-to-enable-biometrics
(fn [{:keys [db]}]
(let [supported-type (get-in db [:biometrics :supported-type])]
{:dispatch (if supported-type
[:open-modal :screen/onboarding.enable-biometrics]
[:open-modal :screen/onboarding.enable-notifications])})))
(rf/defn seed-phrase-validated
{:events [:onboarding/seed-phrase-validated]}
[{:keys [db]} seed-phrase key-uid]
(let [syncing-account-recovered? (and (seq (:syncing/key-uid db))
(= (:syncing/key-uid db) key-uid))
next-screen (if syncing-account-recovered?
:screen/onboarding.create-profile-password
:screen/onboarding.create-profile)]
(if (contains? (:profile/profiles-overview db) key-uid)
{:effects.utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :screen/profile.profiles])
(re-frame/dispatch
[:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
{:db (-> db
(assoc-in [:onboarding/profile :seed-phrase] seed-phrase)
(assoc-in [:onboarding/profile :key-uid] key-uid)
(assoc-in [:onboarding/profile :color] constants/profile-default-color))
:fx [[:dispatch
[:navigate-to-within-stack
[next-screen
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]]]})))
(rf/reg-event-fx
:onboarding/seed-phrase-validated
(fn [{:keys [db]} [seed-phrase key-uid]]
(let [next-screen :screen/onboarding.create-profile-password
from-screen (get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]
(if (contains? (:profile/profiles-overview db) key-uid)
{:fx [[:effects.utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :screen/profile.profiles])
(re-frame/dispatch [:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}]]}
{:db (-> db
(assoc-in [:onboarding/profile :seed-phrase] seed-phrase)
(assoc-in [:onboarding/profile :key-uid] key-uid)
(assoc-in [:onboarding/profile :color] constants/profile-default-color))
:fx [[:dispatch [:navigate-to-within-stack [next-screen from-screen]]]]}))))
(rf/defn navigate-to-create-profile
{:events [:onboarding/navigate-to-create-profile]}
[{:keys [db]}]
;; Restart the flow
{:db (dissoc db :onboarding/profile)
:dispatch [:navigate-to-within-stack
[:screen/onboarding.create-profile :screen/onboarding.new-to-status]]})
(rf/reg-event-fx
:onboarding/navigate-to-create-profile
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:onboarding/profile :color] (rand-nth colors/account-colors))
(update :onboarding/profile dissoc :image-path))
:fx [[:dispatch
[:navigate-to-within-stack
[:screen/onboarding.create-profile-password :screen/onboarding.new-to-status]]]]}))
(rf/reg-event-fx :onboarding/navigate-to-sign-in-by-syncing
(fn [{:keys [db]}]
@@ -180,26 +175,43 @@
(fn [{:keys [db]} [auth-method]]
{:db (assoc db :auth-method auth-method)}))
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding/finalize-setup]}
[{:keys [db]}]
(let [masked-password (get-in db [:onboarding/profile :password])
key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding/profile :syncing?])
auth-method (get-in db [:onboarding/profile :auth-method])
biometric-enabled? (= auth-method constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding/generated-keys? true)}
biometric-enabled?
(assoc :keychain/save-password-and-auth-method
{:key-uid key-uid
:masked-password (if syncing?
masked-password
(security/hash-masked-password masked-password))
:on-success (fn []
(rf/dispatch [:onboarding/set-auth-method auth-method])
(when syncing?
(rf/dispatch
[:onboarding/navigate-to-enable-notifications-from-syncing])))
:on-error #(log/error "failed to save biometrics"
{:key-uid key-uid
:error %})}))))
(def ^:const temp-display-name
"While creating a profile, we cannot use an empty string; this value works as a
placeholder that will be updated later once the compressed key exists. See
`status-im.contexts.profile.edit.name.events/get-default-display-name` for more details."
"temporal username")
(rf/reg-event-fx
:onboarding/use-temporary-display-name
(fn [{:keys [db]} [temporary-display-name?]]
{:db (assoc db
:onboarding/profile
{:temporary-display-name? temporary-display-name?
:display-name (if temporary-display-name?
temp-display-name
"")})}))
(rf/reg-event-fx
:onboarding/finalize-setup
(fn [{db :db}]
(let [{:keys [password syncing? auth-method
temporary-display-name?]} (:onboarding/profile db)
{:keys [key-uid] :as profile} (:profile/profile db)
biometric-enabled? (= auth-method constants/auth-method-biometric)]
{:db (assoc db :onboarding/generated-keys? true)
:fx [(when temporary-display-name?
[:dispatch [:profile/set-default-profile-name profile]])
(when biometric-enabled?
[:keychain/save-password-and-auth-method
{:key-uid key-uid
:masked-password (if syncing?
password
(security/hash-masked-password password))
:on-success (fn []
(rf/dispatch [:onboarding/set-auth-method auth-method])
(when syncing?
(rf/dispatch
[:onboarding/navigate-to-enable-notifications-from-syncing])))
:on-error #(log/error "failed to save biometrics"
{:key-uid key-uid
:error %})}])]})))
@@ -52,6 +52,7 @@
[]
(when-let [blur-show-fn @overlay/blur-show-fn-atom]
(blur-show-fn))
(rf/dispatch [:onboarding/use-temporary-display-name false])
(rf/dispatch [:open-modal
:screen/onboarding.share-usage
{:next-screen :screen/onboarding.sync-or-recover-profile}]))
@@ -60,6 +61,7 @@
[]
(when-let [blur-show-fn @overlay/blur-show-fn-atom]
(blur-show-fn))
(rf/dispatch [:onboarding/use-temporary-display-name true])
(rf/dispatch [:open-modal :screen/onboarding.share-usage
{:next-screen :screen/onboarding.new-to-status}]))
@@ -1,4 +1,5 @@
(ns status-im.contexts.onboarding.share-usage.style)
(ns status-im.contexts.onboarding.share-usage.style
(:require [quo.foundations.colors :as colors]))
(def title-container
{:margin-horizontal 20
@@ -16,3 +17,7 @@
[insets]
{:margin 20
:margin-bottom (+ 12 (:bottom insets))})
(def page
{:flex 1
:background-color colors/neutral-100})
@@ -17,35 +17,38 @@
(rf/dispatch [:navigate-to-within-stack [next-screen :screen/onboarding.share-usage]]) ;; Onboarding
(rf/dispatch [:navigate-back]))) ;; Login Screen
(defn- learn-more
[]
(rf/dispatch [:show-bottom-sheet
{:content learn-more-sheet/view
:shell? true}]))
(defn view
[]
(let [insets (safe-area/get-insets)
next-screen (:next-screen (rf/sub [:get-screen-params :screen/onboarding.share-usage]))
share-usage-data (rn/use-callback #(share-usage-data-fn true next-screen))
maybe-later (rn/use-callback #(share-usage-data-fn false next-screen))
learn-more (rn/use-callback #(rf/dispatch [:show-bottom-sheet
{:content learn-more-sheet/view
:shell? true}]))]
[:<>
maybe-later (rn/use-callback #(share-usage-data-fn false next-screen))]
[rn/view {:style style/page}
[quo/page-nav
{:margin-top (:top insets)
:background :blur
:icon-name :i/arrow-left
:icon-name :i/close
:on-press events-helper/navigate-back
:right-side [{:icon-left :i/info
:accessibility-label :learn-more
:label (i18n/label :t/learn-more)
:on-press learn-more}]}]
[quo/text-combinations
{:container-style style/title-container
:title (i18n/label :t/help-us-improve-status)
[quo/page-top
{:title (i18n/label :t/help-us-improve-status)
:title-accessibility-label :share-usage-title
:description (i18n/label :t/collecting-usage-data)
:description :text
:description-text (i18n/label :t/collecting-usage-data)
:description-accessibility-label :share-usage-subtitle}]
[rn/image
{:resize-mode :contain
:style (style/page-illustration (:width (rn/get-window)))
:source (resources/get-image :biometrics)}]
:source (resources/get-image :usage-data)}]
[rn/view {:style (style/buttons insets)}
[quo/button
{:size 40
@@ -1,5 +1,7 @@
(ns status-im.contexts.profile.edit.name.events
(:require [utils.i18n :as i18n]
(:require [clojure.string :as string]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/reg-event-fx :profile/edit-profile-name-success
@@ -20,3 +22,16 @@
:on-success [:profile/edit-profile-name-success]}]]]})
(rf/reg-event-fx :profile/edit-name edit-profile-name)
(defn display-name-from-compressed-key
[profile]
(-> profile :compressed-key (string/split #"zQ3") second (subs 0 constants/profile-name-max-length)))
(rf/reg-event-fx
:profile/set-default-profile-name
(fn [{db :db} [profile]]
(let [default-display-name (display-name-from-compressed-key profile)]
{:db (assoc-in db [:profile/profile :display-name] default-display-name)
:fx [[:json-rpc/call
[{:method "wakuext_setDisplayName"
:params [default-display-name]}]]]})))
@@ -152,15 +152,12 @@
(rf/reg-event-fx
:profile.login/login-node-signal
(fn [{{:onboarding/keys [recovered-account? new-account?] :as db} :db}
[{:keys [settings account ensUsernames error]}]]
(fn [{db :db} [{:keys [settings account ensUsernames error]}]]
(log/debug "[signals] node.login" "error" error)
(if error
{:db (update db :profile/login #(-> % (dissoc :processing) (assoc :error error)))}
{:db (dissoc db :profile/login)
:fx [(when (and new-account? (not recovered-account?))
[:dispatch-later [{:ms 1000 :dispatch [:wallet-legacy/set-initial-blocks-range]}]])
[:dispatch-later [{:ms 2000 :dispatch [:ens/update-usernames ensUsernames]}]]
:fx [[:dispatch-later [{:ms 2000 :dispatch [:ens/update-usernames ensUsernames]}]]
[:dispatch [:profile.login/login-existing-profile settings account]]]})))
(rf/reg-event-fx
@@ -47,6 +47,7 @@
[]
(when @push-animation-fn-atom
(@push-animation-fn-atom))
(rf/dispatch [:onboarding/use-temporary-display-name true])
(debounce/throttle-and-dispatch
[:open-modal :screen/onboarding.new-to-status]
1000))
@@ -55,6 +56,7 @@
[]
(when @push-animation-fn-atom
(@push-animation-fn-atom))
(rf/dispatch [:onboarding/use-temporary-display-name false])
(debounce/throttle-and-dispatch
[:open-modal :screen/onboarding.sync-or-recover-profile]
1000))
@@ -125,7 +125,7 @@
:blur? true
:image-props :i/light})]
[{:title (i18n/label :t/about)
:on-press #(rf/dispatch [:open-modal :about-app])
:on-press #(rf/dispatch [:open-modal :screen/settings.about])
:action :arrow
:blur? true}
{:title (i18n/label :t/status-help)
@@ -7,33 +7,37 @@
[utils.re-frame :as rf]))
(defn- on-press-biometric-enable
[button-label theme]
[button-label theme keycard-profile?]
(fn []
(rf/dispatch
[:standard-auth/authorize-with-password
{:blur? true
:theme theme
:auth-button-label (i18n/label :t/biometric-enable-button {:bio-type-label button-label})
:on-auth-success (fn [password]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:biometric/authenticate
{:on-success #(rf/dispatch [:biometric/enable password])
:on-fail #(rf/dispatch [:biometric/show-message (ex-cause %)])}]))}])))
(if keycard-profile?
(rf/dispatch [:keycard/feature-unavailable-show {:theme :dark}])
(rf/dispatch
[:standard-auth/authorize-with-password
{:blur? true
:theme theme
:auth-button-label (i18n/label :t/biometric-enable-button {:bio-type-label button-label})
:on-auth-success (fn [password]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:biometric/authenticate
{:on-success #(rf/dispatch [:biometric/enable password])
:on-fail #(rf/dispatch [:biometric/show-message
(ex-cause %)])}]))}]))))
(defn- get-biometric-item
[theme]
(let [auth-method (rf/sub [:auth-method])
biometric-type (rf/sub [:biometrics/supported-type])
label (biometric/get-label-by-type biometric-type)
icon (biometric/get-icon-by-type biometric-type)
supported? (boolean biometric-type)
enabled? (= auth-method constants/auth-method-biometric)
biometric-on? (and supported? enabled?)
press-handler (when supported?
(if biometric-on?
(fn [] (rf/dispatch [:biometric/disable]))
(on-press-biometric-enable label theme)))]
(let [auth-method (rf/sub [:auth-method])
biometric-type (rf/sub [:biometrics/supported-type])
keycard-profile? (rf/sub [:keycard/keycard-profile?])
label (biometric/get-label-by-type biometric-type)
icon (biometric/get-icon-by-type biometric-type)
supported? (boolean biometric-type)
enabled? (= auth-method constants/auth-method-biometric)
biometric-on? (and supported? enabled?)
press-handler (when supported?
(if biometric-on?
(fn [] (rf/dispatch [:biometric/disable]))
(on-press-biometric-enable label theme keycard-profile?)))]
{:title label
:image-props icon
:image :icon
@@ -58,7 +62,8 @@
(defn view
[]
(let [theme (quo.theme/use-theme)]
(let [theme (quo.theme/use-theme)
keycard-profile? (rf/sub [:keycard/keycard-profile?])]
[quo/overlay {:type :shell :top-inset? true}
[quo/page-nav
{:background :blur
@@ -68,6 +73,7 @@
[quo/category
{:key :category
:data [(get-biometric-item theme)
(get-change-password-item)]
(when-not keycard-profile?
(get-change-password-item))]
:blur? true
:list-type :settings}]]))
@@ -0,0 +1,9 @@
(ns status-im.contexts.settings.about.style)
(def category-spacing {:padding-bottom 12})
(def app-info-container
{:padding-horizontal 20
:padding-top 8
:padding-bottom 16
:row-gap 16})
@@ -0,0 +1,101 @@
(ns status-im.contexts.settings.about.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.constants :as constants]
[status-im.contexts.settings.about.style :as style]
[status-im.contexts.settings.common.header :as header]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- open-link
[url]
(rf/dispatch [:browser.ui/open-url url]))
(defn- copy
[data item-name]
(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy data
:post-copy-message (str item-name
" "
(i18n/label :t/sharing-copied-to-clipboard))}]))
(def about-data
[{:app-info? true}
{:category-label (i18n/label :t/website)
:items [{:title "status.app"
:on-press #(open-link constants/status-app-url)
:blur? true
:action :arrow
:action-props {:icon :i/external}}]}
{:category-label (i18n/label :t/github-repos)
:items [{:title "status-mobile"
:on-press #(open-link constants/status-mobile-url)
:blur? true
:action :arrow
:action-props {:icon :i/external}}
{:title "status-go"
:on-press #(open-link constants/status-go-url)
:blur? true
:action :arrow
:action-props {:icon :i/external}}
{:title "go-waku"
:on-press #(open-link constants/go-waku-url)
:blur? true
:action :arrow
:action-props {:icon :i/external}}]}
{:category-label (i18n/label :t/documents)
:items [{:title (i18n/label :t/privacy-policy)
:on-press #(rf/dispatch [:open-modal :screen/settings.privacy-policy])
:blur? true
:action :arrow}
{:title (i18n/label :t/terms-of-service)
:on-press #(rf/dispatch [:open-modal :screen/settings.terms-of-use])
:blur? true
:action :arrow}]}])
(defn info-item
[{:keys [title info]}]
[quo/data-item
{:size :default
:status :default
:right-icon :i/copy
:card? true
:blur? true
:title title
:on-press #(copy info title)
:subtitle info
:subtitle-type :default
:subtitle-text-props {:number-of-lines 1
:ellipsize-mode :middle}}])
(defn- app-info
[]
(let [app-version (rf/sub [:get-app-short-version])
commit-hash (rf/sub [:get-commit-hash])
node-version (rf/sub [:get-app-node-version])]
[rn/view {:style style/app-info-container}
[info-item {:title (i18n/label :t/version) :info app-version}]
[info-item {:title (i18n/label :t/app-commit) :info commit-hash}]
[info-item {:title (i18n/label :t/node-version) :info node-version}]]))
(defn category
[{:keys [app-info? category-label items]}]
(if app-info?
[app-info]
[quo/category
{:label category-label
:list-type :settings
:container-style style/category-spacing
:blur? true
:data items}]))
(defn view
[]
[quo/overlay {:type :shell}
[header/view {:title (i18n/label :t/about)}]
[rn/flat-list
{:data about-data
:shows-vertical-scroll-indicator false
:render-fn category
:bounces false
:over-scroll-mode :never}]])
@@ -0,0 +1,14 @@
(ns status-im.contexts.settings.common.header
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]))
(defn view
[{:keys [title]}]
[rn/view {:style {:padding-top (safe-area/get-top)}}
[quo/page-nav
{:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top {:title title}]])
@@ -41,7 +41,7 @@
(defn- keypair
[{keypair-type :type
:keys [accounts name]
:keys [accounts name keycard?]
:as item}
_ _
{:keys [profile-picture compressed-key customization-color]}]
@@ -59,7 +59,7 @@
:type (if default-keypair?
:default-keypair
:keypair)
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:shortened-key shortened-key
:customization-color customization-color
:profile-picture profile-picture})}))
@@ -68,7 +68,7 @@
[quo/keypair
{:blur? true
:status-indicator false
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:action (if default-keypair? :none :options)
:accounts accounts
:customization-color customization-color
@@ -38,7 +38,11 @@
#(if (:enabled? %)
:paired-devices
:unpaired-devices)
other-devices)]
other-devices)
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show
{:theme :dark}]))]
[quo/overlay {:type :shell :top-inset? true}
[quo/page-nav
{:type :no-title
@@ -60,7 +64,9 @@
:type :primary
:customization-color profile-color
:icon-only? true
:on-press open-setup-syncing-with-customization-color}
:on-press (if keycard?
keycard-feature-unavailable
open-setup-syncing-with-customization-color)}
:i/add]]
[device/view (merge user-device {:this-device? true})]
(when (seq paired-devices)
@@ -69,7 +69,9 @@
(let [{:keys [customization-color] :as profile} (rf/sub [:profile/profile-with-image])
{:keys [address path watch-only?]} (rf/sub [:wallet/current-viewing-account])
{keypair-name :name
keypair-type :type} (rf/sub [:wallet/current-viewing-account-keypair])
keypair-type :type
keycards :keycards} (rf/sub [:wallet/current-viewing-account-keypair])
keypair-keycard? (boolean (seq keycards))
networks (rf/sub [:wallet/network-preference-details])
origin-type (case keypair-type
:seed
@@ -98,7 +100,7 @@
(when (not watch-only?)
[quo/account-origin
{:type origin-type
:stored :on-device
:stored (if keypair-keycard? :on-keycard :on-device)
:profile-picture (profile.utils/photo profile)
:customization-color customization-color
:derivation-path path
@@ -58,13 +58,14 @@
[item _ _
{:keys [profile-picture compressed-key selected-key-uid set-selected-key-uid customization-color]}]
(let [profile-keypair? (= (:type item) :profile)
keycard? (boolean (seq (:keycards item)))
accounts (parse-accounts (:accounts item))]
[quo/keypair
{:customization-color customization-color
:profile-picture (when profile-keypair? profile-picture)
:status-indicator false
:type (if profile-keypair? :default-keypair :other)
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:on-options-press #(js/alert "Options pressed")
:action :selector
:blur? false
@@ -22,7 +22,7 @@
[utils.string]))
(defn- get-keypair-data
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color]}]
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color keycard?]}]
[{:title title
:image (if primary-keypair? :avatar :icon)
:image-props (if primary-keypair?
@@ -35,7 +35,7 @@
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
:description-props {:text (i18n/label :t/on-device)}}
:description-props {:text (i18n/label (if keycard? :on-keycard :on-device))}}
(when-not (string/blank? derivation-path)
(let [formatted-path (string/replace derivation-path #"/" " / ")
on-auth-success (fn [password]
@@ -123,7 +123,7 @@
:container-style color-picker-style}]])])))
(defn- new-account-origin
[{:keys [keypair-title derivation-path customization-color]}]
[{:keys [keypair-title derivation-path customization-color keycard?]}]
(let [{keypair-name :name} (rf/sub [:wallet/selected-keypair])
primary? (rf/sub [:wallet/selected-primary-keypair?])
keypair-name (or keypair-title
@@ -135,7 +135,8 @@
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data {:primary-keypair? primary?
:data (get-keypair-data {:keycard? keycard?
:primary-keypair? primary?
:title keypair-name
:derivation-path derivation-path
:customization-color customization-color})}]]))
@@ -229,20 +230,37 @@
set-derivation-path #(reset! derivation-path %)]
(fn [{:keys [on-change-text set-account-color set-emoji customization-color error]}]
(let [{:keys [derived-from
key-uid]} (rf/sub [:wallet/selected-keypair])
on-auth-success (rn/use-callback
(fn [password]
(let [preferences {:account-name @account-name
:color @account-color
:emoji @emoji}]
(rf/dispatch
[:wallet/derive-address-and-add-account
{:password password
:derived-from-address derived-from
:key-uid key-uid
:derivation-path @derivation-path
:account-preferences preferences}])))
[derived-from])]
key-uid keycards]} (rf/sub [:wallet/selected-keypair])
keycard? (boolean (seq keycards))
on-auth-success (rn/use-callback
(fn [password]
(let [preferences {:account-name @account-name
:color @account-color
:emoji @emoji}]
(rf/dispatch
[:wallet/derive-address-and-add-account
{:password password
:derived-from-address derived-from
:key-uid key-uid
:derivation-path @derivation-path
:account-preferences preferences}])))
[derived-from])
on-complete (rn/use-callback
(fn []
(let [preferences {:account-name @account-name
:color @account-color
:emoji @emoji}]
(rf/dispatch
[:standard-auth/authorize-with-keycard
{:on-complete
#(rf/dispatch
[:keycard/connect-derive-address-and-add-account
{:pin %
:derived-from-address derived-from
:key-uid key-uid
:derivation-path @derivation-path
:account-preferences preferences}])}])))
[derived-from])]
(rn/use-effect
#(rf/dispatch
[:wallet/next-derivation-path
@@ -252,10 +270,13 @@
[floating-button
{:account-color @account-color
:slide-button-props {:on-auth-success on-auth-success
:disabled? (or (empty? @account-name)
(string/blank? @derivation-path)
(some? error))}}
:slide-button-props {:on-auth-success (when-not keycard? on-auth-success)
:on-complete
(when keycard? on-complete)
:disabled?
(or (empty? @account-name)
(string/blank? @derivation-path)
(some? error))}}
[avatar
{:account-color @account-color
:emoji @emoji
@@ -269,7 +290,8 @@
{:account-color @account-color
:set-account-color set-account-color}]
[new-account-origin
{:derivation-path @derivation-path
{:keycard? keycard?
:derivation-path @derivation-path
:customization-color customization-color}]]))))
(defn view
@@ -30,7 +30,8 @@
type :no-title}}]
(let [{:keys [color emoji watch-only?]} (rf/sub [:wallet/current-viewing-account])
networks (rf/sub [:wallet/selected-network-details])
sending-collectible? (rf/sub [:wallet/sending-collectible?])]
sending-collectible? (rf/sub [:wallet/sending-collectible?])
keycard? (rf/sub [:wallet/selected-keypair-keycard?])]
[quo/page-nav
{:type type
:icon-name icon-name
@@ -45,7 +46,10 @@
(not watch-only?)
show-dapps-button?)
{:icon-name :i/dapps
:on-press #(rf/dispatch [:navigate-to :screen/wallet.connected-dapps])})
:on-press #(rf/dispatch
(if keycard?
[:keycard/feature-unavailable-show]
[:navigate-to :screen/wallet.connected-dapps]))})
(when-not sending-collectible?
{:content-type :account-switcher
:customization-color color
@@ -404,15 +404,13 @@
:market-values-per-token market-values-per-token}))
calculated-tokens (map calculate-token tokens)]
(sort-by (fn [token]
(let [fiat-value (get-in token [:values :fiat-unformatted-value])
priority (get constants/token-sort-priority (:token token) ##Inf)]
[(- fiat-value) priority]))
(let [fiat-value (get-in token [:values :fiat-unformatted-value])]
[(- fiat-value)]))
calculated-tokens)))
(defn sort-tokens
[tokens]
(let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)]
(sort-by (juxt (comp - :balance) priority) tokens)))
(sort-by (fn [token] (- (:balance token))) tokens))
(defn- transaction-data
[{:keys [from-address to-address token-address route data eth-transfer?]}]
@@ -543,8 +541,7 @@
(defn sort-tokens-by-name
[tokens]
(let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)]
(sort-by (juxt :symbol priority) tokens)))
(sort-by :symbol tokens))
(defn tokens-with-balance
[tokens networks chain-ids]
@@ -291,3 +291,10 @@
(cske/transform-keys transforms/->kebab-case-keyword)
(map #(assoc % :unique-id (collectible-utils/get-collectible-unique-id %)))
vec))
(defn selected-keypair-keycard?
[db]
(let [keypairs (get-in db [:wallet :keypairs])
selected-keypair-uid (get-in db [:wallet :ui :create-account :selected-keypair-uid])
keypair (get keypairs selected-keypair-uid)]
(boolean (seq (:keycards keypair)))))
@@ -231,3 +231,37 @@
:wallet true
:default-account? true})]}}}
(sut/reconcile-keypairs [raw-keypair-profile])))))
(def mock-db
{:wallet {:keypairs
{"0x123" {:type "key"
:key-uid "0x123"
:keycards [{:id "keycard1"}]}
"0x456" {:type "key"
:key-uid "0x456"
:keycards []}}
:ui {:create-account {:selected-keypair-uid "0x123"}}}})
(deftest selected-keypair-keycard?-test
(testing "returns true when the selected keypair has keycards"
(is (true? (sut/selected-keypair-keycard? mock-db))))
(testing "returns false when the selected keypair does not have keycards"
(let [db (assoc-in mock-db [:wallet :ui :create-account :selected-keypair-uid] "0x456")]
(is (false? (sut/selected-keypair-keycard? db)))))
(testing "returns false when the selected keypair does not exist"
(let [db (assoc-in mock-db [:wallet :ui :create-account :selected-keypair-uid] "0x789")]
(is (false? (sut/selected-keypair-keycard? db)))))
(testing "returns false when keypairs map is empty"
(let [db (assoc-in mock-db [:wallet :keypairs] {})]
(is (false? (sut/selected-keypair-keycard? db)))))
(testing "returns false when no keypair is selected"
(let [db (assoc-in mock-db [:wallet :ui :create-account :selected-keypair-uid] nil)]
(is (false? (sut/selected-keypair-keycard? db)))))
(testing "returns false when db does not contain wallet data"
(let [db {}]
(is (false? (sut/selected-keypair-keycard? db))))))
+2 -3
View File
@@ -307,9 +307,8 @@
(rf/reg-event-fx :wallet/add-account
(fn [_
[{:keys [key-uid password account-name emoji color type]
:or {type :generated}}
{:keys [public-key address path] :as _derived-account}]]
[{:keys [key-uid password account-name emoji color type] :or {type :generated}}
{:keys [public-key address path]}]]
(let [lowercase-address (some-> address
string/lower-case)
account-config {:key-uid (when (= type :generated) key-uid)
@@ -102,6 +102,7 @@
:wallet/wallet-send-tx-type :tx/send
:wallet/wallet-send-fee-fiat-formatted "$5,00"
:wallet/sending-collectible? false
:wallet/selected-keypair-keycard? false
:wallet/send-total-amount-formatted "250 ETH"
:wallet/total-amount (money/bignumber "250")
:wallet/prices-per-token {:ETH {:usd 10}}
@@ -248,7 +248,9 @@
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address
to-address)}]
to-address)}
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])]
;; In token send flow we already have transaction built when
;; we reach confirmation screen. But in send collectible flow
;; routes request happens at the same time with navigation to
@@ -279,20 +281,24 @@
:transaction-type transaction-type}]
(when (and (not loading-suggested-routes?) route (seq route))
[standard-auth/slide-button
{:keycard-supported? true
:size :size-48
{:size :size-48
:track-text (if (= transaction-type :tx/bridge)
(i18n/label :t/slide-to-bridge)
(i18n/label :t/slide-to-send))
:container-style {:z-index 2}
:disabled? (not transaction-for-signing)
:customization-color account-color
:auth-button-label (i18n/label :t/confirm)
:on-complete (when sign-on-keycard?
#(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:send
""]))
:on-auth-success
(fn [data]
(fn [psw]
(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:send data]))
:auth-button-label (i18n/label :t/confirm)}])]
[:wallet/prepare-signatures-for-transactions :send
psw]))}])]
:gradient-cover? true
:customization-color (:color account)}
[rn/view
@@ -217,18 +217,23 @@
(defn- slide-button
[]
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
on-auth-success (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap %]))]
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
transaction-for-signing (rf/sub [:wallet/swap-transaction-for-signing])
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])
on-auth-success (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap %]))
on-complete (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap ""]))]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-sign)
:container-style {:z-index 2}
:customization-color (:color account)
:disabled? (or loading-swap-proposal? (not swap-proposal))
:keycard-supported? true
:on-complete (when sign-on-keycard? on-complete)
:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/confirm)}]))
@@ -168,7 +168,9 @@
transaction-for-signing (rf/sub [:wallet/swap-transaction-for-signing])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)]
account-color (:color account)
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-swap)
@@ -178,7 +180,11 @@
(not swap-proposal)
(not transaction-for-signing))
:auth-button-label (i18n/label :t/confirm)
:keycard-supported? true
:on-complete (when sign-on-keycard?
#(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:swap
""]))
:on-auth-success (fn [data]
(rf/dispatch [:wallet/stop-get-swap-proposal])
(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap data]))}]))
@@ -2,6 +2,7 @@
(:require [clojure.string :as string]
[re-frame.core :as rf]
[react-native.wallet-connect :as wallet-connect]
[status-im.contexts.wallet.data-store :as wallet-data-store]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
@@ -30,11 +31,17 @@
expired? (-> parsed-uri
:expiryTimestamp
uri/timestamp-expired?)
version-supported? (uri/version-supported? version)]
(if (or (not valid-wc-uri?)
(not version-supported?)
(= network-status :offline)
expired?)
version-supported? (uri/version-supported? version)
keycard? (wallet-data-store/selected-keypair-keycard? db)]
(cond
keycard?
{:fx [[:dispatch [:keycard/feature-unavailable-show]]]}
(or (not valid-wc-uri?)
(not version-supported?)
(= network-status :offline)
expired?)
{:fx [[:dispatch
[:toasts/upsert
{:type :negative
@@ -54,6 +61,8 @@
:else
(i18n/label :t/something-went-wrong))}]]]}
:else
{:fx [[:dispatch [:wallet-connect/pair scanned-text]]]}))))
(rf/reg-event-fx
+1
View File
@@ -27,6 +27,7 @@
status-im.contexts.contact.blocking.events
status-im.contexts.keycard.effects
status-im.contexts.keycard.events
status-im.contexts.keycard.feature-unavailable.events
status-im.contexts.network.effects
status-im.contexts.network.events
status-im.contexts.onboarding.common.overlay.events
+9 -10
View File
@@ -25,16 +25,15 @@
(defn root-options
[{:keys [background-color theme status-bar-theme nav-bar-color]}]
(merge
(statusbar-and-navbar-options theme status-bar-theme nav-bar-color)
{:topBar {:visible false}
:layout {:componentBackgroundColor (or background-color
(colors/theme-colors colors/white colors/neutral-100 theme))
:orientation ["portrait"]
:backgroundColor (or background-color
(colors/theme-colors colors/white
colors/neutral-100
theme))}}))
(let [layout-background (or background-color
(colors/theme-colors colors/white colors/neutral-100 theme))
component-background (or background-color
(colors/theme-colors colors/white colors/neutral-100 theme))]
(assoc (statusbar-and-navbar-options theme status-bar-theme nav-bar-color)
:topBar {:visible false}
:layout {:componentBackgroundColor component-background
:orientation ["portrait"]
:backgroundColor layout-background})))
(defn dark-root-options
[]
+84 -80
View File
@@ -6,6 +6,8 @@
[status-im.common.enter-seed-phrase.view :as enter-seed-phrase]
[status-im.common.lightbox.view :as lightbox]
[status-im.common.pdf-viewer.view :as pdf-viewer]
[status-im.common.privacy.view :as privacy-policy]
[status-im.common.terms.view :as terms-of-use]
[status-im.config :as config]
[status-im.contexts.chat.group.create.view :as group.create]
[status-im.contexts.chat.group.details.view :as group.details]
@@ -43,7 +45,6 @@
[status-im.contexts.keycard.pin.enter.view :as keycard.pin.enter]
[status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile]
[status-im.contexts.onboarding.create-password.view :as create-password]
[status-im.contexts.onboarding.create-profile.view :as create-profile]
[status-im.contexts.onboarding.enable-biometrics.view :as enable-biometrics]
[status-im.contexts.onboarding.enable-notifications.view :as enable-notifications]
[status-im.contexts.onboarding.identifiers.view :as identifiers]
@@ -74,6 +75,7 @@
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
[status-im.contexts.profile.settings.screens.syncing.view :as settings.syncing]
[status-im.contexts.profile.settings.view :as settings]
[status-im.contexts.settings.about.view :as settings.about]
[status-im.contexts.settings.keycard.view :as settings.keycard]
[status-im.contexts.settings.language-and-currency.currency.view :as settings.currency-selection]
[status-im.contexts.settings.language-and-currency.view :as settings.language-and-currency]
@@ -154,39 +156,39 @@
(def chat-screens
[{:name :start-a-new-chat
:metrics {:track? :true
:metrics {:track? true
:alias-id :messenger.new-chat}
:options {:sheet? true}
:component new-chat/view}
{:name :chat
:metrics {:track? :true
:metrics {:track? true
:alias-id :messenger.chat}
:options {:popGesture false
:animations transitions/stack-transition-from-bottom}
:component chat/chat}
{:name :screen/group-create
:metrics {:track? :true
:metrics {:track? true
:alias-id :messenger.new-group}
:options {:sheet? true
:skip-background? true}
:component group.create/view}
{:name :screen/group-details
:metrics {:track? :true
:metrics {:track? true
:alias-id :messenger.group-details}
:component group.details/view}
{:name :screen/group-update
:metrics {:track? :true
:metrics {:track? true
:alias-id :messenger.group-update}
:options {:sheet? true
:skip-background? true}
:component group.update/view}
{:name :screen/group-add-manage-members
:metrics {:track? :true
:metrics {:track? true
:flow :messenger
:alias-id :messenger.group-manage-members}
:options {:sheet? true}
@@ -194,12 +196,12 @@
(def community-screens
[{:name :discover-communities
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.discover}
:component communities.discover/view}
{:name :community-overview
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.overview}
:options {:animations transitions/stack-transition-from-bottom}
:component communities.overview/view}
@@ -208,79 +210,79 @@
;; joining a community. The non-sheet screen is used when editing shared
;; addresses after the join request was sent.
{:name :community-account-selection-sheet
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.select-addresses-for-joining-community}
:options {:sheet? true}
:component communities.accounts-selection/view}
{:name :community-account-selection
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.select-addresses-for-community}
:options {:insets {:top? true}}
:component communities.accounts-selection/view}
{:name :community-requests-to-join
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.request-to-join}
:options {:sheet? true}
:component join-menu/view}
{:name :screen/share-community
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.share-community}
:options options/transparent-screen-options
:component share-community/view}
{:name :invite-people-community
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.invite-people}
:options {:sheet? true}
:component communities.invite/view}
{:name :share-community-channel
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.share-channel}
:options options/transparent-screen-options
:component share-community-channel/view}
{:name :screen/chat.view-channel-members-and-details
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.view-channel-members-and-details}
:options {:insets {:top? true}}
:component channel-view-channel-members-and-details/view}
{:name :addresses-for-permissions
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.choose-addresses-for-permissions}
:options {:insets {:top? true}}
:component addresses-for-permissions/view}
{:name :address-for-airdrop
:metrics {:track? :true
:metrics {:track? true
:alias-id :community.choose-addresses-for-airdrop}
:options {:insets {:top? true}}
:component airdrop-addresses/view}])
(def contact-screens
[{:name :new-contact
:metrics {:track? :true
:metrics {:track? true
:alias-id :contact.new-contact}
:options {:sheet? true}
:component add-new-contact/new-contact}
{:name :scan-profile-qr-code
:metrics {:track? :true
:metrics {:track? true
:alias-id :contact.scan-profile-qr-code}
:options options/dark-screen
:component scan-profile-qr-page/view}
{:name :contact-profile
:metrics {:track? :true
:metrics {:track? true
:alias-id :contact.contact-profile}
:options {:modalPresentationStyle :overCurrentContext}
:component contact-profile/view}
{:name :share-contact
:metrics {:track? :true
:metrics {:track? true
:alias-id :contact.share-profile}
:options options/transparent-screen-options
:component share-contact/view}])
@@ -312,56 +314,56 @@
(def settings-screens
[{:name :settings
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.profile-settings}
:options options/transparent-screen-options
:component settings/view}
{:name :screen/settings.keycard
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.keycard}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component settings.keycard/view}
{:name :edit-profile
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.edit-profile}
:options options/transparent-modal-screen-options
:component edit-profile/view}
{:name :edit-accent-colour
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.edit-profile-accent-colour}
:options options/transparent-modal-screen-options
:component edit-accent-colour/view}
{:name :edit-name
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.edit-profile-name}
:options options/transparent-modal-screen-options
:component edit-name/view}
{:name :edit-bio
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.edit-profile-bio}
:options options/transparent-modal-screen-options
:component edit-bio/view}
{:name :screen/settings-password
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.password}
:options options/transparent-modal-screen-options
:component settings-password/view}
{:name :screen/change-password
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.change-password}
:options (assoc options/transparent-modal-screen-options :theme :dark)
:component change-password/view}
{:name :screen/change-password-loading
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.change-password-loading}
:options (assoc
options/transparent-modal-screen-options
@@ -372,25 +374,25 @@
:component change-password-loading/view}
{:name :screen/settings-messages
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.messages}
:options options/transparent-modal-screen-options
:component settings.messages/view}
{:name :screen/settings-blocked-users
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.blocked-users}
:options options/transparent-modal-screen-options
:component settings.blocked-users/view}
{:name :screen/settings-privacy-and-security
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.private-and-security}
:options options/transparent-modal-screen-options
:component settings.privacy-and-security/view}
{:name :screen/settings.share-usage-data
:metrics {:track? :true}
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component settings.share-usage/view}
@@ -406,89 +408,104 @@
:component syncing-devices-list/view}
{:name :screen/settings.language-and-currency
:metrics {:track? :true}
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component settings.language-and-currency/view}
{:name :screen/settings.currency-selection
:metrics {:track? :true}
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component settings.currency-selection/view}])
:component settings.currency-selection/view}
{:name :screen/settings.about
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component settings.about/view}
{:name :screen/settings.privacy-policy
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component privacy-policy/view}
{:name :screen/settings.terms-of-use
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component terms-of-use/view}])
(def wallet-settings-screens
[{:name :screen/settings.wallet
:metrics {:track? :true}
:metrics {:track? true}
:options options/transparent-modal-screen-options
:component wallet-options/view}
{:name :screen/settings.rename-keypair
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-rename-keypair}
:options options/transparent-screen-options
:component keypair-rename/view}
{:name :screen/settings.encrypted-keypair-qr
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-encrypted-keypair-qr}
:options options/transparent-screen-options
:component encrypted-keypair-qr/view}
{:name :screen/settings.saved-addresses
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-saved-addresses}
:options options/transparent-modal-screen-options
:component saved-addresses-settings/view}
{:name :screen/settings.keypairs-and-accounts
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-keypairs-and-accounts}
:options options/transparent-modal-screen-options
:component keypairs-and-accounts/view}
{:name :screen/settings.scan-keypair-qr
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-scan-keypair-qr}
:options options/transparent-screen-options
:component scan-keypair-qr/view}
{:name :screen/settings.missing-keypair.import-seed-phrase
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-missing-keypair-import-seed-phrase}
:options options/transparent-screen-options
:component missing-keypairs.import-seed-phrase/view}
{:name :screen/settings.missing-keypair-import-private-key
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-missing-keypair-import-private-key}
:options options/transparent-screen-options
:component missing-keypairs.import-private-key/view}
{:name :screen/settings.network-settings
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-network-settings}
:options options/transparent-modal-screen-options
:component network-settings/view}
{:name :screen/settings.save-address
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-saved-addresses}
:options options/transparent-modal-screen-options
:component wallet-save-address/view}
{:name :screen/settings.edit-saved-address
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-edit-saved-addresses}
:options (assoc options/dark-screen :sheet? true)
:component wallet-save-address/view}
{:name :screen/settings.add-address-to-save
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-add-saved-address}
:options options/transparent-modal-screen-options
:component wallet-add-address-to-save/view}
{:name :screen/settings.share-saved-address
:metrics {:track? :true
:metrics {:track? true
:alias-id :settings.wallet-share-saved-address}
:options options/transparent-screen-options
:component share-saved-address/view}])
@@ -728,16 +745,6 @@
:modalPresentationStyle :overCurrentContext}
:component create-or-sync-profile/sync-or-recover-profile})
(def onboarding-create-profile
{:name :screen/onboarding.create-profile
:metrics {:track? true
:alias-id :onboarding.create-profile-info}
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations transitions/push-animations-for-transparent-background
:popGesture false}
:component create-profile/create-profile})
(def onboarding-create-profile-password
{:name :screen/onboarding.create-profile-password
:metrics {:track? true
@@ -768,9 +775,7 @@
:metrics {:track? true}
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations (merge
transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)
:animations transitions/modal-animations-vertical
:popGesture false
:modalPresentationStyle :overCurrentContext}
:component onboarding.share-usage/view})
@@ -872,7 +877,6 @@
[onboarding-intro
onboarding-new-to-status
onboarding-sync-or-recover-profile
onboarding-create-profile
onboarding-create-profile-password
onboarding-enable-biometrics
onboarding-preparing-status
@@ -888,48 +892,48 @@
(def keycard-screens
[{:name :screen/keycard.check
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.check/view}
{:name :screen/keycard.empty
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.empty/view}
{:name :screen/keycard.empty-create
:metrics {:track? :true}
:metrics {:track? true}
:options {:insets {:top? true :bottom? true}}
:component keycard.empty/create}
{:name :screen/keycard.error
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.error/view}
{:name :screen/keycard.not-keycard
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.not-keycard/view}
{:name :screen/keycard.authorise
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.authorise/view}
{:name :screen/keycard.migrate
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.migrate/view}
{:name :screen/keycard.re-encrypting
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}
:popGesture false
@@ -938,7 +942,7 @@
:component keycard.re-encrypting/view}
{:name :screen/keycard.migrate.success
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}
:popGesture false
@@ -947,7 +951,7 @@
:component keycard.migrate.success/view}
{:name :screen/keycard.migrate.fail
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}
:popGesture false
@@ -956,30 +960,30 @@
:component keycard.migrate.fail/view}
{:name :screen/keycard.pin.create
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.pin.create/view}
{:name :screen/keycard.pin.enter
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.pin.enter/view}
{:name :screen/keycard.profile-keys
:metrics {:track? :true}
:metrics {:track? true}
:options {:theme :dark
:insets {:top? true :bottom? true}}
:component keycard.migrate.profile-keys/view}
{:name :screen/keycard.create-profile
:metrics {:track? :true}
:metrics {:track? true}
:options {:insets {:top? true :bottom? true}}
:component keycard.create/view}
{:name :screen/keycard.create.ready-to-add
:metrics {:track? :true}
:metrics {:track? true}
:options {:insets {:top? true :bottom? true}}
:component keycard.create/ready-to-add}])
@@ -40,6 +40,14 @@
:to (:width (rn/get-window))
:duration constants/onboarding-modal-animation-duration}}})
(def modal-animations-vertical
{:showModal {:translationY {:from (:height (rn/get-window))
:to 0
:duration constants/onboarding-modal-animation-duration}}
:dismissModal {:translationY {:from 0
:to (:height (rn/get-window))
:duration constants/onboarding-modal-animation-duration}}})
;;;; Stack Transitions
(def stack-slide-transition
{:push {:content {:translationX {:from (:width (rn/get-window))
+14 -8
View File
@@ -286,6 +286,12 @@
:<- [:wallet/create-account]
:-> :selected-keypair-uid)
(rf/reg-sub
:wallet/selected-keypair-keycard?
:<- [:wallet/selected-keypair]
(fn [{:keys [keycards]}]
(boolean (seq keycards))))
(rf/reg-sub
:wallet/selected-keypair
:<- [:wallet/keypairs]
@@ -343,12 +349,13 @@
:<- [:wallet/keypairs-list]
(fn [keypairs [_ format-options]]
(reduce
(fn [acc {:keys [accounts name type key-uid lowest-operability]}]
(fn [acc {:keys [accounts name type key-uid lowest-operability keycards]}]
(if (= lowest-operability :no)
(update acc
:missing
conj
{:type type
:keycard? (boolean (seq keycards))
:name name
:key-uid key-uid
:accounts (format-settings-missing-keypair-accounts accounts)})
@@ -356,6 +363,7 @@
:operable
conj
{:type type
:keycard? (boolean (seq keycards))
:name name
:key-uid key-uid
:accounts (format-settings-keypair-accounts accounts format-options)})))
@@ -711,13 +719,11 @@
(rf/reg-sub
:wallet/zero-balance-in-all-non-watched-accounts?
:<- [:wallet/aggregated-tokens]
:<- [:profile/currency]
:<- [:wallet/prices-per-token]
(fn [[aggregated-tokens currency prices-per-token]]
(let [balance (utils/calculate-balance-from-tokens {:currency currency
:tokens aggregated-tokens
:prices-per-token prices-per-token})]
(and (not-empty aggregated-tokens) (money/equal-to balance 0)))))
(fn [aggregated-tokens]
(let [zero-balance? (->> aggregated-tokens
(mapcat (comp vals :balances-per-chain))
(every? #(money/equal-to (:raw-balance %) 0)))]
(and (not-empty aggregated-tokens) zero-balance?))))
(rf/reg-sub
:wallet/network-preference-details
+147
View File
@@ -1008,3 +1008,150 @@
(update accounts "0x2" assoc :operable :partially)))
(is (true? (rf/sub [sub-name])))))
(h/deftest-sub :wallet/zero-balance-in-all-non-watched-accounts?
[sub-name]
(testing "returns true if the balance is zero in all non-watched accounts"
(swap! rf-db/app-db
#(assoc-in %
[:wallet :accounts]
{"0x1" {:address "0x1"
:watch-only? false
:tokens
[{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}
{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}]}
"0x2" {:address "0x2"
:watch-only? false
:tokens
[{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}
{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}]}
"0x3"
{:address "0x3"
:watch-only? true
:tokens [{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance
(money/bignumber
"2")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance
(money/bignumber
"1")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance
(money/bignumber
"0")
:has-error false}}}
{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance
(money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance
(money/bignumber
"2")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance
(money/bignumber
"0")
:has-error
false}}}]}}))
(is (true? (rf/sub [sub-name]))))
(testing "returns false if the balance is not zero in all non-watched accounts"
(swap! rf-db/app-db
#(assoc-in %
[:wallet :accounts]
{"0x1" {:address "0x1"
:watch-only? false
:tokens
[{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"2")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"1")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}
{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"2")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}]}
"0x2" {:address "0x2"
:watch-only? true
:tokens
[{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"2")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"1")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}
{:balances-per-chain {constants/ethereum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}
constants/optimism-mainnet-chain-id {:raw-balance (money/bignumber
"2")
:has-error false}
constants/arbitrum-mainnet-chain-id {:raw-balance (money/bignumber
"0")
:has-error false}}}]}}))
(is (false? (rf/sub [sub-name])))))
(h/deftest-sub :wallet/selected-keypair-keycard?
[sub-name]
(testing "returns true if the selected keypair has keycards"
(swap! rf-db/app-db
#(assoc-in %
[:wallet :keypairs]
{:keypair-1 {:id :keypair-1
:keycards [:keycard-1 :keycard-2]}}))
(swap! rf-db/app-db
#(assoc-in % [:wallet :ui :create-account :selected-keypair-uid] :keypair-1))
(is (true? (rf/sub [sub-name]))))
(testing "returns false if the selected keypair has no keycards"
(swap! rf-db/app-db
#(assoc-in %
[:wallet :keypairs]
{:keypair-2 {:id :keypair-2
:keycards []}}))
(swap! rf-db/app-db
#(assoc-in % [:wallet :ui :create-account :selected-keypair-uid] :keypair-2))
(is (false? (rf/sub [sub-name])))))
+11
View File
@@ -141,3 +141,14 @@
(defn zero-address?
[address]
(= address zero-address))
(defn public-key->address
[public-key]
(let [length (count public-key)
normalized-key (case length
132 (str "0x" (subs public-key 4))
130 public-key
128 (str "0x" public-key)
nil)]
(when normalized-key
(subs (native-module/sha3 normalized-key) 26))))
@@ -17,11 +17,10 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = 'sender', 'receiver'
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True,
'username': self.username_1}),
(self.device_2.create_user, {'username': self.username_2}))))
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True}),
(self.device_2.create_user,))))
self.homes = self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.profile_1, self.profile_2 = self.home_1.get_profile_view(), self.home_2.get_profile_view()
self.public_key_1 = self.home_1.get_public_key()
self.profile_link_2 = self.home_2.get_link_to_profile()
@@ -98,8 +97,8 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
def test_activity_center_contact_request_accept_swipe_mark_all_as_read(self):
self.device_2.just_fyi('Creating a new user on Device2')
self.home_2.reopen_app(sign_in=False)
new_username = "new user"
self.device_2.create_user(username=new_username, first_user=False)
self.device_2.create_user(first_user=False)
new_username = self.home_2.get_username()
self.device_2.just_fyi('Device2 sends a contact request to Device1 via Paste button and check user details')
self.home_2.chats_tab.click()
@@ -149,30 +148,21 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
@marks.testrail_id(702777)
def test_add_contact_field_validation(self):
# Profile link encoded data validation
encoded_data = re.findall("u/(.*)#", self.profile_link_2)[0]
decoded_string = base64.b64decode(encoded_data).decode("utf-8", "ignore")
decoded_username = re.sub('[^A-Za-z0-9]+', '', decoded_string)
if decoded_username != self.username_2:
self.errors.append(
"Can't find username '%s' in data which profile link '%s' contains. String '%s' is found instead" % (
self.username_2, self.profile_link_2, decoded_username))
public_key_2 = self.profile_link_2.split("#")[-1]
new_username_1 = "test user 123"
def _device_1_creates_user():
self.home_1.just_fyi("Device 1 creates a new user")
self.home_1.reopen_app(sign_in=False)
self.device_1.create_user(username=new_username_1, first_user=False)
self.device_1.create_user(first_user=False)
return self.home_1.get_username()
def _device_2_sign_in():
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.sign_in(user_name=self.username_2)
self.loop.run_until_complete(run_in_parallel(((_device_1_creates_user, {}),
(_device_2_sign_in, {}))))
new_username_1, _ = self.loop.run_until_complete(run_in_parallel(((_device_1_creates_user, {}),
(_device_2_sign_in, {}))))
self.device_1.just_fyi('Device1 sends a contact request to Device2 using his profile link')
self.home_1.chats_tab.click()
@@ -242,11 +232,11 @@ class TestActivityMultipleDevicePR(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = 'user1', 'user2'
self.loop.run_until_complete(
run_in_parallel(((self.device_1.create_user, {'username': self.username_1}),
(self.device_2.create_user, {'username': self.username_2}))))
run_in_parallel(((self.device_1.create_user,),
(self.device_2.create_user,))))
self.homes = self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.profile_1, self.profile_2 = self.home_1.get_profile_view(), self.home_2.get_profile_view()
self.public_key_2 = self.home_2.get_public_key()
self.home_2.navigate_back_to_home_view()
@@ -382,11 +372,11 @@ class TestActivityMultipleDevicePRTwo(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = 'user1', 'user2'
self.loop.run_until_complete(
run_in_parallel(((self.device_1.create_user, {'username': self.username_1}),
(self.device_2.create_user, {'username': self.username_2}))))
run_in_parallel(((self.device_1.create_user,),
(self.device_2.create_user,))))
self.homes = self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.profile_1, self.profile_2 = self.home_1.get_profile_view(), self.home_2.get_profile_view()
self.public_key_2 = self.home_2.get_public_key()
self.home_2.navigate_back_to_home_view()
@@ -20,13 +20,11 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = 'sender', 'receiver'
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True,
'username': self.username_1}),
(self.device_2.create_user, {'enable_notifications': True,
'username': self.username_2}))))
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True}),
(self.device_2.create_user, {'enable_notifications': True}))))
self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.homes = (self.home_1, self.home_2)
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.profile_1, self.profile_2 = (home.get_profile_view() for home in self.homes)
self.public_key_2 = self.home_2.get_public_key()
@@ -543,13 +541,11 @@ class TestOneToOneChatMultipleSharedDevicesNewUiTwo(MultipleSharedDeviceTestCase
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = 'sender', 'receiver'
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True,
'username': self.username_1}),
(self.device_2.create_user, {'enable_notifications': True,
'username': self.username_2}))))
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True}),
(self.device_2.create_user, {'enable_notifications': True}))))
self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.homes = (self.home_1, self.home_2)
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.profile_1, self.profile_2 = (home.get_profile_view() for home in self.homes)
self.public_key_2 = self.home_2.get_public_key()
@@ -21,17 +21,25 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.message_to_admin = 'Hey, admin!'
self.public_keys, self.usernames, self.chats = {}, {}, {}
self.sign_in_views = [SignInView(self.drivers[key]) for key in self.drivers]
self.usernames = ('user admin', 'member_1', 'member_2')
self.loop.run_until_complete(
run_in_parallel(
(
(self.sign_in_views[0].create_user, {'enable_notifications': True, 'username': self.usernames[0]}),
(self.sign_in_views[1].create_user, {'enable_notifications': True, 'username': self.usernames[1]}),
(self.sign_in_views[2].create_user, {'enable_notifications': True, 'username': self.usernames[2]})
(self.sign_in_views[0].create_user, {'enable_notifications': True}),
(self.sign_in_views[1].create_user, {'enable_notifications': True}),
(self.sign_in_views[2].create_user, {'enable_notifications': True})
)
)
)
self.homes = [sign_in.get_home_view() for sign_in in self.sign_in_views]
self.usernames = self.loop.run_until_complete(
run_in_parallel(
(
(self.homes[0].get_username,),
(self.homes[1].get_username,),
(self.homes[2].get_username,)
)
)
)
self.public_keys = self.loop.run_until_complete(
run_in_parallel(
(
@@ -445,7 +453,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
current_time = datetime.datetime.strptime(device_time, "%Y-%m-%dT%H:%M:%S%z")
expected_times = [current_time + datetime.timedelta(minutes=i) for i in range(479, 482)]
expected_texts = [
"Muted until %s %s" % (exp_time.strftime('%H:%M'), "today" if current_time.hour < 23 else "tomorrow") for
"Muted until %s %s" % (exp_time.strftime('%H:%M'), "today" if current_time.hour < 16 else "tomorrow") for
exp_time in expected_times]
chat = self.homes[1].get_chat(self.chat_name)
chat.long_press_element()
@@ -475,7 +483,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
if after_mute_counter > initial_counter:
self.errors.append("New messages counter near chats tab button is %s after mute, but should be %s" % (
after_mute_counter, initial_counter))
if not chat.chat_preview.text.startswith("%s: %s" % (self.usernames[0], muted_message)):
if not chat.chat_preview.text.startswith("%s: %s" % (self.usernames[0], muted_message[:25])):
self.errors.append("Message text '%s' is not shown in chat preview after mute" % muted_message)
chat.click()
if not self.chats[1].chat_element_by_text(muted_message).is_element_displayed(30):
@@ -21,9 +21,9 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(1)
self.sign_in = SignInView(self.drivers[0])
self.username = 'first user'
self.home = self.sign_in.create_user(username=self.username)
self.home = self.sign_in.create_user()
self.username = self.home.get_username()
self.home.communities_tab.click_until_presence_of_element(self.home.plus_community_button)
self.community_name = "closed community"
self.channel_name = "cats"
@@ -158,8 +158,8 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
def test_restore_multiaccount_with_waku_backup_remove_profile_switch(self):
self.home.reopen_app(sign_in=False)
self.home.just_fyi("Restore user with predefined communities and contacts")
recover_user_name = 'Recover user'
self.sign_in.recover_access(passphrase=waku_user.seed, second_user=True, username=recover_user_name)
self.sign_in.recover_access(passphrase=waku_user.seed, second_user=True)
recover_user_name = self.home.get_username()
self.home.just_fyi("Check contacts/blocked users")
self.home.chats_tab.click()
@@ -306,11 +306,10 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = "user_1", "user_2"
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True,
'username': self.username_1}),
(self.device_2.create_user, {'username': self.username_2}))))
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True}),
(self.device_2.create_user,))))
self.homes = self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.public_key_2 = self.home_2.get_public_key()
self.profile_1 = self.home_1.get_profile_view()
[home.navigate_back_to_home_view() for home in self.homes]
@@ -836,12 +835,10 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.username_1, self.username_2 = "user_1", "user_2"
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True,
'username': self.username_1}),
(self.device_2.create_user, {'enable_notifications': True,
'username': self.username_2}))))
self.loop.run_until_complete(run_in_parallel(((self.device_1.create_user, {'enable_notifications': True}),
(self.device_2.create_user, {'enable_notifications': True}))))
self.homes = self.home_1, self.home_2 = self.device_1.get_home_view(), self.device_2.get_home_view()
self.username_1, self.username_2 = self.home_1.get_username(), self.home_2.get_username()
self.public_key_2 = self.home_2.get_public_key()
self.profile_1 = self.home_1.get_profile_view()
[home.navigate_back_to_home_view() for home in self.homes]
@@ -1024,6 +1021,8 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
self.errors.append('Community is still shown in the list after leave')
self.errors.verify_no_errors()
@marks.xfail(reason="Can't navigate to a community channel before joining the community, "
"https://github.com/status-im/status-mobile/issues/21776")
@marks.testrail_id(702948)
def test_community_hashtag_links_to_community_channels(self):
for home in self.homes:
@@ -1124,6 +1123,8 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.xfail(reason="Can't navigate to a community channel before joining the community, "
"https://github.com/status-im/status-mobile/issues/21776")
@marks.testrail_id(703629)
def test_community_join_when_node_owner_offline(self):
for home in self.homes:
@@ -13,9 +13,9 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase):
self.drivers, self.loop = create_shared_drivers(1)
self.driver = self.drivers[0]
self.sign_in = SignInView(self.driver)
self.username = 'test user'
self.home = self.sign_in.create_user(username=self.username)
self.home = self.sign_in.create_user()
self.username = self.home.get_username()
self.home.communities_tab.click_until_presence_of_element(self.home.plus_community_button)
self.open_community_name = "open community"
self.channel_name = "general"
@@ -89,7 +89,7 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase):
self.home.get_chat(snt_community_name, community=True).click()
snt_community_url = self.community_view.copy_community_link()
self.home.reopen_app(sign_in=False)
self.sign_in.create_user(username="second user", first_user=False)
self.sign_in.create_user(first_user=False)
self.home.browser_tab.click()
old, new = "https://status.app/", "status-app://"
+4 -4
View File
@@ -20,10 +20,10 @@ class TestFallbackMultipleDevice(MultipleSharedDeviceTestCase):
self.sign_in_3.get_home_view()
self.sign_in_1.just_fyi("Device 1: create a new user")
self.sign_in_3.just_fyi("Device 3: create a new user")
self.user_name_1, self.user_name_3 = 'first user', 'third user'
self.loop.run_until_complete(
run_in_parallel(((self.sign_in_1.create_user, {'username': self.user_name_1}),
(self.sign_in_3.create_user, {'username': self.user_name_3}))))
run_in_parallel(((self.sign_in_1.create_user,),
(self.sign_in_3.create_user,))))
self.user_name_1, self.user_name_3 = self.home_1.get_username(), self.home_3.get_username()
self.profile_1 = self.home_1.profile_button.click()
self.profile_2 = self.home_2.get_profile_view()
self.sign_in_3.just_fyi("Device 3: get public key")
@@ -269,7 +269,7 @@ class TestFallbackMultipleDevice(MultipleSharedDeviceTestCase):
self.sign_in_2.passphrase_edit_box.clear()
self.sign_in_2.passphrase_edit_box.send_keys(transaction_senders['A']['passphrase'])
self.sign_in_2.continue_button.click()
if not self.sign_in_2.profile_title_input.is_element_displayed():
if not self.sign_in_2.password_input.is_element_displayed():
self.errors.append("Can't recover an access with a valid passphrase")
self.sign_in_2.click_system_back_button()
+9 -11
View File
@@ -24,13 +24,11 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
self.sender, self.receiver = transaction_senders['ETH_1'], transaction_senders['ETH_2']
self.sender['wallet_address'] = '0x' + self.sender['address']
self.receiver['wallet_address'] = '0x' + self.receiver['address']
self.sender_username, self.receiver_username = 'sender', 'receiver'
self.loop.run_until_complete(
run_in_parallel(((self.sign_in_1.recover_access, {'passphrase': self.sender['passphrase'],
'username': self.sender_username}),
(self.sign_in_2.recover_access, {'passphrase': self.receiver['passphrase'],
'username': self.receiver_username}))))
run_in_parallel(((self.sign_in_1.recover_access, {'passphrase': self.sender['passphrase']}),
(self.sign_in_2.recover_access, {'passphrase': self.receiver['passphrase']}))))
self.home_1, self.home_2 = self.sign_in_1.get_home_view(), self.sign_in_2.get_home_view()
self.sender_username, self.receiver_username = self.home_1.get_username(), self.home_2.get_username()
self.wallet_1, self.wallet_2 = self.sign_in_1.get_wallet_view(), self.sign_in_2.get_wallet_view()
self.wallet_1.wallet_tab.click()
self.wallet_2.wallet_tab.click()
@@ -61,19 +59,19 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
def wait_for_wallet_balance_to_update(wallet_view, user_name, initial_eth_amount):
wallet_view.just_fyi("Getting ETH amount in the wallet of the %s after transaction" % user_name)
if user_name == 'sender':
if user_name == self.sender_username:
exp_amount = round(initial_eth_amount - amount_to_send, 4)
else:
exp_amount = round(initial_eth_amount + amount_to_send, 4)
# for _ in range(12): # ToDo: 120 sec wait time, enable when autoupdate feature is ready
new_eth_amount = round(wallet_view.get_asset(asset_name='Ether').get_amount(), 4)
if user_name == 'sender' and new_eth_amount <= exp_amount:
if user_name == self.sender_username and new_eth_amount <= exp_amount:
return
if user_name == 'receiver' and new_eth_amount >= exp_amount:
if user_name == self.receiver_username and new_eth_amount >= exp_amount:
return
self.errors.append(
"Eth amount in the %ss wallet is %s but should be %s" % (user_name, new_eth_amount, exp_amount))
"Eth amount in the %s's wallet is %s but should be %s" % (user_name, new_eth_amount, exp_amount))
# ToDo: disable relogin when autoupdate feature is ready
self.home_1.just_fyi("Relogin for getting an updated balance")
@@ -206,10 +204,10 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
self.arb_balance = {'Ether': 0.0001, 'USDCoin': 0.0, 'Status': 0.0, 'Uniswap': 0.5, 'Dai Stablecoin': 0.0}
self.sender['wallet_address'] = '0x' + self.sender['address']
self.receiver['wallet_address'] = '0x' + self.receiver['address']
self.sender_username, self.receiver_username = 'sender', 'receiver'
self.sign_in_view.recover_access(passphrase=self.sender['passphrase'], username=self.sender_username)
self.sign_in_view.recover_access(passphrase=self.sender['passphrase'])
self.home_view = self.sign_in_view.get_home_view()
self.sender_username = self.home_view.get_username()
self.wallet_view = self.home_view.wallet_tab.click()
@marks.testrail_id(740490)
+9
View File
@@ -624,3 +624,12 @@ class HomeView(BaseView):
element = Text(self.driver, accessibility_id='new-device-installation-id')
element.wait_for_visibility_of_element()
return element.text
def get_username(self):
self.toast_content_element.wait_for_invisibility_of_element()
profile_view = self.get_profile_view()
profile_view = self.profile_button.click_until_presence_of_element(profile_view.default_username_text)
profile_view.default_username_text.wait_for_element(3)
username = profile_view.default_username_text.text
profile_view.click_system_back_button()
return username
+1 -1
View File
@@ -179,7 +179,7 @@ class ProfileView(BaseView):
# Header
self.public_key_text = Text(self.driver, accessibility_id="chat-key")
self.default_username_text = Text(self.driver, accessibility_id="default-username")
self.default_username_text = Text(self.driver, accessibility_id="username")
self.contact_name_text = Text(self.driver, accessibility_id="contact-name")
self.share_my_profile_button = Button(self.driver, accessibility_id="share-header-button")
self.profile_picture = ProfilePictureElement(self.driver)
+7 -10
View File
@@ -211,7 +211,7 @@ class SignInView(BaseView):
self.profile_continue_button = Button(self.driver, accessibility_id="submit-create-profile-button")
self.profile_password_edit_box = EditBox(self.driver, translation_id="password-creation-placeholder-1")
self.profile_repeat_password_edit_box = EditBox(self.driver, translation_id="password-creation-placeholder-2")
self.profile_confirm_password_button = Button(self.driver, translation_id="password-creation-confirm")
self.profile_confirm_password_button = Button(self.driver, accessibility_id="Confirm password")
self.enable_biometric_maybe_later_button = Button(self.driver, translation_id="maybe-later")
self.identifiers_button = Button(self.driver, accessibility_id="skip-identifiers")
self.start_button = Button(self.driver, accessibility_id="welcome-button")
@@ -228,8 +228,6 @@ class SignInView(BaseView):
input_elements[0].send_keys(password)
input_elements[1].click()
input_elements[1].send_keys(password)
self.checkbox_button.scroll_to_element()
self.checkbox_button.click()
self.profile_confirm_password_button.click()
def set_profile(self, username: str, set_image=False):
@@ -238,7 +236,7 @@ class SignInView(BaseView):
if set_image:
pass
def create_user(self, password=common_password, username="test user", first_user=True, enable_notifications=False):
def create_user(self, password=common_password, first_user=True, enable_notifications=False):
self.driver.info("## Creating new multiaccount with password:'%s'" % password, device=False)
if first_user:
self.create_profile_button.click_until_presence_of_element(self.start_fresh_lets_go_button)
@@ -249,7 +247,6 @@ class SignInView(BaseView):
self.plus_profiles_button.click()
self.create_new_profile_button.click()
self.start_fresh_lets_go_button.click_until_presence_of_element(self.profile_title_input)
self.set_profile(username)
self.set_password(password)
self.chats_tab.wait_for_visibility_of_element(30)
self.driver.info("## New multiaccount is created successfully!", device=False)
@@ -261,7 +258,7 @@ class SignInView(BaseView):
return home_view
def recover_access(self, passphrase: str, password: str = common_password, enable_notifications=False,
second_user=False, username='Restore user', set_image=False, after_sync_code=False):
second_user=False, after_sync_code=False):
self.driver.info("## Recover access (password:%s)" % password, device=False)
if not after_sync_code:
@@ -274,8 +271,6 @@ class SignInView(BaseView):
self.use_recovery_phrase_button.click()
self.passphrase_edit_box.send_keys(passphrase)
self.continue_button.click_until_presence_of_element(self.profile_title_input)
if not after_sync_code:
self.set_profile(username, set_image)
self.set_password(password)
self.chats_tab.wait_for_visibility_of_element(30)
self.driver.info("## Multiaccount is recovered successfully!", device=False)
@@ -366,8 +361,10 @@ class SignInView(BaseView):
def get_user_profile_by_name(self, username: str):
self.driver.info("Getting username card by '%s'" % username)
expected_element = UserProfileElement(self.driver, username=username)
return expected_element if expected_element.is_element_displayed(10) else self.driver.fail(
"User %s is not found!" % username)
if expected_element.is_element_displayed(10):
return expected_element
else:
raise NoSuchElementException(msg="User %s is not found!" % username)
def get_user_profile_by_index(self, index: int):
self.driver.info("Getting username card by index %s" % index)
+8 -1
View File
@@ -801,6 +801,7 @@
"do-not-quit": "Do not quit the application or turn off your device. Doing so will lead to data corruption, loss of your Status profile and the inability to use Status.",
"do-not-share": "Do not share",
"documents": "Documents",
"done": "Done",
"dont-ask": "Don't ask me again",
"dont-auto-recalculate-network": "Don't auto recalculate {{network}}",
@@ -1037,6 +1038,9 @@
"favourite-description": "Your favourite websites will appear here",
"favourites": "Favourites",
"favourites-empty": "Addresses added to favourites will appear here",
"feature-unavailable": "Feature unavailable",
"feature-unavailable-for-keycard-description": "Its not currently supported for Keycard users.",
"feature-unavailable-info": "If you'd like this feature on mobile, feel free to ",
"featured": "Featured",
"feb": "Feb",
"fee-cap": "Fee cap",
@@ -1113,6 +1117,7 @@
"getting-started-generate-keys-on-keycard-description": "A Keycard is a physical card (similar to a credit card) that securely stores your Status keys. Using a Keycard adds an extra layer of security to your digital assets and identity. This option creates a new Ethereum address.",
"getting-started-with-status": "Getting started with Status",
"gif": "GIF",
"github-repos": "GitHub repos",
"give-a-short-description": "Give a short description",
"give-a-short-description-community": "Give it a short description",
"give-permissions-camera": "Give permission\nto access camera",
@@ -1930,7 +1935,7 @@
"password-creation-max-length-hint": "Maximum 100 characters",
"password-creation-placeholder-1": "Type password",
"password-creation-placeholder-2": "Repeat password",
"password-creation-subtitle": "To log in to Status and sign transactions",
"password-creation-subtitle": "This password can't be recovered",
"password-creation-tips-1": "Lower case",
"password-creation-tips-2": "Upper case",
"password-creation-tips-3": "Numbers",
@@ -2719,6 +2724,7 @@
"update-to-see-sticker": "Update to latest version to see a nice sticker here!",
"updates-to-tos": "Updates to Terms of Use",
"updates-to-tos-desc": "Before you continue, please review the Terms of Use and confirm you take full responsibility for how you use the app.",
"upvote-it": "upvote it",
"url": "URL",
"usage-data-shared-from-all-profiles": "Usage data will be shared from all profiles added to device. ",
"usd-currency": "USD",
@@ -2868,6 +2874,7 @@
"we-will-receive-from-all-profiles": "We will receive from all profiles:",
"we-will-receive-from-the-current-profile": "We will receive from the current profile:",
"web-view-error": "Unable to load page",
"website": "Website",
"websites": "Websites",
"webview-camera-permission-requests": "Webview camera permission requests",
"webview-camera-permission-requests-subtitle": "When enabled, websites and dapps can ask to use your camera",