feat: implement onboarding modal transition for sign in screen (#16167)

This commit is contained in:
Brian Sztamfater 2023-07-04 12:43:08 -03:00 committed by GitHub
parent affd2a5e76
commit c2c79cc1ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 553 additions and 181 deletions

View File

@ -1,9 +1,19 @@
(ns quo2.components.drawers.drawer-buttons.component-spec (ns quo2.components.drawers.drawer-buttons.component-spec
(:require [quo2.components.drawers.drawer-buttons.view :as drawer-buttons] (:require [quo2.components.drawers.drawer-buttons.view :as drawer-buttons]
[react-native.core :as rn] [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/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/test "the top heading and subheading render"
(h/render [drawer-buttons/view (h/render [drawer-buttons/view
{:top-card {:heading :top-heading} {:top-card {:heading :top-heading}
@ -29,15 +39,16 @@
(h/test "it clicks the top card" (h/test "it clicks the top card"
(let [event (h/mock-fn)] (let [event (h/mock-fn)]
(h/render [drawer-buttons/view (with-redefs [safe-area/get-top (fn [] 10)]
{:top-card {:on-press event (h/render [drawer-buttons/view
:heading :top-heading} {:top-card {:on-press event
:bottom-card {:heading :bottom-heading}} :heading :top-heading}
:top-sub-heading :bottom-card {:heading :bottom-heading}}
:bottom-sub-heading]) :top-sub-heading
(h/fire-event :press (h/get-by-text "top-heading")) :bottom-sub-heading])
(-> (js/expect event) (h/fire-event :press (h/get-by-text "top-heading"))
(.toHaveBeenCalled)))) (-> (js/expect event)
(.toHaveBeenCalled)))))
(h/test "it clicks the bottom card" (h/test "it clicks the bottom card"
(let [event (h/mock-fn)] (let [event (h/mock-fn)]

View File

@ -1,11 +1,20 @@
(ns quo2.components.drawers.drawer-buttons.style (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 (defn outer-container
{:height 216 [height border-radius container-style]
:border-top-left-radius 20 (reanimated/apply-animations-to-style
:border-top-right-radius 20 {:height height
:overflow :hidden}) :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 (def top-card
{:flex 1 {:flex 1
@ -17,7 +26,6 @@
(def bottom-card (def bottom-card
{:position :absolute {:position :absolute
:top 80
:left 0 :left 0
:right 0 :right 0
:bottom 0 :bottom 0
@ -41,6 +49,20 @@
:align-items :center :align-items :center
:border-color colors/white-opa-5}) :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 (def bottom-text
{:flex 1 {:flex 1
:color colors/white-70-blur}) :color colors/white-70-blur})

View File

@ -4,7 +4,12 @@
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.blur :as blur] [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 (defn render-bottom
[children] [children]
@ -29,35 +34,74 @@
children]] children]]
[render-bottom children])) [render-bottom children]))
(defn render-children-top (defn- f-render-children-top
[children] [children top-children-opacity]
(if (label? children) (let [scale (reanimated/interpolate top-children-opacity [1 0] [1 1.1])
[text/text padding-left (reanimated/interpolate scale [1 1.1] [0 14])]
{:size :paragraph-2 [reanimated/view
:style style/top-text {:style (reanimated/apply-animations-to-style
:weight :semi-bold} {:opacity top-children-opacity
children] :transform [{:scale scale}
children)) {:translate-x padding-left}]}
{})}
(if (label? children)
[text/text
{:size :paragraph-2
:style style/top-text
:weight :semi-bold}
children]
children)]))
(defn card (defn- f-card
[{:keys [on-press style heading gap accessibility-label top?]} children] [{: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 [rn/touchable-highlight
{:accessibility-label accessibility-label {: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 :border-radius 20
:style style :style style
:underlay-color (:background-color style)} :underlay-color (:background-color style)}
[rn/view [rn/view
[text/text [reanimated/view
{:size :heading-1 {:style (reanimated/apply-animations-to-style
:style (style/heading-text gap) {:opacity top-title-opacity}
:weight :semi-bold} {})}
heading] [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? (if top?
[render-children-top children] [:f> f-render-children-top children top-children-opacity]
[render-children-bottom children])]]) [render-children-bottom children])]])
(defn view (defn card
[props children]
[:f> f-card props children])
(defn f-view
"[view opts] "[view opts]
opts opts
{:container-style style-object {:container-style style-object
@ -70,29 +114,101 @@
child-1 string, keyword or hiccup child-1 string, keyword or hiccup
child-2 string, keyword or hiccup child-2 string, keyword or hiccup
" "
[{:keys [container-style top-card bottom-card]} child-1 child-2] [{:keys [container-style top-card bottom-card on-init animations-duration animations-delay]}
[rn/view child-1
{:style (merge container-style style/outer-container)} child-2]
[blur/view (let [max-height (+ (:height (rn/get-window)) (if platform/android? (safe-area/get-top) 0))
{:blur-type :dark height (reanimated/use-shared-value default-height)
:blur-amount 10 top-padding (reanimated/use-shared-value 12)
:style {:flex 1 border-radius (reanimated/use-shared-value 20)
:border-top-left-radius 20 bottom-view-top (reanimated/use-shared-value 80)
:border-top-right-radius 20}}] top-title-opacity (reanimated/use-shared-value 1)
[rn/view top-children-opacity (reanimated/use-shared-value 1)
{:style {:flex 1 animations-delay (/ animations-delay 1.4)
:background-color :transparent start-top-animation (fn []
:position :absolute (reanimated/animate-shared-value-with-delay bottom-view-top
:top 0 (:height (rn/get-screen))
:left 0 animations-duration
:right 0 :easing4
:bottom 0}} animations-delay)
[card (reanimated/animate-shared-value-with-delay
(merge {:gap 4 height
:top? true max-height
:style style/top-card} animations-duration
top-card) child-1] :easing4
[card animations-delay)
(merge {:style style/bottom-card (reanimated/animate-shared-value-with-delay
:gap 20} top-padding
bottom-card) child-2]]]) (+ 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])

View File

@ -81,7 +81,8 @@
{:linear (bezier 0 0 1 1) {:linear (bezier 0 0 1 1)
:easing1 (bezier 0.25 0.1 0.25 1) :easing1 (bezier 0.25 0.1 0.25 1)
:easing2 (bezier 0 0.3 0.6 0.9) :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 ;; Helper functions
(defn get-shared-value (defn get-shared-value

View File

@ -80,7 +80,9 @@
multiaccount-data (when received-account? multiaccount-data (when received-account?
(merge account {:password password})) (merge account {:password password}))
navigate-to-syncing-devices? (and (or connection-success? error-on-pairing?) receiver?) 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 (merge {:db (cond-> db
connection-success? connection-success?
(assoc-in [:syncing :pairing-status] :connected) (assoc-in [:syncing :pairing-status] :connected)
@ -95,7 +97,9 @@
(assoc-in [:syncing :pairing-status] :completed))} (assoc-in [:syncing :pairing-status] :completed))}
(cond (cond
(and navigate-to-syncing-devices? (not user-in-syncing-devices-screen?)) (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?) (and completed-pairing? sender?)
{:dispatch [:syncing/clear-states]} {:dispatch [:syncing/clear-states]}

View File

@ -336,3 +336,6 @@
(def ^:const image-description-in-lightbox? false) (def ^:const image-description-in-lightbox? false)
(def ^:const audio-max-duration-ms 120000) (def ^:const audio-max-duration-ms 120000)
(def ^:const onboarding-modal-animation-duration 300)
(def ^:const onboarding-modal-animation-delay 400)

View File

@ -5,7 +5,6 @@
{:background-color colors/neutral-95 {:background-color colors/neutral-95
:flex-direction :row :flex-direction :row
:position :absolute :position :absolute
:overflow :hidden
:top 0 :top 0
:bottom 0 :bottom 0
:left 0 :left 0

View File

@ -4,23 +4,34 @@
[utils.re-frame :as rf] [utils.re-frame :as rf]
[react-native.core :as rn] [react-native.core :as rn]
[status-im2.contexts.onboarding.intro.style :as style] [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 (defn view
[] []
[rn/view {:style style/page-container} [rn/view {:style style/page-container}
[background/view false] [background/view false]
[quo/drawer-buttons [quo/drawer-buttons
{:top-card {:on-press (fn [] {:on-init (fn [reset-top-animation-fn]
(rf/dispatch [:navigate-to :sign-in]) (reset! scan-sync-code/dismiss-animations reset-top-animation-fn))
(rf/dispatch [:hide-terms-of-services-opt-in-screen])) :animations-duration constants/onboarding-modal-animation-duration
:heading (i18n/label :t/sign-in) :animations-delay constants/onboarding-modal-animation-delay
:accessibility-label :already-use-status-button} :top-card {:on-press (fn []
:bottom-card {:on-press (fn [] (debounce/dispatch-and-chill [:open-modal
(rf/dispatch [:navigate-to :new-to-status]) :sign-in-intro]
(rf/dispatch [:hide-terms-of-services-opt-in-screen])) 2000)
:heading (i18n/label :t/new-to-status) (rf/dispatch [:hide-terms-of-services-opt-in-screen]))
:accessibility-label :new-to-status-button}} :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 [quo/text
{:style style/plain-text} {:style style/plain-text}
(i18n/label :t/you-already-use-status)] (i18n/label :t/you-already-use-status)]

View File

@ -28,7 +28,7 @@
:accessibility-label :create-new-profile} :accessibility-label :create-new-profile}
{:icon :i/multi-profile {:icon :i/multi-profile
:label (i18n/label :t/add-existing-status-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}]]]) :accessibility-label :multi-profile}]]])
(defn show-new-account-options (defn show-new-account-options

View File

@ -3,9 +3,22 @@
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code])) [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 (defn view
[] []
[scan-sync-code/view [scan-sync-code/view
{:title (i18n/label :t/sign-in-by-syncing) {:title (i18n/label :t/sign-in-by-syncing)
:show-bottom-view? true :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}])

View File

@ -1,7 +1,8 @@
(ns status-im2.contexts.onboarding.syncing.progress.style (ns status-im2.contexts.onboarding.syncing.progress.style
(:require [quo2.foundations.colors :as colors])) (:require [quo2.foundations.colors :as colors]))
(def page-container (defn page-container
[in-onboarding?]
{:flex 1 {:flex 1
:position :absolute :position :absolute
:top 0 :top 0
@ -9,7 +10,7 @@
:left 0 :left 0
:right 0 :right 0
:padding-bottom 20 :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 (def page-illustration
{:flex 1 {:flex 1

View File

@ -30,22 +30,23 @@
:subtitle-accessibility-label :progress-screen-sub-title}]) :subtitle-accessibility-label :progress-screen-sub-title}])
(defn try-again-button (defn try-again-button
[profile-color] [profile-color in-onboarding?]
[quo/button [quo/button
{:on-press (fn [] {:on-press (fn []
(rf/dispatch [:syncing/clear-states]) (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 :accessibility-label :try-again-later-button
:override-background-color (colors/custom-color profile-color 60) :override-background-color (colors/custom-color profile-color 60)
:style style/try-again-button} :style style/try-again-button}
(i18n/label :t/try-again)]) (i18n/label :t/try-again)])
(defn view (defn view
[] [in-onboarding?]
(let [pairing-status (rf/sub [:pairing/pairing-status]) (let [pairing-status (rf/sub [:pairing/pairing-status])
profile-color (:color (rf/sub [:onboarding-2/profile]))] profile-color (:color (rf/sub [:onboarding-2/profile]))]
[rn/view {:style style/page-container} [rn/view {:style (style/page-container in-onboarding?)}
[background/view true] (when-not in-onboarding? [background/view true])
[quo/page-nav] [quo/page-nav]
[page-title (pairing-progress pairing-status)] [page-title (pairing-progress pairing-status)]
(if (pairing-progress pairing-status) (if (pairing-progress pairing-status)
@ -54,4 +55,8 @@
[rn/view {:style style/page-illustration} [rn/view {:style style/page-illustration}
[quo/text "[Error here]"]]) [quo/text "[Error here]"]])
(when-not (pairing-progress pairing-status) (when-not (pairing-progress pairing-status)
[try-again-button profile-color])])) [try-again-button profile-color in-onboarding?])]))
(defn view-onboarding
[]
[view true])

View File

@ -1,5 +1,6 @@
(ns status-im2.contexts.syncing.scan-sync-code.style (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) (def screen-padding 20)
@ -62,7 +63,7 @@
[viewfinder] [viewfinder]
{:position :absolute {:position :absolute
:left (:x viewfinder) :left (:x viewfinder)
:top (:y viewfinder)}) :top 19})
(def view-finder-border-container (def view-finder-border-container
{:flex-direction :row {:flex-direction :row
@ -104,6 +105,7 @@
(def camera-permission-container (def camera-permission-container
{:height 335 {:height 335
:margin-top 19
:margin-horizontal screen-padding :margin-horizontal screen-padding
:background-color colors/white-opa-5 :background-color colors/white-opa-5
:border-color colors/white-opa-10 :border-color colors/white-opa-10
@ -126,15 +128,17 @@
:align-items :center}) :align-items :center})
(defn bottom-container (defn bottom-container
[padding-bottom] [translate-y padding-bottom]
{:z-index 6 (reanimated/apply-animations-to-style
:padding-top 12 {:transform [{:translate-y translate-y}]}
:padding-bottom padding-bottom {:z-index 6
:background-color colors/white-opa-5 :padding-top 12
:border-top-left-radius 20 :padding-bottom padding-bottom
:border-top-right-radius 20 :background-color colors/white-opa-5
:align-items :center :border-top-left-radius 20
:justify-content :center}) :border-top-right-radius 20
:align-items :center
:justify-content :center}))
(def bottom-text (def bottom-text
{:color colors/white {:color colors/white

View File

@ -14,12 +14,17 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.contexts.syncing.utils :as sync-utils] [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. ;; 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 preflight-check-passed? (reagent/atom (if platform/ios? false true)))
(defonce camera-permission-granted? (reagent/atom false)) (defonce camera-permission-granted? (reagent/atom false))
(defonce dismiss-animations (atom nil))
(defonce navigate-back-fn (atom nil))
(defn request-camera-permission (defn request-camera-permission
[] []
@ -42,47 +47,83 @@
[] []
(rf/dispatch [:syncing/preflight-outbound-check #(reset! preflight-check-passed? %)])) (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 (defn- header
[active-tab read-qr-once? title] [props]
[:<> [:f> f-header props])
[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))}]]])
(defn get-labels-and-on-press-method (defn get-labels-and-on-press-method
[] []
@ -192,9 +233,6 @@
(defn- scan-qr-code-tab (defn- scan-qr-code-tab
[qr-view-finder] [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? (if (and @preflight-check-passed?
@camera-permission-granted? @camera-permission-granted?
(boolean (not-empty @qr-view-finder))) (boolean (not-empty @qr-view-finder)))
@ -210,24 +248,30 @@
:style {:color colors/white}} :style {:color colors/white}}
"Yet to be implemented"]]) "Yet to be implemented"]])
(defn- bottom-view (defn- f-bottom-view
[insets] [insets translate-y]
[rn/touchable-without-feedback [rn/touchable-without-feedback
{:on-press #(js/alert "Yet to be implemented")} {:on-press #(js/alert "Yet to be implemented")}
[rn/view [reanimated/view
{:style (style/bottom-container (:bottom insets))} {:style (style/bottom-container translate-y (:bottom insets))}
[quo/text [quo/text
{:size :paragraph-2 {:size :paragraph-2
:weight :medium :weight :medium
:style style/bottom-text} :style style/bottom-text}
(i18n/label :t/i-dont-have-status-on-another-device)]]]) (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 (defn- check-qr-code-data
[event] [event]
(let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue")) (let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue"))
valid-connection-string? (sync-utils/valid-connection-string? connection-string)] valid-connection-string? (sync-utils/valid-connection-string? connection-string)]
(if valid-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 (rf/dispatch [:toasts/upsert
{:icon :i/info {:icon :i/info
:icon-color colors/danger-50 :icon-color colors/danger-50
@ -262,47 +306,124 @@
:background-color colors/neutral-80-opa-80}]]])) :background-color colors/neutral-80-opa-80}]]]))
(defn f-view (defn f-view
[{:keys [title show-bottom-view? background]}] [{:keys [title show-bottom-view? background animated?]}]
(let [insets (safe-area/get-insets) (let [insets (safe-area/get-insets)
active-tab (reagent/atom 1) active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {})] qr-view-finder (reagent/atom {})
render-camera? (reagent/atom false)]
(fn [] (fn []
(let [camera-ref (atom nil) (let [camera-ref (atom nil)
read-qr-once? (atom false) read-qr-once? (atom false)
;; The below check is to prevent scanning of any QR code ;; The below check is to prevent scanning of any QR code
;; when the user is in syncing progress screen ;; when the user is in syncing progress screen
user-in-syncing-progress-screen? (= (rf/sub [:view-id]) :syncing-progress) user-in-syncing-progress-screen? (= (rf/sub [:view-id]) :syncing-progress)
on-read-code (fn [data] on-read-code (fn [data]
(when (and (not @read-qr-once?) (when (and (not @read-qr-once?)
(not user-in-syncing-progress-screen?)) (not user-in-syncing-progress-screen?))
(reset! read-qr-once? true) (reset! read-qr-once? true)
(js/setTimeout (fn [] (js/setTimeout (fn []
(reset! read-qr-once? false)) (reset! read-qr-once? false))
3000) 3000)
(check-qr-code-data data))) (check-qr-code-data data)))
scan-qr-code-tab? (= @active-tab 1) scan-qr-code-tab? (= @active-tab 1)
show-camera? (and scan-qr-code-tab? show-camera? (and scan-qr-code-tab?
@camera-permission-granted? @camera-permission-granted?
@preflight-check-passed?) @preflight-check-passed?)
show-holes? (and show-camera? show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))] (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 (rn/use-effect
(fn [] (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? (when-not @camera-permission-granted?
(permissions/permission-granted? :camera (permissions/permission-granted? :camera
#(reset! camera-permission-granted? %) #(reset! camera-permission-granted? %)
#(reset! camera-permission-granted? false))))) #(reset! camera-permission-granted? false)))))
[:<> [:<>
background 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))} [rn/view {:style (style/root-container (:top insets))}
[header active-tab read-qr-once? title] [header
(case @active-tab {:active-tab active-tab
1 [scan-qr-code-tab qr-view-finder] :read-qr-once? read-qr-once?
2 [enter-sync-code-tab] :title title
nil) :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}] [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 (defn view
[props] [props]

View File

@ -5,7 +5,6 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[re-frame.interop :as interop] [re-frame.interop :as interop]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.languages :as react-native-languages]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.shake :as react-native-shake] [react-native.shake :as react-native-shake]
[reagent.impl.batching :as batching] [reagent.impl.batching :as batching]
@ -39,9 +38,8 @@
(global-error/register-handler) (global-error/register-handler)
(notifications/listen-notifications) (notifications/listen-notifications)
(.addEventListener rn/app-state "change" #(re-frame/dispatch [:app-state-change %])) (.addEventListener rn/app-state "change" #(re-frame/dispatch [:app-state-change %]))
(react-native-languages/add-change-listener #(fn [lang] (i18n/set-language "en")
(i18n/set-language lang) (i18n-resources/load-language "en")
(i18n-resources/load-language lang)))
(react-native-shake/add-shake-listener #(re-frame/dispatch [:shake-event])) (react-native-shake/add-shake-listener #(re-frame/dispatch [:shake-event]))
(utils.universal-links/initialize) (utils.universal-links/initialize)

View File

@ -164,11 +164,18 @@
(navigation/reg-button-pressed-listener (navigation/reg-button-pressed-listener
(fn [id] (fn [id]
(if (= "dismiss-modal" id) (cond
(= "dismiss-modal" id)
(do (do
(when-let [event (get-in views/screens [(last @state/modals) :on-dissmiss])] (when-let [event (get-in views/screens [(last @state/modals) :on-dissmiss])]
(re-frame/dispatch event)) (re-frame/dispatch event))
(dissmissModal)) (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])] (when-let [handler (get-in views/screens [(keyword id) :right-handler])]
(handler))))) (handler)))))

View File

@ -37,6 +37,11 @@
:orientation ["portrait"] :orientation ["portrait"]
:backgroundColor colors/neutral-80-opa-80-blur}) :backgroundColor colors/neutral-80-opa-80-blur})
(def onboarding-transparent-layout
{:componentBackgroundColor :transparent
:orientation ["portrait"]
:backgroundColor :transparent})
(defn navbar (defn navbar
([dark?] ([dark?]
{:navigationBar {:backgroundColor (if (or dark? (colors/dark?)) colors/neutral-100 colors/white)}}) {:navigationBar {:backgroundColor (if (or dark? (colors/dark?)) colors/neutral-100 colors/white)}})

View File

@ -34,7 +34,37 @@
[status-im2.contexts.shell.share.view :as share] [status-im2.contexts.shell.share.view :as share]
[status-im2.contexts.onboarding.syncing.results.view :as syncing-results] [status-im2.contexts.onboarding.syncing.results.view :as syncing-results]
[status-im2.contexts.onboarding.syncing.progress.view :as syncing-devices] [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 (defn screens
[] []
@ -164,12 +194,26 @@
:popGesture false :popGesture false
:hardwareBackButton {:dismissModalOnPress false :hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}} :popStackOnPress false}}}
{:name :scan-sync-code-page {:name :scan-sync-code-page
:options options/dark-screen :options options/dark-screen
:component scan-sync-code-page/view} :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 {:name :sign-in
:options {:layout options/onboarding-layout} :options {:theme :dark
:modalPresentationStyle :overCurrentContext
:layout options/onboarding-layout}
:component sign-in/view} :component sign-in/view}
{:name :syncing-progress {:name :syncing-progress
@ -178,6 +222,13 @@
:popGesture false} :popGesture false}
:component syncing-devices/view} :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 {:name :syncing-results
:options {:theme :dark} :options {:theme :dark}
:component syncing-results/view} :component syncing-results/view}