Browse Source

Onboarding setup wizard

New onboarding e2e tests updates

New onboarding e2e fix 2

Signed-off-by: Vitaliy Vlasov <siphiuel@gmail.com>
tags/pre-yarn2nix-push
Vitaliy Vlasov 3 months ago
parent
commit
e9fd6e1a6b
No account linked to committer's email address
66 changed files with 978 additions and 167 deletions
  1. BIN
      android/app/src/main/res/drawable-hdpi/bell.png
  2. BIN
      android/app/src/main/res/drawable-hdpi/fingerprint.png
  3. BIN
      android/app/src/main/res/drawable-hdpi/keycard_logo.png
  4. BIN
      android/app/src/main/res/drawable-mdpi/bell.png
  5. BIN
      android/app/src/main/res/drawable-mdpi/fingerprint.png
  6. BIN
      android/app/src/main/res/drawable-mdpi/keycard_logo.png
  7. BIN
      android/app/src/main/res/drawable-xhdpi/bell.png
  8. BIN
      android/app/src/main/res/drawable-xhdpi/fingerprint.png
  9. BIN
      android/app/src/main/res/drawable-xhdpi/keycard_logo.png
  10. BIN
      android/app/src/main/res/drawable-xxhdpi/bell.png
  11. BIN
      android/app/src/main/res/drawable-xxhdpi/fingerprint.png
  12. BIN
      android/app/src/main/res/drawable-xxhdpi/keycard_logo.png
  13. BIN
      android/app/src/main/res/drawable-xxxhdpi/bell.png
  14. BIN
      android/app/src/main/res/drawable-xxxhdpi/fingerprint.png
  15. BIN
      android/app/src/main/res/drawable-xxxhdpi/keycard_logo.png
  16. 3
    0
      externs.js
  17. 23
    0
      ios/StatusIm/Images.xcassets/bell.imageset/Contents.json
  18. BIN
      ios/StatusIm/Images.xcassets/bell.imageset/bell_1x.png
  19. BIN
      ios/StatusIm/Images.xcassets/bell.imageset/bell_2x.png
  20. BIN
      ios/StatusIm/Images.xcassets/bell.imageset/bell_3x.png
  21. 23
    0
      ios/StatusIm/Images.xcassets/fingerprint.imageset/Contents.json
  22. BIN
      ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_1x.png
  23. BIN
      ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_2x.png
  24. BIN
      ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_3x.png
  25. 38
    0
      modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java
  26. 24
    0
      modules/react-native-status/ios/RCTStatus/RCTStatus.m
  27. BIN
      resources/images/ui/intro1.png
  28. BIN
      resources/images/ui/intro2.png
  29. BIN
      resources/images/ui/intro3.png
  30. BIN
      resources/images/ui/sample-key.png
  31. 220
    53
      src/status_im/accounts/create/core.cljs
  32. 14
    13
      src/status_im/init/core.cljs
  33. 6
    0
      src/status_im/native_module/core.cljs
  34. 8
    0
      src/status_im/native_module/impl/module.cljs
  35. 4
    0
      src/status_im/react_native/resources.cljs
  36. 5
    2
      src/status_im/signals/core.cljs
  37. 3
    0
      src/status_im/subs.cljs
  38. 11
    9
      src/status_im/ui/components/typography.cljs
  39. 6
    0
      src/status_im/ui/screens/about_app/styles.cljs
  40. 17
    1
      src/status_im/ui/screens/about_app/views.cljs
  41. 88
    16
      src/status_im/ui/screens/intro/styles.cljs
  42. 289
    20
      src/status_im/ui/screens/intro/views.cljs
  43. 1
    1
      src/status_im/ui/screens/profile/user/views.cljs
  44. 2
    0
      src/status_im/ui/screens/routing/intro_login_stack.cljs
  45. 1
    0
      src/status_im/ui/screens/routing/screens.cljs
  46. 4
    0
      src/status_im/ui/screens/views.cljs
  47. 1
    1
      src/status_im/ui/screens/wallet/send/views.cljs
  48. 3
    0
      src/status_im/utils/utils.cljs
  49. 12
    1
      test/appium/tests/atomic/account_management/test_create_account.py
  50. 4
    3
      test/appium/tests/atomic/account_management/test_sign_in.py
  51. 1
    1
      test/appium/tests/atomic/chats/test_chats_management.py
  52. 1
    2
      test/appium/tests/atomic/chats/test_public.py
  53. 52
    12
      test/appium/views/sign_in_view.py
  54. 62
    19
      test/cljs/status_im/test/accounts/create/core.cljs
  55. 3
    1
      test/cljs/status_im/test/runner.cljs
  56. 1
    1
      translations/el.json
  57. 39
    2
      translations/en.json
  58. 1
    1
      translations/es_419.json
  59. 1
    1
      translations/fa.json
  60. 1
    1
      translations/ja.json
  61. 1
    1
      translations/ko.json
  62. 1
    1
      translations/ms.json
  63. 1
    1
      translations/ne.json
  64. 1
    1
      translations/pl.json
  65. 1
    1
      translations/ru.json
  66. 1
    1
      translations/zh_Hans_CN.json

BIN
android/app/src/main/res/drawable-hdpi/bell.png View File


BIN
android/app/src/main/res/drawable-hdpi/fingerprint.png View File


BIN
android/app/src/main/res/drawable-hdpi/keycard_logo.png View File


BIN
android/app/src/main/res/drawable-mdpi/bell.png View File


BIN
android/app/src/main/res/drawable-mdpi/fingerprint.png View File


BIN
android/app/src/main/res/drawable-mdpi/keycard_logo.png View File


BIN
android/app/src/main/res/drawable-xhdpi/bell.png View File


BIN
android/app/src/main/res/drawable-xhdpi/fingerprint.png View File


BIN
android/app/src/main/res/drawable-xhdpi/keycard_logo.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/bell.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/fingerprint.png View File


BIN
android/app/src/main/res/drawable-xxhdpi/keycard_logo.png View File


BIN
android/app/src/main/res/drawable-xxxhdpi/bell.png View File


BIN
android/app/src/main/res/drawable-xxxhdpi/fingerprint.png View File


BIN
android/app/src/main/res/drawable-xxxhdpi/keycard_logo.png View File


+ 3
- 0
externs.js View File

@@ -187,6 +187,7 @@ var TopLevel = {
"hide" : function () {},
"i18n" : function () {},
"ignoreWarnings" : function () {},
"importOnboardingAccount": function () {},
"in" : function () {},
"index" : function () {},
"indexOf" : function () {},
@@ -465,6 +466,7 @@ var TopLevel = {
"StackActions" : function () {},
"start" : function () {},
"startNode" : function () {},
"startOnboarding": function () {},
"state" : function () {},
"Status" : function () {},
"status" : function () {},
@@ -518,6 +520,7 @@ var TopLevel = {
"verifyPin" : function () {},
"Version" : function () {},
"version" : function () {},
"vibrate" : function () {},
"View" : function () {},
"warn" : function () {},
"Web3" : function () {},

+ 23
- 0
ios/StatusIm/Images.xcassets/bell.imageset/Contents.json View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "bell_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "bell_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "bell_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/StatusIm/Images.xcassets/bell.imageset/bell_1x.png View File


BIN
ios/StatusIm/Images.xcassets/bell.imageset/bell_2x.png View File


BIN
ios/StatusIm/Images.xcassets/bell.imageset/bell_3x.png View File


+ 23
- 0
ios/StatusIm/Images.xcassets/fingerprint.imageset/Contents.json View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "fingerprint_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "fingerprint_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "fingerprint_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_1x.png View File


BIN
ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_2x.png View File


BIN
ios/StatusIm/Images.xcassets/fingerprint.imageset/fingerprint_3x.png View File


+ 38
- 0
modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java View File

@@ -660,6 +660,44 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}

@ReactMethod
public void startOnboarding(final Integer n, final Integer mnemonicLength, final Callback callback) {
Log.d(TAG, "startOnboarding");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.startOnboarding(n, mnemonicLength);

callback.invoke(res);
}
};

StatusThreadPoolExecutor.getInstance().execute(r);
}

@ReactMethod
public void importOnboardingAccount(final String id, final String password, final Callback callback) {
Log.d(TAG, "importOnboardingAccount");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.importOnboardingAccount(id, password);

callback.invoke(res);
}
};

StatusThreadPoolExecutor.getInstance().execute(r);
}

private String createIdentifier() {
return UUID.randomUUID().toString();
}

