Onboarding setup wizard

New onboarding e2e tests updates

New onboarding e2e fix 2

Signed-off-by: Vitaliy Vlasov <siphiuel@gmail.com>
This commit is contained in:
Vitaliy Vlasov 2019-05-13 10:58:41 +03:00
parent ac25f6766d
commit e9fd6e1a6b
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
66 changed files with 981 additions and 170 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -660,6 +660,44 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r); 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() { private String createIdentifier() {
return UUID.randomUUID().toString(); return UUID.randomUUID().toString();
} }

View File

@ -335,6 +335,29 @@ RCT_EXPORT_METHOD(recoverAccount:(NSString *)passphrase
callback(@[result]); 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 //////////////////////////////////////////////////////////////////// login
RCT_EXPORT_METHOD(login:(NSString *)address RCT_EXPORT_METHOD(login:(NSString *)address
password:(NSString *)password password:(NSString *)password
@ -449,6 +472,7 @@ RCT_EXPORT_METHOD(setSoftInputMode: (NSInteger) i) {
RCT_EXPORT_METHOD(clearCookies) { RCT_EXPORT_METHOD(clearCookies) {
NSHTTPCookie *cookie; NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) { for (cookie in [storage cookies]) {
[storage deleteCookie:cookie]; [storage deleteCookie:cookie];
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -17,7 +17,8 @@
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]
[status-im.utils.signing-phrase.core :as signing-phrase] [status-im.utils.signing-phrase.core :as signing-phrase]
[status-im.utils.types :as types] [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.utils.fx :as fx]
[status-im.node.core :as node] [status-im.node.core :as node]
[status-im.ui.screens.mobile-network-settings.events :as mobile-network] [status-im.ui.screens.mobile-network-settings.events :as mobile-network]
@ -26,26 +27,57 @@
(defn get-signing-phrase [cofx] (defn get-signing-phrase [cofx]
(assoc cofx :signing-phrase (signing-phrase/generate))) (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] (defn get-status [cofx]
(assoc cofx :status (rand-nth statuses/data))) (assoc cofx :status (rand-nth statuses/data)))
(defn create-account! [password] (defn create-account! [{:keys [id password]}]
(status/create-account (if id
password (status/import-onboarding-account
#(re-frame/dispatch [:accounts.create.callback/create-account-success (types/json->clj %) password]))) 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 ;;;; Handlers
(defn create-account (defn create-account
[{:keys [db random-guid-generator] :as cofx}] [{:keys [db] :as cofx}]
(fx/merge (if (:intro-wizard db)
cofx (fx/merge
{:db (-> db cofx
(update :accounts/create assoc {:accounts.create/create-account {:id (get-in db [:intro-wizard :selected-id])
:step :account-creating :password (or (get-in db [:accounts/create :password])
:error nil) (get-in db [:intro-wizard :key-code]))}})
(assoc :node/on-ready :create-account (fx/merge
:accounts/new-installation-id (random-guid-generator)))} cofx
(node/initialize nil))) {: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 (fx/defn add-account
"Takes db and new account, creates map of effects describing adding account to database and realm" "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) {:db (assoc-in db [:accounts/accounts address] enriched-account)
:data-store/base-tx [(accounts-store/save-account-tx 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}] (defn reset-account-creation [{db :db}]
{:db (update db :accounts/create assoc {:db (update db :accounts/create assoc
:step :enter-password :step :enter-password
@ -145,6 +139,179 @@
(dissoc :password :password-confirm :name :error)))} (dissoc :password :password-confirm :name :error)))}
(navigation/navigate-to-cofx :create-account nil))) (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 ;;;; COFX
(re-frame/reg-cofx (re-frame/reg-cofx

View File

@ -166,6 +166,7 @@
:keys [accounts/accounts accounts/create networks/networks network :keys [accounts/accounts accounts/create networks/networks network
network-status peers-count peers-summary view-id navigation-stack network-status peers-count peers-summary view-id navigation-stack
mailserver/mailservers mailserver/mailservers
intro-wizard
desktop/desktop hardwallet custom-fleets supported-biometric-auth desktop/desktop hardwallet custom-fleets supported-biometric-auth
device-UUID semaphores accounts/login] device-UUID semaphores accounts/login]
:node/keys [status on-ready] :node/keys [status on-ready]
@ -177,6 +178,7 @@
:view-id view-id :view-id view-id
:navigation-stack navigation-stack :navigation-stack navigation-stack
:node/status status :node/status status
:intro-wizard intro-wizard
:node/on-ready on-ready :node/on-ready on-ready
:accounts/create create :accounts/create create
:desktop/desktop (merge desktop (:desktop/desktop app-db)) :desktop/desktop (merge desktop (:desktop/desktop app-db))
@ -201,20 +203,19 @@
(= view-id :create-account) (= view-id :create-account)
(assoc-in [:accounts/create :step] :enter-name))})) (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 (fx/merge cofx
(cond-> (when-not (:intro-wizard db)
{:notifications/request-notifications-permissions nil} (cond-> {:notifications/request-notifications-permissions nil}
platform/ios?
platform/ios? ;; on ios navigation state might be not initialized yet when
;; on ios navigation state might be not initialized yet when ;; navigate-to call happens.
;; navigate-to call happens. ;; That's why it should be delayed a bit.
;; That's why it should be delayed a bit. ;; TODO(rasom): revisit this later and find better solution
;; TODO(rasom): revisit this later and find better solution (assoc :dispatch-later
(assoc :dispatch-later [{:ms 1
[{:ms 1 :dispatch [:navigate-to :home]}])))
:dispatch [:navigate-to :home]}])) (when-not (or (:intro-wizard db) platform/ios?)
(when-not platform/ios?
(navigation/navigate-to-cofx :home nil)) (navigation/navigate-to-cofx :home nil))
(notifications/process-stored-event address stored-pns) (notifications/process-stored-event address stored-pns)
(when platform/desktop? (when platform/desktop?

View File

@ -22,6 +22,12 @@
(defn recover-account [passphrase password callback] (defn recover-account [passphrase password callback]
(native-module/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] (defn login [address password callback]
(native-module/login address password callback)) (native-module/login address password callback))

View File

@ -61,6 +61,14 @@
(when (and @node-started (status)) (when (and @node-started (status))
(.recoverAccount (status) passphrase password on-result))) (.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] (defn login [address password on-result]
(when (and @node-started (status)) (when (and @node-started (status))
(.login (status) address password on-result))) (.login (status) address password on-result)))

View File

@ -6,6 +6,10 @@
:empty-recent (js-require/js-require "./resources/images/ui/empty-recent.png") :empty-recent (js-require/js-require "./resources/images/ui/empty-recent.png")
:analytics-image (js-require/js-require "./resources/images/ui/analytics-image.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") :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") :lock (js-require/js-require "./resources/images/ui/lock.png")
:tribute-to-talk (js-require/js-require "./resources/images/ui/tribute-to-talk.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") :wallet-welcome (js-require/js-require "./resources/images/ui/wallet-welcome.png")

View File

@ -37,14 +37,17 @@
[address password (:realm-error db)]})) [address password (:realm-error db)]}))
:create-account :create-account
(fn [_] (fn [_]
{:accounts.create/create-account (:password create)}) {:accounts.create/create-account (select-keys create [:id :password])})
:recover-account :recover-account
(fn [{:keys [db]}] (fn [{:keys [db]}]
(let [{:keys [password passphrase]} (:accounts/recover db)] (let [{:keys [password passphrase]} (:accounts/recover db)]
{:accounts.recover/recover-account {:accounts.recover/recover-account
[(security/mask-data passphrase) password]})) [(security/mask-data passphrase) password]}))
:create-keycard-account :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 (fx/defn status-node-stopped
[{db :db}] [{db :db}]

View File

@ -178,6 +178,9 @@
(reg-root-key-sub :signing/sign :signing/sign) (reg-root-key-sub :signing/sign :signing/sign)
(reg-root-key-sub :signing/edit-fee :signing/edit-fee) (reg-root-key-sub :signing/edit-fee :signing/edit-fee)
;;intro-wizard
(reg-root-key-sub :intro-wizard :intro-wizard)
;;GENERAL ============================================================================================================== ;;GENERAL ==============================================================================================================
(re-frame/reg-sub (re-frame/reg-sub

View File

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

View File

@ -17,3 +17,9 @@
(def about-title-text (def about-title-text
{:font-size 20}) {:font-size 20})
(def learn-more-title
{:typography :title-bold})
(def learn-more-text
{:color colors/gray})

View File

@ -1,11 +1,14 @@
(ns status-im.ui.screens.about-app.views (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] (:require [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar] [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.ui.screens.about-app.styles :as styles]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.transport.utils :as transport.utils] [status-im.transport.utils :as transport.utils]
[status-im.ui.components.colors :as colors]
[status-im.ui.screens.profile.components.views :as profile.components] [status-im.ui.screens.profile.components.views :as profile.components]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
@ -20,6 +23,19 @@
nil nil
4)) 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]}] (defn peer-view [{:keys [enode]}]
(let [[enode-id ip-address port] (transport.utils/extract-url-components enode)] (let [[enode-id ip-address port] (transport.utils/extract-url-components enode)]
^{:key enode} ^{:key enode}

View File

@ -4,30 +4,102 @@
(def intro-view (def intro-view
{:flex 1 {:flex 1
:padding-horizontal 30}) :justify-content :flex-end
:padding-horizontal 30
:margin-bottom 12})
(def intro-logo-container (def intro-logo-container
{:flex 1 {:align-items :center
:align-items :center
:justify-content :center}) :justify-content :center})
(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)))}))
(defn dot [color selected?]
{:background-color (if selected?
color
(colors/alpha color 0.2))
:width 6
:height 6
:border-radius 3})
(def welcome-image-container
{:align-items :center
:margin-top 42})
(def wizard-title
{:typography :header
:text-align :center
: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 (def intro-logo
{:size 111}) {:size 111})
(defstyle intro-text (defn password-text-input [width]
{:text-align :center {:typography :header
:font-weight "700" :width width})
:font-size 24})
(def intro-text-description
{:margin-top 8
:margin-bottom 16
:text-align :center
:color colors/gray})
(def buttons-container (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 (def bottom-button-container
{:margin-bottom 6 {:margin-bottom 24
:margin-top 38}) :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})

View File

@ -2,28 +2,297 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react] (:require [status-im.ui.components.react :as react]
[re-frame.core :as re-frame] [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.components.common.common :as components.common]
[status-im.ui.screens.intro.styles :as styles] [status-im.ui.screens.intro.styles :as styles]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.status-bar.view :as status-bar]))
[status-im.ui.screens.privacy-policy.views :as privacy-policy]))
(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 [] (defview intro []
[react/view {:style styles/intro-view} (letsubs [window-width [:dimensions/window-width]]
[status-bar/status-bar {:flat? true}] [react/view {:style styles/intro-view}
[react/view {:style styles/intro-logo-container} [status-bar/status-bar {:flat? true}]
[components.common/logo styles/intro-logo]] [intro-viewer [{:image (:intro1 resources/ui)
[react/i18n-text {:style styles/intro-text :title :intro-title1
:key :intro-text}] :text :intro-text1}
[react/view {:image (:intro2 resources/ui)
[react/i18n-text {:style styles/intro-text-description :title :intro-title2
:key :intro-text-description}]] :text :intro-text2}
[react/view styles/buttons-container {:image (:intro3 resources/ui)
[components.common/button {:button-style {:flex-direction :row} :title :intro-title3
:on-press #(re-frame/dispatch [:accounts.create.ui/create-new-account-button-pressed]) :text :intro-text3}] window-width]
:label (i18n/label :t/create-account)}] [react/view styles/buttons-container
[react/view styles/bottom-button-container [components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16)
[components.common/button {:on-press #(re-frame/dispatch [:accounts.recover.ui/recover-account-button-pressed]) :on-press #(re-frame/dispatch [:accounts.create.ui/intro-wizard])
:label (i18n/label :t/already-have-account) :label (i18n/label :t/get-started)}]
:background? false}]] [components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 24)
[privacy-policy/privacy-policy-button]]]) :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]]]))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,9 @@
(when on-dismiss (when on-dismiss
(clj->js {:cancelable false}))))) (clj->js {:cancelable false})))))
(defn vibrate []
#_(.vibrate (.-Vibration rn-dependencies/react-native)))
(re-frame/reg-fx (re-frame/reg-fx
:utils/show-popup :utils/show-popup
(fn [{:keys [title content on-dismiss]}] (fn [{:keys [title content on-dismiss]}]

View File

@ -41,7 +41,18 @@ class TestCreateAccount(SingleDeviceTestCase):
if sign_in.ok_button.is_element_displayed(): if sign_in.ok_button.is_element_displayed():
sign_in.ok_button.click() sign_in.ok_button.click()
sign_in.other_accounts_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: if sign_in.get_public_key() == public_key:
pytest.fail('New account was not created') pytest.fail('New account was not created')

View File

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

View File

@ -230,7 +230,7 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase):
username_1 = 'user_%s' % get_current_time() 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_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 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 # device 1, device 2: join to public chat and send several messages
chat_name = device_1.get_public_chat_name() chat_name = device_1.get_public_chat_name()

View File

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

View File

@ -6,20 +6,32 @@ from views.base_view import BaseView
class AccountButton(BaseButton): class AccountButton(BaseButton):
def __init__(self, driver): def __init__(self, driver):
super(AccountButton, self).__init__(driver) super(AccountButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[contains(@text,'0x')]") self.locator = self.Locator.xpath_selector("//*[contains(@text,'0x')]")
class PasswordInput(BaseEditBox): class PasswordInput(BaseEditBox):
def __init__(self, driver): def __init__(self, driver):
super(PasswordInput, self).__init__(driver) super(PasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Password']" self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Password']"
"/following-sibling::android.view.ViewGroup/android.widget.EditText") "/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): class SignInButton(BaseButton):
def __init__(self, driver): 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']") 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): class IHaveAccountButton(RecoverAccessButton):
def __init__(self, driver): def __init__(self, driver):
super(IHaveAccountButton, self).__init__(driver) super(IHaveAccountButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//*[@text='I already have an account']") 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): class AddExistingAccountButton(RecoverAccessButton):
def __init__(self, driver): def __init__(self, driver):
super(AddExistingAccountButton, self).__init__(driver) super(AddExistingAccountButton, self).__init__(driver)
@ -106,25 +136,35 @@ class SignInView(BaseView):
# new design # new design
self.create_account_button = CreateAccountButton(self.driver) self.create_account_button = CreateAccountButton(self.driver)
self.i_have_account_button = IHaveAccountButton(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.add_existing_account_button = AddExistingAccountButton(self.driver)
self.confirm_password_input = ConfirmPasswordInput(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.name_input = NameInput(self.driver)
self.other_accounts_button = OtherAccountsButton(self.driver) self.other_accounts_button = OtherAccountsButton(self.driver)
self.privacy_policy_link = PrivacyPolicyLink(self.driver) self.privacy_policy_link = PrivacyPolicyLink(self.driver)
def create_user(self, username: str = '', password=common_password): def create_user(self, password=common_password):
self.create_account_button.click()
self.password_input.set_value(password)
self.next_button.click()
self.confirm_password_input.set_value(password)
self.next_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.next_button.click()
self.get_started_button.click() 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_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.next_button.click()
# self.get_started_button.click()
return self.get_home_view() return self.get_home_view()
def recover_access(self, passphrase: str, password: str = common_password): def recover_access(self, passphrase: str, password: str = common_password):
@ -132,7 +172,7 @@ class SignInView(BaseView):
self.other_accounts_button.click() self.other_accounts_button.click()
recover_access_view = self.add_existing_account_button.click() recover_access_view = self.add_existing_account_button.click()
else: 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.click()
recover_access_view.passphrase_input.set_value(passphrase) recover_access_view.passphrase_input.set_value(passphrase)
recover_access_view.password_input.click() recover_access_view.password_input.click()

View File

@ -1,5 +1,6 @@
(ns status-im.test.accounts.create.core (ns status-im.test.accounts.create.core
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.utils :as utils]
[status-im.accounts.create.core :as models])) [status-im.accounts.create.core :as models]))
(deftest on-account-created (deftest on-account-created
@ -11,23 +12,65 @@
:mnemonic "hello world"} :mnemonic "hello world"}
"password" "password"
true)] 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) (is (= (keys result)
[:db [:db :accounts.login/clear-web-data :data-store/change-account :data-store/base-tx]))))
:node/start
: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)))))

