From 3899368aa0507be8f5574954390006622eba2b4a Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Fri, 31 Jan 2025 18:42:08 +0100 Subject: [PATCH] [#21930] Keycard - Back up keycard (#21990) --- .../contexts/keycard/backup/events.cljs | 133 ++++++++++++++++++ .../contexts/keycard/backup/view.cljs | 96 +++++++++++++ .../contexts/keycard/check/events.cljs | 25 ++++ .../contexts/keycard/check/view.cljs | 5 +- .../contexts/keycard/common/view.cljs | 10 ++ .../contexts/keycard/create/events.cljs | 2 +- .../contexts/keycard/create/view.cljs | 9 +- src/status_im/contexts/keycard/effects.cljs | 6 +- .../contexts/keycard/error/view.cljs | 43 ++++-- src/status_im/contexts/keycard/events.cljs | 10 ++ .../keycard/manage/profile_keys/view.cljs | 40 ++++++ .../contexts/keycard/migrate/events.cljs | 19 +-- .../contexts/keycard/migrate/view.cljs | 5 +- .../contexts/settings/keycard/view.cljs | 121 +++++----------- src/status_im/navigation/screens.cljs | 42 +++++- translations/en.json | 10 ++ 16 files changed, 441 insertions(+), 135 deletions(-) create mode 100644 src/status_im/contexts/keycard/backup/events.cljs create mode 100644 src/status_im/contexts/keycard/backup/view.cljs create mode 100644 src/status_im/contexts/keycard/check/events.cljs create mode 100644 src/status_im/contexts/keycard/common/view.cljs create mode 100644 src/status_im/contexts/keycard/manage/profile_keys/view.cljs diff --git a/src/status_im/contexts/keycard/backup/events.cljs b/src/status_im/contexts/keycard/backup/events.cljs new file mode 100644 index 0000000000..d7a42a89de --- /dev/null +++ b/src/status_im/contexts/keycard/backup/events.cljs @@ -0,0 +1,133 @@ +(ns status-im.contexts.keycard.backup.events + (:require [utils.re-frame :as rf] + [utils.security.core :as security])) + +(rf/reg-event-fx :keycard/backup.generate-and-load-key + (fn [{:keys [db]}] + (let [{:keys [masked-phrase pin]} (get-in db [:keycard :backup])] + {:fx [[:effects.keycard/generate-and-load-key + {:mnemonic (security/safe-unmask-data masked-phrase) + :pin pin + :on-success (fn [] + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:open-modal :screen/keycard.backup.success])) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}))) + +(defn- ready-to-add-not-empty + [error] + (rf/dispatch [:navigate-back]) + (if (= error :keycard/error.not-keycard) + (rf/dispatch [:keycard/on-application-info-error error]) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.backup.not-empty + {:on-press #(rf/dispatch [:keycard/backup.ready-to-add-connect])}])))) + +(rf/reg-event-fx :keycard/backup.ready-to-add-connect + (fn [_] + {:fx [[:dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (rf/dispatch [:keycard/backup.generate-and-load-key]) + (ready-to-add-not-empty error)))}]]]})) + +(defn- scan-empty-card-not-empty + [error] + (rf/dispatch [:navigate-back]) + (if (= error :keycard/error.not-keycard) + (rf/dispatch [:keycard/on-application-info-error error]) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.backup.not-empty + {:on-press #(rf/dispatch [:keycard/backup.scan-empty-card])}])))) + +(rf/reg-event-fx :keycard/backup.scan-empty-card + (fn [_] + {:fx [[:dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:keycard/backup.create-or-enter-pin])) + (scan-empty-card-not-empty error)))}]]]})) + +(rf/reg-event-fx :keycard/backup.save-pin + (fn [{:keys [db]} [pin]] + {:db (assoc-in db [:keycard :backup :pin] pin)})) + +(defn- save-pin-and-navigate-to-phrase + [pin] + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/backup.save-pin pin]) + (rf/dispatch + [:open-modal :screen/use-recovery-phrase-dark + {:on-success #(rf/dispatch [:keycard/backup.phrase-entered %])}])) + +(defn- verify-entered-pin-and-continue + [pin] + (rf/dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (rf/dispatch + [:keycard/verify-pin + {:pin pin + :on-success #(save-pin-and-navigate-to-phrase pin) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]) + (rf/dispatch [:keycard/on-application-info-error error])))}])) + +(declare init-card-not-empty) + +(defn- init-card-with-pin-and-continue + [pin] + (rf/dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (rf/dispatch + [:keycard/init-card + {:pin pin + :on-success #(rf/dispatch + [:keycard/get-application-info + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (save-pin-and-navigate-to-phrase pin) + (init-card-not-empty pin error)))}])}]) + (init-card-not-empty pin error)))}])) + +(defn- init-card-not-empty + [pin error] + (rf/dispatch [:navigate-back]) + (if (= error :keycard/error.not-keycard) + (rf/dispatch [:keycard/on-application-info-error error]) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.backup.not-empty + {:on-press #(init-card-with-pin-and-continue pin)}])))) + +(rf/reg-event-fx :keycard/backup.create-or-enter-pin + (fn [{:keys [db]}] + (let [{:keys [initialized?]} (get-in db [:keycard :application-info])] + {:fx [[:dispatch [:navigate-back]] + (if initialized? + [:dispatch + [:open-modal :screen/keycard.pin.enter + {:on-complete verify-entered-pin-and-continue}]] + [:dispatch + [:open-modal :screen/keycard.pin.create + {:on-complete init-card-with-pin-and-continue}]])]}))) + +(rf/reg-event-fx :keycard/backup.phrase-entered + (fn [{:keys [db]} [{:keys [phrase]}]] + {:db (assoc-in db [:keycard :backup :masked-phrase] phrase) + :fx [[:dispatch [:navigate-back]] + [:dispatch [:open-modal :screen/keycard.backup.ready-to-add]]]})) diff --git a/src/status_im/contexts/keycard/backup/view.cljs b/src/status_im/contexts/keycard/backup/view.cljs new file mode 100644 index 0000000000..a9dea34058 --- /dev/null +++ b/src/status_im/contexts/keycard/backup/view.cljs @@ -0,0 +1,96 @@ +(ns status-im.contexts.keycard.backup.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [status-im.common.resources :as resources] + [status-im.contexts.keycard.common.view :as common.view] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn ready-to-add + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/ready-add-keypair-keycard)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain + :source (resources/get-image :keycard-migration)}]] + [common.view/tips] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/scan-keycard) + :button-one-props {:on-press #(rf/dispatch [:keycard/backup.ready-to-add-connect])}}]]) + +(defn success-view + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/backup-keycard-created)}] + [rn/view {:style {:flex 1}}] + [rn/view {:style {:padding-horizontal 20}} + [quo/button {:on-press events-helper/navigate-back} + (i18n/label :t/done)]]]) + +(defn not-empty-view + [] + (let [{:keys [on-press]} (rf/sub [:get-screen-params])] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-not-empty) + :description :text + :description-text (i18n/label :t/scan-empty-keycard)}] + [rn/view {:style {:flex 1}}] + [common.view/tips] + [rn/view {:style {:padding-horizontal 20}} + [quo/button {:on-press on-press} + (i18n/label :t/try-again)]]])) + +(defn scan-empty + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/scan-empty-keycard) + :description :text + :description-text (i18n/label :t/backup-empty-keycard-only)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain + :source (resources/get-image :check-your-keycard)}]] + [common.view/tips] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/ready-to-scan) + :button-one-props {:on-press #(rf/dispatch [:keycard/backup.scan-empty-card])}}]]) + +(defn sheet + [{:keys [on-continue]}] + (let [customization-color (rf/sub [:profile/customization-color])] + [:<> + [quo/drawer-top {:title (i18n/label :t/create-backup-keycard)}] + [quo/text + {:style {:padding-horizontal 20 + :padding-vertical 8}} + (i18n/label :t/backup-keycard-instructions)] + [quo/bottom-actions + {:actions :two-actions + :button-one-label (i18n/label :t/continue) + :button-one-props {:customization-color customization-color + :on-press (fn [] + (rf/dispatch [:hide-bottom-sheet]) + (on-continue))} + :button-two-label (i18n/label :t/cancel) + :button-two-props {:type :grey + :on-press #(rf/dispatch [:hide-bottom-sheet])}}]])) diff --git a/src/status_im/contexts/keycard/check/events.cljs b/src/status_im/contexts/keycard/check/events.cljs new file mode 100644 index 0000000000..4a6be8e8a5 --- /dev/null +++ b/src/status_im/contexts/keycard/check/events.cljs @@ -0,0 +1,25 @@ +(ns status-im.contexts.keycard.check.events + (:require [utils.re-frame :as rf])) + +(rf/reg-event-fx :keycard/check-empty-card + (fn [{:keys [db]}] + (let [keycard-profile? (not (nil? (get-in db [:profile/profile :keycard-pairing])))] + {:fx [[:dispatch + [:keycard/connect + {:key-uid (get-in db [:profile/profile :key-uid]) + :on-success + (fn [] + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:navigate-back]) + (if keycard-profile? + (rf/dispatch [:open-modal :screen/keycard.manage.profile-keys]) + (rf/dispatch [:open-modal :screen/keycard.migrate.profile-keys]))) + :on-error + (fn [error] + (rf/dispatch [:navigate-back]) + (if (and (= error :keycard/error.keycard-blank) + (not keycard-profile?)) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.empty])) + (rf/dispatch [:keycard/on-application-info-error error])))}]]]}))) diff --git a/src/status_im/contexts/keycard/check/view.cljs b/src/status_im/contexts/keycard/check/view.cljs index d942f85a09..bcec04d3bb 100644 --- a/src/status_im/contexts/keycard/check/view.cljs +++ b/src/status_im/contexts/keycard/check/view.cljs @@ -4,6 +4,7 @@ [status-im.common.events-helper :as events-helper] [status-im.common.resources :as resources] [status-im.constants :as constants] + [status-im.contexts.keycard.common.view :as common.view] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -44,9 +45,7 @@ [rn/image {:resize-mode :contain :source (resources/get-image :check-your-keycard)}]] - [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)}] + [common.view/tips] [quo/bottom-actions {:actions :one-action :button-one-label (i18n/label :t/ready-to-scan) diff --git a/src/status_im/contexts/keycard/common/view.cljs b/src/status_im/contexts/keycard/common/view.cljs new file mode 100644 index 0000000000..d24c12e01b --- /dev/null +++ b/src/status_im/contexts/keycard/common/view.cljs @@ -0,0 +1,10 @@ +(ns status-im.contexts.keycard.common.view + (:require [quo.core :as quo] + [utils.i18n :as i18n])) + +(defn tips + [] + [:<> + [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)}]]) diff --git a/src/status_im/contexts/keycard/create/events.cljs b/src/status_im/contexts/keycard/create/events.cljs index 46eae0841f..22fd2318f1 100644 --- a/src/status_im/contexts/keycard/create/events.cljs +++ b/src/status_im/contexts/keycard/create/events.cljs @@ -90,7 +90,7 @@ (cond (not initialized?) - {:fx [[:keycard/init-card + {:fx [[:effects.keycard/init-card {:pin pin :on-success get-application-info-and-continue}]]} diff --git a/src/status_im/contexts/keycard/create/view.cljs b/src/status_im/contexts/keycard/create/view.cljs index 157c9757d1..ccbfba55a7 100644 --- a/src/status_im/contexts/keycard/create/view.cljs +++ b/src/status_im/contexts/keycard/create/view.cljs @@ -6,6 +6,7 @@ [status-im.common.resources :as resources] [status-im.config :as config] [status-im.constants :as constants] + [status-im.contexts.keycard.common.view :as common.view] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -37,9 +38,7 @@ :image (resources/get-image :use-keycard) :on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}]] [rn/view {:style {:flex 1}}] - [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)}]]) + [common.view/tips]]) (defn ready-to-add [] @@ -58,9 +57,7 @@ {:resize-mode :contain :style {:flex 1 :align-self :center :margin-vertical 37} :source (resources/get-image :add-key-to-keycard)}] - [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)}] + [common.view/tips] [quo/bottom-actions {:actions :one-action :button-one-label (i18n/label :t/scan-keycard) diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs index 8d7c127d07..a58fc3151f 100644 --- a/src/status_im/contexts/keycard/effects.cljs +++ b/src/status_im/contexts/keycard/effects.cljs @@ -56,6 +56,10 @@ (fn [args] (keycard/factory-reset (keycard.utils/wrap-handlers args)))) +(rf/reg-fx :effects.keycard/verify-pin + (fn [args] + (keycard/verify-pin (keycard.utils/wrap-handlers args)))) + (rf/reg-fx :effects.keycard/sign (fn [args] (-> (keycard/sign args) @@ -75,7 +79,7 @@ (promesa/then on-success) (promesa/catch (keycard.utils/get-on-failure args))))) -(rf/reg-fx :keycard/init-card +(rf/reg-fx :effects.keycard/init-card (fn [args] (keycard/init-card (keycard.utils/wrap-handlers args)))) diff --git a/src/status_im/contexts/keycard/error/view.cljs b/src/status_im/contexts/keycard/error/view.cljs index 75ca069d12..baf89e4d74 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.backup.view :as backup.view] [status-im.contexts.keycard.factory-reset.view :as factory-reset] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -32,17 +33,31 @@ :description-text description}] [rn/view {:style {:margin-horizontal 20}} [quo/keycard {:holder-name ""}] - (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}]))}]])]])) + [quo/section-label + {:section (i18n/label :t/what-you-can-do) :container-style {:padding-vertical 8}}] + (if (= error :keycard/error.keycard-blank) + [quo/settings-item + {:title (i18n/label :t/use-backup-keycard) + :image :icon + :image-props :i/placeholder + :action :arrow + :description :text + :description-props {:text (i18n/label :t/create-backup-profile-keycard)} + :on-press (fn [] + (rf/dispatch [:show-bottom-sheet + {:content + (fn [] + [backup.view/sheet + {:on-continue + (rf/dispatch + [:keycard/backup.create-or-enter-pin])}])}]))}] + [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 216530f22d..cfa9c2af8b 100644 --- a/src/status_im/contexts/keycard/events.cljs +++ b/src/status_im/contexts/keycard/events.cljs @@ -1,5 +1,7 @@ (ns status-im.contexts.keycard.events (:require [re-frame.core :as rf] + status-im.contexts.keycard.backup.events + status-im.contexts.keycard.check.events status-im.contexts.keycard.create.events status-im.contexts.keycard.login.events status-im.contexts.keycard.migrate.events @@ -56,6 +58,10 @@ [:keycard/on-application-info-error :keycard/error.keycard-locked]])]})))) +(rf/reg-event-fx :keycard/init-card + (fn [_ [data]] + {:effects.keycard/init-card data})) + (rf/reg-event-fx :keycard/get-keys (fn [_ [data]] {:effects.keycard/get-keys data})) @@ -72,6 +78,10 @@ (fn [_ [data]] {:effects.keycard/factory-reset data})) +(rf/reg-event-fx :keycard/verify-pin + (fn [_ [data]] + {:effects.keycard/verify-pin 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/manage/profile_keys/view.cljs b/src/status_im/contexts/keycard/manage/profile_keys/view.cljs new file mode 100644 index 0000000000..78711a228b --- /dev/null +++ b/src/status_im/contexts/keycard/manage/profile_keys/view.cljs @@ -0,0 +1,40 @@ +(ns status-im.contexts.keycard.manage.profile-keys.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [status-im.contexts.keycard.backup.view :as backup.view] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- backup-sheet + [] + [backup.view/sheet + {:on-continue + (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch + [:open-modal :screen/keycard.backup.scan-empty]))}]) + +(defn view + [] + (let [profile-name (rf/sub [:profile/name])] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title profile-name}] + [rn/view {:style {:margin-horizontal 20}} + [rn/view {:style {:padding-top 8 :padding-bottom 20}} + [quo/keycard {:holder-name ""}]] + [quo/section-label + {:section (i18n/label :t/what-you-can-do) :container-style {:padding-vertical 8}}] + [quo/settings-item + {:title (i18n/label :t/backup-keycard) + :image :icon + :image-props :i/profile + :action :arrow + :description :text + :description-props {:text (i18n/label :t/create-backup-profile-keycard)} + :on-press (fn [] + (rf/dispatch [:show-bottom-sheet {:content backup-sheet}]))}]]])) diff --git a/src/status_im/contexts/keycard/migrate/events.cljs b/src/status_im/contexts/keycard/migrate/events.cljs index 0c89aadcc9..24983b735c 100644 --- a/src/status_im/contexts/keycard/migrate/events.cljs +++ b/src/status_im/contexts/keycard/migrate/events.cljs @@ -3,23 +3,6 @@ [utils.re-frame :as rf] [utils.security.core :as security])) -(rf/reg-event-fx :keycard/migration.check-empty-card - (fn [{:keys [db]}] - {:fx [[:dispatch - [:keycard/connect - {:key-uid (get-in db [:profile/profile :key-uid]) - :on-success - (fn [] - (rf/dispatch [:keycard/disconnect]) - (rf/dispatch [:open-modal :screen/keycard.profile-keys])) - :on-error - (fn [error] - (if (= error :keycard/error.keycard-blank) - (do - (rf/dispatch [:keycard/disconnect]) - (rf/dispatch [:open-modal :screen/keycard.empty])) - (rf/dispatch [:keycard/on-application-info-error error])))}]]]})) - (defn get-application-info-and-continue [key-uid] (rf/dispatch [:keycard/get-application-info @@ -40,7 +23,7 @@ (cond (not initialized?) - {:fx [[:keycard/init-card + {:fx [[:effects.keycard/init-card {:pin pin :on-success #(get-application-info-and-continue key-uid)}]]} diff --git a/src/status_im/contexts/keycard/migrate/view.cljs b/src/status_im/contexts/keycard/migrate/view.cljs index aef01f0ff6..be4fc69794 100644 --- a/src/status_im/contexts/keycard/migrate/view.cljs +++ b/src/status_im/contexts/keycard/migrate/view.cljs @@ -3,6 +3,7 @@ [react-native.core :as rn] [status-im.common.events-helper :as events-helper] [status-im.common.resources :as resources] + [status-im.contexts.keycard.common.view :as common.view] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -26,9 +27,7 @@ [rn/image {:resize-mode :contain :source (resources/get-image :keycard-migration)}]] - [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)}] + [common.view/tips] [quo/bottom-actions {:actions :one-action :button-one-label (if initialized? (i18n/label :t/enter-keycard-pin) (i18n/label :t/scan-keycard)) diff --git a/src/status_im/contexts/settings/keycard/view.cljs b/src/status_im/contexts/settings/keycard/view.cljs index bb790cd498..dacce3b706 100644 --- a/src/status_im/contexts/settings/keycard/view.cljs +++ b/src/status_im/contexts/settings/keycard/view.cljs @@ -5,95 +5,42 @@ [status-im.common.events-helper :as events-helper] [status-im.common.resources :as resources] [status-im.constants :as constants] - [status-im.contexts.settings.keycard.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn registered-keycard - [{:keys [profile-name profile-image customization-color]}] - [rn/view {:style style/keycard-row} - [quo/icon :i/keycard-card - {:size 20 - :color colors/white-70-blur}] - [rn/view - [quo/text profile-name] - [rn/view {:style style/keycard-owner} - [quo/user-avatar - {:full-name profile-name - :profile-picture profile-image - :customization-color customization-color - :status-indicator false - :ring? false - :size :xxxs}] - [quo/text - {:size :paragraph-2 - :style style/keycard-owner-name} - profile-name]]]]) - -(defn registered-keycards - [] - (let [keycards (rf/sub [:keycard/registered-keycards])] - [:<> - [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 [] - (let [keycard-profile? (rf/sub [:keycard/keycard-profile?])] - [:<> - [quo/page-nav - {:key :header - :background :blur - :icon-name :i/arrow-left - :on-press events-helper/navigate-back}] - [quo/page-top - {:title (i18n/label :t/keycard)}] - (if keycard-profile? - [registered-keycards] - [rn/view {:style {:padding-horizontal 28 :padding-top 20}} - [quo/small-option-card - {:variant :main - :title (i18n/label :t/get-keycard) - :subtitle (i18n/label :t/secure-wallet-card) - :button-label (i18n/label :t/buy-keycard) - :accessibility-label :get-keycard - :image (resources/get-image :keycard-buy) - :on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}] - [rn/view {:style {:margin-top 24}} - [quo/text - {:style {:margin-bottom 1 - :color colors/white-opa-70} - :size :paragraph-2 - :weight :medium} - (i18n/label :t/own-keycard)]] - [quo/small-option-card - {:variant :icon - :title (i18n/label :t/setup-keycard) - :subtitle (i18n/label :t/ready-keycard) - :accessibility-label :setup-keycard - :image (resources/get-image :use-keycard) - :on-press (fn [] - (rf/dispatch [:open-modal :screen/keycard.check - {:on-press - #(rf/dispatch - [:keycard/migration.check-empty-card])}]))}]])])) + [:<> + [quo/page-nav + {:key :header + :background :blur + :icon-name :i/arrow-left + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard)}] + [rn/view {:style {:padding-horizontal 28 :padding-top 20}} + [quo/small-option-card + {:variant :main + :title (i18n/label :t/get-keycard) + :subtitle (i18n/label :t/secure-wallet-card) + :button-label (i18n/label :t/buy-keycard) + :accessibility-label :get-keycard + :image (resources/get-image :keycard-buy) + :on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}] + [rn/view {:style {:margin-top 24}} + [quo/text + {:style {:margin-bottom 1 + :color colors/white-opa-70} + :size :paragraph-2 + :weight :medium} + (i18n/label :t/own-keycard)]] + [quo/small-option-card + {:variant :icon + :title (i18n/label :t/manage-keycard) + :subtitle (i18n/label :t/setup-keycard-description) + :accessibility-label :setup-keycard + :image (resources/get-image :use-keycard) + :on-press (fn [] + (rf/dispatch [:open-modal :screen/keycard.check + {:on-press + #(rf/dispatch [:keycard/check-empty-card])}]))}]]]) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 0341a4e273..046e43c712 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -31,12 +31,14 @@ [status-im.contexts.communities.discover.view :as communities.discover] [status-im.contexts.communities.overview.view :as communities.overview] [status-im.contexts.keycard.authorise.view :as keycard.authorise] + [status-im.contexts.keycard.backup.view :as keycard.backup] [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.manage.profile-keys.view :as keycard.manage.profile-keys] [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] @@ -1004,13 +1006,20 @@ :insets {:top? true :bottom? true}} :component keycard.pin.enter/view} - {:name :screen/keycard.profile-keys + {:name :screen/keycard.migrate.profile-keys :metrics {:track? true} :options {:theme :dark :modalPresentationStyle :fullScreen :insets {:top? true :bottom? true}} :component keycard.migrate.profile-keys/view} + {:name :screen/keycard.manage.profile-keys + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.manage.profile-keys/view} + {:name :screen/keycard.create-profile :metrics {:track? true} :options {:insets {:top? true :bottom? true} @@ -1035,7 +1044,36 @@ :options {:theme :dark :modalPresentationStyle :fullScreen :insets {:top? true :bottom? true}} - :component keycard.factory-reset/failed-view}]) + :component keycard.factory-reset/failed-view} + + {:name :screen/keycard.backup.scan-empty + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.backup/scan-empty} + + {:name :screen/keycard.backup.not-empty + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.backup/not-empty-view} + + {:name :screen/keycard.backup.ready-to-add + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.backup/ready-to-add} + + {:name :screen/keycard.backup.success + :metrics {:track? true} + :options {:theme :dark + :modalPresentationStyle :fullScreen + :insets {:top? true :bottom? true}} + :component keycard.backup/success-view}]) + (defn screens [] diff --git a/translations/en.json b/translations/en.json index a93f7d67b4..8cc125df90 100644 --- a/translations/en.json +++ b/translations/en.json @@ -184,7 +184,11 @@ "back-up-your-seed-phrase": "Backup your recovery phrase", "backing-up": "Backing up...", "backup-disabled": "Disabled", + "backup-empty-keycard-only": "You can only back up to an empty Keycard.", "backup-enabled": "Enabled", + "backup-keycard": "Backup Keycard", + "backup-keycard-created": "Backup Keycard successfully created", + "backup-keycard-instructions": "You’ll need an empty Keycard to create backup. You’ll be able to create a different PIN for backup Keycard. It might make sense to mark your Keycards, so you don’t mix them up.", "backup-recovery-phrase": "Backup recovery phrase", "backup-recovery-phrase-description": "Save in a secure place that only you control, these 12 words give access to all of your funds.", "backup-settings": "Backup settings", @@ -547,6 +551,8 @@ "create-a-pin": "Create a 6-digit passcode", "create-a-puk": "Create a 12-digit PUK", "create-account": "Create account", + "create-backup-keycard": "Create backup Keycard", + "create-backup-profile-keycard": "Create backup for your profile Keycard", "create-category": "Create category", "create-channel": "Create a channel", "create-channel-title": "New channel", @@ -1561,6 +1567,7 @@ "make-sure-keycard": "Make sure the card you scanned is a Keycard.", "make-sure-no-camera-warning": "Make sure no camera or person can see this screen before revealing", "manage-connections": "Manage connections from within Application Connections", + "manage-keycard": "Manage Keycard", "manage-keys-and-storage": "Manage keys and storage", "manage-members": "Manage members", "manage-tokens": "Manage tokens", @@ -2260,6 +2267,7 @@ "say-hi": "Say hi", "scan-an-account-qr-code": "Scan an account QR code", "scan-an-address-qr-code": "Scan an address QR code", + "scan-empty-keycard": "Scan empty Keycard", "scan-key-pairs-qr-code": "Scan key pairs QR code", "scan-keycard": "Scan Keycard", "scan-keycard-actions": "Scan Keycard to see available actions", @@ -2377,6 +2385,7 @@ "settings": "Settings", "setup-group-chat": "Setup group chat", "setup-keycard": "Setup Keycard", + "setup-keycard-description": "Set up your Keycard to log in, sign transactions, create backups and much more.", "setup-syncing": "Pair devices to sync", "share": "Share", "share-account": "Share account", @@ -2778,6 +2787,7 @@ "use-an-empty-keycard": "Use an empty Keycard", "use-an-empty-keycard-subtitle": "Store your new profile keys on Keycard", "use-as-profile-picture": "Use as profile picture", + "use-backup-keycard": "Use as backup Keycard", "use-biometrics": "Use biometrics to fill in your password", "use-keycard": "Use Keycard", "use-keycard-for-status": "From now on, use this Keycard to login to Status and transact with derived accounts on all synced devices. To start using it, logout and log back in with this Keycard.",