+ 24
- 0
modules/react-native-status/ios/RCTStatus/RCTStatus.m View File

@@ -335,6 +335,29 @@ RCT_EXPORT_METHOD(recoverAccount:(NSString *)passphrase
callback(@[result]);
}

//////////////////////////////////////////////////////////////////// startOnboarding
RCT_EXPORT_METHOD(startOnboarding:(NSInteger)n
password:(NSInteger)mnemonicLength
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"StartOnboarding() method called");
#endif
NSString *result = StatusgoStartOnboarding(n, mnemonicLength);
callback(@[result]);
}

//////////////////////////////////////////////////////////////////// importOnboardingAccount
RCT_EXPORT_METHOD(importOnboardingAccount:(NSString *)id
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"ImportOnboardingAccount() method called");
#endif
NSString *result = StatusgoImportOnboardingAccount(id, password);
callback(@[result]);
}


//////////////////////////////////////////////////////////////////// login
RCT_EXPORT_METHOD(login:(NSString *)address
password:(NSString *)password
@@ -449,6 +472,7 @@ RCT_EXPORT_METHOD(setSoftInputMode: (NSInteger) i) {
RCT_EXPORT_METHOD(clearCookies) {
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];

for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}

BIN
resources/images/ui/intro1.png View File


BIN
resources/images/ui/intro2.png View File


BIN
resources/images/ui/intro3.png View File


BIN
resources/images/ui/sample-key.png View File


+ 220
- 53
src/status_im/accounts/create/core.cljs View File

@@ -17,7 +17,8 @@
[status-im.utils.identicon :as identicon]
[status-im.utils.signing-phrase.core :as signing-phrase]
[status-im.utils.types :as types]
[taoensso.timbre :as log]
[status-im.utils.utils :as utils]
[clojure.set :refer [map-invert]]
[status-im.utils.fx :as fx]
[status-im.node.core :as node]
[status-im.ui.screens.mobile-network-settings.events :as mobile-network]
@@ -26,26 +27,57 @@
(defn get-signing-phrase [cofx]
(assoc cofx :signing-phrase (signing-phrase/generate)))

(def step-kw-to-num
{:generate-key 1
:choose-key 2
:select-key-storage 3
:create-code 4
:confirm-code 5
:enable-fingerprint 6
:enable-notifications 7})

(defn dec-step [step]
(let [inverted (map-invert step-kw-to-num)]
(inverted (dec (step-kw-to-num step)))))

(defn inc-step [step]
(let [inverted (map-invert step-kw-to-num)]
(inverted (inc (step-kw-to-num step)))))

(defn get-status [cofx]
(assoc cofx :status (rand-nth statuses/data)))

(defn create-account! [password]
(status/create-account
password
#(re-frame/dispatch [:accounts.create.callback/create-account-success (types/json->clj %) password])))
(defn create-account! [{:keys [id password]}]
(if id
(status/import-onboarding-account
id
password
#(re-frame/dispatch [:accounts.create.callback/create-account-success (types/json->clj %) password]))
(status/create-account
password
#(re-frame/dispatch [:accounts.create.callback/create-account-success (types/json->clj %) password]))))

;;;; Handlers
(defn create-account
[{:keys [db random-guid-generator] :as cofx}]
(fx/merge
cofx
{:db (-> db
(update :accounts/create assoc
:step :account-creating
:error nil)
(assoc :node/on-ready :create-account
:accounts/new-installation-id (random-guid-generator)))}
(node/initialize nil)))
[{:keys [db] :as cofx}]
(if (:intro-wizard db)
(fx/merge
cofx
{:accounts.create/create-account {:id (get-in db [:intro-wizard :selected-id])
:password (or (get-in db [:accounts/create :password])
(get-in db [:intro-wizard :key-code]))}})
(fx/merge
cofx
{:db (-> db
(update :accounts/create assoc
:id (get-in db [:intro-wizard :selected-id])
:password (or (get-in db [:accounts/create :password])
(get-in db [:intro-wizard :key-code]))
:step :account-creating
:error nil)
(assoc :node/on-ready :create-account
:accounts/new-installation-id (random/guid)))}
(node/initialize nil))))

(fx/defn add-account
"Takes db and new account, creates map of effects describing adding account to database and realm"
@@ -59,44 +91,6 @@
{:db (assoc-in db [:accounts/accounts address] enriched-account)
:data-store/base-tx [(accounts-store/save-account-tx enriched-account)]}))

(fx/defn on-account-created
[{:keys [signing-phrase
status
db] :as cofx}
{:keys [pubkey address mnemonic installation-id
keycard-instance-uid keycard-key-uid keycard-pairing keycard-paired-on]}
password
{:keys [seed-backed-up? login? new-account?] :or {login? true}}]
(let [normalized-address (utils.hex/normalize-hex address)
account {:public-key pubkey
:installation-id (or installation-id (get-in db [:accounts/new-installation-id]))
:address normalized-address
:name (gfycat/generate-gfy pubkey)
:status status
:signed-up? true
:desktop-notifications? false
:photo-path (identicon/identicon pubkey)
:signing-phrase signing-phrase
:seed-backed-up? seed-backed-up?
:mnemonic mnemonic
:keycard-instance-uid keycard-instance-uid
:keycard-key-uid keycard-key-uid
:keycard-pairing keycard-pairing
:keycard-paired-on keycard-paired-on
:settings (constants/default-account-settings)
:syncing-on-mobile-network? false
:remember-syncing-choice? false
:new-account? new-account?}]
(log/debug "account-created")
(when-not (string/blank? pubkey)
(fx/merge cofx
{:db (assoc db :accounts/login {:address normalized-address
:password password
:processing true})}
(add-account account)
(when login?
(accounts.login/user-login true))))))

(defn reset-account-creation [{db :db}]
{:db (update db :accounts/create assoc
:step :enter-password
@@ -145,6 +139,179 @@
(dissoc :password :password-confirm :name :error)))}
(navigation/navigate-to-cofx :create-account nil)))

(fx/defn intro-wizard
{:events [:accounts.create.ui/intro-wizard]}
[{:keys [db] :as cofx}]
(fx/merge {:db (assoc db :intro-wizard {:step :generate-key
:weak-password? true
:encrypt-with-password? true}
:accounts/new-installation-id (random/guid))}
(navigation/navigate-to-cofx :intro-wizard nil)))

(fx/defn intro-step-back
{:events [:intro-wizard/step-back-pressed]}
[{:keys [db] :as cofx}]
(let [step (get-in db [:intro-wizard :step])]
(if (not= :generate-key step)
(fx/merge {:db (cond-> (assoc-in db [:intro-wizard :step] (dec-step step))
(#{:create-code :confirm-code} step)
(update :intro-wizard assoc :weak-password? true :key-code nil)
(= step :confirm-code)
(assoc-in [:intro-wizard :confirm-failure?] false))}
(navigation/navigate-to-cofx :intro-wizard nil))

(fx/merge {:db (dissoc db :intro-wizard)}
(navigation/navigate-to-clean :intro nil)))))

(fx/defn exit-wizard [{:keys [db] :as cofx}]
(fx/merge {:db (dissoc db :intro-wizard)
:notifications/request-notifications-permissions nil}
(navigation/navigate-to-cofx :home nil)))

(fx/defn init-key-generation [{:keys [db] :as cofx}]
(fx/merge
{:db (-> db
(assoc-in [:intro-wizard :generating-keys?] true)
(assoc :node/on-ready :start-onboarding))}
(node/initialize nil)))

(fx/defn on-confirm-failure [{:keys [db] :as cofx}]
(do
(utils/vibrate)
{:db (assoc-in db [:intro-wizard :confirm-failure?] true)}))

(defn confirm-failure? [db]
(let [step (get-in db [:intro-wizard :step])]
(and (= step :confirm-code)
(not (:accounts/login db))
(get-in db [:intro-wizard :encrypt-with-password?])
(not= (get-in db [:intro-wizard :stored-key-code]) (get-in db [:intro-wizard :key-code])))))

(fx/defn store-key-code [{:keys [db] :as cofx}]
(let [key-code (get-in db [:intro-wizard :key-code])]
{:db (update db :intro-wizard
assoc :stored-key-code key-code
:key-code nil
:step :confirm-code)}))

(fx/defn intro-step-forward
{:events [:intro-wizard/step-forward-pressed]}
[{:keys [db] :as cofx} {:keys [skip?] :as opts}]
(let [step (get-in db [:intro-wizard :step])]
(cond (= step :enable-notifications)
(exit-wizard cofx)

(= step :generate-key)
(init-key-generation cofx)

(confirm-failure? db)
(on-confirm-failure cofx)

(= step :create-code)
(store-key-code cofx)

:else (fx/merge {:db (assoc-in db [:intro-wizard :step]
(inc-step step))}
(when (and (= step :confirm-code)
(not (:accounts/login db)))
(create-account cofx))))))

(fx/defn on-account-created
[{:keys [signing-phrase
status
db] :as cofx}
{:keys [pubkey address mnemonic installation-id
keycard-instance-uid keycard-key-uid keycard-pairing keycard-paired-on] :as result}
password
{:keys [seed-backed-up? login? new-account?] :or {login? true}}]
(let [normalized-address (utils.hex/normalize-hex address)
account {:public-key pubkey
:installation-id (or installation-id (get-in db [:accounts/new-installation-id]))
:address normalized-address
:name (gfycat/generate-gfy pubkey)
:status status
:signed-up? true
:desktop-notifications? false
:photo-path (identicon/identicon pubkey)
:signing-phrase signing-phrase
:seed-backed-up? seed-backed-up?
:mnemonic mnemonic
:keycard-instance-uid keycard-instance-uid
:keycard-key-uid keycard-key-uid
:keycard-pairing keycard-pairing
:keycard-paired-on keycard-paired-on
:settings (constants/default-account-settings)
:syncing-on-mobile-network? false
:remember-syncing-choice? false
:new-account? new-account?}]
(when-not (string/blank? pubkey)
(fx/merge cofx
{:db (assoc db :accounts/login {:address normalized-address
:password password
:processing true})}
(add-account account)
(when login?
(accounts.login/user-login true))
(when (:intro-wizard db)
(intro-step-forward {}))))))

(re-frame/reg-fx
:intro-wizard/start-onboarding
(fn [{:keys [n mnemonic-length]}]
(status/start-onboarding n mnemonic-length
#(re-frame/dispatch [:intro-wizard/on-keys-generated (types/json->clj %)]))))

(fx/defn on-keys-generated
{:events [:intro-wizard/on-keys-generated]}
[{:keys [db] :as cofx} result]
(fx/merge
{:db (update db :intro-wizard
(fn [data]
(-> data
(dissoc :generating-keys?)
(assoc :accounts (:accounts result)
:selected-storage-type :default
:selected-id (-> result :accounts first :id)
:step :choose-key))))}
(navigation/navigate-to-cofx :intro-wizard nil)))

(fx/defn on-key-selected
{:events [:intro-wizard/on-key-selected]}
[{:keys [db] :as cofx} id]
{:db (assoc-in db [:intro-wizard :selected-id] id)})

(fx/defn on-key-storage-selected
{:events [:intro-wizard/on-key-storage-selected]}
[{:keys [db] :as cofx} storage-type]
{:db (assoc-in db [:intro-wizard :selected-storage-type] storage-type)})

(fx/defn on-encrypt-with-password-pressed
{:events [:intro-wizard/on-encrypt-with-password-pressed]}
[{:keys [db] :as cofx}]
{:db (assoc-in db [:intro-wizard :encrypt-with-password?] true)})

(fx/defn on-learn-more-pressed
{:events [:intro-wizard/on-learn-more-pressed]}
[{:keys [db] :as cofx}]
{:db (assoc-in db [:intro-wizard :show-learn-more?] true)})

(defn get-new-key-code [current-code sym encrypt-with-password?]
(cond (or (= sym :remove) (= sym "Backspace"))
(subs current-code 0 (dec (count current-code)))
(and (not encrypt-with-password?) (= (count current-code) 6))
current-code
(= (count sym) 1)
(str current-code sym)
:else current-code))

(fx/defn code-symbol-pressed
{:events [:intro-wizard/code-symbol-pressed]}
[{:keys [db] :as cofx} new-key-code]
(let [encrypt-with-password? (get-in db [:intro-wizard :encrypt-with-password?])]
{:db (update db :intro-wizard assoc :key-code new-key-code
:confirm-failure? false
:weak-password? (< (count new-key-code) 6))}))

;;;; COFX

(re-frame/reg-cofx

+ 14
- 13
src/status_im/init/core.cljs View File

@@ -166,6 +166,7 @@
:keys [accounts/accounts accounts/create networks/networks network
network-status peers-count peers-summary view-id navigation-stack
mailserver/mailservers
intro-wizard
desktop/desktop hardwallet custom-fleets supported-biometric-auth
device-UUID semaphores accounts/login]
:node/keys [status on-ready]
@@ -177,6 +178,7 @@
:view-id view-id
:navigation-stack navigation-stack
:node/status status
:intro-wizard intro-wizard
:node/on-ready on-ready
:accounts/create create
:desktop/desktop (merge desktop (:desktop/desktop app-db))
@@ -201,20 +203,19 @@
(= view-id :create-account)
(assoc-in [:accounts/create :step] :enter-name))}))

(defn login-only-events [cofx address stored-pns]
(defn login-only-events [{:keys [db] :as cofx} address stored-pns]
(fx/merge cofx
(cond->
{:notifications/request-notifications-permissions nil}

platform/ios?
;; on ios navigation state might be not initialized yet when
;; navigate-to call happens.
;; That's why it should be delayed a bit.
;; TODO(rasom): revisit this later and find better solution
(assoc :dispatch-later
[{:ms 1
:dispatch [:navigate-to :home]}]))
(when-not platform/ios?
(when-not (:intro-wizard db)
(cond-> {:notifications/request-notifications-permissions nil}
platform/ios?
;; on ios navigation state might be not initialized yet when
;; navigate-to call happens.
;; That's why it should be delayed a bit.
;; TODO(rasom): revisit this later and find better solution
(assoc :dispatch-later
[{:ms 1
:dispatch [:navigate-to :home]}])))
(when-not (or (:intro-wizard db) platform/ios?)
(navigation/navigate-to-cofx :home nil))
(notifications/process-stored-event address stored-pns)
(when platform/desktop?

+ 6
- 0
src/status_im/native_module/core.cljs View File

@@ -22,6 +22,12 @@
(defn recover-account [passphrase password callback]
(native-module/recover-account passphrase password callback))

(defn start-onboarding [n mnemonic-length callback]
(native-module/start-onboarding n mnemonic-length callback))

(defn import-onboarding-account [id password callback]
(native-module/import-onboarding-account id password callback))

(defn login [address password callback]
(native-module/login address password callback))


+ 8
- 0
src/status_im/native_module/impl/module.cljs View File

@@ -61,6 +61,14 @@
(when (and @node-started (status))
(.recoverAccount (status) passphrase password on-result)))

(defn start-onboarding [n mnemonic-length on-result]
(when (and @node-started (status))
(.startOnboarding (status) n mnemonic-length on-result)))

(defn import-onboarding-account [id password on-result]
(when (and @node-started (status))
(.importOnboardingAccount (status) id password on-result)))

(defn login [address password on-result]
(when (and @node-started (status))
(.login (status) address password on-result)))

+ 4
- 0
src/status_im/react_native/resources.cljs View File

@@ -6,6 +6,10 @@
:empty-recent (js-require/js-require "./resources/images/ui/empty-recent.png")
:analytics-image (js-require/js-require "./resources/images/ui/analytics-image.png")
:welcome-image (js-require/js-require "./resources/images/ui/welcome-image.png")
:intro1 (js-require/js-require "./resources/images/ui/intro1.png")
:intro2 (js-require/js-require "./resources/images/ui/intro2.png")
:intro3 (js-require/js-require "./resources/images/ui/intro3.png")
:sample-key (js-require/js-require "./resources/images/ui/sample-key.png")
:lock (js-require/js-require "./resources/images/ui/lock.png")
:tribute-to-talk (js-require/js-require "./resources/images/ui/tribute-to-talk.png")
:wallet-welcome (js-require/js-require "./resources/images/ui/wallet-welcome.png")

+ 5
- 2
src/status_im/signals/core.cljs View File

@@ -37,14 +37,17 @@
[address password (:realm-error db)]}))
:create-account
(fn [_]
{:accounts.create/create-account (:password create)})
{:accounts.create/create-account (select-keys create [:id :password])})
:recover-account
(fn [{:keys [db]}]
(let [{:keys [password passphrase]} (:accounts/recover db)]
{:accounts.recover/recover-account
[(security/mask-data passphrase) password]}))
:create-keycard-account
(hardwallet/create-keycard-account)))))
(hardwallet/create-keycard-account)
:start-onboarding
(fn []
{:intro-wizard/start-onboarding {:n 5 :mnemonic-length 12}})))))

