diff --git a/src/status_im/contexts/keycard/different_card/view.cljs b/src/status_im/contexts/keycard/different_card/view.cljs new file mode 100644 index 00000000000..200405cb61e --- /dev/null +++ b/src/status_im/contexts/keycard/different_card/view.cljs @@ -0,0 +1,21 @@ +(ns status-im.contexts.keycard.different-card.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [utils.i18n :as i18n])) + +(defn view + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/different-keycard) + :description :text + :description-text (i18n/label :t/scan-previous-keycard)}] + [rn/view {:style {:flex 1}}] + [rn/view {:style {:padding-horizontal 20}} + [quo/button {:on-press events-helper/navigate-back} + (i18n/label :t/try-again)]]]) + diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs index ed990659525..8d7c127d07e 100644 --- a/src/status_im/contexts/keycard/effects.cljs +++ b/src/status_im/contexts/keycard/effects.cljs @@ -52,6 +52,10 @@ (fn [args] (keycard/export-key (keycard.utils/wrap-handlers args)))) +(rf/reg-fx :effects.keycard/factory-reset + (fn [args] + (keycard/factory-reset (keycard.utils/wrap-handlers args)))) + (rf/reg-fx :effects.keycard/sign (fn [args] (-> (keycard/sign args) diff --git a/src/status_im/contexts/keycard/error/view.cljs b/src/status_im/contexts/keycard/error/view.cljs index 2da99270cb8..75ca069d12a 100644 --- a/src/status_im/contexts/keycard/error/view.cljs +++ b/src/status_im/contexts/keycard/error/view.cljs @@ -2,6 +2,7 @@ (:require [quo.core :as quo] [react-native.core :as rn] [status-im.common.events-helper :as events-helper] + [status-im.contexts.keycard.factory-reset.view :as factory-reset] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -31,7 +32,17 @@ :description-text description}] [rn/view {:style {:margin-horizontal 20}} [quo/keycard {:holder-name ""}] - [quo/information-box - {:type :default - :style {:margin-top 20}} - (i18n/label :t/unlock-reset-instructions)]]])) + (when (not= :keycard/error.keycard-blank error) + [:<> + [quo/section-label + {:section (i18n/label :t/what-you-can-do) :container-style {:padding-vertical 8}}] + [quo/settings-item + {:title (i18n/label :t/factory-reset) + :image :icon + :image-props :i/placeholder + :action :arrow + :description :text + :description-props {:text (i18n/label :t/remove-keycard-content)} + :on-press (fn [] + (rf/dispatch [:show-bottom-sheet + {:content factory-reset/sheet}]))}]])]])) diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs index 1363365b0c8..216530f22db 100644 --- a/src/status_im/contexts/keycard/events.cljs +++ b/src/status_im/contexts/keycard/events.cljs @@ -68,6 +68,10 @@ (fn [_ [data]] {:effects.keycard/export-key data})) +(rf/reg-event-fx :keycard/factory-reset + (fn [_ [data]] + {:effects.keycard/factory-reset data})) + (rf/reg-event-fx :keycard/connect-derive-address-and-add-account (fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]] {:fx [[:dispatch diff --git a/src/status_im/contexts/keycard/factory_reset/view.cljs b/src/status_im/contexts/keycard/factory_reset/view.cljs new file mode 100644 index 00000000000..af95b6b7637 --- /dev/null +++ b/src/status_im/contexts/keycard/factory_reset/view.cljs @@ -0,0 +1,96 @@ +(ns status-im.contexts.keycard.factory-reset.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- reset-card + [] + (rf/dispatch + [:keycard/factory-reset + {:on-success (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.factory-reset.success])) + :on-failure (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.factory-reset.fail]))}])) + +(defn- connect-and-reset + [key-uid] + (rf/dispatch + [:keycard/connect + {:key-uid key-uid + :on-success reset-card + :on-error (fn [error] + (if (or (= error :keycard/error.keycard-frozen) + (= error :keycard/error.keycard-locked) + (= error :keycard/error.keycard-unpaired)) + (reset-card) + (do + (rf/dispatch [:navigate-back]) + (if (= error :keycard/error.keycard-wrong-profile) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.different-card])) + (rf/dispatch [:keycard/on-application-info-error error])))))}])) + +(defn success-view + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-reset-success) + :description :text + :description-text (i18n/label :t/keycard-empty-ready)}] + [rn/view {:style {:flex 1}}] + [rn/view {:style {:padding-horizontal 20}} + [quo/button {:on-press events-helper/navigate-back} + (i18n/label :t/done)]]]) + +(defn failed-view + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-reset-failed)}] + [rn/view {:style {:flex 1}}] + [rn/view {:style {:padding-horizontal 20}} + [quo/button {:on-press events-helper/navigate-back} + (i18n/label :t/try-again)]]]) + +(defn sheet + [] + (let [customization-color (rf/sub [:profile/customization-color]) + key-uid (rf/sub [:keycard/key-uid]) + [checked? set-checked] (rn/use-state false)] + [:<> + [quo/drawer-top {:title (i18n/label :t/factory-reset-keycard)}] + [quo/text + {:style {:padding-horizontal 20 + :padding-top 8 + :padding-bottom 8}} + (i18n/label :t/factory-reset-warning)] + [quo/disclaimer + {:checked? checked? + :container-style {:margin-horizontal 20} + :on-change #(set-checked (not checked?))} + (i18n/label :t/key-pair-erased)] + [quo/bottom-actions + {:actions :two-actions + :button-one-label (i18n/label :t/continue) + :button-one-props {:disabled? (not checked?) + :customization-color customization-color + :on-press (fn [] + (rf/dispatch [:hide-bottom-sheet]) + (connect-and-reset key-uid))} + :button-two-label (i18n/label :t/cancel) + :button-two-props {:type :grey + :on-press (fn [] + (rf/dispatch [:hide-bottom-sheet]))}}]])) diff --git a/src/status_im/contexts/keycard/not_keycard/view.cljs b/src/status_im/contexts/keycard/not_keycard/view.cljs index abdb6348127..3d3e6af3f90 100644 --- a/src/status_im/contexts/keycard/not_keycard/view.cljs +++ b/src/status_im/contexts/keycard/not_keycard/view.cljs @@ -19,6 +19,6 @@ [rn/image {:resize-mode :contain :source (resources/get-image :not-keycard)}]] - [rn/view {:padding-horizontal 20} + [rn/view {:style {:padding-horizontal 20}} [quo/button {:on-press events-helper/navigate-back} (i18n/label :t/try-again)]]]) diff --git a/src/status_im/contexts/settings/keycard/view.cljs b/src/status_im/contexts/settings/keycard/view.cljs index 0763a3ea0f7..bb790cd498d 100644 --- a/src/status_im/contexts/settings/keycard/view.cljs +++ b/src/status_im/contexts/settings/keycard/view.cljs @@ -34,15 +34,28 @@ [] (let [keycards (rf/sub [:keycard/registered-keycards])] [:<> - [quo/divider-label - {:counter? false - :tight? true - :blur? true} - (i18n/label :t/registered-keycards)] - [rn/view {:style style/registered-keycards-container} - (for [keycard keycards] - ^{:key (:keycard-uid keycard)} - [registered-keycard keycard])]])) + [rn/view {:style {:flex 1}} + [quo/divider-label + {:counter? false + :tight? true + :blur? true} + (i18n/label :t/registered-keycards)] + [rn/view {:style style/registered-keycards-container} + (for [keycard keycards] + ^{:key (:keycard-uid keycard)} + [registered-keycard keycard])]] + [quo/text + {:size :heading-2 + :weight :semi-bold + :style {:margin-left 20}} + (i18n/label :t/scan-keycard-actions)] + [quo/divider-label (i18n/label :t/tips-scan-keycard)] + [quo/markdown-list {:description (i18n/label :t/remove-phone-case)}] + [quo/markdown-list {:description (i18n/label :t/keep-card-steady)}] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/ready-to-scan) + :button-one-props {:on-press #(rf/dispatch [:keycard/connect])}}]])) (defn view [] diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 098bff5c7ae..88d286c18da 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -33,8 +33,10 @@ [status-im.contexts.keycard.authorise.view :as keycard.authorise] [status-im.contexts.keycard.check.view :as keycard.check] [status-im.contexts.keycard.create.view :as keycard.create] + [status-im.contexts.keycard.different-card.view :as keycard.different-card] [status-im.contexts.keycard.empty.view :as keycard.empty] [status-im.contexts.keycard.error.view :as keycard.error] + [status-im.contexts.keycard.factory-reset.view :as keycard.factory-reset] [status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail] [status-im.contexts.keycard.migrate.profile-keys.view :as keycard.migrate.profile-keys] [status-im.contexts.keycard.migrate.re-encrypting.view :as keycard.re-encrypting] @@ -895,6 +897,13 @@ :insets {:top? true :bottom? true}} :component keycard.error/view} + {:name :screen/keycard.different-card + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.different-card/view} + {:name :screen/keycard.not-keycard :metrics {:track? true} :options {:theme :dark @@ -966,7 +975,21 @@ {:name :screen/keycard.create.ready-to-add :metrics {:track? true} :options {:insets {:top? true :bottom? true}} - :component keycard.create/ready-to-add}]) + :component keycard.create/ready-to-add} + + {:name :screen/keycard.factory-reset.success + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.factory-reset/success-view} + + {:name :screen/keycard.factory-reset.fail + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.factory-reset/failed-view}]) (defn screens [] diff --git a/src/status_im/subs/keycard.cljs b/src/status_im/subs/keycard.cljs index d0758e87b97..73d2142f623 100644 --- a/src/status_im/subs/keycard.cljs +++ b/src/status_im/subs/keycard.cljs @@ -82,3 +82,9 @@ :<- [:keycard] (fn [keycard] (get-in keycard [:application-info :initialized?]))) + +(rf/reg-sub + :keycard/key-uid + :<- [:keycard] + (fn [keycard] + (get-in keycard [:application-info :key-uid]))) diff --git a/translations/en.json b/translations/en.json index 9e5f5fea35b..8cea8fdad99 100644 --- a/translations/en.json +++ b/translations/en.json @@ -779,6 +779,7 @@ "device-syncing": "Device syncing", "devices": "Devices", "devices-count": "{{number}} devices", + "different-keycard": "It’s a different Keycard", "disable": "disable", "disable-all": "Disable all", "disable-later-in-settings": "You can disable this later in Settings", @@ -1028,6 +1029,9 @@ "export-key": "Export private key", "external-link": "External link", "external-storage-denied": "Access to external storage is denied", + "factory-reset": "Factory reset", + "factory-reset-keycard": "Factory reset this Keycard", + "factory-reset-warning": "Please ensure you have a backup of your recovery phrase before proceeding with the factory reset. If you want to switch to password authentication instead of Keycard, remove the profile from the device and log in with your recovery phrase.", "failed": "Failed", "failed-on": "Failed on", "failed-to-fetch-community": "Failed to fetch community", @@ -1346,6 +1350,7 @@ "key-name-error-taken": "Key pair name already in use", "key-name-error-too-short": "Key pair name must be at least {{count}} characters", "key-on-device": "Private key is saved on this device", + "key-pair-erased": "Key pair will be erased from Keycard", "key-pair-imported-successfully": "{{name}} key pair imported successfully", "key-pair-migrated-successfully": "Profile key pair successfully migrated", "key-pair-name-updated": "Key pair name updated", @@ -1369,6 +1374,7 @@ "keycard-desc": "Own a Keycard? Store your keys on it; you’ll need it for transactions", "keycard-dont-ask-card": "Don't ask for card to sign in", "keycard-empty": "Keycard is empty", + "keycard-empty-ready": "Keycard is empty and ready to be used", "keycard-enter-new-passcode": "Enter new passcode {{step}}/2", "keycard-error-description": "Connect the card again to continue", "keycard-error-title": "Connection lost", @@ -1427,7 +1433,9 @@ "keycard-redeem-title": "Redeem to", "keycard-redeem-tx": "Redeem assets", "keycard-redeem-tx-desc": "Tap the card to sign and receive assets", + "keycard-reset-failed": "Failed to reset Keycard", "keycard-reset-passcode": "Reset passcode", + "keycard-reset-success": "Keycard has been reset", "keycard-success-description": "You may remove the card now", "keycard-success-title": "Success", "keycard-unauthorized-operation": "You're unauthorized to perform this operation.\n Please tap valid card and try again.", @@ -2140,6 +2148,7 @@ "remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them", "remove-group": "Remove group", "remove-key-pair-and-derived-accounts": "Remove key pair and derived accounts", + "remove-keycard-content": "Remove all content from this Keycard", "remove-network": "Remove network", "remove-nickname": "Remove nickname", "remove-nickname-toast": "You have removed {{secondary-name}}'s nickname", @@ -2232,9 +2241,11 @@ "scan-an-address-qr-code": "Scan an address QR code", "scan-key-pairs-qr-code": "Scan key pairs QR code", "scan-keycard": "Scan Keycard", + "scan-keycard-actions": "Scan Keycard to see available actions", "scan-or-enter-a-sync-code": "Scan or enter a sync code", "scan-or-enter-sync-code": "Scan or enter sync code", "scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device", + "scan-previous-keycard": "Please scan Keycard you previously scanned", "scan-qr": "Scan QR", "scan-qr-code": "Scan QR code", "scan-sync-code": "Scan Sync Code",