diff --git a/components/src/status_im/react_native/resources.cljs b/components/src/status_im/react_native/resources.cljs index d462c39be83..fcb986520b5 100644 --- a/components/src/status_im/react_native/resources.cljs +++ b/components/src/status_im/react_native/resources.cljs @@ -5,10 +5,10 @@ :empty-recent (js/require "./resources/images/ui/empty-recent.png") :analytics-image (js/require "./resources/images/ui/analytics-image.png") :welcome-image (js/require "./resources/images/ui/welcome-image.png") - :intro1 (js/require "./resources/images/ui/intro1.png") - :intro2 (js/require "./resources/images/ui/intro2.png") - :intro3 (js/require "./resources/images/ui/intro3.png") - :sample-key (js/require "./resources/images/ui/sample-key.png") + :intro1 (js/require "./resources/images/ui/intro1.jpg") + :intro2 (js/require "./resources/images/ui/intro2.jpg") + :intro3 (js/require "./resources/images/ui/intro3.jpg") + :sample-key (js/require "./resources/images/ui/sample-key.jpg") :lock (js/require "./resources/images/ui/lock.png") :tribute-to-talk (js/require "./resources/images/ui/tribute-to-talk.png") :wallet-welcome (js/require "./resources/images/ui/wallet-welcome.png") diff --git a/externs.js b/externs.js index bb428f1fc06..d045b466eeb 100644 --- a/externs.js +++ b/externs.js @@ -22,6 +22,7 @@ var TopLevel = { "AsyncStorage" : function () {}, "at" : function () {}, "back" : function () {}, + "BACK" : function () {}, "BackHandler" : function () {}, "balanceOf" : function () {}, "bezier" : function () {}, @@ -162,6 +163,7 @@ var TopLevel = { "getNodesFromContract" : function () {}, "getPublicKey" : function () {}, "getSecurityLevel" : function () {}, + "getStateForAction" : function () {}, "getString" : function () {}, "getSymKey" : function () {}, "getSyncing" : function () {}, @@ -403,6 +405,7 @@ var TopLevel = { "rightPad" : function () {}, "round" : function () {}, "routeName" : function () {}, + "router" : function () {}, "routes" : function () {}, "saveAccountAndLogin" : function () {}, "saveAccountAndLoginWithKeycard" : function () {}, diff --git a/resources/images/ui/intro1.jpg b/resources/images/ui/intro1.jpg new file mode 100644 index 00000000000..efa45e877e6 Binary files /dev/null and b/resources/images/ui/intro1.jpg differ diff --git a/resources/images/ui/intro1.png b/resources/images/ui/intro1.png deleted file mode 100644 index 2dc713333e9..00000000000 Binary files a/resources/images/ui/intro1.png and /dev/null differ diff --git a/resources/images/ui/intro2.jpg b/resources/images/ui/intro2.jpg new file mode 100644 index 00000000000..f44e3f86de3 Binary files /dev/null and b/resources/images/ui/intro2.jpg differ diff --git a/resources/images/ui/intro2.png b/resources/images/ui/intro2.png deleted file mode 100644 index b6b967ea87b..00000000000 Binary files a/resources/images/ui/intro2.png and /dev/null differ diff --git a/resources/images/ui/intro3.jpg b/resources/images/ui/intro3.jpg new file mode 100644 index 00000000000..589afa05ab8 Binary files /dev/null and b/resources/images/ui/intro3.jpg differ diff --git a/resources/images/ui/intro3.png b/resources/images/ui/intro3.png deleted file mode 100644 index 885733f73b0..00000000000 Binary files a/resources/images/ui/intro3.png and /dev/null differ diff --git a/resources/images/ui/sample-key.jpg b/resources/images/ui/sample-key.jpg new file mode 100644 index 00000000000..71d3df7174b Binary files /dev/null and b/resources/images/ui/sample-key.jpg differ diff --git a/resources/images/ui/sample-key.png b/resources/images/ui/sample-key.png deleted file mode 100644 index 6ba485e18a8..00000000000 Binary files a/resources/images/ui/sample-key.png and /dev/null differ diff --git a/src/status_im/multiaccounts/create/core.cljs b/src/status_im/multiaccounts/create/core.cljs index afa8c63e954..b621f42d9fd 100644 --- a/src/status_im/multiaccounts/create/core.cljs +++ b/src/status_im/multiaccounts/create/core.cljs @@ -3,6 +3,9 @@ [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.ethereum.core :as ethereum] + [taoensso.timbre :as log] + [status-im.i18n :as i18n] + [status-im.multiaccounts.db :as db] [status-im.native-module.core :as status] [status-im.node.core :as node] [status-im.ui.components.colors :as colors] @@ -25,10 +28,9 @@ :select-key-storage 3 :create-code 4 :confirm-code 5 - :enable-fingerprint 6 - :enable-notifications 7}) + :enable-notifications 6}) -(defn dec-step [step] +(defn decrement-step [step] (let [inverted (map-invert step-kw-to-num)] (if (and (= step :create-code) (not platform/android?)) @@ -58,35 +60,62 @@ (fx/defn intro-wizard {:events [:multiaccounts.create.ui/intro-wizard]} [{:keys [db] :as cofx} first-time-setup?] - (fx/merge {:db (assoc db :intro-wizard {:step :generate-key + (fx/merge cofx + {:db (assoc db :intro-wizard {:step :generate-key :weak-password? true + :back-action :intro-wizard/navigate-back + :forward-action :intro-wizard/step-forward-pressed :encrypt-with-password? true - :first-time-setup? first-time-setup?})} - (navigation/navigate-to-cofx :intro-wizard nil))) + :first-time-setup? first-time-setup?}) + ::navigation/add-wizard-back-event [:intro-wizard/step-back-pressed]} + (navigation/navigate-to-cofx :create-multiaccount-generate-key nil))) -(fx/defn intro-step-back - {:events [:intro-wizard/step-back-pressed]} +(fx/defn dec-step + {:events [:intro-wizard/dec-step]} [{:keys [db] :as cofx}] - (let [step (get-in db [:intro-wizard :step]) - first-time-setup? (get-in db [:intro-wizard :first-time-setup?])] - (if (not= :generate-key step) - (fx/merge {:db (cond-> (assoc-in db [:intro-wizard :step] (dec-step step)) + (let [step (get-in db [:intro-wizard :step])] + (fx/merge cofx + (when-not (= :generate-key step) + {:db (cond-> (assoc-in db [:intro-wizard :step] (decrement-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)) + (assoc-in [:intro-wizard :confirm-failure?] false))}) + (when (= :generate-key-step) + {:db (dissoc db :intro-wizard) + ::navigation/remove-wizard-back-event nil})))) - (fx/merge {:db (dissoc db :intro-wizard)} - (navigation/navigate-back))))) +(fx/defn navigate-back + {:events [:intro-wizard/navigate-back]} + [cofx] + {::navigation/navigate-back nil}) + +(fx/defn intro-step-back + {:events [:intro-wizard/step-back-pressed]} + [{:keys [db] :as cofx} skip-alert?] + (let [step (get-in db [:intro-wizard :step])] + ;; Cannot go back after account has been created + ;; and we're on "Enable notifications" step + (when-not (= :enable-notifications step) + (if (and (= step :choose-key) (not skip-alert?)) + (utils/show-question + (i18n/label :t/are-you-sure-to-cancel) + (i18n/label :t/you-will-start-from-scratch) + #(re-frame/dispatch [:intro-wizard/step-back-pressed true]) + #(re-frame/dispatch [:navigation/reset-processing-flag])) + (fx/merge cofx + dec-step + navigation/navigate-back))))) (fx/defn exit-wizard [{:keys [db] :as cofx}] - (fx/merge {:db (dissoc db :intro-wizard)} + (fx/merge cofx + {:db (dissoc db :intro-wizard) + ::navigation/remove-wizard-back-event nil} (navigation/navigate-to-cofx :home nil))) (fx/defn init-key-generation [{:keys [db] :as cofx}] - {:db (assoc-in db [:intro-wizard :generating-keys?] true) + {:db (assoc-in db [:intro-wizard :processing?] true) :intro-wizard/start-onboarding nil}) (fx/defn on-confirm-failure [{:keys [db] :as cofx}] @@ -105,8 +134,7 @@ (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)})) + :key-code nil)})) (fx/defn intro-step-forward {:events [:intro-wizard/step-forward-pressed]} @@ -126,9 +154,6 @@ (= step :generate-key) (init-key-generation cofx) - (= step :create-code) - (store-key-code cofx) - (and (= step :confirm-code) (not (:multiaccounts/login db)) (not processing?)) @@ -140,9 +165,18 @@ (= :advanced selected-storage-type)) {:dispatch [:keycard/start-onboarding-flow]} - :else {:db (update db :intro-wizard - assoc :processing? false - :step (inc-step step))}))) + :else (let [next-step (inc-step step)] + (fx/merge cofx + {:db (update db :intro-wizard + assoc :processing? false + :step next-step)} + (when (= step :create-code) + store-key-code) + (when (= next-step :enable-notifications) + (navigation/navigate-reset {:index 0 + :actions [{:routeName :create-multiaccount-enable-notifications}]})) + (when (not= next-step :enable-notifications) + (navigation/navigate-to-cofx (->> next-step name (str "create-multiaccount-") keyword) nil))))))) (defn prepare-accounts-data [multiaccount] @@ -248,12 +282,12 @@ {:db (update db :intro-wizard (fn [data] (-> data - (dissoc :generating-keys?) + (dissoc :processing?) (assoc :multiaccounts result :selected-storage-type :default :selected-id (-> result first :id) :step :choose-key))))} - (navigation/navigate-to-cofx :intro-wizard nil))) + (navigation/navigate-to-cofx :create-multiaccount-choose-key nil))) (fx/defn on-key-selected {:events [:intro-wizard/on-key-selected]} @@ -290,7 +324,7 @@ (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))})) + :weak-password? (not (db/valid-length? new-key-code)))})) (re-frame/reg-cofx ::get-signing-phrase diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 5f7eeb2bb9d..1052d419dc2 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -187,9 +187,6 @@ (when platform/desktop? (chat-model/update-dock-badge-label))))) -(defn- recovering-multiaccount? [cofx] - (boolean (get-in cofx [:db :multiaccounts/recover]))) - (fx/defn create-only-events [{:keys [db] :as cofx} address password] (let [{:keys [multiaccount :networks/networks :networks/current-network]} db] @@ -221,7 +218,6 @@ :mailserver-ranges {} :mailserver-topics {} :default-mailserver true}) - (mobile-network/on-network-status-change) (chaos-mode/check-chaos-mode) (when-not platform/desktop? (initialize-wallet))))) @@ -232,8 +228,7 @@ (fx/defn multiaccount-login-success [{:keys [db] :as cofx}] (let [{:keys [address password save-password? name photo-path creating?]} (:multiaccounts/login db) - step (get-in db [:intro-wizard :step]) - recovering? (recovering-multiaccount? cofx) + recovering? (get-in db [:intro-wizard :recovering?]) login-only? (not (or creating? recovering? (keycard-setup? cofx))) diff --git a/src/status_im/multiaccounts/recover/core.cljs b/src/status_im/multiaccounts/recover/core.cljs index 7ce0a24cbad..41cb0198480 100644 --- a/src/status_im/multiaccounts/recover/core.cljs +++ b/src/status_im/multiaccounts/recover/core.cljs @@ -13,17 +13,8 @@ [status-im.utils.fx :as fx] [status-im.utils.security :as security] [status-im.utils.types :as types] - [status-im.utils.platform :as platform])) - -(defn check-password-errors [password] - (cond (string/blank? password) :required-field - (not (db/valid-length? password)) :recover-password-too-short)) - -(defn check-phrase-errors [recovery-phrase] - (cond (string/blank? recovery-phrase) :required-field - (not (mnemonic/valid-words? recovery-phrase)) :recovery-phrase-invalid - (not (mnemonic/valid-length? recovery-phrase)) :recovery-phrase-wrong-length - (not (mnemonic/status-generated-phrase? recovery-phrase)) :recovery-phrase-unknown-words)) + [status-im.utils.platform :as platform] + [status-im.utils.utils :as utils])) (defn check-phrase-warnings [recovery-phrase] (cond (string/blank? recovery-phrase) :required-field @@ -31,51 +22,28 @@ (not (mnemonic/status-generated-phrase? recovery-phrase)) :recovery-phrase-unknown-words)) (fx/defn set-phrase - {:events [::passphrase-input-changed]} + {:events [:multiaccounts.recover/passphrase-input-changed]} [{:keys [db]} masked-recovery-phrase] (let [recovery-phrase (security/safe-unmask-data masked-recovery-phrase)] (fx/merge - {:db (update db :multiaccounts/recover assoc + {:db (update db :intro-wizard assoc :passphrase (string/lower-case recovery-phrase) :passphrase-error nil :next-button-disabled? (or (empty? recovery-phrase) (not (mnemonic/valid-length? recovery-phrase))))}))) -(fx/defn validate-phrase - {:events [::passphrase-input-blured]} - [{:keys [db]}] - (let [recovery-phrase (get-in db [:multiaccounts/recover :passphrase])] - {:db (update db :multiaccounts/recover assoc - :passphrase-error (check-phrase-errors recovery-phrase))})) - (fx/defn validate-phrase-for-warnings [{:keys [db]}] - (let [recovery-phrase (get-in db [:multiaccounts/recover :passphrase])] - {:db (update db :multiaccounts/recover assoc + (let [recovery-phrase (get-in db [:intro-wizard :passphrase])] + {:db (update db :intro-wizard assoc :passphrase-error (check-phrase-warnings recovery-phrase))})) -(fx/defn set-password - {:events [::password-input-changed - ::enter-password-input-changed]} - [{:keys [db]} masked-password] - (let [password (security/safe-unmask-data masked-password)] - {:db (update db :multiaccounts/recover assoc - :password password - :password-error nil - :password-valid? (not (check-password-errors password)))})) - -(fx/defn validate-password - {:events [::password-input-blured]} - [{:keys [db]}] - (let [password (get-in db [:multiaccounts/recover :password])] - {:db (assoc-in db [:multiaccounts/recover :password-error] (check-password-errors password))})) - (fx/defn on-store-multiaccount-success {:events [::store-multiaccount-success] :interceptors [(re-frame/inject-cofx :random-guid-generator) (re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]} [{:keys [db] :as cofx} password] - (let [multiaccount (get-in db [:multiaccounts/recover :root-key]) + (let [multiaccount (get-in db [:intro-wizard :root-key]) multiaccount-address (-> (:address multiaccount) (string/lower-case) (string/replace-first "0x" "")) @@ -83,12 +51,12 @@ (if keycard-multiaccount? ;; trying to recover multiaccount created with keycard {:db (-> db - (update :multiaccounts/recover assoc + (update :intro-wizard assoc :processing? false :passphrase-error :recover-keycard-multiaccount-not-supported) - (update :multiaccounts/recover dissoc + (update :intro-wizard dissoc :passphrase-valid?))} - (let [multiaccount (assoc multiaccount :derived (get-in db [:multiaccounts/recover :derived]))] + (let [multiaccount (assoc multiaccount :derived (get-in db [:intro-wizard :derived]))] (multiaccounts.create/on-multiaccount-created cofx multiaccount password @@ -97,17 +65,18 @@ (fx/defn store-multiaccount {:events [::recover-multiaccount-confirmed]} [{:keys [db] :as cofx}] - (let [{:keys [password passphrase root-key]} (:multiaccounts/recover db) + (let [password (get-in db [:intro-wizard :key-code]) + {:keys [passphrase root-key]} (:intro-wizard db) {:keys [id address]} root-key callback #(re-frame/dispatch [::store-multiaccount-success password]) hashed-password (ethereum/sha3 (security/safe-unmask-data password))] - {:db (assoc-in db [:multiaccounts/recover :processing?] true) + {:db (assoc-in db [:intro-wizard :processing?] true) ::multiaccounts.create/store-multiaccount [id address hashed-password callback]})) (fx/defn recover-multiaccount-with-checks {:events [::sign-in-button-pressed]} [{:keys [db] :as cofx}] - (let [{:keys [passphrase processing?]} (:multiaccounts/recover db)] + (let [{:keys [passphrase processing?]} (:intro-wizard db)] (when-not processing? (if (mnemonic/status-generated-phrase? passphrase) (store-multiaccount cofx) @@ -140,23 +109,34 @@ {:events [::import-multiaccount-success]} [{:keys [db] :as cofx} root-data derived-data] (fx/merge cofx - {:db (-> db - (assoc-in [:multiaccounts/recover :root-key] root-data) - (assoc-in [:multiaccounts/recover :derived] derived-data))} + {:db (update db :intro-wizard + assoc :root-key root-data + :derived derived-data + :step :recovery-success + :forward-action :multiaccounts.recover/re-encrypt-pressed)} (navigation/navigate-to-cofx :recover-multiaccount-success nil))) (fx/defn enter-phrase-pressed {:events [::enter-phrase-pressed]} [{:keys [db] :as cofx}] (fx/merge cofx - {:db (assoc db :multiaccounts/recover {:next-button-disabled? true}) + {:db (assoc db + :intro-wizard {:step :enter-phrase + :recovering? true + :next-button-disabled? true + :weak-password? true + :encrypt-with-password? true + :first-time-setup? false + :back-action :intro-wizard/navigate-back + :forward-action :multiaccounts.recover/enter-phrase-next-pressed}) + ::navigation/add-wizard-back-event [:multiaccounts.recover/cancel-pressed] :dispatch [:bottom-sheet/hide-sheet]} (navigation/navigate-to-cofx :recover-multiaccount-enter-phrase nil))) (fx/defn proceed-to-import-mnemonic - {:events [::enter-phrase-next-pressed]} + {:events [:multiaccounts.recover/enter-phrase-next-pressed]} [{:keys [db] :as cofx}] - (let [{:keys [password passphrase]} (:multiaccounts/recover db)] + (let [{:keys [password passphrase]} (:intro-wizard db)] (if (check-phrase-warnings passphrase) (popover/show-popover cofx {:view :custom-seed-phrase}) (when (mnemonic/valid-length? passphrase) @@ -172,83 +152,106 @@ :password password}} (popover/hide-popover)))) -(fx/defn cancel-pressed - {:events [::cancel-pressed]} +(fx/defn dec-step + {:events [:multiaccounts.recover/dec-step]} [{:keys [db] :as cofx}] + (let [step (get-in db [:intro-wizard :step])] + (if (= step :enter-phrase) + {:db (dissoc db :intro-wizard) + ::navigation/remove-wizard-back-event nil} + {:db (update db :intro-wizard assoc :step + (case step + :recovery-success :enter-phrase + :select-key-storage :recovery-success + :create-code :select-key-storage + :confirm-code :create-code) + :confirm-failure? false + :key-code nil + :weak-password? true)}))) + +(fx/defn cancel-pressed + {:events [:multiaccounts.recover/cancel-pressed]} + [{:keys [db] :as cofx} skip-alert?] ;; Workaround for multiple Cancel button clicks ;; that can break navigation tree - (when-not (#{:multiaccounts :login} (:view-id db)) - (navigation/navigate-back cofx))) + (let [step (get-in db [:intro-wizard :step])] + (when-not (#{:multiaccounts :login} (:view-id db)) + (if (and (= step :select-key-storage) (not skip-alert?)) + (utils/show-question + (i18n/label :t/are-you-sure-to-cancel) + (i18n/label :t/you-will-start-from-scratch) + #(re-frame/dispatch [:multiaccounts.recover/cancel-pressed true]) + #(re-frame/dispatch [:navigation/reset-processing-flag])) + (fx/merge cofx + dec-step + navigation/navigate-back))))) (fx/defn select-storage-next-pressed - {:events [::select-storage-next-pressed] + {:events [:multiaccounts.recover/select-storage-next-pressed] :interceptors [(re-frame/inject-cofx :random-guid-generator)]} [{:keys [db] :as cofx}] (let [storage-type (get-in db [:intro-wizard :selected-storage-type])] (if (= storage-type :advanced) ;;TODO: fix circular dependency to remove dispatch here - {:dispatch [:recovery.ui/keycard-option-pressed]} - (navigation/navigate-to-cofx cofx :recover-multiaccount-enter-password nil)))) + {:dispatch [:recovery.ui/keycard-option-pressed]}) + (fx/merge cofx + {:db (update db :intro-wizard assoc :step :create-code + :forward-action :multiaccounts.recover/enter-password-next-pressed)} + (navigation/navigate-to-cofx :recover-multiaccount-enter-password nil)))) (fx/defn re-encrypt-pressed - {:events [::re-encrypt-pressed]} + {:events [:multiaccounts.recover/re-encrypt-pressed]} [{:keys [db] :as cofx}] (fx/merge cofx - {:db (assoc-in db [:intro-wizard :selected-storage-type] :default)} + {:db (update db :intro-wizard + assoc :step :select-key-storage + :forward-action :multiaccounts.recover/select-storage-next-pressed + :selected-storage-type :default)} (if platform/android? (navigation/navigate-to-cofx :recover-multiaccount-select-storage nil) (select-storage-next-pressed)))) (fx/defn proceed-to-password-confirm [{:keys [db] :as cofx}] - (when (nil? (get-in db [:multiaccounts/recover :password-error])) - (navigation/navigate-to-cofx cofx :recover-multiaccount-confirm-password nil))) + (fx/merge cofx + {:db (update db :intro-wizard assoc :step :confirm-code + :forward-action :multiaccounts.recover/confirm-password-next-pressed)} + (navigation/navigate-to-cofx :recover-multiaccount-confirm-password nil))) (fx/defn enter-password-next-button-pressed - {:events [::enter-password-input-submitted - ::enter-password-next-pressed]} + {:events [:multiaccounts.recover/enter-password-next-pressed]} [{:keys [db] :as cofx}] (fx/merge cofx - (validate-password) + {:db (assoc-in db [:intro-wizard :stored-key-code] (get-in db [:intro-wizard :key-code]))} (proceed-to-password-confirm))) (fx/defn confirm-password-next-button-pressed - {:events [::confirm-password-input-submitted - ::confirm-password-next-pressed] + {:events [:multiaccounts.recover/confirm-password-next-pressed] :interceptors [(re-frame/inject-cofx :random-guid-generator)]} [{:keys [db] :as cofx}] - (let [{:keys [password password-confirmation]} (:multiaccounts/recover db)] - (if (= password password-confirmation) + (let [{:keys [key-code stored-key-code]} (:intro-wizard db)] + (if (= key-code stored-key-code) (fx/merge cofx - {:db (assoc db :intro-wizard nil)} - (store-multiaccount) - (navigation/navigate-to-cofx :keycard-welcome nil)) - {:db (assoc-in db [:multiaccounts/recover :password-error] :password_error1)}))) + (store-multiaccount)) + {:db (assoc-in db [:intro-wizard :confirm-failure?] true)}))) (fx/defn count-words [{:keys [db]}] - (let [passphrase (get-in db [:multiaccounts/recover :passphrase])] - {:db (assoc-in db [:multiaccounts/recover :words-count] + (let [passphrase (get-in db [:intro-wizard :passphrase])] + {:db (assoc-in db [:intro-wizard :passphrase-word-count] (mnemonic/words-count passphrase))})) (fx/defn run-validation [{:keys [db] :as cofx}] - (let [passphrase (get-in db [:multiaccounts/recover :passphrase])] + (let [passphrase (get-in db [:intro-wizard :passphrase])] (when (= (last passphrase) " ") (fx/merge cofx (validate-phrase-for-warnings))))) (fx/defn enter-phrase-input-changed - {:events [::enter-phrase-input-changed]} + {:events [:multiaccounts.recover/enter-phrase-input-changed]} [cofx input] (fx/merge cofx (set-phrase input) (count-words) (run-validation))) - -(fx/defn confirm-password-input-changed - {:events [::confirm-password-input-changed]} - [{:keys [db]} input] - {:db (-> db - (assoc-in [:multiaccounts/recover :password-confirmation] input) - (assoc-in [:multiaccounts/recover :password-error] nil))}) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 4af8ea51b52..e2802ffdb48 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -1,6 +1,7 @@ (ns status-im.subs (:require [cljs.spec.alpha :as spec] [clojure.string :as string] + [taoensso.timbre :as log] [re-frame.core :as re-frame] [status-im.browser.core :as browser] [status-im.chat.commands.core :as commands] @@ -176,7 +177,7 @@ (reg-root-key-sub :signing/edit-fee :signing/edit-fee) ;;intro-wizard -(reg-root-key-sub :intro-wizard :intro-wizard) +(reg-root-key-sub :intro-wizard-state :intro-wizard) (reg-root-key-sub :popover/popover :popover/popover) (reg-root-key-sub :generate-account :generate-account) @@ -193,6 +194,72 @@ (fn [desktop _] (get desktop :debug-metrics))) +;; Intro wizard +(re-frame/reg-sub + :intro-wizard + :<- [:intro-wizard-state] + :<- [:dimensions/window] + (fn [[wizard-state + {:keys [width height] :as dimensions} + view-id]] + (assoc wizard-state + :view-height height :view-width width))) + +(re-frame/reg-sub + :intro-wizard/generate-key + :<- [:intro-wizard] + (fn [wizard-state] + (select-keys wizard-state [:processing? :view-height]))) + +(re-frame/reg-sub + :intro-wizard/choose-key + :<- [:intro-wizard] + (fn [wizard-state] + (select-keys wizard-state [:multiaccounts :selected-id :view-height]))) + +(re-frame/reg-sub + :intro-wizard/select-key-storage + :<- [:intro-wizard] + (fn [wizard-state] + (merge (select-keys wizard-state [:selected-storage-type :view-height :recovering?]) + (if (:recovering? wizard-state) + {:forward-action :multiaccounts.recover/select-storage-next-pressed} + {:forward-action :intro-wizard/step-forward-pressed})))) + +(re-frame/reg-sub + :intro-wizard/create-code + :<- [:intro-wizard] + (fn [wizard-state] + (merge (select-keys wizard-state [:confirm-failure? :encrypt-with-password? :weak-password? :view-width]) + (if (:recovering? wizard-state) + {:forward-action :multiaccounts.recover/enter-password-next-pressed} + {:forward-action :intro-wizard/step-forward-pressed})))) + +(re-frame/reg-sub + :intro-wizard/confirm-code + :<- [:intro-wizard] + (fn [wizard-state] + (merge (select-keys wizard-state [:confirm-failure? :encrypt-with-password? :processing? :view-width]) + (if (:recovering? wizard-state) + {:forward-action :multiaccounts.recover/confirm-password-next-pressed} + {:forward-action :intro-wizard/step-forward-pressed})))) + +(re-frame/reg-sub + :intro-wizard/enter-phrase + :<- [:intro-wizard] + (fn [wizard-state] + (select-keys wizard-state [:processing? + :passphrase-word-count + :next-button-disabled? + :passphrase-error]))) + +(re-frame/reg-sub + :intro-wizard/recovery-success + :<- [:intro-wizard] + (fn [wizard-state] + {:pubkey (get-in wizard-state [:derived constants/path-whisper-keyword :publicKey]) + :processing? (:processing? wizard-state)})) + (re-frame/reg-sub :settings/logging-enabled :<- [:desktop/desktop] diff --git a/src/status_im/ui/components/text_input/styles.cljs b/src/status_im/ui/components/text_input/styles.cljs index 18455f987b7..1e8272e9269 100644 --- a/src/status_im/ui/components/text_input/styles.cljs +++ b/src/status_im/ui/components/text_input/styles.cljs @@ -21,11 +21,11 @@ (styles/def input {:padding 0 - :text-align-vertical :top + :text-align-vertical :center :desktop {:height 52}}) -(defn error [label?] - {:bottom-value (if label? 20 0) +(defn error [bottom-value] + {:bottom-value bottom-value :container-style {:shadow-offset {:width 0 :height 1} :shadow-radius 6 :shadow-opacity 1 diff --git a/src/status_im/ui/components/text_input/view.cljs b/src/status_im/ui/components/text_input/view.cljs index 9c213db9dff..a6e63bbd150 100644 --- a/src/status_im/ui/components/text_input/view.cljs +++ b/src/status_im/ui/components/text_input/view.cljs @@ -15,10 +15,11 @@ merged-styles))) (defn text-input-with-label - [{:keys [label content error style height container text editable keyboard-type] + [{:keys [label content error style height container + parent-container bottom-value text editable keyboard-type] :as props :or {editable true}}] - [react/view + [react/view {:style parent-container} (when label [react/text {:style (styles/label editable)} label]) @@ -39,4 +40,7 @@ {:value text}))] (when content content)] (when error - [tooltip/tooltip error (styles/error label)])]) + [tooltip/tooltip error (styles/error + (cond bottom-value bottom-value + label 20 + :else 0))])]) diff --git a/src/status_im/ui/screens/about_app/views.cljs b/src/status_im/ui/screens/about_app/views.cljs index a2daf032447..21aee17d6d5 100644 --- a/src/status_im/ui/screens/about_app/views.cljs +++ b/src/status_im/ui/screens/about_app/views.cljs @@ -4,11 +4,13 @@ [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors] [status-im.ui.components.copyable-text :as copyable-text] + [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.list.views :as list] [status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.react :as react] [status-im.ui.components.status-bar.view :as status-bar] - [status-im.ui.components.toolbar.view :as toolbar])) + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.ui.screens.about-app.styles :as styles])) (defn- data [app-version node-version] [{:type :small @@ -62,3 +64,16 @@ {:data (data app-version node-version) :key-fn (fn [_ i] (str i)) :render-fn list/flat-list-generic-render-fn}]])) + +(views/defview learn-more-sheet [] + (views/letsubs [{:keys [title content]} [:bottom-sheet/options]] + [react/view {:style {:padding-left 16 :padding-top 16 + :padding-right 34 :padding-bottom 0}} + [react/view {:style {:align-items :center :flex-direction :row :margin-bottom 16}} + [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}) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index dc4a16d19aa..ed66da07e01 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -12,6 +12,7 @@ [status-im.chat.models :as chat] [status-im.hardwallet.core :as hardwallet] [status-im.mailserver.core :as mailserver] + [status-im.multiaccounts.recover.core :as recovery] [status-im.native-module.core :as status] [status-im.ui.components.permissions :as permissions] [status-im.utils.dimensions :as dimensions] diff --git a/src/status_im/ui/screens/intro/styles.cljs b/src/status_im/ui/screens/intro/styles.cljs index bda963e442d..54377c85cf1 100644 --- a/src/status_im/ui/screens/intro/styles.cljs +++ b/src/status_im/ui/screens/intro/styles.cljs @@ -4,7 +4,6 @@ (def intro-view {:flex 1 :justify-content :flex-end - :padding-horizontal 30 :margin-bottom 12}) (def intro-logo-container @@ -58,7 +57,7 @@ :padding-left 16 :padding-right 10 :background-color (if selected? colors/blue-light colors/white) - :padding-vertical 10}) + :padding-vertical 12}) (def multiaccount-image {:width 40 @@ -81,8 +80,8 @@ :width width}) (def buttons-container - {:align-items :center - :margin-top 32}) + {:align-items :center + :padding-horizontal 32}) (def bottom-button {:padding-horizontal 24 @@ -100,5 +99,4 @@ :align-self :stretch :padding-top 16 :border-top-width 1 - :border-top-color colors/gray-lighter - :margin-right 20}) + :border-top-color colors/gray-lighter}) diff --git a/src/status_im/ui/screens/intro/views.cljs b/src/status_im/ui/screens/intro/views.cljs index 863d8972688..ab24e0895f5 100644 --- a/src/status_im/ui/screens/intro/views.cljs +++ b/src/status_im/ui/screens/intro/views.cljs @@ -9,6 +9,7 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.utils.identicon :as identicon] [status-im.ui.components.radio :as radio] + [status-im.ui.components.text-input.view :as text-input] [taoensso.timbre :as log] [status-im.utils.gfycat.core :as gfy] [status-im.ui.components.colors :as colors] @@ -17,6 +18,8 @@ [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.utils.platform :as platform] + [status-im.utils.security :as security] [status-im.i18n :as i18n] [status-im.ui.components.status-bar.view :as status-bar] [status-im.constants :as constants] @@ -30,16 +33,19 @@ ^{: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) +(defn intro-viewer [slides window-height] + (let [scroll-x (r/atom 0) scroll-view-ref (atom nil) - max-width (* view-width (dec (count slides)))] + width (r/atom 0) + height (r/atom 0) + bottom-margin (if (> window-height 600) 32 16)] (fn [] - [react/view {:style {:margin-horizontal 32 - :align-items :center - :justify-content :flex-end}} + [react/view {:style {:align-items :center + :flex 1 + :margin-bottom bottom-margin + :justify-content :flex-end} + :on-layout (fn [e] + (reset! width (-> e .-nativeEvent .-layout .-width)))} [react/scroll-view {:horizontal true :paging-enabled true :ref #(reset! scroll-view-ref %) @@ -48,25 +54,34 @@ :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))] + :style {;:width @width + :margin-bottom bottom-margin}} + (doall + (for [s slides] + ^{:key (:title s)} + [react/view {:style {:flex 1 + :width @width + :justify-content :flex-end + :align-items :center + :padding-horizontal 32}} + (let [margin 32 + size (min @width @height) #_(- (min @width @height) #_(* 2 margin))] + [react/view {:style {:flex 1} + :on-layout (fn [e] + (reset! height (-> e .-nativeEvent .-layout .-height)))} + [react/image {:source (:image s) + :resize-mode :contain + :style {:width size + :height size}}]]) + [react/i18n-text {:style styles/wizard-title :key (:title s)}] + [react/i18n-text {:style styles/wizard-text + :key (:text s)}]]))] + (let [selected (hash-set (quot (int @scroll-x) (int @width)))] [dots-selector {:selected selected :n (count slides) :color colors/blue}])]))) (defview intro [] - (letsubs [window-width [:dimensions/window-width]] + (letsubs [{window-height :height} [:dimensions/window]] [react/view {:style styles/intro-view} [status-bar/status-bar {:flat? true}] [intro-viewer [{:image (:intro1 resources/ui) @@ -77,7 +92,7 @@ :text :intro-text2} {:image (:intro3 resources/ui) :title :intro-title3 - :text :intro-text3}] window-width] + :text :intro-text3}] window-height] [react/view styles/buttons-container [components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16) :on-press #(re-frame/dispatch [:multiaccounts.create.ui/intro-wizard true]) @@ -94,25 +109,35 @@ (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}]) + (let [dimensions (r/atom {})] + (fn [] + [react/view {:on-layout (fn [e] + (reset! dimensions (js->clj (-> e .-nativeEvent .-layout) :keywordize-keys true))) + :style {:align-items :center + :justify-content :center + :flex 1}} + (let [padding 40 + image-size (- (min (:width @dimensions) (:height @dimensions)) padding)] + [react/image {:source (resources/get-image :sample-key) + :resize-mode :contain + :style {:width image-size :height image-size}}])]))) -(defn choose-key [{:keys [multiaccounts selected-id] :as wizard-state} view-height] +(defn choose-key [{:keys [multiaccounts selected-id view-height]}] [react/scroll-view {:content-container-style {:flex 1 - :justify-content :flex-end + :justify-content (if (< view-height 600) :flex-start :flex-end) + :margin-top (if (< view-height 600) 20 0) ;; We have to align top multiaccount entry ;; with top key storage entry on the next screen :margin-bottom (if (< view-height 600) - -20 + 0 (/ view-height 12))}} - (for [acc multiaccounts] + (for [[acc accessibility-n] (map vector multiaccounts (range (count multiaccounts)))] (let [selected? (= (:id acc) selected-id) public-key (get-in acc [:derived constants/path-whisper-keyword :publicKey])] ^{:key public-key} [react/touchable-highlight - {:on-press #(re-frame/dispatch [:intro-wizard/on-key-selected (:id acc)])} + {:accessibility-label (keyword (str "select-account-button-" accessibility-n)) + :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 public-key)} @@ -140,11 +165,12 @@ [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 (if (and config/hardwallet-enabled? + {:accessibility-label (keyword (str "select-storage-" type)) + :on-press #(re-frame/dispatch [:intro-wizard/on-key-storage-selected (if (and config/hardwallet-enabled? platform/android?) type :default)])} [react/view (assoc (styles/list-item selected?) :align-items :flex-start - :padding-top 20 + :padding-top 16 :padding-bottom 12) (if image [react/image @@ -160,7 +186,7 @@ (i18n/label desc)]] [radio/radio selected?]]]])) -(defn select-key-storage [{:keys [selected-storage-type] :as wizard-state} view-height] +(defn select-key-storage [{:keys [selected-storage-type view-height]}] (let [storage-types [{:type :default :icon :main-icons/mobile :icon-width 24 @@ -175,12 +201,12 @@ :title :keycard :desc :keycard-desc}]] [react/view {:style {:flex 1 - :justify-content :flex-end + :justify-content (if (< view-height 600) :flex-start :flex-end) ;; We have to align top storage entry ;; with top multiaccount entry on the previous screen - :margin-bottom (+ (- 300 232) (if (< view-height 600) - -20 - (/ view-height 12)))}} + :margin-bottom (if (< view-height 600) + 0 + (+ (- 322 226) (/ 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]])) @@ -197,16 +223,17 @@ [react/text-input {:secure-text-entry true :auto-focus true + :accessibility-label :password-input :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] +(defn create-code [{:keys [confirm-failure? view-width]}] [password-container confirm-failure? view-width]) -(defn confirm-code [{:keys [confirm-failure? processing?] :as wizard-state} view-width] +(defn confirm-code [{:keys [confirm-failure? processing? view-width]}] (if processing? [react/view {:style {:justify-content :center :align-items :center}} @@ -217,63 +244,71 @@ (i18n/label :t/processing)]] [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? +(defn bottom-bar [{:keys [step weak-password? encrypt-with-password? + forward-action + next-button-disabled? processing?] :as wizard-state}] - [react/view {:style {:margin-bottom (if (or (#{:choose-key :select-key-storage} step) + [react/view {:style {:margin-bottom (if (or (#{:choose-key :select-key-storage + :enter-phrase :recovery-success} 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) + (cond (and (#{:generate-key :recovery-success} step) processing?) + [react/view {:min-height 46 :max-height 46 :align-self :stretch} + [react/activity-indicator {:animating true + :size :large}]] + (#{:generate-key :recovery-success :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)}]) + :recovery-success :re-encrypt-key + :enable-notifications :intro-wizard-title6)] + [react/view {:min-height 46 :max-height 46} + [components.common/button {:button-style styles/bottom-button + :on-press #(re-frame/dispatch + [forward-action]) + :accessibility-label :onboarding-next-button + :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) + :accessibility-label :encrypt-with-password-button :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? (or processing? - (and (= step :create-code) weak-password?)) - :forward? true}]]) - (when (#{:enable-fingerprint :enable-notifications} step) + [react/view {:style {:margin-right 10}} + [components.common/bottom-button {:on-press #(re-frame/dispatch [forward-action]) + :accessibility-label :onboarding-next-button + :disabled? (or processing? + (and (= step :create-code) weak-password?) + (and (= step :enter-phrase) next-button-disabled?)) + :forward? true}]]]) + (when (= :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}]) + :accessibility-label :skip-notifications-button + :on-press #(re-frame/dispatch [forward-action {:skip? true}]) :background? false}]) - (when (= :generate-key step) + + (when (or (= :generate-key step) (and processing? (= :recovery-success 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))])]) + (i18n/label (cond (= :recovery-success step) + :t/processing + processing? :t/generating-keys + :else :t/this-will-take-few-seconds))])]) (defn top-bar [{:keys [step encrypt-with-password?]}] (let [hide-subtitle? (or (= step :confirm-code) + (= step :enter-phrase) (and (#{:create-code :confirm-code} step) encrypt-with-password?))] [react/view {:style {:margin-top 16 :margin-horizontal 32}} @@ -281,8 +316,14 @@ [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))))] + (i18n/label + (cond (= step :enter-phrase) + :t/multiaccounts-recover-enter-phrase-title + (= step :recovery-success) + :t/keycard-recovery-success-header + :else (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} @@ -290,35 +331,260 @@ [{: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}} + :style {:color colors/blue} + :accessibility-label :learn-more} (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))))] + (i18n/label (cond (= step :recovery-success) + :t/recovery-success-text + :else (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}} +(defn enter-phrase [{:keys [processing? + passphrase-word-count + next-button-disabled? + passphrase-error] :as wizard-state}] + [react/keyboard-avoiding-view {:flex 1 + :justify-content :flex-start + :background-color colors/white} + [text-input/text-input-with-label + {:on-change-text #(re-frame/dispatch [:multiaccounts.recover/enter-phrase-input-changed (security/mask-data %)]) + :auto-focus true + :error (when passphrase-error (i18n/label passphrase-error)) + :accessibility-label :passphrase-input + :placeholder nil + :bottom-value 40 + :multiline true + :auto-correct false + :keyboard-type "visible-password" + :parent-container {:flex 1 + :align-self :stretch + :justify-content :center + :align-items :center} + :container {:background-color :white + :flex 1 + :justify-content :center + :align-items :center} + :style (merge {:background-color :white + :text-align :center + :text-align-vertical :center + :min-width 40 + :font-size 16 + :font-weight "700"} + (when platform/android? + {:flex 1}))}] + [react/view {:align-items :center} + (if passphrase-word-count + [react/view {:flex-direction :row + :margin-bottom 4 + :min-height 24 + :max-height 24 + :align-items :center} + [react/nested-text {:style {:font-size 14 + :padding-right 4 + :text-align :center + :color colors/gray}} + (str (i18n/label :t/word-count) ": ") + [{:style {:font-weight "500" + :color colors/black}} + (i18n/label-pluralize passphrase-word-count :t/words-n)]] + (when-not next-button-disabled? + [react/view {:style {:background-color colors/green-transparent-10 + :border-radius 12 + :width 24 + :justify-content :center + :align-items :center + :height 24}} + [vector-icons/tiny-icon :tiny-icons/tiny-check {:color colors/green}]])] + [react/view {:align-self :stretch :margin-bottom 4 + :max-height 24 :min-height 24}]) + [react/text {:style {:color colors/gray + :font-size 14 + :margin-bottom 8 + :text-align :center}} + (i18n/label :t/multiaccounts-recover-enter-phrase-text)]] + (when processing? + [react/view {:flex 1 :align-items :center} + [react/activity-indicator {:size :large + :animating true}] + [react/text {:style {:color colors/gray + :margin-top 8}} + (i18n/label :t/processing)]])]) + +(defn recovery-success [pubkey] + [react/view {:flex 1 + :justify-content :space-between + :background-color colors/white} + [react/view {:flex 1 + :justify-content :space-between + :align-items :center} + [react/view {:flex-direction :column + :flex 1 + :justify-content :center + :align-items :center} + [react/view {:margin-horizontal 16 + :flex-direction :column} + [react/view {:justify-content :center + :align-items :center + :margin-bottom 11} + [react/image {:source {:uri (identicon/identicon pubkey)} + :style {:width 61 + :height 61 + :border-radius 30 + :border-width 1 + :border-color colors/black-transparent}}]] + [react/text {:style {:text-align :center + :color colors/black + :font-weight "500"} + :number-of-lines 1 + :ellipsize-mode :middle} + (gfy/generate-gfy pubkey)] + [react/text {:style {:text-align :center + :margin-top 4 + :color colors/gray + :font-family "monospace"} + :number-of-lines 1 + :ellipsize-mode :middle} + (utils/get-shortened-address pubkey)]]]]]) + +(defview wizard-generate-key [] + (letsubs [wizard-state [:intro-wizard/generate-key]] + [react/view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + (toolbar/nav-button + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back]))) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :generate-key}] + [generate-key] + [bottom-bar {:step :generate-key + :forward-action :intro-wizard/step-forward-pressed + :processing? (:processing? wizard-state)}]]])) + +(defview wizard-choose-key [] + (letsubs [wizard-state [:intro-wizard/choose-key]] + [react/view {:style {:flex 1}} [toolbar/toolbar {:style {:border-bottom-width 0 - :margin-top 0}} - (when-not (#{:enable-fingerprint :enable-notifications} step) + :margin-left 16 + :margin-top 16}} + (toolbar/nav-text + {:handler #(re-frame/dispatch [:intro-wizard/navigate-back])} + (i18n/label :t/cancel)) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :choose-key}] + [choose-key wizard-state] + [bottom-bar {:step :choose-key + :forward-action :intro-wizard/step-forward-pressed}]]])) + +(defview wizard-select-key-storage [] + (letsubs [wizard-state [:intro-wizard/select-key-storage]] + [react/view {:style {:flex 1}} + [toolbar/toolbar + {:style (merge {:border-bottom-width 0 + :margin-top 16} + (when (:recovering? wizard-state) + {:margin-left 16}))} + (if (:recovering? wizard-state) + (toolbar/nav-text + {:handler #(re-frame/dispatch [:intro-wizard/navigate-back])} + (i18n/label :t/cancel)) (toolbar/nav-button - (actions/back #(re-frame/dispatch - [:intro-wizard/step-back-pressed])))) + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back])))) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :select-key-storage}] + [select-key-storage wizard-state] + [bottom-bar {:step :select-key-storage + :forward-action (:forward-action wizard-state)}]]])) + +(defview wizard-create-code [] + (letsubs [wizard-state [:intro-wizard/create-code]] + [react/keyboard-avoiding-view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + (toolbar/nav-button + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back]))) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :create-code :encrypt-with-password? (:encrypt-with-password? wizard-state)}] + [create-code wizard-state] + [bottom-bar (merge {:step :create-code + :forward-action (:forward-action wizard-state)} + wizard-state)]]])) + +(defview wizard-confirm-code [] + (letsubs [wizard-state [:intro-wizard/confirm-code]] + [react/keyboard-avoiding-view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + (toolbar/nav-button + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back]))) 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]]])) + [top-bar {:step :confirm-code :encrypt-with-password? (:encrypt-with-password? wizard-state)}] + [confirm-code wizard-state] + [bottom-bar (merge {:step :confirm-code + :forward-action (:forward-action wizard-state)} + wizard-state)]]])) + +(defn wizard-enable-notifications [] + [react/view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + nil + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :enable-notifications}] + [enable-notifications] + [bottom-bar {:step :enable-notifications + :forward-action :intro-wizard/step-forward-pressed}]]]) + +(defview wizard-enter-phrase [] + (letsubs [wizard-state [:intro-wizard/enter-phrase]] + [react/keyboard-avoiding-view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + (toolbar/nav-button + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back]))) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :enter-phrase}] + [enter-phrase wizard-state] + [bottom-bar (merge {:step :enter-phrase + :forward-action :multiaccounts.recover/enter-phrase-next-pressed} + wizard-state)]]])) + +(defview wizard-recovery-success [] + (letsubs [{:keys [pubkey processing?]} [:intro-wizard/recovery-success]] + [react/view {:style {:flex 1}} + [toolbar/toolbar + {:style {:border-bottom-width 0 + :margin-top 16}} + (toolbar/nav-button + (actions/back #(re-frame/dispatch [:intro-wizard/navigate-back]))) + nil] + [react/view {:style {:flex 1 + :justify-content :space-between}} + [top-bar {:step :recovery-success}] + [recovery-success pubkey] + [bottom-bar {:step :recovery-success + :forward-action :multiaccounts.recover/re-encrypt-pressed + :processing? processing?}]]])) + diff --git a/src/status_im/ui/screens/keycard/recovery/views.cljs b/src/status_im/ui/screens/keycard/recovery/views.cljs index 959f2a7f7d5..597beec6fdb 100644 --- a/src/status_im/ui/screens/keycard/recovery/views.cljs +++ b/src/status_im/ui/screens/keycard/recovery/views.cljs @@ -25,8 +25,7 @@ {:transparent? true :style {:margin-top 32}} (toolbar/nav-button - (actions/back #(re-frame/dispatch - [::multiaccounts.recover/cancel-pressed]))) + (actions/back #(re-frame/dispatch [:navigate-back]))) nil] [react/view {:flex 1 :flex-direction :column diff --git a/src/status_im/ui/screens/multiaccounts/recover/views.cljs b/src/status_im/ui/screens/multiaccounts/recover/views.cljs index 9d69bea3479..0ec3a66a260 100644 --- a/src/status_im/ui/screens/multiaccounts/recover/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/recover/views.cljs @@ -89,320 +89,3 @@ (def bottom-sheet {:content bottom-sheet-view :content-height (if platform/android? 130 65)}) - -(defview enter-phrase [] - (letsubs [{:keys [processing? - passphrase-error - words-count - next-button-disabled?]} [:get-recover-multiaccount]] - [react/keyboard-avoiding-view {:flex 1 - :justify-content :space-between - :background-color colors/white} - [toolbar/toolbar - {:transparent? true - :style {:margin-top 32}} - [toolbar/nav-text - {:handler #(re-frame/dispatch [::multiaccounts.recover/cancel-pressed]) - :style {:padding-left 21}} - (i18n/label :t/cancel)] - [react/text {:style {:color colors/gray}} - (i18n/label :t/step-i-of-n {:step "1" - :number "2"})]] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/multiaccounts-recover-enter-phrase-title)]] - [react/view {:margin-top 16} - [text-input/text-input-with-label - {:on-change-text #(re-frame/dispatch [::multiaccounts.recover/enter-phrase-input-changed (security/mask-data %)]) - :auto-focus true - :error (when passphrase-error (i18n/label passphrase-error)) - :placeholder nil - :height 120 - :multiline true - :auto-correct false - :keyboard-type "visible-password" - :container {:background-color :white - :min-width "50%"} - :style {:background-color :white - :text-align :center - :font-size 16 - :font-weight "700"}}]] - [react/view {:align-items :center} - (when words-count - [react/view {:flex-direction :row - :height 14 - :align-items :center} - (when-not next-button-disabled? - [vector-icons/tiny-icon :tiny-icons/tiny-check]) - [react/text {:style {:font-size 14 - :padding-left 4 - :text-align :center - :color colors/black}} - (i18n/label-pluralize words-count :t/words-n)]])] - (when next-button-disabled? - [react/view {:margin-top 17 - :align-items :center} - [react/text {:style {:color colors/black - :font-size 14 - :text-align :center}} - (i18n/label :t/multiaccounts-recover-enter-phrase-text)]])] - (when processing? - [react/view - [react/activity-indicator {:size :large - :animating true}] - [react/text {:style {:color colors/gray - :margin-top 8}} - (i18n/label :t/processing)]]) - [react/view {:flex-direction :row - :justify-content :space-between - :align-items :center - :width "100%" - :height 86} - (when-not processing? - [react/view]) - (when-not processing? - [react/view {:margin-right 20} - [components.common/bottom-button - {:on-press #(re-frame/dispatch [::multiaccounts.recover/enter-phrase-next-pressed]) - :label (i18n/label :t/next) - :disabled? next-button-disabled? - :forward? true}]])]]])) - -(defview success [] - (letsubs [multiaccount [:get-recover-multiaccount]] - (let [pubkey (get-in multiaccount [:derived constants/path-whisper-keyword :publicKey])] - [react/view {:flex 1 - :justify-content :space-between - :background-color colors/white} - [toolbar/toolbar - {:transparent? true - :style {:margin-top 32}} - nil - nil] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/keycard-recovery-success-header)]] - [react/view {:margin-top 16 - :width "85%" - :align-items :center} - [react/text {:style {:color colors/gray - :text-align :center}} - (i18n/label :t/recovery-success-text)]]] - [react/view {:flex-direction :column - :flex 1 - :justify-content :center - :align-items :center} - [react/view {:margin-horizontal 16 - :flex-direction :column} - [react/view {:justify-content :center - :align-items :center - :margin-bottom 11} - [react/image {:source {:uri (identicon/identicon pubkey)} - :style {:width 61 - :height 61 - :border-radius 30 - :border-width 1 - :border-color colors/black-transparent}}]] - [react/text {:style {:text-align :center - :color colors/black - :font-weight "500"} - :number-of-lines 1 - :ellipsize-mode :middle} - (gfy/generate-gfy pubkey)] - [react/text {:style {:text-align :center - :margin-top 4 - :color colors/gray - :font-family "monospace"} - :number-of-lines 1 - :ellipsize-mode :middle} - (utils/get-shortened-address pubkey)]]] - [react/view {:margin-bottom 50} - [react/touchable-highlight - {:on-press #(re-frame/dispatch [::multiaccounts.recover/re-encrypt-pressed])} - [react/view {:background-color colors/blue-light - :align-items :center - :justify-content :center - :flex-direction :row - :width 193 - :height 44 - :border-radius 10} - [react/text {:style {:color colors/blue}} - (i18n/label :t/re-encrypt-key)]]]]]]))) - -(defview select-storage [] - (letsubs [{:keys [selected-storage-type]} [:intro-wizard] - {view-height :height} [:dimensions/window]] - [react/view {:flex 1 - :justify-content :space-between - :background-color colors/white} - [toolbar/toolbar - {:transparent? true - :style {:margin-top 32}} - [toolbar/nav-text - {:handler #(re-frame/dispatch [::multiaccounts.recover/cancel-pressed]) - :style {:padding-left 21}} - (i18n/label :t/cancel)] - nil] - [react/view {:flex 1 - :justify-content :space-between} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/intro-wizard-title3)]] - [react/view {:margin-top 16 - :width "85%" - :align-items :center} - [react/text {:style {:color colors/gray - :text-align :center}} - (i18n/label :t/intro-wizard-text3)]]] - [intro.views/select-key-storage {:selected-storage-type (if config/hardwallet-enabled? selected-storage-type :default)} view-height] - [react/view {:flex-direction :row - :justify-content :space-between - :align-items :center - :width "100%" - :height 86} - [react/view components.styles/flex] - [react/view {:margin-right 20} - [components.common/bottom-button - {:on-press #(re-frame/dispatch [::multiaccounts.recover/select-storage-next-pressed]) - :forward? true}]]]]])) - -(defview enter-password [] - (letsubs [{:keys [password password-error]} [:get-recover-multiaccount]] - [react/keyboard-avoiding-view {:flex 1 - :justify-content :space-between - :background-color colors/white} - [toolbar/toolbar - {:transparent? true - :style {:margin-top 32}} - [toolbar/nav-text - {:handler #(re-frame/dispatch [::multiaccounts.recover/cancel-pressed]) - :style {:padding-left 21}} - (i18n/label :t/cancel)] - [react/text {:style {:color colors/gray}} - (i18n/label :t/step-i-of-n {:step "1" - :number "2"})]] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/intro-wizard-title-alt4)]] - [react/view {:margin-top 16 - :width "85%" - :align-items :center} - [react/text {:style {:color colors/gray - :text-align :center}} - (i18n/label :t/password-description)]] - [react/view {:margin-top 16} - [text-input/text-input-with-label - {:on-change-text #(re-frame/dispatch [::multiaccounts.recover/enter-password-input-changed (security/mask-data %)]) - :auto-focus true - :on-submit-editing #(re-frame/dispatch [::multiaccounts.recover/enter-password-input-submitted]) - :secure-text-entry true - :error (when password-error (i18n/label password-error)) - :placeholder nil - :height 125 - :multiline false - :auto-correct false - :container {:background-color :white - :min-width "50%"} - :style {:background-color :white - :width 200 - :text-align :center - :font-size 20 - :font-weight "700"}}]]] - [react/view {:flex-direction :row - :justify-content :space-between - :align-items :center - :width "100%" - :height 86} - [react/view] - [react/view {:margin-right 20} - [components.common/bottom-button - {:on-press #(re-frame/dispatch [::multiaccounts.recover/enter-password-next-pressed]) - :label (i18n/label :t/next) - :disabled? (empty? password) - :forward? true}]]]]])) - -(defview confirm-password [] - (letsubs [{:keys [password-confirmation password-error]} [:get-recover-multiaccount]] - [react/keyboard-avoiding-view {:flex 1 - :justify-content :space-between - :background-color colors/white} - [toolbar/toolbar - {:transparent? true - :style {:margin-top 32}} - [toolbar/nav-text - {:handler #(re-frame/dispatch [::multiaccounts.recover/cancel-pressed]) - :style {:padding-left 21}} - (i18n/label :t/cancel)] - [react/text {:style {:color colors/gray}} - (i18n/label :t/step-i-of-n {:step "1" - :number "2"})]] - [react/view {:flex 1 - :flex-direction :column - :justify-content :space-between - :align-items :center} - [react/view {:flex-direction :column - :align-items :center} - [react/view {:margin-top 16} - [react/text {:style {:typography :header - :text-align :center}} - (i18n/label :t/intro-wizard-title-alt5)]] - [react/view {:margin-top 16 - :width "85%" - :align-items :center} - [react/text {:style {:color colors/gray - :text-align :center}} - (i18n/label :t/password-description)]] - [react/view {:margin-top 16} - [text-input/text-input-with-label - {:on-change-text #(re-frame/dispatch [::multiaccounts.recover/confirm-password-input-changed %]) - :auto-focus true - :on-submit-editing #(re-frame/dispatch [::multiaccounts.recover/confirm-password-input-submitted]) - :error (when password-error (i18n/label password-error)) - :secure-text-entry true - :placeholder nil - :height 125 - :multiline false - :auto-correct false - :container {:background-color :white - :min-width "50%"} - :style {:background-color :white - :width 200 - :text-align :center - :font-size 20 - :font-weight "700"}}]]] - [react/view {:flex-direction :row - :justify-content :space-between - :align-items :center - :width "100%" - :height 86} - [react/view] - [react/view {:margin-right 20} - [components.common/bottom-button - {:on-press #(re-frame/dispatch [::multiaccounts.recover/confirm-password-next-pressed]) - :label (i18n/label :t/next) - :disabled? (empty? password-confirmation) - :forward? true}]]]]])) diff --git a/src/status_im/ui/screens/navigation.cljs b/src/status_im/ui/screens/navigation.cljs index 12600001f97..c8463cfe57f 100644 --- a/src/status_im/ui/screens/navigation.cljs +++ b/src/status_im/ui/screens/navigation.cljs @@ -1,7 +1,9 @@ (ns status-im.ui.screens.navigation (:require [re-frame.core :as re-frame] + [status-im.react-native.js-dependencies :as js-dependencies] [status-im.utils.handlers :as handlers] [status-im.utils.navigation :as navigation] + [status-im.utils.platform :as platform] [taoensso.timbre :as log] [status-im.utils.fx :as fx])) @@ -135,3 +137,37 @@ (assoc :prev-tab-view-id (:view-id db)) (assoc :prev-view-id (:view-id db)))} (navigate-to-cofx view-id {})))) + +;; This atom stores event vector +;; to be dispatched when a react-navigation's BACK +;; actions is invoked +(def wizard-back-event (atom nil)) + +;; This atom exists in order to avoid +;; endless loop when processing NavigationActions/BACK +;; in react-navigation's getStateForAction fn +(def processing-back-event? (atom false)) + +(fx/defn reset-processing-flag + {:events [:navigation/reset-processing-flag]} + [{:keys [db] :as cofx}] + {::reset-processing-flag nil}) + +(re-frame/reg-fx + ::reset-processing-flag + (fn [] + (reset! processing-back-event? false))) + +;; Below two effects are added when we need +;; to override default react-navigation's BACK action +;; processing +(re-frame/reg-fx + ::add-wizard-back-event + (fn [event] + (reset! wizard-back-event event))) + +(re-frame/reg-fx + ::remove-wizard-back-event + (fn [event] + (reset! processing-back-event? false) + (reset! wizard-back-event nil))) diff --git a/src/status_im/ui/screens/routing/core.cljs b/src/status_im/ui/screens/routing/core.cljs index b01e4def67b..dd37b769bc9 100644 --- a/src/status_im/ui/screens/routing/core.cljs +++ b/src/status_im/ui/screens/routing/core.cljs @@ -1,6 +1,7 @@ (ns status-im.ui.screens.routing.core (:require [status-im.ui.components.react :as react] + [status-im.ui.screens.navigation :as screens.navigation] [status-im.ui.components.styles :as common-styles] [status-im.utils.navigation :as navigation] [cljs-react-navigation.reagent :as nav-reagent] @@ -25,7 +26,7 @@ (defn navigation-events [current-view-id modal? screen-focused?] [:> navigation/navigation-events {:on-will-focus - (fn [] + (fn [payload] (reset! screen-focused? true) (when (not= @view-id current-view-id) (reset! view-id current-view-id)) @@ -38,8 +39,9 @@ (when-not modal? (status-bar/set-status-bar current-view-id))) :on-will-blur - (fn [] + (fn [payload] (reset! screen-focused? false) + (log/debug :on-will-blur current-view-id) ;; Reset currently mounted text inputs to their default values ;; on navigating away; this is a privacy measure (doseq [[text-input default-value] @react/text-input-refs] @@ -117,27 +119,45 @@ (utils/update-if-present :mode name))) (defn stack-navigator [routes config] - (nav-reagent/stack-navigator - routes - (merge {:headerMode "none" - :cardStyle {:backgroundColor :white} - #_:transitionConfig - #_(fn [] - #js {:transitionSpec #js{:duration 10}}) - :onTransitionStart (fn [n] - (let [idx (.. n - -navigation - -state - -index) - routes (.. n - -navigation - -state - -routes)] - (when (and (array? routes) (int? idx)) - (let [route (aget routes idx) - route-name (keyword (.-routeName route))] - (tabbar/minimize-bar route-name)))))} - (prepare-config config)))) + (let [res (nav-reagent/stack-navigator + routes + (merge {:headerMode "none" + :cardStyle {:backgroundColor :white} + #_:transitionConfig + #_(fn [] + #js {:transitionSpec #js{:duration 10}}) + :onTransitionStart (fn [n] + (let [idx (.. n + -navigation + -state + -index) + routes (.. n + -navigation + -state + -routes)] + (when (and (array? routes) (int? idx)) + (let [route (aget routes idx) + route-name (keyword (.-routeName route))] + (tabbar/minimize-bar route-name)))))} + (prepare-config config))) + default-get-state-for-action (.-getStateForAction (.-router res)) + new-get-state-for-action (fn [action state] + ;; Override default getStateForAction on this stack navigator + ;; if we have a custom event set in wizard-back-event atom + (if (and (= (.-type action) (.-BACK navigation/navigation-actions)) + @screens.navigation/wizard-back-event) + (if @screens.navigation/processing-back-event? + (do + (reset! screens.navigation/processing-back-event? false) + (default-get-state-for-action action state)) + (do + (reset! screens.navigation/processing-back-event? true) + (re-frame/dispatch @screens.navigation/wizard-back-event) + ;; Return nil so that BACK event processing ends here + nil)) + (default-get-state-for-action action state)))] + (set! (-> res .-router .-getStateForAction) new-get-state-for-action) + res)) (defn twopane-navigator [routes config] (navigation/twopane-navigator @@ -176,7 +196,7 @@ - keyword, which points to some specific route - vector of [:modal :screen-key] type when screen should be wrapped as modal - map with `name`, `screens`, `config` keys, where `screens` is a vector - of children and `config` is `stack-navigator` configuration" + of children and `config` is `stack-navigator` configuration" (let [[screen-name screen-config] (cond (keyword? screen) [screen (screens/get-screen screen)] @@ -198,6 +218,12 @@ :else (nav-reagent/stack-screen (wrap screen-name screen-config)))] [screen-name (cond-> {:screen res} + ;; TODO issue #8947 + ;; replace this hack with configuration + (#{:create-multiaccount-choose-key + :recover-multiaccount-select-storage} + screen-name) + (assoc :navigationOptions {:gesturesEnabled false}) (:navigation screen-config) (assoc :navigationOptions (:navigation screen-config)))]))) diff --git a/src/status_im/ui/screens/routing/intro_login_stack.cljs b/src/status_im/ui/screens/routing/intro_login_stack.cljs index b9f380f98d6..818eba0eee8 100644 --- a/src/status_im/ui/screens/routing/intro_login_stack.cljs +++ b/src/status_im/ui/screens/routing/intro_login_stack.cljs @@ -5,6 +5,12 @@ #{:login :progress :create-multiaccount + :create-multiaccount-generate-key + :create-multiaccount-choose-key + :create-multiaccount-select-key-storage + :create-multiaccount-create-code + :create-multiaccount-confirm-code + :create-multiaccount-enable-notifications :recover-multiaccount-enter-phrase :recover-multiaccount-select-storage :recover-multiaccount-enter-password @@ -52,6 +58,12 @@ :progress :keycard-recovery-intro :create-multiaccount + :create-multiaccount-generate-key + :create-multiaccount-choose-key + :create-multiaccount-select-key-storage + :create-multiaccount-create-code + :create-multiaccount-confirm-code + :create-multiaccount-enable-notifications :recover-multiaccount-enter-phrase :recover-multiaccount-select-storage :recover-multiaccount-enter-password diff --git a/src/status_im/ui/screens/routing/screens.cljs b/src/status_im/ui/screens/routing/screens.cljs index 6817223bc7c..ba73cf37127 100644 --- a/src/status_im/ui/screens/routing/screens.cljs +++ b/src/status_im/ui/screens/routing/screens.cljs @@ -73,14 +73,19 @@ (def all-screens {:login login/login :progress progress/progress - :recover-multiaccount-enter-phrase multiaccounts.recover/enter-phrase - :recover-multiaccount-select-storage multiaccounts.recover/select-storage - :recover-multiaccount-enter-password multiaccounts.recover/enter-password - :recover-multiaccount-confirm-password multiaccounts.recover/confirm-password - :recover-multiaccount-success multiaccounts.recover/success + :create-multiaccount-generate-key intro/wizard-generate-key + :create-multiaccount-choose-key intro/wizard-choose-key + :create-multiaccount-select-key-storage intro/wizard-select-key-storage + :create-multiaccount-create-code intro/wizard-create-code + :create-multiaccount-confirm-code intro/wizard-confirm-code + :create-multiaccount-enable-notifications intro/wizard-enable-notifications + :recover-multiaccount-enter-phrase intro/wizard-enter-phrase + :recover-multiaccount-success intro/wizard-recovery-success + :recover-multiaccount-select-storage intro/wizard-select-key-storage + :recover-multiaccount-enter-password intro/wizard-create-code + :recover-multiaccount-confirm-password intro/wizard-confirm-code :multiaccounts multiaccounts/multiaccounts :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 diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 4c47945a7eb..7c98dfa471d 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -30,6 +30,20 @@ (defonce initial-view-id (atom nil)) +(defn bottom-sheet-comp [opts height-atom] + ;; We compute bottom sheet height dynamically by rendering it + ;; on an invisible view; then, if height is already available + ;; (either because it is statically provided or computed), + ;; we render the sheet itself + (if (or (not @height-atom) (= 0 @height-atom)) + [react/view {:style {:position :absolute :opacity 0} + :on-layout (fn [e] + (let [h (-> e .-nativeEvent .-layout .-height)] + (reset! height-atom h)))} + (when (:content opts) + [(:content opts)])] + [bottom-sheet/bottom-sheet (assoc opts :content-height @height-atom)])) + (views/defview bottom-sheet [] (views/letsubs [{:keys [show? view]} [:bottom-sheet]] (let [opts (cond-> {:show? show? @@ -53,6 +67,9 @@ (= view :keycard.login/more) (merge keycard/more-sheet) + (= view :learn-more) + (merge about-app/learn-more) + (= view :private-chat-actions) (merge home.sheet/private-chat-actions) @@ -60,9 +77,9 @@ (merge home.sheet/group-chat-actions) (= view :recover-sheet) - (merge recover.views/bottom-sheet))] - - [bottom-sheet/bottom-sheet opts]))) + (merge recover.views/bottom-sheet)) + height-atom (reagent/atom (if (:content-height opts) (:content-height opts) nil))] + [bottom-sheet-comp opts height-atom]))) (defn reset-component-on-mount [view-id component two-pane?] (when (and @initial-view-id diff --git a/test/appium/views/sign_in_view.py b/test/appium/views/sign_in_view.py index b52765e3cda..3f5723943ca 100644 --- a/test/appium/views/sign_in_view.py +++ b/test/appium/views/sign_in_view.py @@ -189,7 +189,6 @@ def create_user(self, password=common_password): self.confirm_your_password_input.set_value(password) self.next_button.click() self.maybe_later_button.click() - self.maybe_later_button.click() return self.get_home_view() def recover_access(self, passphrase: str, password: str = common_password): diff --git a/test/cljs/status_im/test/i18n.cljs b/test/cljs/status_im/test/i18n.cljs index a010f524677..a806a773fab 100644 --- a/test/cljs/status_im/test/i18n.cljs +++ b/test/cljs/status_im/test/i18n.cljs @@ -547,7 +547,6 @@ :intro-wizard-text3 :intro-wizard-text4 :intro-wizard-text6 - :intro-wizard-text7 :intro-wizard-title-alt4 :intro-wizard-title-alt5 :intro-wizard-title1 @@ -556,7 +555,6 @@ :intro-wizard-title4 :intro-wizard-title5 :intro-wizard-title6 - :intro-wizard-title7 :invalid-extension :invalid-format :invalid-key-confirm diff --git a/test/cljs/status_im/test/multiaccounts/recover/core.cljs b/test/cljs/status_im/test/multiaccounts/recover/core.cljs index 7bd29a42876..5c843d305ae 100644 --- a/test/cljs/status_im/test/multiaccounts/recover/core.cljs +++ b/test/cljs/status_im/test/multiaccounts/recover/core.cljs @@ -8,32 +8,6 @@ ;;;; helpers -(deftest check-password-errors - (is (= :required-field (models/check-password-errors nil))) - (is (= :required-field (models/check-password-errors " "))) - (is (= :required-field (models/check-password-errors " \t\n "))) - (is (= :recover-password-too-short) (models/check-password-errors "a")) - (is (= :recover-password-too-short) (models/check-password-errors "abc")) - (is (= :recover-password-too-short) (models/check-password-errors "12345")) - (is (nil? (models/check-password-errors "123456"))) - (is (nil? (models/check-password-errors "thisisapasswoord")))) - -(deftest check-phrase-errors - (is (= :required-field (models/check-phrase-errors nil))) - (is (= :required-field (models/check-phrase-errors " "))) - (is (= :required-field (models/check-phrase-errors " \t\n "))) - (is (= :recovery-phrase-wrong-length (models/check-phrase-errors "phrase with four words"))) - (is (= :recovery-phrase-wrong-length (models/check-phrase-errors "phrase with five cool words"))) - (is (nil? (models/check-phrase-errors "monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey"))) - (is (nil? (models/check-phrase-errors (string/join " " (repeat 15 "monkey"))))) - (is (nil? (models/check-phrase-errors (string/join " " (repeat 18 "monkey"))))) - (is (nil? (models/check-phrase-errors (string/join " " (repeat 24 "monkey"))))) - (is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 14 "monkey"))))) - (is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 11 "monkey"))))) - (is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 19 "monkey"))))) - (is (= :recovery-phrase-invalid (models/check-phrase-errors "monkey monkey monkey 12345 monkey adf+123 monkey monkey monkey monkey monkey monkey"))) - ;;NOTE(goranjovic): the following check should be ok because we sanitize extra whitespace - (is (nil? (models/check-phrase-errors " monkey monkey monkey\t monkey monkey monkey monkey monkey monkey monkey monkey monkey \t ")))) (deftest check-phrase-warnings (is (nil? (models/check-phrase-warnings "monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey"))) @@ -43,75 +17,39 @@ ;;;; handlers (deftest set-phrase - (is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} + (is (= {:db {:intro-wizard {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" + :passphrase-error nil + :next-button-disabled? false}}} (models/set-phrase {:db {}} (security/mask-data "game buzz method pretty olympic fat quit display velvet unveil marine crater")))) - (is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} + (is (= {:db {:intro-wizard {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" + :passphrase-error nil + :next-button-disabled? false}}} (models/set-phrase {:db {}} (security/mask-data "Game buzz method pretty Olympic fat quit DISPLAY velvet unveil marine crater")))) - (is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} + (is (= {:db {:intro-wizard {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater" + :passphrase-error nil + :next-button-disabled? false}}} (models/set-phrase {:db {}} (security/mask-data "game buzz method pretty zeus fat quit display velvet unveil marine crater")))) - (is (= {:db {:multiaccounts/recover {:passphrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater " - :passphrase-error nil - :next-button-disabled? false}}} + (is (= {:db {:intro-wizard {:passphrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater " + :passphrase-error nil + :next-button-disabled? false}}} (models/set-phrase {:db {}} (security/mask-data " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater ")))) - (is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater" - :passphrase-error nil - :next-button-disabled? false}}} + (is (= {:db {:intro-wizard {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater" + :passphrase-error nil + :next-button-disabled? false}}} (models/set-phrase {:db {}} (security/mask-data "game buzz method pretty 1234 fat quit display velvet unveil marine crater"))))) -(deftest validate-phrase - (is (= {:db {:multiaccounts/recover {:passphrase-error nil - :passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}} - (models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}}))) - (is (= {:db {:multiaccounts/recover {:passphrase-error :recovery-phrase-unknown-words - :passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}} - (models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}}))) - (is (= {:db {:multiaccounts/recover {:passphrase-error :recovery-phrase-invalid - :passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}} - (models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}})))) - -(deftest set-password - (is (= {:db {:multiaccounts/recover {:password " " - :password-error nil - :password-valid? false}}} - (models/set-password {:db {}} (security/mask-data " ")))) - (is (= {:db {:multiaccounts/recover {:password "abc" - :password-error nil - :password-valid? false}}} - (models/set-password {:db {}} (security/mask-data "abc")))) - (is (= {:db {:multiaccounts/recover {:password "thisisapaswoord" - :password-error nil - :password-valid? true}}} - (models/set-password {:db {}} (security/mask-data "thisisapaswoord"))))) - -(deftest validate-password - (is (= {:db {:multiaccounts/recover {:password " " - :password-error :required-field}}} - (models/validate-password {:db {:multiaccounts/recover {:password " "}}}))) - (is (= {:db {:multiaccounts/recover {:password "abc" - :password-error :recover-password-too-short}}} - (models/validate-password {:db {:multiaccounts/recover {:password "abc"}}}))) - (is (= {:db {:multiaccounts/recover {:password "thisisapaswoord" - :password-error nil}}} - (models/validate-password {:db {:multiaccounts/recover {:password "thisisapaswoord"}}})))) - (deftest store-multiaccount - (let [new-cofx (models/store-multiaccount {:db {:multiaccounts/recover + (let [new-cofx (models/store-multiaccount {:db {:intro-wizard {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater" :password "thisisapaswoord"}}})] (is (::multiaccounts.create/store-multiaccount new-cofx)))) (deftest recover-multiaccount-with-checks - (let [new-cofx (models/recover-multiaccount-with-checks {:db {:multiaccounts/recover + (let [new-cofx (models/recover-multiaccount-with-checks {:db {:intro-wizard {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" :password "thisisapaswoord"}}})] (is (::multiaccounts.create/store-multiaccount new-cofx))) - (let [new-cofx (models/recover-multiaccount-with-checks {:db {:multiaccounts/recover + (let [new-cofx (models/recover-multiaccount-with-checks {:db {:intro-wizard {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater" :password "thisisapaswoord"}}})] (is (= (i18n/label :recovery-typo-dialog-title) (-> new-cofx :ui/show-confirmation :title))) diff --git a/translations/en.json b/translations/en.json index f5039014c49..25d73d98f7c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -527,8 +527,7 @@ "intro-wizard-text2": "This name is your identity in Status. It can’t be changed once you choose one. ", "intro-wizard-text3": "Your key is stored locally. There is no copy. Only you have access.", "intro-wizard-text4": "Secure and encrypt your key", - "intro-wizard-text6": "Make it easy to sign and send transactions by enabling fingerprint signing", - "intro-wizard-text7": "Status will notify you about new messages. You can edit your notification preferences later in settings", + "intro-wizard-text6": "Status will notify you about new messages. You can edit your notification preferences later in settings", "intro-wizard-title-alt4": "Create a password", "intro-wizard-title-alt5": "Confirm your password", "intro-wizard-title1": "Get yourself a key first", @@ -536,8 +535,9 @@ "intro-wizard-title3": "Select key storage", "intro-wizard-title4": "Create a 6-digit passcode", "intro-wizard-title5": "Confirm the passcode", - "intro-wizard-title6": "Enable fingerprint", - "intro-wizard-title7": "Enable notifications", + "intro-wizard-title6": "Enable notifications", + "are-you-sure-to-cancel": "Are you sure you want to cancel?", + "you-will-start-from-scratch": "You will start from scratch with a new set of keys", "invalid-extension": "Invalid extension URI", "invalid-format": "Invalid format\nMust be {{format}}", "invalid-key-confirm": "Apply", @@ -670,7 +670,7 @@ "mobile-syncing-sheet-details": "Status tends to use a lot of data when syncing chats. You can choose not to sync when on mobile network", "mobile-syncing-sheet-title": "Sync using Mobile data", "more": "more", - "multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeperate words by a single space.", + "multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeparate words by a single space.", "multiaccounts-recover-enter-phrase-title": "Enter your seed phrase", "name": "Name", "name-description": "Change this anytime in your Profile.", @@ -1069,6 +1069,7 @@ "welcome-screen-text": "Set up your wallet, invite friends to chat\n and browse popular dapps!", "welcome-to-status": "Welcome to Status", "welcome-to-status-description": "Here you can chat with people in a secure private chat, browse and interact with DApps.", + "word-count": "Word count", "word-n": "Word #{{number}}", "word-n-description": "In order to check if you have backed up your seed phrase correctly, enter the word #{{number}} above.", "words-n": { diff --git a/translations/fr.json b/translations/fr.json index 155c9f992bc..e7bfb70aa12 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -514,8 +514,7 @@ "intro-wizard-text2": "Ce nom est votre identité dans Status. Il ne peut pas être changé une fois que vous en choisissez un.", "intro-wizard-text3": "Votre clé est stockée localement. Il n'y a pas de copie. Vous êtes le seul à y avoir accès.", "intro-wizard-text4": "Sécurisez et chiffrez votre clé", - "intro-wizard-text6": "Facilitez la signature et l'envoi de transactions en activant la signature par empreinte digitale.", - "intro-wizard-text7": "Status vous informera des nouveaux messages. Vous pouvez modifier vos préférences de notification ultérieurement dans les paramètres.", + "intro-wizard-text6": "Status vous informera des nouveaux messages. Vous pouvez modifier vos préférences de notification ultérieurement dans les paramètres.", "intro-wizard-title-alt4": "Créer un mot de passe", "intro-wizard-title-alt5": "Confirmer votre mot de passe", "intro-wizard-title1": "Procurez-vous une clé en premier", @@ -523,8 +522,7 @@ "intro-wizard-title3": "Sélectionner le stockage des clés", "intro-wizard-title4": "Créer un code à 6 chiffres", "intro-wizard-title5": "Confirmer le code", - "intro-wizard-title6": "Activer l'empreinte digitale", - "intro-wizard-title7": "Activer les notifications", + "intro-wizard-title6": "Activer les notifications", "invalid-extension": "Extension URI invalide", "invalid-format": "Format invalide \nDoit être {{format}}", "invalid-key-confirm": "Appliquer", diff --git a/translations/ja.json b/translations/ja.json index 706e8b749c0..56775ac58d9 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -512,8 +512,7 @@ "intro-wizard-text2": "この名前はStatusにおけるあなたの身分情報です。1度選択すれば変更できません。", "intro-wizard-text3": "あなたの鍵はローカルに保存されています。コピーはありません。アクセスできるのはあなただけです。", "intro-wizard-text4": "鍵を暗号化し保護する", - "intro-wizard-text6": "指紋を使った署名を有効化してトランザクションの送信と署名を簡単にする", - "intro-wizard-text7": "Statusは新しいメッセージを通知します。通知設定は設定でいつでも変更できます。", + "intro-wizard-text6": "Statusは新しいメッセージを通知します。通知設定は設定でいつでも変更できます。", "intro-wizard-title-alt4": "パスワードを作成", "intro-wizard-title-alt5": "パスワードを確認", "intro-wizard-title1": "まず鍵を取得する", @@ -521,8 +520,7 @@ "intro-wizard-title3": "鍵の保管場所を選択", "intro-wizard-title4": "6桁のコードを作成する", "intro-wizard-title5": "コードを確認", - "intro-wizard-title6": "指紋を有効にする", - "intro-wizard-title7": "通知を有効にする", + "intro-wizard-title6": "通知を有効にする", "invalid-extension": "拡張機能のURLが無効です", "invalid-format": "無効な形式\n {{format}}の形式である必要があります", "invalid-key-confirm": "適用する", diff --git a/translations/ko.json b/translations/ko.json index 2b83f1bf890..c9ce6b3b230 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -524,8 +524,7 @@ "intro-wizard-text2": "아래에서 스테이터스 닉네임을 선택합니다. 닉네임은 변경할 수 없습니다.", "intro-wizard-text3": "키는 기기에 저장되며 사본을 생성하지 않습니다. 사용자만이 키에 접근 할 수 있습니다.", "intro-wizard-text4": "키 보안 및 암호화", - "intro-wizard-text6": "지문 인식을 통해 트랜잭션을 안전하게 서명하고 진행할 수 있습니다", - "intro-wizard-text7": "새 메시지에 대한 알림을 수신합니다. 알림 설정은 프로필 설정에서 변경할 수 있습니다.", + "intro-wizard-text6": "새 메시지에 대한 알림을 수신합니다. 알림 설정은 프로필 설정에서 변경할 수 있습니다.", "intro-wizard-title-alt4": "비밀번호 만들기", "intro-wizard-title-alt5": "비밀번호 확인", "intro-wizard-title1": "키 생성하기", @@ -533,8 +532,7 @@ "intro-wizard-title3": "키 스토리지 선택", "intro-wizard-title4": "6자리 패스코드 만들기", "intro-wizard-title5": "패스코드 확인", - "intro-wizard-title6": "지문 사용", - "intro-wizard-title7": "알림 사용", + "intro-wizard-title6": "알림 사용", "invalid-extension": "잘못된 확장 URI", "invalid-format": "잘못된 형식\n{{format}} 이어야 합니다", "invalid-key-confirm": "적용", diff --git a/translations/ru.json b/translations/ru.json index 7a60a0e4a86..c052bcc0f65 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -541,8 +541,7 @@ "intro-wizard-text2": "Это имя - ваша личность в Status. Его нельзя изменить после первоначального выбора. ", "intro-wizard-text3": "Ваш ключ хранится локально. Не существует никакой копии. Только вы имеете доступ.", "intro-wizard-text4": "Защитите и зашифруйте свой ключ", - "intro-wizard-text6": "Упростите подписание и отправку транзакций, включив подпись по отпечатку пальца", - "intro-wizard-text7": "Status будет уведомлять вас о новых сообщениях. Вы можете изменить настройки уведомлений позже в настройках", + "intro-wizard-text6": "Status будет уведомлять вас о новых сообщениях. Вы можете изменить настройки уведомлений позже в настройках", "intro-wizard-title-alt4": "Создать пароль", "intro-wizard-title-alt5": "Подтвердить пароль", "intro-wizard-title1": "Получить ключ", @@ -550,8 +549,7 @@ "intro-wizard-title3": "Выбрать хранилище ключей", "intro-wizard-title4": "Создать 6-значный код", "intro-wizard-title5": "Подтвердить код", - "intro-wizard-title6": "Включить отпечаток пальца", - "intro-wizard-title7": "Включить уведомления", + "intro-wizard-title6": "Включить уведомления", "invalid-extension": "Неверный URI расширения", "invalid-format": "Недопустимый формат \n Ожидается {{format}}", "invalid-key-confirm": "Применить", diff --git a/translations/zh_Hans_CN.json b/translations/zh_Hans_CN.json index 1798f828458..1c9a8ec88a1 100644 --- a/translations/zh_Hans_CN.json +++ b/translations/zh_Hans_CN.json @@ -523,8 +523,7 @@ "intro-wizard-text2": "此名称是您在Status中的身份。一旦选择,就无法更改。", "intro-wizard-text3": "您的密钥存储在本地。没有副本。只有你有权访问。", "intro-wizard-text4": "保护并加密您的密钥", - "intro-wizard-text6": "通过启用指纹签名,可以轻松签名和发送交易", - "intro-wizard-text7": "有新消息Status将通知您。您可以稍后在设置中编辑通知偏好设置", + "intro-wizard-text6": "有新消息Status将通知您。您可以稍后在设置中编辑通知偏好设置", "intro-wizard-title-alt4": "创建密码", "intro-wizard-title-alt5": "确认你的密码", "intro-wizard-title1": "先获取一个秘钥", @@ -532,8 +531,7 @@ "intro-wizard-title3": "选择密钥存储", "intro-wizard-title4": "创建一个6位数字密码", "intro-wizard-title5": "确认密码", - "intro-wizard-title6": "启用指纹", - "intro-wizard-title7": "启用通知", + "intro-wizard-title6": "启用通知", "invalid-extension": "无效的扩展网址", "invalid-format": "无效的格式\n必须是{{format}}", "invalid-key-confirm": "应用", diff --git a/translations/zh_hans.json b/translations/zh_hans.json index c731ee10aad..43d58822dc4 100644 --- a/translations/zh_hans.json +++ b/translations/zh_hans.json @@ -487,7 +487,6 @@ "intro-wizard-text3": "", "intro-wizard-text4": "", "intro-wizard-text6": "", - "intro-wizard-text7": "", "intro-wizard-title-alt4": "", "intro-wizard-title-alt5": "", "intro-wizard-title1": "", @@ -496,7 +495,6 @@ "intro-wizard-title4": "", "intro-wizard-title5": "", "intro-wizard-title6": "", - "intro-wizard-title7": "", "invalid-extension": "无效的扩展网址", "invalid-format": "无效的格式\n必须是{{format}}", "invalid-key-confirm": "应用",