(fx/defn status-node-stopped
[{db :db}]

+ 3
- 0
src/status_im/subs.cljs View File

@@ -178,6 +178,9 @@
(reg-root-key-sub :signing/sign :signing/sign)
(reg-root-key-sub :signing/edit-fee :signing/edit-fee)

;;intro-wizard
(reg-root-key-sub :intro-wizard :intro-wizard)

;;GENERAL ==============================================================================================================

(re-frame/reg-sub

+ 11
- 9
src/status_im/ui/components/typography.cljs View File

@@ -70,15 +70,17 @@
(assoc style :font-family default-font-family)
(-> style
(assoc :font-family
(str default-font-family "-"
(case font-weight
"400" (when-not (= font-style :italic)
"Regular")
"500" "Medium"
"600" "SemiBold"
"700" "Bold")
(when (= font-style :italic)
"Italic")))
(if (= (:font-family style) "monospace")
(if platform/ios? "Menlo-Regular" "monospace")
(str default-font-family "-"
(case font-weight
"400" (when-not (= font-style :italic)
"Regular")
"500" "Medium"
"600" "SemiBold"
"700" "Bold")
(when (= font-style :italic)
"Italic"))))
(dissoc :font-weight :font-style)))))

(defn get-nested-style

+ 6
- 0
src/status_im/ui/screens/about_app/styles.cljs View File

@@ -17,3 +17,9 @@

(def about-title-text
{:font-size 20})

(def learn-more-title
{:typography :title-bold})

(def learn-more-text
{:color colors/gray})

+ 17
- 1
src/status_im/ui/screens/about_app/views.cljs View File

@@ -1,11 +1,14 @@
(ns status-im.ui.screens.about-app.views
(:require-macros [status-im.utils.views :as views])
(:require-macros [status-im.utils.views :as views]
[status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.screens.about-app.styles :as styles]
[status-im.i18n :as i18n]
[status-im.transport.utils :as transport.utils]
[status-im.ui.components.colors :as colors]
[status-im.ui.screens.profile.components.views :as profile.components]
[re-frame.core :as re-frame]))

@@ -20,6 +23,19 @@
nil
4))