View File

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

View File

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

View File

@ -151,6 +151,41 @@
"other": "{{count}} members" "other": "{{count}} members"
}, },
"intro-message1": "Welcome to Status!\nTap this message to set your password and get started.", "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 cant 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", "not-implemented": "!not implemented",
"new-contact": "New contact", "new-contact": "New contact",
"datetime-second": { "datetime-second": {
@ -249,7 +284,7 @@
"group-chat-admin-added": "*{{member}}* has been made admin", "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", "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 ", "agree-by-continuing": "By continuing you agree\n to our ",
"wallet-advanced": "Advanced", "advanced": "Advanced",
"currency-display-name-sos": "Somalia Shilling", "currency-display-name-sos": "Somalia Shilling",
"currency-display-name-zar": "South Africa Rand", "currency-display-name-zar": "South Africa Rand",
"offline-messaging": "Mailserver", "offline-messaging": "Mailserver",
@ -649,7 +684,7 @@
"wallet-asset": "Asset", "wallet-asset": "Asset",
"close-app-content": "The app will stop and close. When you reopen it, the selected network will be used", "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", "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", "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.", "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", "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", "transactions-history": "Transaction history",
"fetching-messages": "Fetching messages... ({{requests-left}} requests left)", "fetching-messages": "Fetching messages... ({{requests-left}} requests left)",
"password_error1": "Passwords don't match.", "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", "your-contact-code": "Granting access authorizes this DApp to retrieve your contact code",
"password-placeholder": "At least 6 characters", "password-placeholder": "At least 6 characters",
"clear-history-action": "Clear", "clear-history-action": "Clear",
@ -919,6 +955,7 @@
"view-my-wallet": "View my wallet", "view-my-wallet": "View my wallet",
"share-my-profile": "Share my profile", "share-my-profile": "Share my profile",
"get-started": "Get started", "get-started": "Get started",
"access-key": "Access key",
"tribute-to-talk": "Tribute to talk", "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-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", "tribute-to-talk-set-snt-amount": "Set the amount of SNT required for new people to start a chat",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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