From c2c79cc1acec2f67eca31bdd2fba4ebaf9676bdb Mon Sep 17 00:00:00 2001 From: Brian Sztamfater Date: Tue, 4 Jul 2023 12:43:08 -0300 Subject: [PATCH] feat: implement onboarding modal transition for sign in screen (#16167) --- .../drawer_buttons/component_spec.cljs | 31 +- .../drawers/drawer_buttons/style.cljs | 38 ++- .../drawers/drawer_buttons/view.cljs | 208 +++++++++++--- src/react_native/reanimated.cljs | 3 +- src/status_im/signals/core.cljs | 8 +- src/status_im2/constants.cljs | 3 + .../onboarding/common/background/style.cljs | 1 - .../contexts/onboarding/intro/view.cljs | 33 ++- .../contexts/onboarding/profiles/view.cljs | 2 +- .../contexts/onboarding/sign_in/view.cljs | 15 +- .../onboarding/syncing/progress/style.cljs | 5 +- .../onboarding/syncing/progress/view.cljs | 17 +- .../syncing/scan_sync_code/style.cljs | 26 +- .../contexts/syncing/scan_sync_code/view.cljs | 269 +++++++++++++----- src/status_im2/core.cljs | 6 +- src/status_im2/navigation/core.cljs | 9 +- src/status_im2/navigation/options.cljs | 5 + src/status_im2/navigation/screens.cljs | 55 +++- 18 files changed, 553 insertions(+), 181 deletions(-) diff --git a/src/quo2/components/drawers/drawer_buttons/component_spec.cljs b/src/quo2/components/drawers/drawer_buttons/component_spec.cljs index fdfcf423b4..ca1b6eeee0 100644 --- a/src/quo2/components/drawers/drawer_buttons/component_spec.cljs +++ b/src/quo2/components/drawers/drawer_buttons/component_spec.cljs @@ -1,9 +1,19 @@ (ns quo2.components.drawers.drawer-buttons.component-spec (:require [quo2.components.drawers.drawer-buttons.view :as drawer-buttons] [react-native.core :as rn] - [test-helpers.component :as h])) + [test-helpers.component :as h] + [react-native.safe-area :as safe-area])) (h/describe "drawer-buttons" + (h/before-each + (fn [] + (h/use-fake-timers))) + + (h/after-each + (fn [] + (h/clear-all-timers) + (h/use-real-timers))) + (h/test "the top heading and subheading render" (h/render [drawer-buttons/view {:top-card {:heading :top-heading} @@ -29,15 +39,16 @@ (h/test "it clicks the top card" (let [event (h/mock-fn)] - (h/render [drawer-buttons/view - {:top-card {:on-press event - :heading :top-heading} - :bottom-card {:heading :bottom-heading}} - :top-sub-heading - :bottom-sub-heading]) - (h/fire-event :press (h/get-by-text "top-heading")) - (-> (js/expect event) - (.toHaveBeenCalled)))) + (with-redefs [safe-area/get-top (fn [] 10)] + (h/render [drawer-buttons/view + {:top-card {:on-press event + :heading :top-heading} + :bottom-card {:heading :bottom-heading}} + :top-sub-heading + :bottom-sub-heading]) + (h/fire-event :press (h/get-by-text "top-heading")) + (-> (js/expect event) + (.toHaveBeenCalled))))) (h/test "it clicks the bottom card" (let [event (h/mock-fn)] diff --git a/src/quo2/components/drawers/drawer_buttons/style.cljs b/src/quo2/components/drawers/drawer_buttons/style.cljs index 9cc7956c8e..f4b5631de2 100644 --- a/src/quo2/components/drawers/drawer_buttons/style.cljs +++ b/src/quo2/components/drawers/drawer_buttons/style.cljs @@ -1,11 +1,20 @@ (ns quo2.components.drawers.drawer-buttons.style - (:require [quo2.foundations.colors :as colors])) + (:require [quo2.foundations.colors :as colors] + [react-native.reanimated :as reanimated])) -(def outer-container - {:height 216 - :border-top-left-radius 20 - :border-top-right-radius 20 - :overflow :hidden}) +(defn outer-container + [height border-radius container-style] + (reanimated/apply-animations-to-style + {:height height + :border-top-left-radius border-radius + :border-top-right-radius border-radius} + (assoc + container-style + :position :absolute + :left 0 + :right 0 + :bottom 0 + :overflow :hidden))) (def top-card {:flex 1 @@ -17,7 +26,6 @@ (def bottom-card {:position :absolute - :top 80 :left 0 :right 0 :bottom 0 @@ -41,6 +49,20 @@ :align-items :center :border-color colors/white-opa-5}) +(def blur-view-style + {:flex 1 + :border-top-left-radius 20 + :border-top-right-radius 20}) + +(def blur-content-style + {:flex 1 + :background-color :transparent + :position :absolute + :top 0 + :left 0 + :right 0 + :bottom 0}) + (def bottom-text {:flex 1 :color colors/white-70-blur}) @@ -51,4 +73,4 @@ (defn heading-text [gap] {:color colors/white - :margin-bottom gap}) \ No newline at end of file + :margin-bottom gap}) diff --git a/src/quo2/components/drawers/drawer_buttons/view.cljs b/src/quo2/components/drawers/drawer_buttons/view.cljs index b28b15ad1e..459694fe81 100644 --- a/src/quo2/components/drawers/drawer_buttons/view.cljs +++ b/src/quo2/components/drawers/drawer_buttons/view.cljs @@ -4,7 +4,12 @@ [quo2.components.markdown.text :as text] [quo2.foundations.colors :as colors] [react-native.blur :as blur] - [quo2.components.drawers.drawer-buttons.style :as style])) + [quo2.components.drawers.drawer-buttons.style :as style] + [react-native.reanimated :as reanimated] + [react-native.platform :as platform] + [react-native.safe-area :as safe-area])) + +(def default-height 216) (defn render-bottom [children] @@ -29,35 +34,74 @@ children]] [render-bottom children])) -(defn render-children-top - [children] - (if (label? children) - [text/text - {:size :paragraph-2 - :style style/top-text - :weight :semi-bold} - children] - children)) +(defn- f-render-children-top + [children top-children-opacity] + (let [scale (reanimated/interpolate top-children-opacity [1 0] [1 1.1]) + padding-left (reanimated/interpolate scale [1 1.1] [0 14])] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity top-children-opacity + :transform [{:scale scale} + {:translate-x padding-left}]} + {})} + (if (label? children) + [text/text + {:size :paragraph-2 + :style style/top-text + :weight :semi-bold} + children] + children)])) -(defn card - [{:keys [on-press style heading gap accessibility-label top?]} children] +(defn- f-card + [{:keys [on-press default-on-press style heading gap accessibility-label top? top-title-opacity + top-children-opacity + animated-heading] + :or {top-title-opacity (reanimated/use-shared-value 1)}} + children] [rn/touchable-highlight {:accessibility-label accessibility-label - :on-press on-press + :on-press (fn [] + (when on-press + (on-press)) + (when default-on-press + (default-on-press))) :border-radius 20 :style style :underlay-color (:background-color style)} [rn/view - [text/text - {:size :heading-1 - :style (style/heading-text gap) - :weight :semi-bold} - heading] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity top-title-opacity} + {})} + [text/text + {:size :heading-1 + :style (style/heading-text gap) + :weight :semi-bold} + heading]] + (when animated-heading + (let [animated-heading-opacity (reanimated/interpolate top-children-opacity [1 0] [0 1])] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity top-title-opacity} + {:position :absolute})} + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity animated-heading-opacity} + {})} + [text/text + {:size :heading-1 + :style (style/heading-text gap) + :weight :semi-bold} + animated-heading]]])) (if top? - [render-children-top children] + [:f> f-render-children-top children top-children-opacity] [render-children-bottom children])]]) -(defn view +(defn card + [props children] + [:f> f-card props children]) + +(defn f-view "[view opts] opts {:container-style style-object @@ -70,29 +114,101 @@ child-1 string, keyword or hiccup child-2 string, keyword or hiccup " - [{:keys [container-style top-card bottom-card]} child-1 child-2] - [rn/view - {:style (merge container-style style/outer-container)} - [blur/view - {:blur-type :dark - :blur-amount 10 - :style {:flex 1 - :border-top-left-radius 20 - :border-top-right-radius 20}}] - [rn/view - {:style {:flex 1 - :background-color :transparent - :position :absolute - :top 0 - :left 0 - :right 0 - :bottom 0}} - [card - (merge {:gap 4 - :top? true - :style style/top-card} - top-card) child-1] - [card - (merge {:style style/bottom-card - :gap 20} - bottom-card) child-2]]]) \ No newline at end of file + [{:keys [container-style top-card bottom-card on-init animations-duration animations-delay]} + child-1 + child-2] + (let [max-height (+ (:height (rn/get-window)) (if platform/android? (safe-area/get-top) 0)) + height (reanimated/use-shared-value default-height) + top-padding (reanimated/use-shared-value 12) + border-radius (reanimated/use-shared-value 20) + bottom-view-top (reanimated/use-shared-value 80) + top-title-opacity (reanimated/use-shared-value 1) + top-children-opacity (reanimated/use-shared-value 1) + animations-delay (/ animations-delay 1.4) + start-top-animation (fn [] + (reanimated/animate-shared-value-with-delay bottom-view-top + (:height (rn/get-screen)) + animations-duration + :easing4 + animations-delay) + (reanimated/animate-shared-value-with-delay + height + max-height + animations-duration + :easing4 + animations-delay) + (reanimated/animate-shared-value-with-delay + top-padding + (+ 68 (safe-area/get-top)) + animations-duration + :easing4 + animations-delay) + (reanimated/animate-shared-value-with-delay + top-children-opacity + 0 + animations-duration + :easing4 animations-delay) + (reanimated/animate-shared-value-with-delay + top-title-opacity + 0 + 0 + :linear + (+ animations-delay animations-duration 500))) + reset-top-animation (fn [] + (reanimated/set-shared-value top-title-opacity 1) + (reanimated/animate-shared-value-with-delay bottom-view-top + 80 + animations-duration + :easing4 + 50) + (reanimated/animate-shared-value-with-timing + height + default-height + animations-duration + :easing4) + (reanimated/animate-shared-value-with-timing + top-padding + 12 + animations-duration + :easing4) + (reanimated/animate-shared-value-with-timing + top-children-opacity + 1 + animations-duration + :easing4))] + (rn/use-effect (fn [] + (when on-init + (on-init reset-top-animation)))) + [reanimated/view {:style (style/outer-container height border-radius container-style)} + [blur/view + {:blur-type :dark + :blur-amount 10 + :style style/blur-view-style} + [rn/view + {:style style/blur-content-style} + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:padding-top top-padding} + style/top-card)} + [card + (merge {:gap 4 + :top? true + :style {:flex 1} + :top-children-opacity top-children-opacity + :top-title-opacity top-title-opacity + :default-on-press start-top-animation} + top-card) + child-1]] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:top bottom-view-top} + style/bottom-card)} + [card + (merge {:style {:flex 1} + :gap 10} + bottom-card) + child-2]]]]])) + +(defn view + [props child-1 child-2] + [:f> f-view props child-1 child-2]) diff --git a/src/react_native/reanimated.cljs b/src/react_native/reanimated.cljs index fc34787a80..7167b52b8c 100644 --- a/src/react_native/reanimated.cljs +++ b/src/react_native/reanimated.cljs @@ -81,7 +81,8 @@ {:linear (bezier 0 0 1 1) :easing1 (bezier 0.25 0.1 0.25 1) :easing2 (bezier 0 0.3 0.6 0.9) - :easing3 (bezier 0.3 0.3 0.3 0.9)}) + :easing3 (bezier 0.3 0.3 0.3 0.9) + :easing4 (bezier 0.3 0.3 1 1)}) ;; Helper functions (defn get-shared-value diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 27da2297c7..83050cf588 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -80,7 +80,9 @@ multiaccount-data (when received-account? (merge account {:password password})) navigate-to-syncing-devices? (and (or connection-success? error-on-pairing?) receiver?) - user-in-syncing-devices-screen? (= (:view-id db) :syncing-progress)] + user-in-syncing-devices-screen? (or (= (:view-id db) :syncing-progress) + (= (:view-id db) :syncing-progress-intro)) + user-in-sign-in-intro-screen? (= (:view-id db) :sign-in-intro)] (merge {:db (cond-> db connection-success? (assoc-in [:syncing :pairing-status] :connected) @@ -95,7 +97,9 @@ (assoc-in [:syncing :pairing-status] :completed))} (cond (and navigate-to-syncing-devices? (not user-in-syncing-devices-screen?)) - {:dispatch [:navigate-to :syncing-progress]} + {:dispatch (if user-in-sign-in-intro-screen? + [:navigate-to-within-stack [:syncing-progress-intro :sign-in-intro]] + [:navigate-to :syncing-progress])} (and completed-pairing? sender?) {:dispatch [:syncing/clear-states]} diff --git a/src/status_im2/constants.cljs b/src/status_im2/constants.cljs index bbcd13e8a8..26e37c2e67 100644 --- a/src/status_im2/constants.cljs +++ b/src/status_im2/constants.cljs @@ -336,3 +336,6 @@ (def ^:const image-description-in-lightbox? false) (def ^:const audio-max-duration-ms 120000) + +(def ^:const onboarding-modal-animation-duration 300) +(def ^:const onboarding-modal-animation-delay 400) diff --git a/src/status_im2/contexts/onboarding/common/background/style.cljs b/src/status_im2/contexts/onboarding/common/background/style.cljs index de3f246792..880bac5775 100644 --- a/src/status_im2/contexts/onboarding/common/background/style.cljs +++ b/src/status_im2/contexts/onboarding/common/background/style.cljs @@ -5,7 +5,6 @@ {:background-color colors/neutral-95 :flex-direction :row :position :absolute - :overflow :hidden :top 0 :bottom 0 :left 0 diff --git a/src/status_im2/contexts/onboarding/intro/view.cljs b/src/status_im2/contexts/onboarding/intro/view.cljs index 0f15339256..78d4b2a54f 100644 --- a/src/status_im2/contexts/onboarding/intro/view.cljs +++ b/src/status_im2/contexts/onboarding/intro/view.cljs @@ -4,23 +4,34 @@ [utils.re-frame :as rf] [react-native.core :as rn] [status-im2.contexts.onboarding.intro.style :as style] - [status-im2.contexts.onboarding.common.background.view :as background])) + [status-im2.contexts.onboarding.common.background.view :as background] + [status-im2.constants :as constants] + [status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code] + [utils.debounce :as debounce])) (defn view [] [rn/view {:style style/page-container} [background/view false] [quo/drawer-buttons - {:top-card {:on-press (fn [] - (rf/dispatch [:navigate-to :sign-in]) - (rf/dispatch [:hide-terms-of-services-opt-in-screen])) - :heading (i18n/label :t/sign-in) - :accessibility-label :already-use-status-button} - :bottom-card {:on-press (fn [] - (rf/dispatch [:navigate-to :new-to-status]) - (rf/dispatch [:hide-terms-of-services-opt-in-screen])) - :heading (i18n/label :t/new-to-status) - :accessibility-label :new-to-status-button}} + {:on-init (fn [reset-top-animation-fn] + (reset! scan-sync-code/dismiss-animations reset-top-animation-fn)) + :animations-duration constants/onboarding-modal-animation-duration + :animations-delay constants/onboarding-modal-animation-delay + :top-card {:on-press (fn [] + (debounce/dispatch-and-chill [:open-modal + :sign-in-intro] + 2000) + (rf/dispatch [:hide-terms-of-services-opt-in-screen])) + :heading (i18n/label :t/sign-in) + :animated-heading (i18n/label :t/sign-in-by-syncing) + :accessibility-label :already-use-status-button} + :bottom-card {:on-press (fn [] + (rf/dispatch [:navigate-to :new-to-status]) + (rf/dispatch + [:hide-terms-of-services-opt-in-screen])) + :heading (i18n/label :t/new-to-status) + :accessibility-label :new-to-status-button}} [quo/text {:style style/plain-text} (i18n/label :t/you-already-use-status)] diff --git a/src/status_im2/contexts/onboarding/profiles/view.cljs b/src/status_im2/contexts/onboarding/profiles/view.cljs index 2a08dc4940..ab18d27273 100644 --- a/src/status_im2/contexts/onboarding/profiles/view.cljs +++ b/src/status_im2/contexts/onboarding/profiles/view.cljs @@ -28,7 +28,7 @@ :accessibility-label :create-new-profile} {:icon :i/multi-profile :label (i18n/label :t/add-existing-status-profile) - :on-press #(rf/dispatch [:navigate-to :sign-in]) + :on-press #(rf/dispatch [:open-modal :sign-in]) :accessibility-label :multi-profile}]]]) (defn show-new-account-options diff --git a/src/status_im2/contexts/onboarding/sign_in/view.cljs b/src/status_im2/contexts/onboarding/sign_in/view.cljs index 54c6e66584..17a685d9a8 100644 --- a/src/status_im2/contexts/onboarding/sign_in/view.cljs +++ b/src/status_im2/contexts/onboarding/sign_in/view.cljs @@ -3,9 +3,22 @@ [status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code])) +(defn navigate-back + [] + (when @scan-sync-code/navigate-back-fn + (@scan-sync-code/navigate-back-fn))) + (defn view [] [scan-sync-code/view {:title (i18n/label :t/sign-in-by-syncing) :show-bottom-view? true - :background [background/view true]}]) + :background [background/view true] + :animated? false}]) + +(defn animated-view + [] + [scan-sync-code/view + {:title (i18n/label :t/sign-in-by-syncing) + :show-bottom-view? true + :animated? true}]) diff --git a/src/status_im2/contexts/onboarding/syncing/progress/style.cljs b/src/status_im2/contexts/onboarding/syncing/progress/style.cljs index 7aa328e84d..d15bb4df24 100644 --- a/src/status_im2/contexts/onboarding/syncing/progress/style.cljs +++ b/src/status_im2/contexts/onboarding/syncing/progress/style.cljs @@ -1,7 +1,8 @@ (ns status-im2.contexts.onboarding.syncing.progress.style (:require [quo2.foundations.colors :as colors])) -(def page-container +(defn page-container + [in-onboarding?] {:flex 1 :position :absolute :top 0 @@ -9,7 +10,7 @@ :left 0 :right 0 :padding-bottom 20 - :background-color colors/neutral-80-opa-80-blur}) + :background-color (when-not in-onboarding? colors/neutral-80-opa-80-blur)}) (def page-illustration {:flex 1 diff --git a/src/status_im2/contexts/onboarding/syncing/progress/view.cljs b/src/status_im2/contexts/onboarding/syncing/progress/view.cljs index c5e876d6c7..d574af0091 100644 --- a/src/status_im2/contexts/onboarding/syncing/progress/view.cljs +++ b/src/status_im2/contexts/onboarding/syncing/progress/view.cljs @@ -30,22 +30,23 @@ :subtitle-accessibility-label :progress-screen-sub-title}]) (defn try-again-button - [profile-color] + [profile-color in-onboarding?] [quo/button {:on-press (fn [] (rf/dispatch [:syncing/clear-states]) - (rf/dispatch [:navigate-back])) + (rf/dispatch [:navigate-back-to + (if in-onboarding? :sign-in-intro :sign-in)])) :accessibility-label :try-again-later-button :override-background-color (colors/custom-color profile-color 60) :style style/try-again-button} (i18n/label :t/try-again)]) (defn view - [] + [in-onboarding?] (let [pairing-status (rf/sub [:pairing/pairing-status]) profile-color (:color (rf/sub [:onboarding-2/profile]))] - [rn/view {:style style/page-container} - [background/view true] + [rn/view {:style (style/page-container in-onboarding?)} + (when-not in-onboarding? [background/view true]) [quo/page-nav] [page-title (pairing-progress pairing-status)] (if (pairing-progress pairing-status) @@ -54,4 +55,8 @@ [rn/view {:style style/page-illustration} [quo/text "[Error here]"]]) (when-not (pairing-progress pairing-status) - [try-again-button profile-color])])) + [try-again-button profile-color in-onboarding?])])) + +(defn view-onboarding + [] + [view true]) diff --git a/src/status_im2/contexts/syncing/scan_sync_code/style.cljs b/src/status_im2/contexts/syncing/scan_sync_code/style.cljs index 2ab24afc4b..99dee2194f 100644 --- a/src/status_im2/contexts/syncing/scan_sync_code/style.cljs +++ b/src/status_im2/contexts/syncing/scan_sync_code/style.cljs @@ -1,5 +1,6 @@ (ns status-im2.contexts.syncing.scan-sync-code.style - (:require [quo2.foundations.colors :as colors])) + (:require [quo2.foundations.colors :as colors] + [react-native.reanimated :as reanimated])) (def screen-padding 20) @@ -62,7 +63,7 @@ [viewfinder] {:position :absolute :left (:x viewfinder) - :top (:y viewfinder)}) + :top 19}) (def view-finder-border-container {:flex-direction :row @@ -104,6 +105,7 @@ (def camera-permission-container {:height 335 + :margin-top 19 :margin-horizontal screen-padding :background-color colors/white-opa-5 :border-color colors/white-opa-10 @@ -126,15 +128,17 @@ :align-items :center}) (defn bottom-container - [padding-bottom] - {:z-index 6 - :padding-top 12 - :padding-bottom padding-bottom - :background-color colors/white-opa-5 - :border-top-left-radius 20 - :border-top-right-radius 20 - :align-items :center - :justify-content :center}) + [translate-y padding-bottom] + (reanimated/apply-animations-to-style + {:transform [{:translate-y translate-y}]} + {:z-index 6 + :padding-top 12 + :padding-bottom padding-bottom + :background-color colors/white-opa-5 + :border-top-left-radius 20 + :border-top-right-radius 20 + :align-items :center + :justify-content :center})) (def bottom-text {:color colors/white diff --git a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs index 02df2d3360..d535229afa 100644 --- a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs +++ b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs @@ -14,12 +14,17 @@ [utils.i18n :as i18n] [utils.re-frame :as rf] [status-im2.contexts.syncing.utils :as sync-utils] - [status-im.utils.platform :as platform])) + [status-im.utils.platform :as platform] + [react-native.reanimated :as reanimated] + [status-im2.constants :as constants] + [utils.debounce :as debounce])) ;; Android allow local network access by default. So, we need this check on iOS only. (defonce preflight-check-passed? (reagent/atom (if platform/ios? false true))) (defonce camera-permission-granted? (reagent/atom false)) +(defonce dismiss-animations (atom nil)) +(defonce navigate-back-fn (atom nil)) (defn request-camera-permission [] @@ -42,47 +47,83 @@ [] (rf/dispatch [:syncing/preflight-outbound-check #(reset! preflight-check-passed? %)])) +(defn- f-header + [{:keys [active-tab read-qr-once? title title-opacity subtitle-opacity reset-animations-fn animated?]}] + (let [subtitle-translate-x (reanimated/interpolate subtitle-opacity [0 1] [-13 0]) + subtitle-translate-y (reanimated/interpolate subtitle-opacity [0 1] [-85 0]) + subtitle-scale (reanimated/interpolate subtitle-opacity [0 1] [0.9 1]) + controls-translate-y (reanimated/interpolate subtitle-opacity [0 1] [85 0])] + [:<> + [rn/view {:style style/header-container} + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity subtitle-opacity + :transform [{:translate-y controls-translate-y}]} + {})} + [quo/button + {:icon true + :type :blur-bg + :size 32 + :accessibility-label :close-sign-in-by-syncing + :override-theme :dark + :on-press (fn [] + (if (and animated? reset-animations-fn) + (reset-animations-fn) + (rf/dispatch [:navigate-back])))} + :i/arrow-left]] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity subtitle-opacity + :transform [{:translate-y controls-translate-y}]} + {})} + [quo/button + {:before :i/info + :type :blur-bg + :size 32 + :accessibility-label :find-sync-code + :override-theme :dark + :on-press #(rf/dispatch [:open-modal :find-sync-code])} + (i18n/label :t/find-sync-code)]]] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity title-opacity} + {})} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/header-text} + title]] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity subtitle-opacity + :transform [{:translate-x subtitle-translate-x} + {:translate-y subtitle-translate-y} + {:scale subtitle-scale}]} + {})} + [quo/text + {:size :paragraph-1 + :weight :regular + :style style/header-sub-text} + (i18n/label :t/synchronise-your-data-across-your-devices)]] + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity subtitle-opacity + :transform [{:translate-y controls-translate-y}]} + style/tabs-container)} + [quo/segmented-control + {:size 32 + :override-theme :dark + :blur? true + :default-active @active-tab + :data [{:id 1 :label (i18n/label :t/scan-sync-qr-code)} + {:id 2 :label (i18n/label :t/enter-sync-code)}] + :on-change (fn [id] + (reset! active-tab id) + (reset! read-qr-once? false))}]]])) + (defn- header - [active-tab read-qr-once? title] - [:<> - [rn/view {:style style/header-container} - [quo/button - {:icon true - :type :blur-bg - :size 32 - :accessibility-label :close-sign-in-by-syncing - :override-theme :dark - :on-press #(rf/dispatch [:navigate-back])} - :i/arrow-left] - [quo/button - {:before :i/info - :type :blur-bg - :size 32 - :accessibility-label :find-sync-code - :override-theme :dark - :on-press #(rf/dispatch [:open-modal :find-sync-code])} - (i18n/label :t/find-sync-code)]] - [quo/text - {:size :heading-1 - :weight :semi-bold - :style style/header-text} - title] - [quo/text - {:size :paragraph-1 - :weight :regular - :style style/header-sub-text} - (i18n/label :t/synchronise-your-data-across-your-devices)] - [rn/view {:style style/tabs-container} - [quo/segmented-control - {:size 32 - :override-theme :dark - :blur? true - :default-active @active-tab - :data [{:id 1 :label (i18n/label :t/scan-sync-qr-code)} - {:id 2 :label (i18n/label :t/enter-sync-code)}] - :on-change (fn [id] - (reset! active-tab id) - (reset! read-qr-once? false))}]]]) + [props] + [:f> f-header props]) (defn get-labels-and-on-press-method [] @@ -192,9 +233,6 @@ (defn- scan-qr-code-tab [qr-view-finder] [:<> - [rn/view {:style style/scan-qr-code-container}] - (when (empty? @qr-view-finder) - [qr-scan-hole-area qr-view-finder]) (if (and @preflight-check-passed? @camera-permission-granted? (boolean (not-empty @qr-view-finder))) @@ -210,24 +248,30 @@ :style {:color colors/white}} "Yet to be implemented"]]) -(defn- bottom-view - [insets] +(defn- f-bottom-view + [insets translate-y] [rn/touchable-without-feedback {:on-press #(js/alert "Yet to be implemented")} - [rn/view - {:style (style/bottom-container (:bottom insets))} + [reanimated/view + {:style (style/bottom-container translate-y (:bottom insets))} [quo/text {:size :paragraph-2 :weight :medium :style style/bottom-text} (i18n/label :t/i-dont-have-status-on-another-device)]]]) +(defn- bottom-view + [insets translate-y] + [:f> f-bottom-view insets translate-y]) + (defn- check-qr-code-data [event] (let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue")) valid-connection-string? (sync-utils/valid-connection-string? connection-string)] (if valid-connection-string? - (rf/dispatch [:syncing/input-connection-string-for-bootstrapping connection-string]) + (debounce/debounce-and-dispatch [:syncing/input-connection-string-for-bootstrapping + connection-string] + 300) (rf/dispatch [:toasts/upsert {:icon :i/info :icon-color colors/danger-50 @@ -262,47 +306,124 @@ :background-color colors/neutral-80-opa-80}]]])) (defn f-view - [{:keys [title show-bottom-view? background]}] + [{:keys [title show-bottom-view? background animated?]}] (let [insets (safe-area/get-insets) active-tab (reagent/atom 1) - qr-view-finder (reagent/atom {})] + qr-view-finder (reagent/atom {}) + render-camera? (reagent/atom false)] (fn [] - (let [camera-ref (atom nil) - read-qr-once? (atom false) + (let [camera-ref (atom nil) + read-qr-once? (atom false) ;; The below check is to prevent scanning of any QR code ;; when the user is in syncing progress screen user-in-syncing-progress-screen? (= (rf/sub [:view-id]) :syncing-progress) - on-read-code (fn [data] - (when (and (not @read-qr-once?) - (not user-in-syncing-progress-screen?)) - (reset! read-qr-once? true) - (js/setTimeout (fn [] - (reset! read-qr-once? false)) - 3000) - (check-qr-code-data data))) - scan-qr-code-tab? (= @active-tab 1) - show-camera? (and scan-qr-code-tab? - @camera-permission-granted? - @preflight-check-passed?) - show-holes? (and show-camera? - (boolean (not-empty @qr-view-finder)))] + on-read-code (fn [data] + (when (and (not @read-qr-once?) + (not user-in-syncing-progress-screen?)) + (reset! read-qr-once? true) + (js/setTimeout (fn [] + (reset! read-qr-once? false)) + 3000) + (check-qr-code-data data))) + scan-qr-code-tab? (= @active-tab 1) + show-camera? (and scan-qr-code-tab? + @camera-permission-granted? + @preflight-check-passed?) + show-holes? (and show-camera? + (boolean (not-empty @qr-view-finder))) + title-opacity (reanimated/use-shared-value (if animated? 0 1)) + subtitle-opacity (reanimated/use-shared-value (if animated? 0 1)) + content-opacity (reanimated/use-shared-value (if animated? 0 1)) + content-translate-y (reanimated/interpolate subtitle-opacity [0 1] [85 0]) + bottom-view-translate-y (reanimated/use-shared-value + (if animated? (+ 42.2 (:bottom insets)) 0)) + reset-animations-fn + (fn [] + (reset! render-camera? false) + (js/setTimeout + (fn [] + (rf/dispatch [:navigate-back]) + (when @dismiss-animations + (@dismiss-animations)) + (reanimated/animate-shared-value-with-timing + content-opacity + 0 + (/ constants/onboarding-modal-animation-duration 8) + :easing4) + (reanimated/animate-shared-value-with-timing + subtitle-opacity + 0 + (- constants/onboarding-modal-animation-duration + constants/onboarding-modal-animation-delay) + :easing4) + (reanimated/animate-shared-value-with-timing title-opacity + 0 + 0 + :easing4)) + (if show-camera? 500 0)))] + (when animated? + (reanimated/animate-shared-value-with-delay subtitle-opacity + 1 constants/onboarding-modal-animation-duration + :easing4 + (/ + constants/onboarding-modal-animation-delay + 2)) + (reanimated/animate-shared-value-with-delay title-opacity + 1 0 + :easing4 + (+ constants/onboarding-modal-animation-duration + constants/onboarding-modal-animation-delay)) + (reanimated/animate-delay bottom-view-translate-y + 0 + (+ constants/onboarding-modal-animation-duration + constants/onboarding-modal-animation-delay) + 100)) (rn/use-effect (fn [] + (when animated? + (reanimated/animate-shared-value-with-delay content-opacity + 1 constants/onboarding-modal-animation-duration + :easing4 + (/ + constants/onboarding-modal-animation-delay + 2)) + (js/setTimeout #(reset! render-camera? true) + (+ constants/onboarding-modal-animation-duration + constants/onboarding-modal-animation-delay + 300)) + (reset! navigate-back-fn reset-animations-fn)) (when-not @camera-permission-granted? (permissions/permission-granted? :camera #(reset! camera-permission-granted? %) #(reset! camera-permission-granted? false))))) [:<> background - [render-camera show-camera? @qr-view-finder camera-ref on-read-code show-holes?] + (when (or (not animated?) @render-camera?) + [render-camera show-camera? @qr-view-finder camera-ref on-read-code show-holes?]) [rn/view {:style (style/root-container (:top insets))} - [header active-tab read-qr-once? title] - (case @active-tab - 1 [scan-qr-code-tab qr-view-finder] - 2 [enter-sync-code-tab] - nil) + [header + {:active-tab active-tab + :read-qr-once? read-qr-once? + :title title + :title-opacity title-opacity + :subtitle-opacity subtitle-opacity + :reset-animations-fn reset-animations-fn + :animated? animated?}] + (when (empty? @qr-view-finder) + [:<> + [rn/view {:style style/scan-qr-code-container}] + [qr-scan-hole-area qr-view-finder]]) + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:opacity content-opacity + :transform [{:translate-y content-translate-y}]} + {})} + (case @active-tab + 1 [scan-qr-code-tab qr-view-finder request-camera-permission] + 2 [enter-sync-code-tab] + nil)] [rn/view {:style style/flex-spacer}] - (when show-bottom-view? [bottom-view insets])]])))) + (when show-bottom-view? [bottom-view insets bottom-view-translate-y])]])))) (defn view [props] diff --git a/src/status_im2/core.cljs b/src/status_im2/core.cljs index a37bbc6e60..ba8fcc9881 100644 --- a/src/status_im2/core.cljs +++ b/src/status_im2/core.cljs @@ -5,7 +5,6 @@ [re-frame.core :as re-frame] [re-frame.interop :as interop] [react-native.core :as rn] - [react-native.languages :as react-native-languages] [react-native.platform :as platform] [react-native.shake :as react-native-shake] [reagent.impl.batching :as batching] @@ -39,9 +38,8 @@ (global-error/register-handler) (notifications/listen-notifications) (.addEventListener rn/app-state "change" #(re-frame/dispatch [:app-state-change %])) - (react-native-languages/add-change-listener #(fn [lang] - (i18n/set-language lang) - (i18n-resources/load-language lang))) + (i18n/set-language "en") + (i18n-resources/load-language "en") (react-native-shake/add-shake-listener #(re-frame/dispatch [:shake-event])) (utils.universal-links/initialize) diff --git a/src/status_im2/navigation/core.cljs b/src/status_im2/navigation/core.cljs index 1a9c7095a8..8432e6ff00 100644 --- a/src/status_im2/navigation/core.cljs +++ b/src/status_im2/navigation/core.cljs @@ -164,11 +164,18 @@ (navigation/reg-button-pressed-listener (fn [id] - (if (= "dismiss-modal" id) + (cond + (= "dismiss-modal" id) (do (when-let [event (get-in views/screens [(last @state/modals) :on-dissmiss])] (re-frame/dispatch event)) (dissmissModal)) + (= "RNN.hardwareBackButton" id) + (when-let [handler (get-in views/screens + [(or (last @state/modals) @state/pushed-screen-id) + :hardware-back-button-handler])] + (handler)) + :else (when-let [handler (get-in views/screens [(keyword id) :right-handler])] (handler))))) diff --git a/src/status_im2/navigation/options.cljs b/src/status_im2/navigation/options.cljs index 70931833c0..e176dcaa29 100644 --- a/src/status_im2/navigation/options.cljs +++ b/src/status_im2/navigation/options.cljs @@ -37,6 +37,11 @@ :orientation ["portrait"] :backgroundColor colors/neutral-80-opa-80-blur}) +(def onboarding-transparent-layout + {:componentBackgroundColor :transparent + :orientation ["portrait"] + :backgroundColor :transparent}) + (defn navbar ([dark?] {:navigationBar {:backgroundColor (if (or dark? (colors/dark?)) colors/neutral-100 colors/white)}}) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index c9da26f4b3..44395e5e67 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -34,7 +34,37 @@ [status-im2.contexts.shell.share.view :as share] [status-im2.contexts.onboarding.syncing.results.view :as syncing-results] [status-im2.contexts.onboarding.syncing.progress.view :as syncing-devices] - [status-im2.contexts.chat.new-chat.view :as new-chat])) + [status-im2.contexts.chat.new-chat.view :as new-chat] + [react-native.core :as rn] + [status-im2.constants :as constants])) + +(def sign-in-modal-animations + {:showModal {:translationY {:from (:height (rn/get-window)) + :to 0 + :duration constants/onboarding-modal-animation-duration} + :alpha {:from 1 + :to 1 + :duration 0}} + :dismissModal {:translationY {:from 0 + :to (:height (rn/get-window)) + :duration constants/onboarding-modal-animation-duration} + :alpha {:from 1 + :to 0 + :duration constants/onboarding-modal-animation-duration}}}) + +(def push-animations-for-transparent-background + {:push {:content {:enter {:translationX {:from (:width (rn/get-window)) + :to 0 + :duration constants/onboarding-modal-animation-duration}} + :exit {:translationX {:from 0 + :to (- (:width (rn/get-window))) + :duration constants/onboarding-modal-animation-duration}}}} + :pop {:content {:exit {:translationX {:from 0 + :to (:width (rn/get-window)) + :duration constants/onboarding-modal-animation-duration}} + :enter {:translationX {:from (- (:width (rn/get-window))) + :to 0 + :duration constants/onboarding-modal-animation-duration}}}}}) (defn screens [] @@ -164,12 +194,26 @@ :popGesture false :hardwareBackButton {:dismissModalOnPress false :popStackOnPress false}}} + {:name :scan-sync-code-page :options options/dark-screen :component scan-sync-code-page/view} + {:name :sign-in-intro + :options {:layout options/onboarding-transparent-layout + :animations (merge + sign-in-modal-animations + push-animations-for-transparent-background) + :modalPresentationStyle :overCurrentContext + :hardwareBackButton {:dismissModalOnPress false + :popStackOnPress false}} + :hardware-back-button-handler sign-in/navigate-back + :component sign-in/animated-view} + {:name :sign-in - :options {:layout options/onboarding-layout} + :options {:theme :dark + :modalPresentationStyle :overCurrentContext + :layout options/onboarding-layout} :component sign-in/view} {:name :syncing-progress @@ -178,6 +222,13 @@ :popGesture false} :component syncing-devices/view} + {:name :syncing-progress-intro + :options {:theme :dark + :layout options/onboarding-transparent-layout + :animations push-animations-for-transparent-background + :popGesture false} + :component syncing-devices/view-onboarding} + {:name :syncing-results :options {:theme :dark} :component syncing-results/view}