(defview learn-more-sheet []
(letsubs [{:keys [title content]} [:bottom-sheet/options]]
[react/view {:style {:padding 16}}
[react/view {:style {:align-items :center :flex-direction :row :margin-bottom 16}}
[vector-icons/icon :main-icons/info {:color colors/blue
:container-style {:margin-right 13}}]
[react/text {:style styles/learn-more-title} title]]
[react/text {:style styles/learn-more-text} content]]))

(def learn-more
{:content learn-more-sheet
:content-height 180})

(defn peer-view [{:keys [enode]}]
(let [[enode-id ip-address port] (transport.utils/extract-url-components enode)]
^{:key enode}

+ 88
- 16
src/status_im/ui/screens/intro/styles.cljs View File

@@ -4,30 +4,102 @@

(def intro-view
{:flex 1
:padding-horizontal 30})
:justify-content :flex-end
:padding-horizontal 30
:margin-bottom 12})

(def intro-logo-container
{:flex 1
:align-items :center
{:align-items :center
:justify-content :center})

(def intro-logo
{:size 111})
(defn dot-selector [n]
(let [diameter 6
interval 10]
{:flex-direction :row
:justify-content :space-between
:align-items :center
:height diameter
:width (+ diameter (* (+ diameter interval)
(dec n)))}))

(defstyle intro-text
{:text-align :center
:font-weight "700"
:font-size 24})
(defn dot [color selected?]
{:background-color (if selected?
color
(colors/alpha color 0.2))
:width 6
:height 6
:border-radius 3})

(def intro-text-description
{:margin-top 8
:margin-bottom 16
(def welcome-image-container
{:align-items :center
:margin-top 42})

(def wizard-title
{:typography :header
:text-align :center
:color colors/gray})
:margin-bottom 16})

(def wizard-text
{:color colors/gray
:text-align :center})

(def welcome-text
{:typography :header
:margin-top 32
:text-align :center})

(def welcome-text-bottom-note
{:typography :caption
:color colors/gray
:text-align :center})

(defn list-item [selected?]
{:flex-direction :row
:align-items :center
:padding-left 16
:padding-right 10
:background-color (if selected? colors/blue-light colors/white)
:padding-vertical 10})

(def account-image
{:width 40
:height 40
:border-radius 20
:border-width 1
:border-color (colors/alpha colors/black 0.1)})

(def welcome-text-description
{:margin-top 8
:text-align :center
:margin-horizontal 32
:color colors/gray})

(def intro-logo
{:size 111})

(defn password-text-input [width]
{:typography :header
:width width})

(def buttons-container
{:align-items :center})
{:align-items :center
:margin-top 32})

(def bottom-button
{:padding-horizontal 24
:justify-content :center
:align-items :center
:flex-direction :row})

(def bottom-button-container
{:margin-bottom 6
:margin-top 38})
{:margin-bottom 24
:margin-top 16})

(def bottom-arrow
{:flex-direction :row
:justify-content :flex-end
:align-self :stretch
:padding-top 16
:border-top-width 1
:border-top-color colors/gray-lighter
:margin-right 20})

+ 289
- 20
src/status_im/ui/screens/intro/views.cljs View File

@@ -2,28 +2,297 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[re-frame.core :as re-frame]
[status-im.react-native.resources :as resources]
[status-im.privacy-policy.core :as privacy-policy]
[status-im.accounts.create.core :refer [step-kw-to-num]]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.utils.identicon :as identicon]
[status-im.ui.components.radio :as radio]
[taoensso.timbre :as log]
[status-im.utils.gfycat.core :as gfy]
[status-im.ui.components.colors :as colors]
[reagent.core :as r]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.screens.intro.styles :as styles]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.i18n :as i18n]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.screens.privacy-policy.views :as privacy-policy]))
[status-im.ui.components.status-bar.view :as status-bar]))

(defn dots-selector [{:keys [on-press n selected color]}]
[react/view {:style (styles/dot-selector n)}
(doall
(for [i (range n)]
^{:key i}
[react/view {:style (styles/dot color (selected i))}]))])

(defn intro-viewer [slides window-width]
(let [margin 24
view-width (- window-width (* 2 margin))
scroll-x (r/atom 0)
scroll-view-ref (atom nil)
max-width (* view-width (dec (count slides)))]
(fn []
[react/view {:style {:margin-horizontal 32
:align-items :center
:justify-content :flex-end}}
[react/scroll-view {:horizontal true
:paging-enabled true
:ref #(reset! scroll-view-ref %)
:shows-vertical-scroll-indicator false
:shows-horizontal-scroll-indicator false
:pinch-gesture-enabled false
:on-scroll #(let [x (.-nativeEvent.contentOffset.x %)]
(reset! scroll-x x))
:style {:width view-width
:margin-vertical 32}}
(for [s slides]
^{:key (:title s)}
[react/view {:style {:width view-width
:padding-horizontal 16}}
[react/view {:style styles/intro-logo-container}
[components.common/image-contain
{:container-style {}}
{:image (:image s) :width view-width :height view-width}]]
[react/i18n-text {:style styles/wizard-title :key (:title s)}]
[react/i18n-text {:style styles/wizard-text
:key (:text s)}]])]
(let [selected (hash-set (/ @scroll-x view-width))]
[dots-selector {:selected selected :n (count slides)
:color colors/blue}])])))

(defview intro []
[react/view {:style styles/intro-view}
[status-bar/status-bar {:flat? true}]
[react/view {:style styles/intro-logo-container}
[components.common/logo styles/intro-logo]]
[react/i18n-text {:style styles/intro-text
:key :intro-text}]
[react/view
[react/i18n-text {:style styles/intro-text-description
:key :intro-text-description}]]
[react/view styles/buttons-container
[components.common/button {:button-style {:flex-direction :row}
:on-press #(re-frame/dispatch [:accounts.create.ui/create-new-account-button-pressed])
:label (i18n/label :t/create-account)}]
[react/view styles/bottom-button-container
[components.common/button {:on-press #(re-frame/dispatch [:accounts.recover.ui/recover-account-button-pressed])
:label (i18n/label :t/already-have-account)
:background? false}]]
[privacy-policy/privacy-policy-button]]])
(letsubs [window-width [:dimensions/window-width]]
[react/view {:style styles/intro-view}
[status-bar/status-bar {:flat? true}]
[intro-viewer [{:image (:intro1 resources/ui)
:title :intro-title1
:text :intro-text1}
{:image (:intro2 resources/ui)
:title :intro-title2
:text :intro-text2}
{:image (:intro3 resources/ui)
:title :intro-title3
:text :intro-text3}] window-width]
[react/view styles/buttons-container
[components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16)
:on-press #(re-frame/dispatch [:accounts.create.ui/intro-wizard])
:label (i18n/label :t/get-started)}]
[components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 24)
:on-press #(re-frame/dispatch [:accounts.recover.ui/recover-account-button-pressed])
:label (i18n/label :t/access-key)
:background? false}]
[react/nested-text
{:style styles/welcome-text-bottom-note}
(i18n/label :t/intro-privacy-policy-note1)
[{:style (assoc styles/welcome-text-bottom-note :color colors/blue)
:on-press privacy-policy/open-privacy-policy-link!}
(i18n/label :t/intro-privacy-policy-note2)]]]]))

