diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs index 39421e225c3..1f249682432 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs @@ -7,13 +7,21 @@ [data] (rf/dispatch [:open-modal :screen/settings.rename-keypair data])) +(defn on-show-qr + [data] + (rf/dispatch [:open-modal :screen/settings.encrypted-key-pair-qr data])) + (defn view [props data] [:<> [quo/drawer-top props] [quo/action-drawer - [(when (= (:type props) :keypair) + (when (= (:type props) :keypair) + [[{:icon :i/qr-code + :accessibility-label :show-key-pr-qr + :label (i18n/label :t/show-encrypted-qr-of-key-pairs) + :on-press #(on-show-qr data)}] [{:icon :i/edit :accessibility-label :rename-key-pair :label (i18n/label :t/rename-key-pair) - :on-press #(on-rename-request data)}])]]]) + :on-press #(on-rename-request data)}]])]]) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/countdown/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/countdown/view.cljs new file mode 100644 index 00000000000..cce451e9c17 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/countdown/view.cljs @@ -0,0 +1,38 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.countdown.view + (:require + [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.hooks :as hooks] + [utils.datetime :as datetime] + [utils.i18n :as i18n])) + +(def code-valid-for-ms 120000) +(def one-min-ms 60000) + +(defn current-ms + [] + (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000)))) + +(defn view + [on-clear] + (let [[valid-for-ms set-valid-for-ms] (rn/use-state code-valid-for-ms) + [timestamp set-timestamp] (rn/use-state current-ms) + clock (rn/use-callback (fn [] + (let [remaining (- code-valid-for-ms + (- (current-ms) + timestamp))] + (when (pos? remaining) + (set-valid-for-ms remaining)) + (when (zero? remaining) + (set-timestamp (current-ms)) + (set-valid-for-ms code-valid-for-ms) + (on-clear)))) + [code-valid-for-ms])] + (hooks/use-interval clock on-clear 1000) + [quo/text + {:size :paragraph-2 + :style {:color (if (< valid-for-ms one-min-ms) + colors/danger-60 + colors/white-opa-40)}} + (i18n/label :t/valid-for-time {:valid-for (datetime/ms-to-duration valid-for-ms)})])) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/style.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/style.cljs new file mode 100644 index 00000000000..5598fbce214 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/style.cljs @@ -0,0 +1,46 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.style + (:require + [quo.foundations.colors :as colors] + [react-native.safe-area :as safe-area])) + +(defn container-main + [] + {:background-color colors/neutral-95 + :padding-top (safe-area/get-top) + :flex 1}) + +(def page-container + {:margin-top 14 + :margin-horizontal 20}) + +(def title-container + {:flex-direction :row + :align-items :center + :justify-content :space-between}) + +(def standard-auth + {:margin-top 12 + :flex 1}) + +(def qr-container + {:margin-top 12 + :background-color colors/white-opa-5 + :border-radius 20 + :flex 1 + :padding 12}) + +(def sub-text-container + {:margin-bottom 8 + :justify-content :space-between + :align-items :center + :flex-direction :row}) + +(def valid-cs-container + {:flex 1 + :margin 12}) + +(def warning-text + {:margin-horizontal 16 + :margin-top 20 + :text-align :center + :color colors/white-opa-70}) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/view.cljs new file mode 100644 index 00000000000..e148aae305e --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/encrypted_qr/view.cljs @@ -0,0 +1,92 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.view + (:require + [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.clipboard :as clipboard] + [react-native.core :as rn] + [status-im.common.qr-codes.view :as qr-codes] + [status-im.common.resources :as resources] + [status-im.common.standard-authentication.core :as standard-auth] + [status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.countdown.view :as countdown] + [status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.style :as style] + [status-im.contexts.syncing.utils :as sync-utils] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn navigate-back [] (rf/dispatch [:navigate-back])) + +(defn view + [] + (let [{:keys [customization-color]} (rf/sub [:profile/profile-with-image]) + [code set-code] (rn/use-state nil) + validate-and-set-code (rn/use-callback (fn [connection-string] + (when (sync-utils/valid-connection-string? + connection-string) + (set-code connection-string)))) + cleanup-clock (rn/use-callback #(set-code nil)) + on-auth-success (fn [entered-password] + (rf/dispatch [:syncing/get-connection-string entered-password + validate-and-set-code]))] + [rn/view {:style (style/container-main)} + [rn/scroll-view + [quo/page-nav + {:type :no-title + :icon-name :i/close + :background :blur + :on-press navigate-back}] + [rn/view {:style style/page-container} + [rn/view {:style style/title-container} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style {:color colors/white}} + (i18n/label :t/encrypted-key-pairs)]] + [rn/view {:style style/qr-container} + (if (sync-utils/valid-connection-string? code) + [qr-codes/qr-code {:url code}] + [rn/view {:style {:flex-direction :row}} + [rn/image + {:source (resources/get-image :qr-code) + :style {:width "100%" + :background-color colors/white-opa-70 + :border-radius 12 + :aspect-ratio 1}}]]) + (when (sync-utils/valid-connection-string? code) + [rn/view {:style style/valid-cs-container} + [rn/view {:style style/sub-text-container} + [quo/text + {:size :paragraph-2 + :style {:color colors/white-opa-40}} + (i18n/label :t/encrypted-key-pairs-code)] + [countdown/view cleanup-clock]] + [quo/input + {:default-value code + :type :password + :default-shown? true + :editable false}] + [quo/button + {:on-press (fn [] + (clipboard/set-string code) + (rf/dispatch [:toasts/upsert + {:type :positive + :text (i18n/label + :t/sharing-copied-to-clipboard)}])) + :type :grey + :container-style {:margin-top 12} + :icon-left :i/copy} + (i18n/label :t/copy-qr)]]) + (when-not (sync-utils/valid-connection-string? code) + [rn/view {:style style/standard-auth} + [standard-auth/slide-button + {:blur? true + :size :size-40 + :track-text (i18n/label :t/slide-to-reveal-qr-code) + :customization-color customization-color + :on-auth-success on-auth-success + :auth-button-label (i18n/label :t/reveal-qr-code) + :auth-button-icon-left :i/reveal}]])]] + (when-not (sync-utils/valid-connection-string? code) + [quo/text + {:size :paragraph-2 + :style style/warning-text} + (i18n/label :t/make-sure-no-camera-warning)])]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 10edaf96dd1..2885067b953 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -57,6 +57,8 @@ [status-im.contexts.profile.settings.screens.password.change-password.view :as change-password] [status-im.contexts.profile.settings.screens.password.view :as settings-password] [status-im.contexts.profile.settings.view :as settings] + [status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.view :as + encrypted-key-pair-qr] [status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename] [status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts] [status-im.contexts.settings.wallet.network-settings.view :as network-settings] @@ -509,6 +511,10 @@ :options (assoc options/dark-screen :sheet? true) :component keypair-rename/view} + {:name :screen/settings.encrypted-key-pair-qr + :options options/transparent-screen-options + :component encrypted-key-pair-qr/view} + {:name :screen/settings.saved-addresses :options options/transparent-modal-screen-options :component saved-addresses-settings/view} diff --git a/translations/en.json b/translations/en.json index e21b0de81d7..c1aed2d5529 100644 --- a/translations/en.json +++ b/translations/en.json @@ -524,6 +524,8 @@ "enable": "Enable", "enable-notifications-sub-title": "Receive notifications about your new messages or wallet transactions", "encrypt-with-password": "Encrypt with password", + "encrypted-key-pairs": "Encrypted key pairs", + "encrypted-key-pairs-code": "Encrypted key pairs code", "ending-not-allowed": "{{ending}} ending is not allowed", "ends-with-space": "Cannot end with space", "ens-10-SNT": "10 SNT", @@ -939,6 +941,7 @@ "main-wallet": "Main Wallet", "make-admin": "Make admin", "make-moderator": "Make moderator", + "make-sure-no-camera-warning": "Make sure no camera or person can see this screen before revealing", "manage-keys-and-storage": "Manage keys and storage", "mark-as-read": "Mark as read", "mark-all-read": "Mark all read", @@ -1272,6 +1275,7 @@ "reset-card-description": "This operation will reset card to initial state. It will erase all card data including private keys. Operation is not reversible.", "retry": "Retry", "reveal-sync-code": "Reveal sync code", + "reveal-qr-code": "Reveal QR code", "revoke-access": "Revoke access", "save": "Save", "save-address": "Save address", @@ -1337,6 +1341,7 @@ "show-more": "Show more", "show-qr": "Show QR code", "show-transaction-data": "Show transaction data", + "show-encrypted-qr-of-key-pairs": "Show encrypted QR of key pairs on device", "sign-and-send": "Sign and send", "sign-in": "Sign in", "sign-message": "Sign Message", @@ -1970,6 +1975,7 @@ "slide-to-request-to-join": "Slide to request to join", "slide-to-reveal-code": "Slide to reveal code", "slide-to-create-account": "Slide to create account", + "slide-to-reveal-qr-code": "Slide to reveal QR code", "minimum-received": "Minimum received", "powered-by-paraswap": "Powered by Paraswap", "priority": "Priority",