From 80ad2b8fca535c8644de1e0057721e71cf6e38dd Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Mon, 7 Oct 2024 20:15:19 +0200 Subject: [PATCH] [#21318] Keycard - Allow user to initiate Profile key pair migration on an empty Keycard (#21359) --- resources/images/ui2/check-your-keycard.png | Bin 0 -> 31491 bytes .../components/info/information_box/view.cljs | 2 +- src/quo/components/markdown/list/style.cljs | 4 +- src/status_im/common/resources.cljs | 1 + .../contexts/keycard/check/view.cljs | 36 ++++++++++ src/status_im/contexts/keycard/effects.cljs | 9 ++- .../contexts/keycard/empty/view.cljs | 36 ++++++++++ .../contexts/keycard/error/view.cljs | 42 ++++++++++++ src/status_im/contexts/keycard/events.cljs | 64 +++++++++++++++--- .../contexts/keycard/login/events.cljs | 36 +--------- .../keycard/{sheet => nfc_sheet}/events.cljs | 16 ++--- .../keycard/{sheet => nfc_sheet}/view.cljs | 11 +-- .../contexts/keycard/not_keycard/view.cljs | 28 ++++++++ .../contexts/keycard/sheets/migrate/view.cljs | 33 +++++++++ .../contexts/keycard/sign/events.cljs | 27 ++++---- src/status_im/contexts/keycard/utils.cljs | 15 ++-- .../contexts/profile/profiles/view.cljs | 13 ++-- .../contexts/profile/settings/list_items.cljs | 5 +- .../contexts/settings/keycard/view.cljs | 45 ++++++++++++ src/status_im/feature_flags.cljs | 3 +- src/status_im/navigation/options.cljs | 6 ++ src/status_im/navigation/screens.cljs | 28 +++++++- src/status_im/navigation/view.cljs | 4 +- src/status_im/subs/keycard.cljs | 5 ++ src/status_im/subs/profile.cljs | 6 ++ translations/en.json | 25 +++++++ 26 files changed, 406 insertions(+), 94 deletions(-) create mode 100644 resources/images/ui2/check-your-keycard.png create mode 100644 src/status_im/contexts/keycard/check/view.cljs create mode 100644 src/status_im/contexts/keycard/empty/view.cljs create mode 100644 src/status_im/contexts/keycard/error/view.cljs rename src/status_im/contexts/keycard/{sheet => nfc_sheet}/events.cljs (64%) rename src/status_im/contexts/keycard/{sheet => nfc_sheet}/view.cljs (83%) create mode 100644 src/status_im/contexts/keycard/not_keycard/view.cljs create mode 100644 src/status_im/contexts/keycard/sheets/migrate/view.cljs create mode 100644 src/status_im/contexts/settings/keycard/view.cljs diff --git a/resources/images/ui2/check-your-keycard.png b/resources/images/ui2/check-your-keycard.png new file mode 100644 index 0000000000000000000000000000000000000000..d4966dfa4815ad4264d2bf105e2fc8b1cb9a929e GIT binary patch literal 31491 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 e9-r^!`TqgAsh`Hx%)zh#0000 + [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/check-keycard) + :description :text + :description-text (i18n/label :t/see-keycard-ready)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain + :source (resources/get-image :check-your-keycard)}]] + [quo/divider-label (i18n/label :t/tips-scan-keycard)] + [rn/view {:style {:padding-horizontal 10}} + [quo/markdown-list + {:container-style {:padding-vertical 10} + :description (i18n/label :t/remove-phone-case)}] + [quo/markdown-list + {:container-style {:padding-bottom 25} + :description (i18n/label :t/keep-card-steady)}]] + [quo/button + {:on-press #(rf/dispatch [:keycard/connect]) + :container-style {:margin-horizontal 20}} + (i18n/label :t/ready-to-scan)]]) diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs index c9e91f371c..04fe3858ec 100644 --- a/src/status_im/contexts/keycard/effects.cljs +++ b/src/status_im/contexts/keycard/effects.cljs @@ -10,10 +10,15 @@ (defonce ^:private active-listeners (atom [])) -(defn register-card-events +(defn unregister-card-events [] (doseq [listener @active-listeners] (keycard/remove-event-listener listener)) + (reset! active-listeners nil)) + +(defn register-card-events + [] + (unregister-card-events) (reset! active-listeners [(keycard/on-card-connected #(rf/dispatch [:keycard/on-card-connected])) (keycard/on-card-disconnected #(rf/dispatch [:keycard/on-card-disconnected])) @@ -23,7 +28,9 @@ (keycard/on-nfc-timeout #(rf/dispatch [:keycard.ios/on-nfc-timeout]))) (keycard/on-nfc-enabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success true])) (keycard/on-nfc-disabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success false]))])) + (rf/reg-fx :effects.keycard/register-card-events register-card-events) +(rf/reg-fx :effects.keycard/unregister-card-events unregister-card-events) (defn check-nfc-enabled [] diff --git a/src/status_im/contexts/keycard/empty/view.cljs b/src/status_im/contexts/keycard/empty/view.cljs new file mode 100644 index 0000000000..6155fbff03 --- /dev/null +++ b/src/status_im/contexts/keycard/empty/view.cljs @@ -0,0 +1,36 @@ +(ns status-im.contexts.keycard.empty.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.sheets.migrate.view :as sheets.migrate] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [] + [:<> + [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-empty) + :description :text + :description-text (i18n/label :t/what-to-do)}] + [rn/view {:style {:padding-horizontal 28 :padding-top 20}} + [quo/small-option-card + {:variant :main + :title (i18n/label :t/import-key-pair-keycard) + :subtitle (i18n/label :t/use-keycard-login-sign) + :button-label (i18n/label :t/import-profile-key-pair) + :accessibility-label :get-keycard + :image (resources/get-image :generate-keys) + :on-press #(rf/dispatch [:show-bottom-sheet + {:theme :dark + :content (fn [] [sheets.migrate/view])}])}]] + [quo/information-box + {:type :default + :style {:margin-top 32 :margin-horizontal 28}} + (i18n/label :t/empty-card-info)]]) diff --git a/src/status_im/contexts/keycard/error/view.cljs b/src/status_im/contexts/keycard/error/view.cljs new file mode 100644 index 0000000000..3f023cdcf7 --- /dev/null +++ b/src/status_im/contexts/keycard/error/view.cljs @@ -0,0 +1,42 @@ +(ns status-im.contexts.keycard.error.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [status-im.common.events-helper :as events-helper] + [utils.re-frame :as rf])) + +(def titles + {:keycard/error.keycard-wrong {:title "Keycard is not empty" + :description "You can’t use it to store new keys right now"} + :keycard/error.keycard-unpaired {:title "Keycard is full" + :description "All pairing slots are occupied"} + :keycard/error.keycard-frozen {:title "Keycard is locked" + :description "You can’t use it right now"} + :keycard/error.keycard-locked {:title "Keycard is locked" + :description "You can’t use it right now"}}) + +(defn view + [] + (let [{:keys [top bottom]} (safe-area/get-insets) + error (rf/sub [:keycard/application-info-error]) + {:keys [title description]} (get titles error)] + [quo/overlay + {:type :shell + :container-style {:padding-top top + :padding-bottom bottom}} + [quo/page-nav + {:key :header + :background :blur + :icon-name :i/arrow-left + :on-press events-helper/navigate-back}] + [quo/page-top + {:title title + :description :text + :description-text description}] + [rn/view {:height 226}] + [rn/view {:padding-horizontal 20} + [quo/info-message + {:container-style {:padding-top 15} + :icon :i/info + :size :default} + "To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this features on mobile, feel free to upvote them and discuss in the Status community."]]])) diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs index e57ba5d2fb..274b78390c 100644 --- a/src/status_im/contexts/keycard/events.cljs +++ b/src/status_im/contexts/keycard/events.cljs @@ -1,8 +1,8 @@ (ns status-im.contexts.keycard.events (:require [re-frame.core :as rf] status-im.contexts.keycard.login.events + status-im.contexts.keycard.nfc-sheet.events status-im.contexts.keycard.pin.events - status-im.contexts.keycard.sheet.events status-im.contexts.keycard.sign.events [status-im.contexts.keycard.utils :as keycard.utils] [taoensso.timbre :as log])) @@ -14,7 +14,9 @@ (rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled (fn [{:keys [db]}] (log/debug "[keycard] nfc user cancelled") - {:db (assoc-in db [:keycard :pin :status] nil) + {:db (-> db + (assoc-in [:keycard :pin :status] nil) + (assoc-in [:keycard :on-nfc-cancelled-event-vector] nil)) :fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])] [:dispatch on-nfc-cancelled-event-vector])]})) @@ -42,12 +44,6 @@ {:db (assoc-in db [:keycard :card-connected?] false) :fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]})) -(rf/reg-event-fx :keycard/get-application-info - (fn [_ [{:keys [on-success on-failure]}]] - (log/debug "[keycard] get-application-info") - {:effects.keycard/get-application-info {:on-success on-success - :on-failure on-failure}})) - (rf/reg-event-fx :keycard/on-retrieve-pairings-success (fn [{:keys [db]} [pairings]] {:db (assoc-in db [:keycard :pairings] pairings) @@ -60,7 +56,7 @@ (rf/reg-event-fx :keycard/on-action-with-pin-error (fn [{:keys [db]} [error]] - (log/debug "[keycard] get keys error: " error) + (log/debug "[keycard] on-action-with-pin-error: " error) (let [tag-was-lost? (keycard.utils/tag-lost? (:error error)) pin-retries-count (keycard.utils/pin-retries (:error error))] (if tag-was-lost? @@ -69,7 +65,53 @@ {:effects.utils/show-popup {:title "wrong-keycard"}} {:db (-> db (assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count) - (update-in [:keycard :pin] assoc :status :error)) - :fx [[:dispatch [:keycard/hide-connection-sheet]] + (assoc-in [:keycard :pin :status] :error)) + :fx [[:dispatch [:keycard/disconnect]] (when (zero? pin-retries-count) [:effects.utils/show-popup {:title "frozen-keycard"}])]}))))) + +(rf/reg-event-fx :keycard/on-get-application-info-success + (fn [{:keys [db]} [application-info {:keys [key-uid on-success-fx]}]] + (let [error (keycard.utils/validate-application-info key-uid application-info)] + (if error + (case error + :keycard/error.not-keycard + {:fx [[:dispatch [:keycard/disconnect]] + [:dispatch [:open-modal :screen/keycard.not-keycard]]]} + :keycard/error.keycard-blank + {:fx [[:dispatch [:keycard/disconnect]] + [:dispatch [:open-modal :screen/keycard.empty]]]} + {:db (assoc-in db [:keycard :application-info-error] error) + :fx [[:dispatch [:keycard/disconnect]] + [:dispatch [:open-modal :screen/keycard.error]]]}) + {:db (-> db + (assoc-in [:keycard :application-info] application-info) + (assoc-in [:keycard :pin :status] :verifying)) + :fx on-success-fx})))) + +(rf/reg-event-fx :keycard/get-application-info + (fn [_ [{:keys [on-success on-failure]}]] + {:effects.keycard/get-application-info {:on-success on-success :on-failure on-failure}})) + +(rf/reg-event-fx :keycard/cancel-connection + (fn [{:keys [db]}] + {:db (-> db + (assoc-in [:keycard :on-card-connected-event-vector] nil) + (assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))})) + +(rf/reg-event-fx :keycard/disconnect + (fn [_ _] + {:fx [[:dispatch [:keycard/cancel-connection]] + [:dispatch [:keycard/hide-connection-sheet]]]})) + +(rf/reg-event-fx :keycard/connect + (fn [{:keys [db]} [args]] + (let [connected? (get-in db [:keycard :card-connected?]) + event-vector [:keycard/get-application-info + {:on-success #(rf/dispatch [:keycard/on-get-application-info-success % args])}]] + {:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector) + :fx [[:dispatch + [:keycard/show-connection-sheet + {:on-cancel-event-vector [:keycard/cancel-connection]}]] + (when connected? + [:dispatch event-vector])]}))) diff --git a/src/status_im/contexts/keycard/login/events.cljs b/src/status_im/contexts/keycard/login/events.cljs index f2d443d432..e5cc657a1c 100644 --- a/src/status_im/contexts/keycard/login/events.cljs +++ b/src/status_im/contexts/keycard/login/events.cljs @@ -1,7 +1,5 @@ (ns status-im.contexts.keycard.login.events - (:require [status-im.contexts.keycard.utils :as keycard.utils] - [taoensso.timbre :as log] - [utils.re-frame :as rf])) + (:require [utils.re-frame :as rf])) (rf/reg-event-fx :keycard.login/on-get-keys-success (fn [{:keys [db]} [data]] @@ -16,7 +14,7 @@ :password encryption-public-key :key-uid key-uid :name (:name profile))) - :fx [[:dispatch [:keycard/hide-connection-sheet]] + :fx [[:dispatch [:keycard/disconnect]] [:effects.keycard/login-with-keycard {:password encryption-public-key :whisper-private-key whisper-private-key @@ -33,36 +31,8 @@ :password encryption-public-key :key-uid key-uid :name (:name profile))) - :fx [[:dispatch [:keycard/hide-connection-sheet]] + :fx [[:dispatch [:keycard/disconnect]] [:effects.keycard/login-with-keycard {:password encryption-public-key :whisper-private-key whisper-private-key :key-uid key-uid}]]})))) - -(rf/reg-event-fx :keycard.login/on-get-application-info-success - (fn [{:keys [db]} [application-info {:keys [key-uid on-read-fx]}]] - (let [error (keycard.utils/validate-application-info key-uid application-info)] - (if error - {:effects.utils/show-popup {:title (str error)}} - {:db (-> db - (assoc-in [:keycard :application-info] application-info) - (assoc-in [:keycard :pin :status] :verifying)) - :fx on-read-fx})))) - -(rf/reg-event-fx :keycard.login/cancel-reading-card - (fn [{:keys [db]}] - {:db (assoc-in db [:keycard :on-card-connected-event-vector] nil)})) - -(rf/reg-event-fx :keycard/read-card - (fn [{:keys [db]} [args]] - (let [connected? (get-in db [:keycard :card-connected?]) - event-vector [:keycard/get-application-info - {:on-success #(rf/dispatch [:keycard.login/on-get-application-info-success % - args])}]] - (log/debug "[keycard] proceed-to-login") - {:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector) - :fx [[:dispatch - [:keycard/show-connection-sheet - {:on-cancel-event-vector [:keycard.login/cancel-reading-card]}]] - (when connected? - [:dispatch event-vector])]}))) diff --git a/src/status_im/contexts/keycard/sheet/events.cljs b/src/status_im/contexts/keycard/nfc_sheet/events.cljs similarity index 64% rename from src/status_im/contexts/keycard/sheet/events.cljs rename to src/status_im/contexts/keycard/nfc_sheet/events.cljs index 01f7419d0a..195d185611 100644 --- a/src/status_im/contexts/keycard/sheet/events.cljs +++ b/src/status_im/contexts/keycard/nfc_sheet/events.cljs @@ -1,18 +1,16 @@ -(ns status-im.contexts.keycard.sheet.events +(ns status-im.contexts.keycard.nfc-sheet.events (:require [re-frame.core :as rf] [react-native.platform :as platform] [taoensso.timbre :as log])) -(rf/reg-event-fx :keycard/show-connection-sheet-component - (fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]] - {:db (assoc-in db [:keycard :connection-sheet-opts] {:on-close #(rf/dispatch on-cancel-event-vector)}) - :fx [[:dismiss-keyboard true] - [:show-nfc-sheet nil]]})) - (rf/reg-event-fx :keycard/show-connection-sheet - (fn [_ [args]] + (fn [{:keys [db]} [{:keys [on-cancel-event-vector]} :as args]] (if platform/android? - {:dispatch [:keycard/show-connection-sheet-component args]} + {:db (assoc-in db + [:keycard :connection-sheet-opts] + {:on-close #(rf/dispatch on-cancel-event-vector)}) + :fx [[:dismiss-keyboard true] + [:show-nfc-sheet nil]]} {:effects.keycard.ios/start-nfc {:on-success (fn [] diff --git a/src/status_im/contexts/keycard/sheet/view.cljs b/src/status_im/contexts/keycard/nfc_sheet/view.cljs similarity index 83% rename from src/status_im/contexts/keycard/sheet/view.cljs rename to src/status_im/contexts/keycard/nfc_sheet/view.cljs index 4422ed3852..8ef8f91fab 100644 --- a/src/status_im/contexts/keycard/sheet/view.cljs +++ b/src/status_im/contexts/keycard/nfc_sheet/view.cljs @@ -1,8 +1,9 @@ -(ns status-im.contexts.keycard.sheet.view +(ns status-im.contexts.keycard.nfc-sheet.view (:require [quo.foundations.colors :as colors] quo.theme [react-native.core :as rn] [status-im.common.resources :as resources] + [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn connect-keycard @@ -18,13 +19,13 @@ :padding-vertical 30 :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}} [rn/text {:style {:font-size 26 :color "#9F9FA5" :margin-bottom 36}} - "Ready to Scan"] + (i18n/label :t/ready-to-scan)] [rn/image {:source (resources/get-image :nfc-prompt)}] [rn/text {:style {:font-size 16 :color :white :margin-vertical 36}} (if connected? - "Connected. Don’t move your card." - "Hold your phone near a Status Keycard")] + (i18n/label :t/connected-dont-move) + (i18n/label :t/hold-phone-near-keycard))] [rn/pressable {:on-press (fn [] (when on-close (on-close)) @@ -33,4 +34,4 @@ [rn/view {:style {:background-color "#8E8E93" :flex 1 :align-items :center :padding 18 :border-radius 10}} [rn/text {:style {:color :white :font-size 16}} - "Cancel"]]]]])) + (i18n/label :t/cancel)]]]]])) diff --git a/src/status_im/contexts/keycard/not_keycard/view.cljs b/src/status_im/contexts/keycard/not_keycard/view.cljs new file mode 100644 index 0000000000..742aae1545 --- /dev/null +++ b/src/status_im/contexts/keycard/not_keycard/view.cljs @@ -0,0 +1,28 @@ +(ns status-im.contexts.keycard.not-keycard.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [status-im.common.events-helper :as events-helper] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [] + (let [{:keys [top bottom]} (safe-area/get-insets)] + [quo/overlay + {:type :shell + :container-style {:padding-top top + :padding-bottom bottom}} + [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/oops-not-keycard) + :description :text + :description-text (i18n/label :t/make-sure-keycard)}] + [rn/view {:flex 1}] + [rn/view {:padding-horizontal 20} + [quo/button {:on-press #(rf/dispatch [:keycard/connect])} + (i18n/label :t/try-again)]]])) diff --git a/src/status_im/contexts/keycard/sheets/migrate/view.cljs b/src/status_im/contexts/keycard/sheets/migrate/view.cljs new file mode 100644 index 0000000000..23b9e02a9a --- /dev/null +++ b/src/status_im/contexts/keycard/sheets/migrate/view.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.keycard.sheets.migrate.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [] + (let [profile-name (rf/sub [:profile/name]) + profile-picture (rf/sub [:profile/image]) + customization-color (rf/sub [:profile/customization-color])] + [:<> + [quo/drawer-top + {:type :context-tag + :context-tag-type :default + :title (i18n/label :t/migrate-key-pair-keycard) + :full-name profile-name + :profile-picture profile-picture + :customization-color customization-color}] + [rn/view {:style {:padding-horizontal 20}} + [quo/text {} + (i18n/label :t/migrate-key-pair-keycard-default-key {:name profile-name})] + [quo/information-box + {:type :default + :style {:margin-top 20 :margin-bottom 12}} + (i18n/label :t/migrate-key-pair-keycard-info)]] + [quo/bottom-actions + {:actions :two-actions + :button-one-label (i18n/label :t/continue) + :button-one-props {:on-press #()} + :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/sign/events.cljs b/src/status_im/contexts/keycard/sign/events.cljs index 3dc5c6388b..f7b9adba7e 100644 --- a/src/status_im/contexts/keycard/sign/events.cljs +++ b/src/status_im/contexts/keycard/sign/events.cljs @@ -14,16 +14,17 @@ path (get-in db [:wallet :accounts current-address :path]) key-uid (get-in db [:profile/profile :key-uid])] {:fx [[:dispatch - [:keycard/read-card - {:key-uid key-uid - :on-read-fx [[:effects.keycard/sign - {:pin pin-text - :path path - :hash (utils.address/naked-address tx-hash) - :on-success - #(do - (rf/dispatch [:keycard/hide-connection-sheet]) - (rf/dispatch - [:wallet/proceed-with-transactions-signatures - (get-signature-map tx-hash %)])) - :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]]]}))) + [:keycard/connect + {:key-uid key-uid + :on-success-fx [[:effects.keycard/sign + {:pin pin-text + :path path + :hash (utils.address/naked-address tx-hash) + :on-success + #(do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch + [:wallet/proceed-with-transactions-signatures + (get-signature-map tx-hash %)])) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error + %])}]]}]]]}))) diff --git a/src/status_im/contexts/keycard/utils.cljs b/src/status_im/contexts/keycard/utils.cljs index 7d4da6422a..44d414beb8 100644 --- a/src/status_im/contexts/keycard/utils.cljs +++ b/src/status_im/contexts/keycard/utils.cljs @@ -18,28 +18,31 @@ (defn validate-application-info [profile-key-uid {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}] (let [profile-mismatch? (or (nil? profile-key-uid) (not= profile-key-uid key-uid))] - (log/debug "[keycard] login-with-keycard" + (log/debug "[keycard]" "login-with-keycard" "empty application info" (empty? application-info) "no key-uid" (empty? key-uid) "profile-mismatch?" profile-mismatch? "no pairing" paired?) (cond (empty? application-info) - :not-keycard + :keycard/error.not-keycard (empty? (:key-uid application-info)) - :keycard-blank + :keycard/error.keycard-blank profile-mismatch? - :keycard-wrong + :keycard/error.keycard-wrong (not paired?) - :keycard-unpaired + :keycard/error.keycard-unpaired (and (zero? pin-retry-counter) (or (nil? puk-retry-counter) (pos? puk-retry-counter))) - nil + :keycard/error.keycard-frozen + + (zero? puk-retry-counter) + :keycard/error.keycard-locked :else nil))) diff --git a/src/status_im/contexts/profile/profiles/view.cljs b/src/status_im/contexts/profile/profiles/view.cljs index 72ebd48f3e..c8942f0ecc 100644 --- a/src/status_im/contexts/profile/profiles/view.cljs +++ b/src/status_im/contexts/profile/profiles/view.cljs @@ -242,12 +242,13 @@ {:on-complete (fn [pin-text] (rf/dispatch - [:keycard/read-card - {:key-uid key-uid - :on-read-fx [[:effects.keycard/get-keys - {:pin pin-text - :on-success #(rf/dispatch [:keycard.login/on-get-keys-success %]) - :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]))}] + [:keycard/connect + {:key-uid key-uid + :on-success-fx [[:effects.keycard/get-keys + {:pin pin-text + :on-success #(rf/dispatch [:keycard.login/on-get-keys-success %]) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error + %])}]]}]))}] [password-input])] (when-not keycard-pairing [quo/button diff --git a/src/status_im/contexts/profile/settings/list_items.cljs b/src/status_im/contexts/profile/settings/list_items.cljs index fb68c3446a..5f735d6069 100644 --- a/src/status_im/contexts/profile/settings/list_items.cljs +++ b/src/status_im/contexts/profile/settings/list_items.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.profile.settings.list-items (:require [status-im.common.not-implemented :as not-implemented] [status-im.config :as config] + [status-im.feature-flags :as ff] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -54,9 +55,9 @@ :image :icon :blur? true :action :arrow}) - (when config/show-not-implemented-features? + (when (ff/enabled? ::ff/keycard.migrate-profile) {:title (i18n/label :t/keycard) - :on-press not-implemented/alert + :on-press #(rf/dispatch [:open-modal :screen/settings.keycard]) :image-props :i/keycard :image :icon :blur? true diff --git a/src/status_im/contexts/settings/keycard/view.cljs b/src/status_im/contexts/settings/keycard/view.cljs new file mode 100644 index 0000000000..cdb1a654b5 --- /dev/null +++ b/src/status_im/contexts/settings/keycard/view.cljs @@ -0,0 +1,45 @@ +(ns status-im.contexts.settings.keycard.view + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [status-im.common.resources :as resources] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(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? + [:<>] + [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 :generate-keys) + :on-press #()}] + [rn/view {:style {:margin-top 24}} + [quo/text + {:style {:margin-bottom 12 + :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 #(rf/dispatch [:open-modal :screen/keycard.check])}]])])) diff --git a/src/status_im/feature_flags.cljs b/src/status_im/feature_flags.cljs index ede8334303..fbb5a4bbda 100644 --- a/src/status_im/feature_flags.cljs +++ b/src/status_im/feature_flags.cljs @@ -32,7 +32,8 @@ ::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED) ::wallet.saved-addresses (enabled-in-env? :WALLET_SAVED_ADDRESSES) ::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED) - ::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)}) + ::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED) + ::keycard.migrate-profile false}) (defonce ^:private feature-flags-config (reagent/atom initial-flags)) diff --git a/src/status_im/navigation/options.cljs b/src/status_im/navigation/options.cljs index 1d7f3faee9..3697c83b11 100644 --- a/src/status_im/navigation/options.cljs +++ b/src/status_im/navigation/options.cljs @@ -69,6 +69,12 @@ transitions/new-to-status-modal-animations transitions/push-animations-for-transparent-background)})) +(def keycard-modal-screen-options + (assoc transparent-modal-screen-options + :layout nil + :theme :dark + :insets {:top? true :bottom? true})) + (def sheet-options {:layout {:componentBackgroundColor :transparent :orientation ["portrait"] diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 07f6dcda59..ce5a29881f 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -26,6 +26,10 @@ [status-im.contexts.communities.actions.share-community.view :as share-community] [status-im.contexts.communities.discover.view :as communities.discover] [status-im.contexts.communities.overview.view :as communities.overview] + [status-im.contexts.keycard.check.view :as keycard.check] + [status-im.contexts.keycard.empty.view :as keycard.empty] + [status-im.contexts.keycard.error.view :as keycard.error] + [status-im.contexts.keycard.not-keycard.view :as keycard.not-keycard] [status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile] [status-im.contexts.onboarding.create-password.view :as create-password] [status-im.contexts.onboarding.create-profile.view :as create-profile] @@ -58,6 +62,7 @@ [status-im.contexts.profile.settings.screens.password.view :as settings-password] [status-im.contexts.profile.settings.screens.syncing.view :as settings.syncing] [status-im.contexts.profile.settings.view :as settings] + [status-im.contexts.settings.keycard.view :as settings.keycard] [status-im.contexts.settings.language-and-currency.currency.view :as settings.currency-selection] [status-im.contexts.settings.language-and-currency.view :as settings.language-and-currency] [status-im.contexts.settings.privacy-and-security.share-usage.view :as settings.share-usage] @@ -584,6 +589,10 @@ :options options/transparent-modal-screen-options :component wallet-options/view} + {:name :screen/settings.keycard + :options options/keycard-modal-screen-options + :component settings.keycard/view} + {:name :screen/settings.rename-keypair :options options/transparent-screen-options :component keypair-rename/view} @@ -671,7 +680,24 @@ :popGesture false :hardwareBackButton {:dismissModalOnPress false :popStackOnPress false}) - :component change-password-loading/view}] + :component change-password-loading/view} + + ;; Keycard + {:name :screen/keycard.check + :options options/keycard-modal-screen-options + :component keycard.check/view} + + {:name :screen/keycard.empty + :options options/keycard-modal-screen-options + :component keycard.empty/view} + + {:name :screen/keycard.error + :options options/keycard-modal-screen-options + :component keycard.error/view} + + {:name :screen/keycard.not-keycard + :options options/keycard-modal-screen-options + :component keycard.not-keycard/view}] [{:name :shell :options {:theme :dark}}] diff --git a/src/status_im/navigation/view.cljs b/src/status_im/navigation/view.cljs index d920670d1c..eaff875400 100644 --- a/src/status_im/navigation/view.cljs +++ b/src/status_im/navigation/view.cljs @@ -11,7 +11,7 @@ [status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen] [status-im.common.bottom-sheet.view :as bottom-sheet] [status-im.common.toasts.view :as toasts] - [status-im.contexts.keycard.sheet.view :as keycard.sheet] + [status-im.contexts.keycard.nfc-sheet.view :as keycard.sheet] [status-im.navigation.screens :as screens] [status-im.setup.hot-reload :as reloader] [utils.re-frame :as rf])) @@ -47,7 +47,7 @@ (merge {:flex 1 :margin-top alert-banners-top-margin - :background-color (or background-color (colors/theme-colors colors/white colors/neutral-100 theme))} + :background-color (or background-color (colors/theme-colors colors/white colors/neutral-95 theme))} (when bottom? {:padding-bottom (safe-area/get-bottom)}) (when top? diff --git a/src/status_im/subs/keycard.cljs b/src/status_im/subs/keycard.cljs index 53db0caecd..245d3a5434 100644 --- a/src/status_im/subs/keycard.cljs +++ b/src/status_im/subs/keycard.cljs @@ -36,3 +36,8 @@ (fn [keycard] (:connection-sheet-opts keycard))) +(rf/reg-sub + :keycard/application-info-error + :<- [:keycard] + (fn [keycard] + (:application-info-error keycard))) diff --git a/src/status_im/subs/profile.cljs b/src/status_im/subs/profile.cljs index 31c32b0c7d..ef1be091f9 100644 --- a/src/status_im/subs/profile.cljs +++ b/src/status_im/subs/profile.cljs @@ -326,6 +326,12 @@ (fn [profile] (profile.utils/photo profile))) +(re-frame/reg-sub + :profile/name + :<- [:profile/profile] + (fn [profile] + (profile.utils/displayed-name profile))) + (re-frame/reg-sub :profile/login-profile :<- [:profile/login] diff --git a/translations/en.json b/translations/en.json index cb525c8cb8..ac22377f41 100644 --- a/translations/en.json +++ b/translations/en.json @@ -274,6 +274,7 @@ "buy-crypto-title": "Looks like your wallet is empty", "buy-eth": "Buy ETH", "buy-ethereum": "Buy Ethereum", + "buy-keycard": "Buy Keycard", "by-continuing-you-accept": "By continuing you accept our ", "camera-access-error": "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected.", "camera-permission-denied": "Permission denied", @@ -362,6 +363,7 @@ "check-before-syncing-doc-checkbox-2": "Make sure you are logged in on the other device", "check-before-syncing-doc-checkbox-3": "Disable the firewall and VPN on your devices", "check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:", + "check-keycard": "Check your Keycard", "check-on-block-explorer": "Check on block explorer", "check-on-opensea": "Check on opensea", "check-other-device-for-pairing": "Check your other device for a pairing request.", @@ -470,6 +472,7 @@ "connect-with-users": "Connect with users", "connected": "Connected", "connected-dapps": "Connected dApps", + "connected-dont-move": "Connected. Don’t move your card.", "connected-peers": "Connected and discovered peers", "connected-to": "Connected to", "connecting": "Connecting...", @@ -827,6 +830,7 @@ "emojihash-description": "A visual representation of your chat key. It will help other users recognize your profile.", "emojis": "Emojis", "empty-activity-center": "Your chat notifications\nwill appear here", + "empty-card-info": "To generate new key pair, or import existent non-profile keys to the Keycard, please use the Status desktop app. If you'd like these features on mobile, feel free to upvote them and discuss them in Status community.", "empty-chat-description": "There are no messages \nin this chat yet", "empty-chat-description-community": "It's been quiet here for the last {{quiet-hours}}.", "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", @@ -1073,6 +1077,7 @@ "generating-mnemonic": "Generating seed phrase", "generic-error": "Error: {{generic-error}}", "get-a-keycard": "Get a Keycard", + "get-keycard": "Get Keycard", "get-started": "Get started", "get-status-at": "Get Status at http://status.im", "get-stickers": "Get Stickers", @@ -1132,6 +1137,7 @@ "history-nodes": "Status nodes", "hit-photos-limit": "You can only add {{max-photos}} photos to your message", "hold-card": "Hold card to the back\n of your phone", + "hold-phone-near-keycard": "Hold your phone near a Status Keycard", "hold-to-post-1": "Hold", "hold-to-post-2": "to post", "home": "Home", @@ -1167,10 +1173,12 @@ "import-community-title": "Import a community", "import-from-keycard": "Import from Keycard", "import-key-pair": "Import key pair", + "import-key-pair-keycard": "Import profile key pair to Keycard", "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", "import-keypair-to-use-account": "Import key pair to use this account", "import-private-key": "Import private key", "import-private-key-info": "New addresses cannot be derived from an account imported from a private key. Import using a seed phrase if you wish to derive addresses.", + "import-profile-key-pair": "Import profile key pair", "import-to-use-derived-accounts": "Import to use derived accounts", "import-using-phrase": "Import using recovery phrase", "in": "in", @@ -1295,6 +1303,7 @@ "jump-to": "Jump to", "jun": "Jun", "K": "K", + "keep-card-steady": "Keep the card steady under the phone", "keep-key": "KeepKey", "key": "Key", "key-managment": "Key management", @@ -1324,6 +1333,7 @@ "keycard-connected-title": "Connected", "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-enter-new-passcode": "Enter new passcode {{step}}/2", "keycard-error-description": "Connect the card again to continue", "keycard-error-title": "Connection lost", @@ -1478,6 +1488,7 @@ "make-admin": "Make admin", "make-moderator": "Make moderator", "make-one-it-is-easy-we-promise": "Make one, it’s easy, we promise!", + "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-keys-and-storage": "Manage keys and storage", @@ -1557,6 +1568,9 @@ "messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group", "messages-gap-warning": "Some messages might be missing", "might-break": "Might break some ÐApps", + "migrate-key-pair-keycard": "Migrate profile key pair to Keycard", + "migrate-key-pair-keycard-default-key": "{{name}} is your default Status key pair. Migrating this key pair to Keycard will require you to use your Keycard to login to Status and to transact with the key pair’s derived accounts on all synced devices.\n\nKey pair will be removed from device and stored on Keycard.", + "migrate-key-pair-keycard-info": "Re-encrypting your data with your new Keycard login method may take some time, during which you won’t be able to use the app.", "migration-successful": "Migration successful", "migration-successful-text": "Account succesfully migrated to Keycard", "migrations-failed-content": "{{message}}\nschema version: initial {{initial-version}}, current {{current-version}}, last {{last-version}}\n\nA database error occured. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. \"{{erase-multiaccounts-data-button-text}}\" button, will remove all other data and allows you to access your funds and send messages.", @@ -1794,6 +1808,7 @@ "online-community-member": "Online", "online-now": "Online now", "only-mentions": "Only @mentions", + "oops-not-keycard": "Oops, this isn’t a Keycard", "oops-this-qr-does-not-contain-an-address": "Oops! This QR does not contain an address", "oops-wrong-password": "Oops, wrong password!", "oops-wrong-word": "Oops! Wrong word", @@ -1824,6 +1839,7 @@ "outgoing": "Outgoing", "outgoing-transaction": "Outgoing transaction", "overview": "Overview", + "own-keycard": "Already own a Keycard?", "own-your-crypto": "Own your crypto", "owner": "Owner", "page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings", @@ -2004,6 +2020,8 @@ "re-encrypt-key": "Re-encrypt your keys", "read": "Read", "read-more": "Read more", + "ready-keycard": "Get your Keycard ready", + "ready-to-scan": "Ready to Scan", "rearrange-categories": "Rearrange Categories", "receive": "Receive", "receive-at-least": "Receive at least", @@ -2058,6 +2076,7 @@ "remove-network": "Remove network", "remove-nickname": "Remove nickname", "remove-nickname-toast": "You have removed {{secondary-name}}'s nickname", + "remove-phone-case": "Remove your phone case if you have one", "remove-private-key-address-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase.", "remove-profile-confirm-message": "All profile data will removed from device.", "remove-profile-message": "Remove profile from this device", @@ -2166,9 +2185,11 @@ "searching-for-activity": "Searching for activity...", "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", "secret-keys-confirmation-title": "Written the codes down?", + "secure-wallet-card": "A secure and private cold wallet in a card format", "security": "Security", "see-details": "See details", "see-it-again": "SEE IT AGAIN", + "see-keycard-ready": "Let’s see what’s on your Keycard. Ready?", "see-recovery-phrase-again": "See recovery phrase again", "see-sticker-set": "See the full sticker set", "see-suggestions": "See suggestions", @@ -2248,6 +2269,7 @@ "set-up-sync": "Set up sync", "settings": "Settings", "setup-group-chat": "Setup group chat", + "setup-keycard": "Setup Keycard", "setup-syncing": "Pair devices to sync", "share": "Share", "share-account": "Share account", @@ -2472,6 +2494,7 @@ "time-in-mins": "{{minutes}} min", "timeline": "Timeline", "tip-cap": "Tip cap", + "tips-scan-keycard": "Tips to scan your Keycard", "to": "to", "to-block": "Block", "to-capitalized": "To", @@ -2626,6 +2649,7 @@ "use-as-profile-picture": "Use as profile picture", "use-biometrics": "Use biometrics to fill in your password", "use-keycard": "Use Keycard", + "use-keycard-login-sign": "Use this Keycard to login and sign transactions", "use-keycard-subtitle": "Keys will be stored on your Keycard", "use-photo": "Use Photo", "use-recovery-phrase": "Use recovery phrase", @@ -2770,6 +2794,7 @@ "what-are-you-waiting-for": "What are you waiting for?", "what-changed": "What changed", "what-is-shared": "What is shared", + "what-to-do": "What you would like to do?", "what-we-will-receive": "What we will receive:", "what-we-wont-receive": "What we won't receive:", "whats-on-your-mind": "What’s on your mind…",