(defn generate-key []
[components.common/image-contain
{:container-style {:margin-horizontal 80}}
{:image (resources/get-image :sample-key)
:width 154 :height 140}])

(defn choose-key [{:keys [accounts selected-id] :as wizard-state} view-height]
[react/scroll-view {:content-container-style {:flex 1
:justify-content :flex-end
;; We have to align top account entry
;; with top key storage entry on the next screen
:margin-bottom (if (< view-height 600)
-20
(/ view-height 12))}}
(for [acc accounts]
(let [selected? (= (:id acc) selected-id)]
^{:key (:pubkey acc)}
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:intro-wizard/on-key-selected (:id acc)])}
[react/view {:style (styles/list-item selected?)}

[react/image {:source {:uri (identicon/identicon (:pubkey acc))}
:style styles/account-image}]
[react/view {:style {:margin-horizontal 16 :flex 1 :justify-content :space-between}}
[react/text {:style (assoc styles/wizard-text :text-align :left
:color colors/black
:font-weight "500")
:number-of-lines 1
:ellipsize-mode :middle}
(gfy/generate-gfy (:pubkey acc))]
[react/text {:style (assoc styles/wizard-text
:text-align :left
:font-family "monospace")
:number-of-lines 1
:ellipsize-mode :middle}
(:pubkey acc)]]
[radio/radio selected?]]]))])

(defn storage-entry [{:keys [type icon title desc]} selected-storage-type]
(let [selected? (= type selected-storage-type)]
[react/view
[react/view {:style {:padding-top 14 :padding-bottom 4}}
[react/text {:style (assoc styles/wizard-text :text-align :left :margin-left 16)}
(i18n/label type)]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:intro-wizard/on-key-storage-selected type])}
[react/view (assoc (styles/list-item selected?)
:align-items :flex-start
:padding-top 20
:padding-bottom 12)
[vector-icons/icon icon {:color (if selected? colors/blue colors/gray)
:width 24 :height 24}]
[react/view {:style {:margin-horizontal 16 :flex 1}}
[react/text {:style (assoc styles/wizard-text :font-weight "500" :color colors/black :text-align :left)}
(i18n/label title)]
[react/view {:style {:min-height 4 :max-height 4}}]
[react/text {:style (assoc styles/wizard-text :text-align :left)}
(i18n/label desc)]]
[radio/radio selected?]]]]))

(defn select-key-storage [{:keys [selected-storage-type] :as wizard-state} view-height]
(let [storage-types [{:type :default
:icon :main-icons/mobile
:title :this-device
:desc :this-device-desc}
{:type :advanced
:icon :main-icons/keycard-logo
:title :keycard
:desc :keycard-desc}]]
[react/view {:style {:flex 1
:justify-content :flex-end
;; We have to align top storage entry
;; with top account entry on the previous screen
:margin-bottom (+ (- 300 232) (if (< view-height 600)
-20
(/ view-height 12)))}}
[storage-entry (first storage-types) selected-storage-type]
[react/view {:style {:min-height 16 :max-height 16}}]
[storage-entry (second storage-types) selected-storage-type]]))

(defn password-container [confirm-failure? view-width]
(let [horizontal-margin 16]
[react/view {:style {:flex 1
:justify-content :space-between
:align-items :center :margin-horizontal horizontal-margin}}
[react/view {:style {:justify-content :center :flex 1}}
[react/text {:style (assoc styles/wizard-text :color colors/red
:margin-bottom 16)}
(if confirm-failure? (i18n/label :t/password_error1) " ")]

[react/text-input {:secure-text-entry true
:auto-focus true
:text-align :center
:placeholder ""
:style (styles/password-text-input (- view-width (* 2 horizontal-margin)))
:on-change-text #(re-frame/dispatch [:intro-wizard/code-symbol-pressed %])}]]
[react/text {:style (assoc styles/wizard-text :margin-bottom 16)} (i18n/label :t/password-description)]]))

(defn create-code [{:keys [confirm-failure?] :as wizard-state} view-width]
[password-container confirm-failure? view-width])

(defn confirm-code [{:keys [confirm-failure?] :as wizard-state} view-width]
[password-container confirm-failure? view-width])

(defn enable-fingerprint []
[vector-icons/icon :main-icons/fingerprint
{:container-style {:align-items :center
:justify-content :center}
:width 76 :height 84}])

(defn enable-notifications []
[vector-icons/icon :main-icons/bell {:container-style {:align-items :center
:justify-content :center}
:width 66 :height 64}])

(defn bottom-bar [{:keys [step generating-keys? weak-password? encrypt-with-password?] :as wizard-state}]
[react/view {:style {:margin-bottom (if (or (#{:choose-key :select-key-storage} step)
(and (#{:create-code :confirm-code} step)
encrypt-with-password?))
20
32)
:align-items :center}}
(cond generating-keys?
[react/activity-indicator {:animating true
:size :large}]
(#{:generate-key :enable-fingerprint :enable-notifications} step)
(let [label-kw (case step
:generate-key :generate-a-key
:enable-fingerprint :intro-wizard-title6
:enable-notifications :intro-wizard-title7)]
[components.common/button {:button-style styles/bottom-button
:on-press #(re-frame/dispatch
[:intro-wizard/step-forward-pressed])
:label (i18n/label label-kw)}])
(and (#{:create-code :confirm-code} step)
(not encrypt-with-password?))
[components.common/button {:button-style styles/bottom-button
:label (i18n/label :t/encrypt-with-password)
:on-press #(re-frame/dispatch [:intro-wizard/on-encrypt-with-password-pressed])
:background? false}]

:else
[react/view {:style styles/bottom-arrow}
[components.common/bottom-button {:on-press #(re-frame/dispatch
[:intro-wizard/step-forward-pressed])
:disabled? (and (= step :create-code) weak-password?)
:forward? true}]])
(when (#{:enable-fingerprint :enable-notifications} step)
[components.common/button {:button-style (assoc styles/bottom-button :margin-top 20)
:label (i18n/label :t/maybe-later)
:on-press #(re-frame/dispatch [:intro-wizard/step-forward-pressed {:skip? true}])
:background? false}])
(when (= :generate-key step)
[react/text {:style (assoc styles/wizard-text :margin-top 20)}
(i18n/label (if generating-keys? :t/generating-keys
:t/this-will-take-few-seconds))])])

(defn top-bar [{:keys [step encrypt-with-password?]}]
(let [hide-subtitle? (or (= step :confirm-code)
(and (#{:create-code :confirm-code} step) encrypt-with-password?))]
[react/view {:style {:margin-top 16
:margin-horizontal 32}}

[react/text {:style (cond-> styles/wizard-title
hide-subtitle?
(assoc :margin-bottom 0))}
(i18n/label (keyword (str "intro-wizard-title" (when (and (#{:create-code :confirm-code} step) encrypt-with-password?)
"-alt") (step-kw-to-num step))))]
(cond (#{:choose-key :select-key-storage} step)
; Use nested text for the "Learn more" link
[react/nested-text {:style styles/wizard-text}
(str (i18n/label (keyword (str "intro-wizard-text" (step-kw-to-num step)))) " ")
[{:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet :learn-more
{:title (i18n/label (if (= step :choose-key) :t/about-names-title :t/about-key-storage-title))
:content (i18n/label (if (= step :choose-key) :t/about-names-content :t/about-key-storage-content))}])
:style {:color colors/blue}}
(i18n/label :learn-more)]]
(not hide-subtitle?)
[react/text {:style styles/wizard-text}
(i18n/label (keyword (str "intro-wizard-text" (step-kw-to-num step))))]
:else nil)]))

(defview wizard []
(letsubs [{:keys [step generating-keys?] :as wizard-state} [:intro-wizard]
{view-height :height view-width :width} [:dimensions/window]]
[react/keyboard-avoiding-view {:style {:flex 1}}
[toolbar/toolbar
{:style {:border-bottom-width 0
:margin-top 0}}
(when-not (#{:enable-fingerprint :enable-notifications} step)
(toolbar/nav-button
(actions/back #(re-frame/dispatch
[:intro-wizard/step-back-pressed]))))
nil]
[react/view {:style {:flex 1
:justify-content :space-between}}
[top-bar wizard-state]
(case step
:generate-key [generate-key]
:choose-key [choose-key wizard-state view-height]
:select-key-storage [select-key-storage wizard-state view-height]
:create-code [create-code wizard-state view-width]
:confirm-code [confirm-code wizard-state view-width]
:enable-fingerprint [enable-fingerprint]
:enable-notifications [enable-notifications]
nil nil)
[bottom-bar wizard-state]]]))

+ 1
- 1
src/status_im/ui/screens/profile/user/views.cljs View File

@@ -253,7 +253,7 @@
[react/view {:style styles/advanced-button-container-background}
[react/view {:style styles/advanced-button-row}
[react/text {:style styles/advanced-button-label}
(i18n/label :t/wallet-advanced)]
(i18n/label :t/advanced)]
[icons/icon (if advanced? :main-icons/dropdown-up :main-icons/dropdown) {:color colors/blue}]]]]]
(when advanced?
[advanced-settings params on-show supported-biometric-auth])]))

+ 2
- 0
src/status_im/ui/screens/routing/intro_login_stack.cljs View File

@@ -8,6 +8,7 @@
:recover
:accounts
:intro
:intro-wizard
:hardwallet-authentication-method
:hardwallet-connect
:enter-pin-login
@@ -52,6 +53,7 @@
(-> (login-stack :intro)
(update :screens conj
:intro
:intro-wizard
:keycard-onboarding-intro
:keycard-onboarding-start
:keycard-onboarding-puk-code

+ 1
- 0
src/status_im/ui/screens/routing/screens.cljs View File

@@ -71,6 +71,7 @@
:recover recover/recover
:accounts accounts/accounts
:intro intro/intro
:intro-wizard intro/wizard
:hardwallet-authentication-method hardwallet.authentication/hardwallet-authentication-method
:hardwallet-connect hardwallet.connect/hardwallet-connect
:hardwallet-connect-settings hardwallet.connect/hardwallet-connect

+ 4
- 0
src/status_im/ui/screens/views.cljs View File

@@ -3,6 +3,7 @@
(:require [re-frame.core :refer [dispatch]]
[status-im.utils.platform :refer [android?]]
[status-im.utils.universal-links.core :as utils.universal-links]
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.components.react :as react]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.utils.navigation :as navigation]
@@ -46,6 +47,9 @@
(= view :public-chat-actions)
(merge home.sheet/public-chat-actions)

(= view :learn-more)
(merge about-app/learn-more)

(= view :private-chat-actions)
(merge home.sheet/private-chat-actions)


+ 1
- 1
src/status_im/ui/screens/wallet/send/views.cljs View File

@@ -87,4 +87,4 @@
:scroll scroll
:chain chain
:all-tokens all-tokens
:network-status network-status}]))
:network-status network-status}]))

