From 52c6687608da6e6f03f251f17b002f8e1bf11630 Mon Sep 17 00:00:00 2001 From: Jamie Caprani Date: Thu, 27 Apr 2023 05:26:32 +0100 Subject: [PATCH] feat: add identifiers screen to onboarding-flow (#15684) --- .../onboarding/common/background/view.cljs | 52 ++++++++++++++-- .../onboarding/common/carousel/animation.cljs | 23 ++++--- .../onboarding/common/carousel/style.cljs | 9 +-- .../onboarding/common/carousel/view.cljs | 51 ++++++---------- .../contexts/onboarding/events.cljs | 2 +- .../onboarding/identifiers/style.cljs | 20 ++++++ .../contexts/onboarding/identifiers/view.cljs | 61 +++++++++++++++++++ src/status_im2/navigation/screens.cljs | 7 +++ test/appium/views/sign_in_view.py | 2 + translations/en.json | 12 +++- 10 files changed, 183 insertions(+), 56 deletions(-) create mode 100644 src/status_im2/contexts/onboarding/identifiers/style.cljs create mode 100644 src/status_im2/contexts/onboarding/identifiers/view.cljs diff --git a/src/status_im2/contexts/onboarding/common/background/view.cljs b/src/status_im2/contexts/onboarding/common/background/view.cljs index b372b78b22..68cdac5f66 100644 --- a/src/status_im2/contexts/onboarding/common/background/view.cljs +++ b/src/status_im2/contexts/onboarding/common/background/view.cljs @@ -1,17 +1,58 @@ (ns status-im2.contexts.onboarding.common.background.view (:require [react-native.core :as rn] [react-native.blur :as blur] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [status-im2.common.resources :as resources] [status-im2.contexts.onboarding.common.carousel.view :as carousel] [status-im2.contexts.onboarding.common.background.style :as style] + [react-native.reanimated :as reanimated] [status-im2.contexts.onboarding.common.carousel.animation :as carousel.animation])) +(def header-text + [{:text (i18n/label :t/join-decentralised-communities) + :sub-text (i18n/label :t/participate-in-the-metaverse)} + {:text (i18n/label :t/chat-with-friends) + :sub-text (i18n/label :t/with-full-encryption)} + {:text (i18n/label :t/own-your-crypto) + :sub-text (i18n/label :t/use-the-multichain-wallet)} + {:text (i18n/label :t/discover-web3) + :sub-text (i18n/label :t/explore-the-decentralized-web)}]) + +(defn background-image + [content-width] + [rn/image + {:style {:resize-mode :stretch + :width content-width} + :source (resources/get-image :onboarding-illustration)}]) + +(defonce progress (atom nil)) +(defonce paused (atom nil)) + (defn f-view [dark-overlay?] - (let [animate? (not dark-overlay?)] - (when animate? (carousel.animation/initialize-animation)) + (let [view-id (rf/sub [:view-id]) + animate? (not dark-overlay?) + window-width (rf/sub [:dimensions/window-width])] + (when animate? + (carousel.animation/use-initialize-animation progress paused animate?)) + + (rn/use-effect + (fn [] + (reanimated/set-shared-value @paused (not= view-id :intro)) + (fn [] + (when (= view-id :generating-keys) + (carousel.animation/cleanup-animation progress paused)))) + [view-id]) + [rn/view {:style style/background-container} - [:f> carousel/f-view animate?] + [carousel/view + {:animate? animate? + :progress progress + :header-text header-text + :header-background true + :background [background-image (* 4 window-width)]}] (when dark-overlay? [blur/view {:style style/background-blur-overlay @@ -20,6 +61,5 @@ :blur-type :transparent :overlay-color :transparent}])])) -(defn view - [dark-overlay?] - [:f> f-view dark-overlay?]) +(defn view [dark-overlay?] [:f> f-view dark-overlay?]) + diff --git a/src/status_im2/contexts/onboarding/common/carousel/animation.cljs b/src/status_im2/contexts/onboarding/common/carousel/animation.cljs index d007774edd..a2eb7a431a 100644 --- a/src/status_im2/contexts/onboarding/common/carousel/animation.cljs +++ b/src/status_im2/contexts/onboarding/common/carousel/animation.cljs @@ -1,11 +1,9 @@ (ns status-im2.contexts.onboarding.common.carousel.animation (:require + [react-native.core :as rn] [react-native.reanimated :as reanimated] [utils.worklets.onboarding-carousel :as worklets.onboarding-carousel])) -(def progress (atom nil)) -(def paused (atom nil)) - (def ^:const progress-bar-animation-delay 300) (def ^:const progress-bar-animation-duration 4000) @@ -33,14 +31,23 @@ -1) paused))) -(defn initialize-animation - [] +(defn use-initialize-animation + [progress paused animate?] (reset! progress (reanimated/use-shared-value 0)) (reset! paused (reanimated/use-shared-value false)) - (animate-progress @progress @paused)) + (rn/use-effect + (fn [] + (animate-progress @progress @paused)) + [animate?])) + +(defn cleanup-animation + [progress paused] + (fn [] + (reanimated/cancel-animation @progress) + (reanimated/cancel-animation @paused))) (defn carousel-left-position - [window-width animate?] + [window-width animate? progress] (if animate? (worklets.onboarding-carousel/carousel-left-position window-width @progress) (-> (or (reanimated/get-shared-value @progress) 0) @@ -48,7 +55,7 @@ (* window-width)))) (defn dynamic-progress-bar-width - [progress-bar-width animate?] + [progress-bar-width animate? progress] (if animate? (worklets.onboarding-carousel/dynamic-progress-bar-width progress-bar-width @progress) (-> (or (reanimated/get-shared-value @progress) 0) diff --git a/src/status_im2/contexts/onboarding/common/carousel/style.cljs b/src/status_im2/contexts/onboarding/common/carousel/style.cljs index 966b376388..ffcc9c6ebd 100644 --- a/src/status_im2/contexts/onboarding/common/carousel/style.cljs +++ b/src/status_im2/contexts/onboarding/common/carousel/style.cljs @@ -4,7 +4,7 @@ [react-native.reanimated :as reanimated])) (defn header-container - [status-bar-height content-width index] + [status-bar-height content-width index header-background] {:position :absolute :top 0 :left (* content-width index) @@ -12,7 +12,7 @@ :width content-width :height (+ 96 status-bar-height) :flex-direction :row - :background-color colors/onboarding-header-black}) + :background-color (when header-background colors/onboarding-header-black)}) (defn header-text-view [window-width] @@ -27,11 +27,6 @@ {:color colors/white :margin-top 2}) -(defn background-image - [content-width] - {:resize-mode :stretch - :width content-width}) - (defn progress-bar-item [static? end?] {:height 2 diff --git a/src/status_im2/contexts/onboarding/common/carousel/view.cljs b/src/status_im2/contexts/onboarding/common/carousel/view.cljs index c23e6bb421..a1550df13b 100644 --- a/src/status_im2/contexts/onboarding/common/carousel/view.cljs +++ b/src/status_im2/contexts/onboarding/common/carousel/view.cljs @@ -1,26 +1,14 @@ (ns status-im2.contexts.onboarding.common.carousel.view (:require [quo2.core :as quo] - [utils.i18n :as i18n] [utils.re-frame :as rf] [react-native.core :as rn] [react-native.navigation :as navigation] [react-native.reanimated :as reanimated] - [status-im2.common.resources :as resources] [status-im2.contexts.onboarding.common.carousel.style :as style] [status-im2.contexts.onboarding.common.carousel.animation :as animation])) -(def header-text - [{:text (i18n/label :t/join-decentralised-communities) - :sub-text (i18n/label :t/participate-in-the-metaverse)} - {:text (i18n/label :t/chat-with-friends) - :sub-text (i18n/label :t/with-full-encryption)} - {:text (i18n/label :t/own-your-crypto) - :sub-text (i18n/label :t/use-the-multichain-wallet)} - {:text (i18n/label :t/discover-web3) - :sub-text (i18n/label :t/explore-the-decentralized-web)}]) - (defn header-text-view - [index window-width] + [index window-width header-text] [rn/view {:style (style/header-text-view window-width)} [quo/text {:style style/carousel-text @@ -33,16 +21,14 @@ (get-in header-text [index :sub-text])]]) (defn content-view - [{:keys [window-width status-bar-height index]}] + [{:keys [window-width status-bar-height index header-text header-background]} content] (let [content-width (* 4 window-width)] [:<> - [rn/image - {:style (style/background-image content-width) - :source (resources/get-image :onboarding-illustration)}] - [rn/view {:style (style/header-container status-bar-height content-width index)} + (when content content) + [rn/view {:style (style/header-container status-bar-height content-width index header-background)} (for [index (range 4)] ^{:key index} - [header-text-view index window-width])]])) + [header-text-view index window-width header-text])]])) (defn progress-bar [{:keys [static? progress-bar-width]}] @@ -54,8 +40,8 @@ [rn/view {:style (style/progress-bar-item static? true)}]]) (defn f-dynamic-progress-bar - [progress-bar-width animate?] - (let [width (animation/dynamic-progress-bar-width progress-bar-width animate?) + [{:keys [progress-bar-width animate? progress]}] + (let [width (animation/dynamic-progress-bar-width progress-bar-width animate? progress) container-view (if animate? reanimated/view rn/view)] [container-view {:style (style/dynamic-progress-bar width animate?)} [progress-bar @@ -63,18 +49,12 @@ :progress-bar-width progress-bar-width}]])) (defn f-view - [animate?] + [{:keys [animate? progress header-text background header-background]}] (let [window-width (rf/sub [:dimensions/window-width]) - view-id (rf/sub [:view-id]) status-bar-height (:status-bar-height @navigation/constants) progress-bar-width (- window-width 40) - carousel-left (animation/carousel-left-position window-width animate?) + carousel-left (animation/carousel-left-position window-width animate? progress) container-view (if animate? reanimated/view rn/view)] - (when animate? - (rn/use-effect - (fn [] - (reanimated/set-shared-value @animation/paused (not= view-id :intro))) - [view-id])) [:<> [container-view {:style (style/carousel-container carousel-left animate?)} (for [index (range 2)] @@ -82,7 +62,10 @@ [content-view {:window-width window-width :status-bar-height status-bar-height - :index index}])] + :index index + :header-text header-text + :header-background header-background} + (when background background)])] [rn/view {:style (style/progress-bar-container progress-bar-width @@ -90,4 +73,10 @@ [progress-bar {:static? true :progress-bar-width progress-bar-width}] - [:f> f-dynamic-progress-bar progress-bar-width animate?]]])) + [:f> f-dynamic-progress-bar + {:progress-bar-width progress-bar-width + :animate? animate? + :progress progress}]]])) + +(defn view [props] [:f> f-view props]) + diff --git a/src/status_im2/contexts/onboarding/events.cljs b/src/status_im2/contexts/onboarding/events.cljs index 3b7979129d..f637c2367c 100644 --- a/src/status_im2/contexts/onboarding/events.cljs +++ b/src/status_im2/contexts/onboarding/events.cljs @@ -166,7 +166,7 @@ constants/auth-method-biometric (get-in db [:onboarding-2/profile :auth-method]))] - (cond-> {:dispatch [:navigate-to :enable-notifications]} + (cond-> {:dispatch [:navigate-to :identifiers]} biometric-enabled? (assoc :biometric/enable-and-save-password {:key-uid key-uid diff --git a/src/status_im2/contexts/onboarding/identifiers/style.cljs b/src/status_im2/contexts/onboarding/identifiers/style.cljs new file mode 100644 index 0000000000..a652e0e35f --- /dev/null +++ b/src/status_im2/contexts/onboarding/identifiers/style.cljs @@ -0,0 +1,20 @@ +(ns status-im2.contexts.onboarding.identifiers.style) + +(def page-container + {:flex 1}) + +(def content-container + {:position :absolute + :top 160 + :bottom 0 + :left 0 + :right 0}) + +(def card-style + {:margin-horizontal 20 + :margin-bottom :auto}) + +(def button + {:justify-self :flex-end + :margin-bottom 46 + :margin-horizontal 20}) diff --git a/src/status_im2/contexts/onboarding/identifiers/view.cljs b/src/status_im2/contexts/onboarding/identifiers/view.cljs new file mode 100644 index 0000000000..da7a927540 --- /dev/null +++ b/src/status_im2/contexts/onboarding/identifiers/view.cljs @@ -0,0 +1,61 @@ +(ns status-im2.contexts.onboarding.identifiers.view + (:require [react-native.core :as rn] + [clojure.string :as string] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [status-im2.contexts.onboarding.identifiers.style :as style] + [status-im2.contexts.onboarding.common.background.view :as background] + [status-im2.contexts.onboarding.common.carousel.view :as carousel] + [status-im2.contexts.onboarding.common.carousel.animation :as carousel.animation])) + +(def header-text + [{:text (i18n/label :t/unique-identifiers) + :sub-text (i18n/label :t/your-identifiers)} + {:text (i18n/label :t/identicon-ring) + :sub-text (i18n/label :t/identicon-ring-explanation)} + {:text (i18n/label :t/chat-key-title) + :sub-text (i18n/label :t/chat-key-description)} + {:text (i18n/label :t/emojihash) + :sub-text (i18n/label :t/emojihash-description)}]) + +(defn f-view + [] + (let [progress (atom nil) + paused (atom nil) + {:keys [emoji-hash display-name compressed-key + public-key]} (rf/sub [:multiaccount]) + {:keys [color]} (rf/sub [:onboarding-2/profile]) + photo-path (rf/sub [:chats/photo-path public-key]) + emoji-string (string/join emoji-hash)] + (carousel.animation/use-initialize-animation progress paused true) + (rn/use-effect + (fn [] + (carousel.animation/cleanup-animation progress paused)) + []) + [:<> + [background/view true] + [rn/view {:style style/page-container} + [carousel/view + {:animate? true + :progress progress + :header-text header-text}] + [rn/view {:style style/content-container} + [quo/profile-card + {:profile-picture photo-path + :name display-name + :hash compressed-key + :customization-color color + :emoji-hash emoji-string + :show-emoji-hash? true + :show-user-hash? true + :card-style style/card-style}] + [quo/button + {:accessibility-label :skip-identifiers + :on-press #(rf/dispatch [:navigate-to :enable-notifications]) + :override-background-color colors/white-opa-5 + :style style/button} + (i18n/label :t/skip)]]]])) + +(defn view [props] [:f> f-view props]) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index 869881d18b..6db014f052 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -14,6 +14,7 @@ [status-im2.contexts.onboarding.create-profile.view :as create-profile] [status-im2.contexts.onboarding.enable-biometrics.view :as enable-biometrics] [status-im2.contexts.onboarding.enable-notifications.view :as enable-notifications] + [status-im2.contexts.onboarding.identifiers.view :as identifiers] [status-im2.contexts.onboarding.welcome.view :as welcome] [status-im2.contexts.onboarding.new-to-status.view :as new-to-status] [status-im2.contexts.onboarding.sign-in.view :as sign-in] @@ -120,6 +121,12 @@ :popStackOnPress false}} :component enable-notifications/enable-notifications} + {:name :identifiers + :component identifiers/view + :options {:popGesture false + :hardwareBackButton {:dismissModalOnPress false + :popStackOnPress false}}} + {:name :sign-in :component sign-in/view} diff --git a/test/appium/views/sign_in_view.py b/test/appium/views/sign_in_view.py index 75f01f1440..3f35d0f62d 100644 --- a/test/appium/views/sign_in_view.py +++ b/test/appium/views/sign_in_view.py @@ -182,6 +182,7 @@ class SignInView(BaseView): self.profile_repeat_password_edit_box = EditBox(self.driver, translation_id="password-creation-placeholder-2") self.profile_confirm_password_button = Button(self.driver, translation_id="password-creation-confirm") self.enable_biometric_maybe_later_button = Button(self.driver, translation_id="maybe-later") + self.identifiers_button = Button(self.driver, accessibility_id="skip-identifiers") self.enable_notifications_button = Button(self.driver, accessibility_id="enable-notifications-button") self.maybe_later_button = Button(self.driver, accessibility_id="enable-notifications-later-button") self.start_button = Button(self.driver, accessibility_id="welcome-button") @@ -222,6 +223,7 @@ class SignInView(BaseView): # self.create_password_input.set_value(password) # self.confirm_your_password_input.set_value(password) # self.next_button.click() + self.identifiers_button.wait_and_click(30) if enable_notifications: self.enable_notifications_button.click_until_presence_of_element(self.start_button) else: diff --git a/translations/en.json b/translations/en.json index 594d1b6a17..e7e23273a3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -478,6 +478,8 @@ "edit": "Edit", "edit-group": "Edit group", "edit-profile": "Edit profile", + "emojihash": "Emojihash", + "emojihash-description": "A visual representation of your chat key. It will help other users recognize your profile.", "emojis": "Emojis", "empty-chat-description": "There are no messages \nin this chat yet", "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", @@ -673,6 +675,8 @@ "hooks": "Hooks", "how-to-scan": "How to scan", "identifier": "Identifier", + "identicon-ring": "Identicon ring", + "identicon-ring-explanation": "This multicoloured ring around your profile picture represents your chat key.", "if-you-cancel": "If you cancel, you can request to join this community at any point.", "image-remove-current": "Remove current photo", "image-source-gallery": "Select from gallery", @@ -959,10 +963,10 @@ "use-recovery-phrase-subtitle": "If you already have an Ethereum address", "use-keycard": "Use Keycard", "use-keycard-subtitle": "Keys will be stored on your Keycard", - "glossary": "Glossary", "account-title": "Account", "account-content": "You can compare accounts in Status to bank accounts. Like a bank account, an account typically has an address and a balance; You use this account to transact on Ethereum. You can have multiple accounts in your wallet. All accessed by unlocking Status.", + "chat-key-description": "Your unique public ID in Status. Others can use it to send you a contact request.", "chat-key-title": "Chat Key", "chat-key-content": "Messages on the Status chat protocol are sent and received using encryption keys. The public chat key is a string of characters you share with others so they can send you messages in Status.", "chat-name-title": "Chat Name", @@ -1403,6 +1407,7 @@ "unable-to-read-this-code": "Unable to read this code", "unblock-contact": "Unblock this user", "undo": "Undo", + "unique-identifiers": "Unique profile identifiers", "unknown-status-go-error": "Unknown status-go error", "unlock": "Unlock", "unpair-card": "Unpair card", @@ -1506,6 +1511,7 @@ "your-contact-code": "Granting access authorizes this DApp to retrieve your chat key", "your-data-belongs-to-you": "If you lose your seed phrase you lose your data and funds", "your-data-belongs-to-you-description": "If you lose access, for example by losing your phone, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe", + "your-identifiers": "Your identicon ring, chat key and emojihash will help others tell you from impersonators", "your-name": "Your Name", "your-recovery-phrase": "Your seed phrase", "your-recovery-phrase-description": "This is your seed phrase. You use it to prove that this is your wallet. You only get to see it once! Write it on paper and keep it in a secure place. You will need it if you lose or reinstall your wallet.", @@ -2044,7 +2050,7 @@ "log-in": "Log in", "type-your-password": "Type your password", "oops-wrong-password": "Oops, wrong password!", - "remove-profile?" : "Remove profile?", + "remove-profile?": "Remove profile?", "remove-profile-message": "Remove profile from this device", "remove-profile-confirm-message": "All profile data will removed from device.", "create-new-profile": "Create new profile", @@ -2059,7 +2065,7 @@ "enable-camera": "Enable camera", "ensure-qr-code-in-focus-to-scan": "Ensure that the QR code is in focus to scan", "i-dont-have-status-on-another-device": "I don’t have Status on another device", - "ensure-qr-code-is-in-focus-to-scan":"Ensure that the QR code is in focus to scan", + "ensure-qr-code-is-in-focus-to-scan": "Ensure that the QR code is in focus to scan", "error-this-is-not-a-sync-qr-code": "Oops! This is not a sync QR code", "error-syncing-connection-failed": "Oops! Connection failed. Try again", "camera-permission-denied": "Permission denied",