diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index 546ab4fbb5..4bb883c8b8 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -37,6 +37,7 @@ :Linking {} :TouchableWithoutFeedback {} :TouchableHighlight {} + :Pressable {} :TouchableOpacity {} :ActivityIndicator {} :StyleSheet {:create (fn [])} diff --git a/src/status_im/multiaccounts/recover/core.cljs b/src/status_im/multiaccounts/recover/core.cljs index ddefcf391f..c9e74031c3 100644 --- a/src/status_im/multiaccounts/recover/core.cljs +++ b/src/status_im/multiaccounts/recover/core.cljs @@ -163,7 +163,8 @@ (popover/show-popover cofx {:view :custom-seed-phrase}) (when (mnemonic/valid-length? passphrase) {::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase) - :password password + :password (when password + (security/safe-unmask-data password)) :success-event ::import-multiaccount-success}})))) (fx/defn seed-phrase-next-pressed @@ -263,3 +264,10 @@ (set-phrase input) (count-words) (run-validation))) + +(fx/defn enter-passphrase-input-changed + {:events [:multiaccounts.recover/enter-passphrase-input-changed]} + [{:keys [db]} masked-passphrase] + {:db (update db :intro-wizard assoc + :password masked-passphrase + :password-error nil)}) diff --git a/src/status_im/ui/components/react.cljs b/src/status_im/ui/components/react.cljs index 6af39c94e5..e88816dad6 100644 --- a/src/status_im/ui/components/react.cljs +++ b/src/status_im/ui/components/react.cljs @@ -53,6 +53,7 @@ [switch-class props]) (def touchable-highlight-class (reagent/adapt-react-class (.-TouchableHighlight react-native))) +(def pressable-class (reagent/adapt-react-class (.-Pressable react-native))) (def touchable-without-feedback-class (reagent/adapt-react-class (.-TouchableWithoutFeedback react-native))) (def touchable-opacity-class (reagent/adapt-react-class (.-TouchableOpacity react-native))) (def activity-indicator-class (reagent/adapt-react-class (.-ActivityIndicator react-native))) @@ -175,6 +176,9 @@ (merge {:underlay-color :transparent} props) content]) +(defn pressable [props content] + [pressable-class props content]) + (defn touchable-without-feedback [props content] [touchable-without-feedback-class props diff --git a/src/status_im/ui/screens/intro/views.cljs b/src/status_im/ui/screens/intro/views.cljs index a1b64f158f..124b27f5be 100644 --- a/src/status_im/ui/screens/intro/views.cljs +++ b/src/status_im/ui/screens/intro/views.cljs @@ -19,7 +19,8 @@ [status-im.utils.debounce :refer [dispatch-and-chill]] [quo.core :as quo] [status-im.ui.screens.intro.carousel :as carousel] - [status-im.utils.utils :as utils]) + [status-im.utils.utils :as utils] + [status-im.utils.datetime :as datetime]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defview intro [] @@ -228,50 +229,74 @@ (step-kw-to-num step)))))] :else nil)])) -(defn enter-phrase [{:keys [processing? - passphrase-word-count - next-button-disabled? - passphrase-error]}] - [react/keyboard-avoiding-view {:flex 1 - :background-color colors/white} - [react/view {:background-color colors/white - :flex 1 - :justify-content :center - :padding-horizontal 16} - [quo/text-input - {: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 (i18n/label :t/seed-phrase-placeholder) - :show-cancel false - :bottom-value 40 - :multiline true - :auto-correct false - :monospace true}] - [react/view {:align-items :flex-end} - [react/view {:flex-direction :row - :align-items :center - :padding-vertical 8 - :opacity (if passphrase-word-count 1 0)} - [quo/text {:color (if next-button-disabled? :secondary :main) - :size :small} - (when-not next-button-disabled? - "✓ ") - (i18n/label-pluralize passphrase-word-count :t/words-n)]]]] - [react/view {:align-items :center} - [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 enter-phrase [_] + (let [show-bip39-password? (reagent.core/atom false) + pressed-in-at (atom nil)] + (fn [{:keys [processing? + passphrase-word-count + next-button-disabled? + passphrase-error]}] + [react/keyboard-avoiding-view {:flex 1 + :background-color colors/white} + [react/pressable + {:style {:background-color colors/white + :flex 1 + :justify-content :center + :padding-horizontal 16} + ;; BIP39 password input will be shown only after pressing on screen + ;; for longer than 2 seconds + :on-press-in (fn [] + (reset! pressed-in-at (datetime/now))) + :on-press-out (fn [] + (when (>= (datetime/seconds-ago @pressed-in-at) 2) + (reset! show-bip39-password? true)))} + [react/view + [quo/text-input + {: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 (i18n/label :t/seed-phrase-placeholder) + :show-cancel false + :bottom-value 40 + :multiline true + :auto-correct false + :monospace true}] + [react/view {:align-items :flex-end} + [react/view {:flex-direction :row + :align-items :center + :padding-vertical 8 + :opacity (if passphrase-word-count 1 0)} + [quo/text {:color (if next-button-disabled? :secondary :main) + :size :small} + (when-not next-button-disabled? + "✓ ") + (i18n/label-pluralize passphrase-word-count :t/words-n)]]] + (when @show-bip39-password? + ;; BIP39 password (`passphrase` in BIP39 https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed) + ;; is an advanced security feature which allows to add an arbitrary + ;; extra word to your existing mnemonic. The password is an empty + ;; string if not provided. If the password is added a completely + ;; different key will be created. + [quo/text-input + {:on-change-text + #(re-frame/dispatch [:multiaccounts.recover/enter-passphrase-input-changed + (security/mask-data %)]) + :placeholder (i18n/label :t/bip39-password-placeholder) + :show-cancel false}])]] + [react/view {:align-items :center} + [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 name photo-path] [react/view {:flex 1 diff --git a/translations/en.json b/translations/en.json index db110937ae..b68c4a3124 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1516,5 +1516,6 @@ "activity": "Activity", "reject-and-delete": "Reject and delete", "accept-and-add": "Accept and add", - "my-profile": "My profile" + "my-profile": "My profile", + "bip39-password-placeholder": "BIP39 password" }