+ 3
- 0
src/status_im/utils/utils.cljs View File

@@ -19,6 +19,9 @@
(when on-dismiss
(clj->js {:cancelable false})))))

(defn vibrate []
#_(.vibrate (.-Vibration rn-dependencies/react-native)))

(re-frame/reg-fx
:utils/show-popup
(fn [{:keys [title content on-dismiss]}]

+ 12
- 1
test/appium/tests/atomic/account_management/test_create_account.py View File

@@ -41,7 +41,18 @@ class TestCreateAccount(SingleDeviceTestCase):
if sign_in.ok_button.is_element_displayed():
sign_in.ok_button.click()
sign_in.other_accounts_button.click()
sign_in.create_user()
sign_in.create_account_button.click()
sign_in.password_input.set_value(common_password)
sign_in.next_button.click()
sign_in.confirm_password_input.set_value(common_password)
sign_in.next_button.click()

sign_in.element_by_text_part('Display name').wait_for_element(60)
username = 'user_%s' % get_current_time()
sign_in.name_input.set_value(username)

sign_in.next_button.click()
sign_in.get_started_button.click()
if sign_in.get_public_key() == public_key:
pytest.fail('New account was not created')


+ 4
- 3
test/appium/tests/atomic/account_management/test_sign_in.py View File

@@ -13,12 +13,13 @@ class TestSignIn(SingleDeviceTestCase):
@marks.critical
def test_login_with_new_account(self):
sign_in = SignInView(self.driver)
username = 'test_user'
sign_in.create_user(username=username)
sign_in.create_user()
profile = sign_in.profile_button.click()
default_username = profile.default_username_text.text
self.driver.close_app()
self.driver.launch_app()
sign_in.accept_agreements()
if not sign_in.element_by_text(username).is_element_displayed():
if not sign_in.element_by_text(default_username).is_element_displayed():
self.errors.append('Username is not shown while login')
sign_in.sign_in()
if not sign_in.home_button.is_element_displayed():

+ 1
- 1
test/appium/tests/atomic/chats/test_chats_management.py View File

@@ -230,7 +230,7 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase):
username_1 = 'user_%s' % get_current_time()
message_before_block_1, message_before_block_2 = "Before block from %s" % device_1.driver.number, "Before block from %s" % device_2.driver.number
message_after_block_2 = "After block from %s" % device_2.driver.number
home_1, home_2 = device_1.create_user(username=username_1), device_2.recover_access(basic_user['passphrase'])
home_1, home_2 = device_1.create_user(), device_2.recover_access(basic_user['passphrase'])

# device 1, device 2: join to public chat and send several messages
chat_name = device_1.get_public_chat_name()

+ 1
- 2
test/appium/tests/atomic/chats/test_public.py View File

@@ -15,8 +15,7 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
def test_public_chat_messaging(self):
self.create_drivers(2)
device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
username_1, username_2 = 'user_1', 'user_2'
home_1, home_2 = device_1.create_user(username=username_1), device_2.create_user(username=username_2)
home_1, home_2 = device_1.create_user(), device_2.create_user()
profile_1 = home_1.profile_button.click()
default_username_1 = profile_1.default_username_text.text
profile_1.home_button.click()

+ 52
- 12
test/appium/views/sign_in_view.py View File

@@ -6,20 +6,32 @@ from views.base_view import BaseView


class AccountButton(BaseButton):

def __init__(self, driver):
super(AccountButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[contains(@text,'0x')]")


class PasswordInput(BaseEditBox):

def __init__(self, driver):
super(PasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Password']"
"/following-sibling::android.view.ViewGroup/android.widget.EditText")


class CreatePasswordInput(BaseEditBox):
def __init__(self, driver):
super(CreatePasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Create a password']"
"/following-sibling::android.widget.EditText")


class ConfirmYourPasswordInput(BaseEditBox):
def __init__(self, driver):
super(ConfirmYourPasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Confirm your password']"
"/following-sibling::android.widget.EditText")


class SignInButton(BaseButton):

def __init__(self, driver):
@@ -48,12 +60,30 @@ class CreateAccountButton(BaseButton):
self.locator = self.Locator.xpath_selector("//*[@text='Create account' or @text='Create new account']")


class GenerateKeyButton(BaseButton):
def __init__(self, driver):
super(GenerateKeyButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[@text='Generate a key']")


class IHaveAccountButton(RecoverAccessButton):
def __init__(self, driver):
super(IHaveAccountButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[@text='I already have an account']")


class AccessKeyButton(RecoverAccessButton):
def __init__(self, driver):
super(AccessKeyButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[@text='Access key']")


class MaybeLaterButton(BaseButton):
def __init__(self, driver):
super(MaybeLaterButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[@text='Maybe later']")


class AddExistingAccountButton(RecoverAccessButton):
def __init__(self, driver):
super(AddExistingAccountButton, self).__init__(driver)
@@ -106,25 +136,35 @@ class SignInView(BaseView):
# new design
self.create_account_button = CreateAccountButton(self.driver)
self.i_have_account_button = IHaveAccountButton(self.driver)
self.access_key_button = AccessKeyButton(self.driver)
self.generate_key_button = GenerateKeyButton(self.driver)
self.add_existing_account_button = AddExistingAccountButton(self.driver)
self.confirm_password_input = ConfirmPasswordInput(self.driver)
self.create_password_input = CreatePasswordInput(self.driver)
self.confirm_your_password_input = ConfirmYourPasswordInput(self.driver)
self.maybe_later_button = MaybeLaterButton(self.driver)
self.name_input = NameInput(self.driver)
self.other_accounts_button = OtherAccountsButton(self.driver)
self.privacy_policy_link = PrivacyPolicyLink(self.driver)

def create_user(self, username: str = '', password=common_password):
self.create_account_button.click()
self.password_input.set_value(password)
def create_user(self, password=common_password):
self.get_started_button.click()
self.generate_key_button.click()
self.next_button.click()
self.next_button.click()
self.create_password_input.set_value(password)
self.next_button.click()
self.confirm_password_input.set_value(password)
self.confirm_your_password_input.set_value(password)
self.next_button.click()
self.maybe_later_button.click()
self.maybe_later_button.click()

self.element_by_text_part('Display name').wait_for_element(60)
username = username if username else 'user_%s' % get_current_time()
self.name_input.set_value(username)
# self.element_by_text_part('Display name').wait_for_element(60)
# username = username if username else 'user_%s' % get_current_time()
# self.name_input.set_value(username)

self.next_button.click()
self.get_started_button.click()
# self.next_button.click()
# self.get_started_button.click()
return self.get_home_view()

def recover_access(self, passphrase: str, password: str = common_password):
@@ -132,7 +172,7 @@ class SignInView(BaseView):
self.other_accounts_button.click()
recover_access_view = self.add_existing_account_button.click()
else:
recover_access_view = self.i_have_account_button.click()
recover_access_view = self.access_key_button.click()
recover_access_view.passphrase_input.click()
recover_access_view.passphrase_input.set_value(passphrase)
recover_access_view.password_input.click()

+ 62
- 19
test/cljs/status_im/test/accounts/create/core.cljs View File

@@ -1,5 +1,6 @@
(ns status-im.test.accounts.create.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.utils :as utils]
[status-im.accounts.create.core :as models]))

(deftest on-account-created
@@ -11,23 +12,65 @@
:mnemonic "hello world"}
"password"
true)]
(is (= (:db result)
{:accounts/login {:address "7e92236392a850980d00d0cd2a4b92886bd7fe7b", :password "password", :processing true},
:accounts/accounts {"7e92236392a850980d00d0cd2a4b92886bd7fe7b"
{:address "7e92236392a850980d00d0cd2a4b92886bd7fe7b", :mnemonic "hello world", :signing-phrase "",
:signed-up? true, :name "Dark Woozy Alligatorsnappingturtle", :desktop-notifications? false,
:settings {:wallet {:visible-tokens {:testnet #{:STT :HND},
:mainnet #{:SNT},
:rinkeby #{:MOKSHA :KDO}, :xdai #{}, :poa #{}}}},
:networks nil,
:photo-path "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAADAFBMVEX////YjowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRS2MAAABAHRSTlP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKmfXxgAABnNJREFUeNoBaAaX+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoRUBAbwwC1cAAAAASUVORK5CYII=",
:seed-backed-up? true,
:network "mainnet_rpc",
:public-key "04de2e21f1642ebee03b9aa4bf1936066124cc89967eaf269544c9b90c539fd5c980166a897d06dd4d3732b38116239f63c89395a8d73eac72881fab802010cb56",
:installation-id ""}},
:network "mainnet_rpc",
:node/status :starting}))
(is (= (keys result)
[:db
:node/start
:data-store/base-tx]))))
[:db :accounts.login/clear-web-data :data-store/change-account :data-store/base-tx]))))

(deftest intro-step-back
(testing "Back from choose-key"
(let [db {:intro-wizard {:step :choose-key}}
result (get-in (models/intro-step-back {:db db}) [:db :intro-wizard])]
(is (= result {:step :generate-key}))))

(testing "Back from create-code"
(let [db {:intro-wizard {:step :create-code :key-code "qwerty"}}
result (get-in (models/intro-step-back {:db db}) [:db :intro-wizard])]
(is (= result {:step :select-key-storage :key-code nil :weak-password? true}))))

(testing "Back from confirm-code"
(let [db {:intro-wizard {:step :confirm-code :confirm-failure? true}}
result (get-in (models/intro-step-back {:db db}) [:db :intro-wizard])]
(is (= result {:step :create-code :key-code nil :confirm-failure? false :weak-password? true})))))

(deftest intro-step-forward
(testing "Forward from choose-key"
(let [db {:intro-wizard {:step :choose-key}}
;; In this case intro-step-forward returns fx/merge result which is an fn
;; to be invoked on cofx
result (get-in ((models/intro-step-forward {:db db}) {:db db}) [:db :intro-wizard])]
(is (= result {:step :select-key-storage}))))

(testing "Forward from generate-key"
(let [db {:intro-wizard {:step :generate-key}}
result ((models/intro-step-forward {:db db}) {:db db})]
(is (= (select-keys (:db result) [:intro-wizard :node/on-ready]) {:intro-wizard {:step :generate-key :generating-keys? true}
:node/on-ready :start-onboarding}))))

(testing "Forward from create-code"
(let [db {:intro-wizard {:step :create-code :key-code "qwerty"}}
result (get-in ((models/intro-step-forward {:db db}) {:db db}) [:db :intro-wizard])]
(is (= result {:step :confirm-code :key-code nil :stored-key-code "qwerty"}))))

(testing "Forward from confirm-code (failure case)"
(with-redefs [utils/vibrate (fn [] "vibrating")]
(let [db {:intro-wizard {:step :confirm-code :key-code "abcdef" :encrypt-with-password? true :stored-key-code "qwerty"}}
result (get-in ((models/intro-step-forward {:db db}) {:db db}) [:db :intro-wizard])]
(is (= result {:step :confirm-code :key-code "abcdef" :confirm-failure? true
:encrypt-with-password? true
:stored-key-code "qwerty"}))))))

(deftest on-keys-generated
(testing "Test merging of generated keys into app-db"
(let [db {:intro-wizard {:step :generate-key :generating-keys true}}
accounts [{:id "0x01"}
{:id "0x02"}
{:id "0x03"}
{:id "0x04"}
{:id "0x05"}]
result (get-in (models/on-keys-generated {:db db} {:accounts accounts}) [:db :intro-wizard])]
(is (= result) {:step :choose-key :accounts accounts :selected-storage-type :default :selected-id (-> accounts first :id)}))))

(deftest get-new-key-code
(testing "Add new character to keycode"
(is (= "abcd" (models/get-new-key-code "abc" "d" true))))
(testing "Remove trailing character from keycode"
(is (= "ab" (models/get-new-key-code "abc" :remove true)))))

+ 3
- 1
test/cljs/status_im/test/runner.cljs View File

@@ -1,5 +1,6 @@
(ns status-im.test.runner
(:require [doo.runner :refer-macros [doo-tests]]
[status-im.test.accounts.create.core]
[status-im.test.accounts.recover.core]
[status-im.test.browser.core]
[status-im.test.browser.permissions]
@@ -81,6 +82,7 @@
(set! goog.DEBUG false)

(doo-tests
'status-im.test.accounts.create.core
'status-im.test.accounts.recover.core
'status-im.test.browser.core
'status-im.test.browser.permissions
@@ -151,4 +153,4 @@
'status-im.test.utils.utils
'status-im.test.wallet.subs
'status-im.test.wallet.transactions
'status-im.test.wallet.transactions.subs)
'status-im.test.wallet.transactions.subs)

+ 1
- 1
translations/el.json View File

@@ -7,7 +7,7 @@
"amount": "Ποσό",
"open": "Άνοιγμα",
"close-app-title": "Προειδοποίηση!",
"wallet-advanced": "Προχωρημένη",
"advanced": "Προχωρημένη",
"members-active": {
"one": "1 μέλος",
"other": "{{count}} μέλη",

+ 39
- 2
translations/en.json View File

@@ -151,6 +151,41 @@
"other": "{{count}} members"
},
"intro-message1": "Welcome to Status!\nTap this message to set your password and get started.",
"intro-title1": "Private, secure communication",
"intro-text1": "An open source platform to securely chat and transact on the Ethereum blockchain",
"intro-title2": "Secure crypto wallet",
"intro-text2": "Your account has been set up. Please dont forget to backup your recovery phrase in your profile",
"intro-title3": "Decentralized apps",
"intro-text3": "Your account has been set up. Please dont forget to backup your recovery phrase in your profile",
"intro-privacy-policy-note1": "Status does not collect, share or sell any personal data. By continuing you agree with the ",
"intro-privacy-policy-note2": "privacy policy",
"intro-wizard-title1": "Get yourself a key first",
"intro-wizard-text1": "Your identity is secure by design. You get a locally generated cryptographic keypair",
"intro-wizard-title2": "Choose a key and name",
"intro-wizard-text2": "This name is your identity in Status. It can’t be changed once you choose one. ",
"intro-wizard-title3": "Select key storage",
"intro-wizard-text3": "Your key is stored locally. There is no copy. Only you have access.",
"intro-wizard-title4": "Create a 6-digit code",
"intro-wizard-title-alt4": "Create a password",
"intro-wizard-text4": "Secure and encrypt your key",
"intro-wizard-title5": "Confirm the code",
"intro-wizard-title-alt5": "Confirm your password",
"intro-wizard-title6": "Enable fingerprint",
"intro-wizard-text6": "Make it easy to sign and send transactions by enabling fingerprint signing",
"intro-wizard-title7": "Enable notifications",
"intro-wizard-text7": "Status will notify you about new messages. You can edit your notification preferences later in settings",
"generate-a-key": "Generate a key",
"generating-keys": "Generating keys...",
"you-will-need-this-code": "You'll need this code to open Status and sign transactions",
"this-device": "This device",
"this-device-desc": "Your key will be encrypted and securely stored",
"keycard-desc": "Android only. You will need to get a Keycard first",
"about-names-title": "About the names",
"about-names-content": "Your identity is secure and private by design. You get a locally generated cryptographic keypair. The name and image are a readable version of this. They are unique. Nobody can pretend to be you. Nobody sees your name unless you provide it.",
"about-key-storage-title": "About key storage",
"about-key-storage-content": "Status will never access your private key. Be sure to backup your Seed phrase. If you lose your phone it is the only way to access your keys.",
"encrypt-with-password": "Encrypt with password",
"maybe-later": "Maybe later",
"not-implemented": "!not implemented",
"new-contact": "New contact",
"datetime-second": {
@@ -249,7 +284,7 @@
"group-chat-admin-added": "*{{member}}* has been made admin",
"group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting",
"agree-by-continuing": "By continuing you agree\n to our ",
"wallet-advanced": "Advanced",
"advanced": "Advanced",
"currency-display-name-sos": "Somalia Shilling",
"currency-display-name-zar": "South Africa Rand",
"offline-messaging": "Mailserver",
@@ -649,7 +684,7 @@
"wallet-asset": "Asset",
"close-app-content": "The app will stop and close. When you reopen it, the selected network will be used",
"logout-app-content": "The account will be logged out. When you log in again, the selected network will be used",
"password-description": "You'll need this password to open the app and confirm transactions.",
"password-description": "At least 6 characters. You'll need this password to open Status and confirm transactions",
"currency-display-name-afn": "Afghanistan Afghani",
"word-n-description": "In order to check if you have backed up your recovery phrase correctly, enter the word #{{number}} above.",
"topic-name-error": "Use only lowercase letters (a to z), numbers & dashes (-). Do not use contact codes",
@@ -704,6 +739,7 @@
"transactions-history": "Transaction history",
"fetching-messages": "Fetching messages... ({{requests-left}} requests left)",
"password_error1": "Passwords don't match.",
"passcode-error": "Passcodes don't match.",
"your-contact-code": "Granting access authorizes this DApp to retrieve your contact code",
"password-placeholder": "At least 6 characters",
"clear-history-action": "Clear",
@@ -919,6 +955,7 @@
"view-my-wallet": "View my wallet",
"share-my-profile": "Share my profile",
"get-started": "Get started",
"access-key": "Access key",
"tribute-to-talk": "Tribute to talk",
"tribute-to-talk-desc": "Monetize your attention by requiring SNT for new people to start a chat",
"tribute-to-talk-set-snt-amount": "Set the amount of SNT required for new people to start a chat",

+ 1
- 1
translations/es_419.json View File

@@ -647,7 +647,7 @@
"wallet": "Billetera",
"wallet-add-asset": "Agregar activo",
"wallet-address-from-clipboard": "Usar la dirección desde el portapapeles",
"wallet-advanced": "Avanzado",
"advanced": "Avanzado",
"wallet-asset": "Activo",
"wallet-assets": "Activos",
"wallet-backup-recovery-description": "Esto te ayudará a mantener tus activos seguros",

+ 1
- 1
translations/fa.json View File

@@ -652,7 +652,7 @@
"wallet": "کیف پول",
"wallet-add-asset": "افزودن دارایی جدید",
"wallet-address-from-clipboard": "از آدرسی که در کلیپ بورد ذخیره کردم استفاده کن",
"wallet-advanced": "پیشرفته",
"advanced": "پیشرفته",
"wallet-asset": "دارایی",
"wallet-assets": "دارایی ها",
"wallet-backup-recovery-description": "این به شما کمک می کند که دارایی خود را امن نگه دارید",

+ 1
- 1
translations/ja.json View File

@@ -7,7 +7,7 @@
"amount": "金額",
"open": "開く",
"close-app-title": "警告!",
"wallet-advanced": "高度な",
"advanced": "高度な",
"members-active": {
"one": "1人がアクティブ",
"other": "{{count}}人がアクティブ",

+ 1
- 1
translations/ko.json View File

@@ -988,7 +988,7 @@
"wallet": "지갑",
"wallet-add-asset": "자산 추가",
"wallet-address-from-clipboard": "클립보드에서 가져오기",
"wallet-advanced": "고급 설정",
"advanced": "고급 설정",
"wallet-asset": "자산",
"wallet-assets": "자산",
"wallet-backup-recovery-description": "자산을 안전하게 보호하세요",

+ 1
- 1
translations/ms.json View File

@@ -701,7 +701,7 @@
"wallet": "Dompet",
"wallet-add-asset": "Tambah aset",
"wallet-address-from-clipboard": "Gunakan Alamat Daripada Papan Klip",
"wallet-advanced": "Lanjutan",
"advanced": "Lanjutan",
"wallet-asset": "Aset",
"wallet-assets": "Aset",
"wallet-backup-recovery-description": "Ini akan membantu anda memastikan aset anda selamat",

+ 1
- 1
translations/ne.json View File

@@ -672,7 +672,7 @@
"wallet": "वालेट",
"wallet-add-asset": "सम्पत्ति थप्नुहोस्",
"wallet-address-from-clipboard": "Gunakan Alamat Daripada Papan Klip",
"wallet-advanced": "Lanjutan",
"advanced": "Lanjutan",
"wallet-asset": "Aset",
"wallet-assets": "Aset",
"wallet-backup-recovery-description": "यसले तपाईंको सम्पत्ती सुरक्षित राख्न तपाईंलाई सहयोग गर्नेछ",

+ 1
- 1
translations/pl.json View File

@@ -631,7 +631,7 @@
"wallet": "Portfel",
"wallet-add-asset": "Dodaj aktywo",
"wallet-address-from-clipboard": "Użyj adresu ze schowka",
"wallet-advanced": "Zaawansowane",
"advanced": "Zaawansowane",
"wallet-asset": "Aktywo",
"wallet-assets": "Aktywa",
"wallet-backup-recovery-description": "Pomoże Ci to zachować bezpieczeństwo Twoich zasobów",

+ 1
- 1
translations/ru.json View File

@@ -669,7 +669,7 @@
"wallet": "Кошелек",
"wallet-add-asset": "Добавить актив",
"wallet-address-from-clipboard": "Использовать Адрес Из Буфера Обмена",
"wallet-advanced": "Дополнительные параметры",
"advanced": "Дополнительные параметры",
"wallet-asset": "Актив",
"wallet-assets": "Активы",
"wallet-backup-recovery-description": "Это поможет сохранить Ваши активы в безопасности",

+ 1
- 1
translations/zh_Hans_CN.json View File

@@ -697,7 +697,7 @@
"wallet": "钱包",
"wallet-add-asset": "添加资产",
"wallet-address-from-clipboard": "使用剪贴板中的地址",
"wallet-advanced": "高级",
"advanced": "高级",
"wallet-asset": "资产",
"wallet-assets": "资产",
"wallet-backup-recovery-description": "这有助于您保护资产安全",

Loading…
Cancel
Save