chore: update ui for syncing page for onboarding and in profile (#15623)

This commit is contained in:
Jamie Caprani 2023-05-15 18:07:13 +01:00 committed by GitHub
parent 05073e7453
commit 4b2d62ee1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 401 additions and 252 deletions

View File

@ -1,241 +1,11 @@
(ns status-im2.contexts.onboarding.sign-in.view
(:require [clojure.string :as string]
[oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[react-native.permissions :as permissions]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.constants :as constants]
(:require [utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.contexts.onboarding.sign-in.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defonce camera-permission-granted? (reagent/atom false))
(defn- header
[active-tab read-qr-once?]
[:<>
[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/close]
[quo/button
{:before :i/info
:type :blur-bg
:size 32
:accessibility-label :find-sync-code
:override-theme :dark
:on-press #(js/alert "Yet to be implemented")}
(i18n/label :t/find-sync-code)]]
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/header-text}
(i18n/label :t/sign-in-by-syncing)]
[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- camera-permission-view
[request-camera-permission]
[rn/view {:style style/camera-permission-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style style/enable-camera-access-header}
(i18n/label :t/enable-access-to-camera)]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/enable-camera-access-sub-text}
(i18n/label :t/to-scan-a-qr-enable-your-camera)]
[quo/button
{:before :i/camera
:type :primary
:size 32
:accessibility-label :request-camera-permission
:override-theme :dark
:on-press request-camera-permission}
(i18n/label :t/enable-camera)]])
(defn- qr-scan-hole-area
[qr-view-finder]
[rn/view
{:style style/qr-view-finder
:on-layout (fn [event]
(let [layout (transforms/js->clj (oops/oget event "nativeEvent.layout"))
width (:width layout)
y (if platform/android?
(+ (safe-area/get-top) (:y layout))
(:y layout))
view-finder (-> layout
(assoc :height width)
(assoc :y y))]
(reset! qr-view-finder view-finder)))}])
(defn- border
[border1 border2 corner]
[rn/view
(assoc {:border-color colors/white :width 80 :height 80} border1 2 border2 2 corner 16)])
(defn- viewfinder
[qr-view-finder]
(let [size (:width qr-view-finder)]
[rn/view {:style (style/viewfinder-container qr-view-finder)}
[rn/view {:width size :height size :justify-content :space-between}
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-top-width :border-left-width :border-top-left-radius]
[border :border-top-width :border-right-width :border-top-right-radius]]
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-bottom-width :border-left-width :border-bottom-left-radius]
[border :border-bottom-width :border-right-width :border-bottom-right-radius]]]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/viewfinder-text}
(i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]))
(defn- scan-qr-code-tab
[qr-view-finder request-camera-permission]
[:<>
[rn/view {:style style/scan-qr-code-container}]
(when (empty? @qr-view-finder)
[qr-scan-hole-area qr-view-finder])
(if (and @camera-permission-granted? (boolean (not-empty @qr-view-finder)))
[viewfinder @qr-view-finder]
[camera-permission-view request-camera-permission])])
(defn- enter-sync-code-tab
[]
[rn/view {:style style/enter-sync-code-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style {:color colors/white}}
"Yet to be implemented"]])
(defn- bottom-view
[insets]
[rn/touchable-without-feedback
{:on-press #(js/alert "Yet to be implemented")}
[rn/view
{:style (style/bottom-container (:bottom insets))}
[quo/text
{:size :paragraph-2
:weight :regular
:style style/bottom-text}
(i18n/label :t/i-dont-have-status-on-another-device)]]])
(defn- check-qr-code-data
[event]
(let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue"))
valid-connection-string? (string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)]
(if valid-connection-string?
(rf/dispatch [:syncing/input-connection-string-for-bootstrapping connection-string])
(rf/dispatch [:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-this-is-not-a-sync-qr-code)}]))))
(defn f-view
[]
(let [camera-ref (atom nil)
read-qr-once? (atom false)
insets (safe-area/get-insets)
active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {})
{:keys [height width]} (rf/sub [:dimensions/window])
request-camera-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label
:t/camera-permission-denied)}])}]))]
(fn []
(let [holes (merge @qr-view-finder {:borderRadius 16})
scan-qr-code-tab? (= @active-tab 1)
show-camera? (and scan-qr-code-tab? @camera-permission-granted?)
show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))
on-read-code (fn [data]
(when-not @read-qr-once?
(reset! read-qr-once? true)
(js/setTimeout (fn []
(reset! read-qr-once? false))
3000)
(check-qr-code-data data)))
hole-view-wrapper (if show-camera?
[hole-view/hole-view
{:style style/absolute-fill
:holes (if show-holes?
[holes]
[])}]
[:<>])]
(rn/use-effect
(fn []
(when-not @camera-permission-granted?
(permissions/permission-granted? :camera
#(reset! camera-permission-granted? %)
#(reset! camera-permission-granted? false)))))
[rn/view {:style (style/root-container (:top insets))}
(if show-camera?
[camera-kit/camera
{:ref #(reset! camera-ref %)
:style (merge style/absolute-fill {:height height :width width})
:camera-options {:zoomMode :off}
:scan-barcode true
:on-read-code on-read-code}]
[background/view true])
(conj hole-view-wrapper
[blur/view
{:style style/absolute-fill
:overlay-color colors/neutral-80-opa-80
:blur-type :dark
:blur-amount (if platform/ios? 15 5)}])
[header active-tab read-qr-once?]
(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}]
[bottom-view insets]]))))
[status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code]))
(defn view
[]
[:f> f-view])
[scan-sync-code/view
{:title (i18n/label :t/sign-in-by-syncing)
:show-bottom-view? true
:background [background/view true]}])

View File

@ -0,0 +1,113 @@
(ns status-im2.contexts.syncing.scan-sync-code.style
(:require [quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform]))
(def screen-padding 20)
(def flex-spacer {:flex 1})
(def absolute-fill
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def hole
(merge absolute-fill
{:z-index 2 :opacity 0.95}))
(defn root-container
[padding-top]
{:z-index 5
:flex 1
:padding-top padding-top})
(def header-container
{:flex-direction :row
:justify-content :space-between
:padding-horizontal screen-padding
:margin-vertical 12})
(def header-text
{:padding-horizontal screen-padding
:padding-top 12
:padding-bottom 8
:color colors/white})
(def header-sub-text
{:padding-horizontal screen-padding
:color colors/white})
(def tabs-container
{:padding-horizontal screen-padding
:margin-top 20})
(def scan-qr-code-container
{:margin-top 19})
(def qr-view-finder
{:margin-horizontal screen-padding
:height 1
:display :flex})
(defn viewfinder-container
[viewfinder]
{:position :absolute
:left (:x viewfinder)
:top (:y viewfinder)
:overflow :hidden})
(def viewfinder-text
{:color colors/white-opa-70
:text-align :center
:padding-top 16})
(def camera-permission-container
{:height 335
:margin-horizontal screen-padding
:background-color colors/white-opa-5
:border-color colors/white-opa-10
:border-radius 12
:align-items :center
:justify-content :center})
(def enable-camera-access-header
{:color colors/white})
(def enable-camera-access-sub-text
{:color colors/white-opa-70
:margin-bottom 16})
(def enter-sync-code-container
{:margin-top 20
:justify-content :center
: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})
(def bottom-text
{:color colors/white
:padding-bottom 12})
(def camera-style
{:height "100%"
:borderRadius 16
:background-color :transparent})
(def camera-container
{:position :absolute
:top (if platform/android? 40 0)
:left 0
:right 0
:bottom 0
:border-radius 16})

View File

@ -0,0 +1,239 @@
(ns status-im2.contexts.syncing.scan-sync-code.view
(:require [clojure.string :as string]
[oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.blur :as blur]
[react-native.hole-view :as hole-view]
[react-native.permissions :as permissions]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.contexts.syncing.scan-sync-code.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.contexts.syncing.utils :as sync-utils]))
(defonce camera-permission-granted? (reagent/atom 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/close]
[quo/button
{:before :i/info
:type :blur-bg
:size 32
:accessibility-label :find-sync-code
:override-theme :dark
:on-press #(js/alert "Yet to be implemented")}
(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- camera-permission-view
[request-camera-permission]
[rn/view {:style style/camera-permission-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style style/enable-camera-access-header}
(i18n/label :t/enable-access-to-camera)]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/enable-camera-access-sub-text}
(i18n/label :t/to-scan-a-qr-enable-your-camera)]
[quo/button
{:before :i/camera
:type :primary
:size 32
:accessibility-label :request-camera-permission
:override-theme :dark
:on-press request-camera-permission}
(i18n/label :t/enable-camera)]])
(defn- qr-scan-hole-area
[qr-view-finder]
[rn/view
{:style style/qr-view-finder
:on-layout (fn [event]
(let [layout (js->clj (oops/oget event "nativeEvent.layout")
:keywordize-keys
true)
view-finder (assoc layout :height (:width layout))]
(reset! qr-view-finder view-finder)))}])
(defn- border
[border1 border2 corner]
[rn/view
(assoc {:border-color colors/white :width 80 :height 80} border1 2 border2 2 corner 16)])
(defn- viewfinder
[qr-view-finder]
(let [size (:width qr-view-finder)]
[:<>
[rn/view {:style (style/viewfinder-container qr-view-finder)}
[rn/view {:width size :height size :justify-content :space-between}
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-top-width :border-left-width :border-top-left-radius]
[border :border-top-width :border-right-width :border-top-right-radius]]
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-bottom-width :border-left-width :border-bottom-left-radius]
[border :border-bottom-width :border-right-width :border-bottom-right-radius]]]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/viewfinder-text}
(i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]]))
(defn- scan-qr-code-tab
[qr-view-finder request-camera-permission]
[:<>
[rn/view {:style style/scan-qr-code-container}]
(when (empty? @qr-view-finder)
[qr-scan-hole-area qr-view-finder])
(if (and @camera-permission-granted? (boolean (not-empty @qr-view-finder)))
[viewfinder @qr-view-finder]
[camera-permission-view request-camera-permission])])
(defn- enter-sync-code-tab
[]
[rn/view {:style style/enter-sync-code-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style {:color colors/white}}
"Yet to be implemented"]])
(defn- bottom-view
[insets]
[rn/touchable-without-feedback
{:on-press #(js/alert "Yet to be implemented")}
[rn/view
{:style (style/bottom-container (:bottom insets))}
[quo/text
{:size :paragraph-2
:weight :regular
:style style/bottom-text}
(i18n/label :t/i-dont-have-status-on-another-device)]]])
(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])
(rf/dispatch [:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-this-is-not-a-sync-qr-code)}]))))
(defn render-camera
[show-camera? qr-view-finder camera-ref on-read-code show-holes?]
(when (and show-camera? (:x qr-view-finder))
[:<>
[rn/view {:style style/camera-container}
[camera-kit/camera
{:ref #(reset! camera-ref %)
:style style/camera-style
:camera-options {:zoomMode :off}
:scan-barcode true
:on-read-code on-read-code}]]
[hole-view/hole-view
{:style style/hole
:holes (if show-holes?
[(merge qr-view-finder
{:borderRadius 16})]
[])}
[blur/view
{:style style/absolute-fill
:blur-amount 10
:blur-type :transparent
:overlay-color colors/neutral-80-opa-80
:background-color colors/neutral-80-opa-80}]
]]))
(defn f-view
[{:keys [title show-bottom-view? background]}]
(let [insets (safe-area/get-insets)
active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {})
request-camera-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label
:t/camera-permission-denied)}])}]))]
(fn []
(let [camera-ref (atom nil)
read-qr-once? (atom false)
on-read-code (fn [data]
(when-not @read-qr-once?
(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?)
show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))]
(rn/use-effect
(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?]
[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 request-camera-permission]
2 [enter-sync-code-tab]
nil)
[rn/view {:style style/flex-spacer}]
(when show-bottom-view? [bottom-view insets])
]]))))
(defn view
[props]
[:f> f-view props])

View File

@ -0,0 +1,10 @@
(ns status-im2.contexts.syncing.scan-sync-code-page.style
(:require [quo2.foundations.colors :as colors]))
(def background
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-95})

View File

@ -0,0 +1,12 @@
(ns status-im2.contexts.syncing.scan-sync-code-page.view
(:require [react-native.core :as rn]
[status-im2.contexts.syncing.scan-sync-code-page.style :as style]
[status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code]
[utils.i18n :as i18n]))
(defn view
[]
[scan-sync-code/view
{:title (i18n/label :t/scan-sync-code)
:background [rn/view
{:style style/background} true]}])

View File

@ -1,20 +1,19 @@
(ns status-im2.contexts.syncing.setup-syncing.view
(:require [utils.i18n :as i18n]
[clojure.string :as string]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[utils.datetime :as datetime]
[status-im2.contexts.syncing.setup-syncing.style :as style]
[utils.re-frame :as rf]
[status-im2.constants :as constants]
[react-native.clipboard :as clipboard]
[status-im2.contexts.syncing.sheets.enter-password.view :as enter-password]
[status-im2.common.qr-code-viewer.view :as qr-code-viewer]
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[react-native.hooks :as hooks]
[react-native.safe-area :as safe-area]))
[react-native.safe-area :as safe-area]
[status-im2.contexts.syncing.utils :as sync-utils]))
(def code-valid-for-ms 120000)
(def one-min-ms 60000)
@ -35,13 +34,6 @@
:icon-override-theme :dark
:on-press #(rf/dispatch [:open-modal :how-to-pair])}]}]])
(defn valid-cs?
[connection-string]
(when connection-string
(string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)))
(defn f-use-interval
[clock cleanup-clock delay]
(hooks/use-interval clock cleanup-clock delay))
@ -53,7 +45,7 @@
delay (reagent/atom nil)
timestamp (reagent/atom nil)
set-code (fn [connection-string]
(when (valid-cs? connection-string)
(when (sync-utils/valid-connection-string? connection-string)
(reset! timestamp (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000))))
(reset! delay 1000)
(reset! code connection-string)))
@ -84,14 +76,14 @@
:weight :semi-bold
:style {:color colors/white}}
(i18n/label :t/setup-syncing)]]
[rn/view {:style (style/qr-container (valid-cs? @code))}
(if (valid-cs? @code)
[rn/view {:style (style/qr-container (sync-utils/valid-connection-string? @code))}
(if (sync-utils/valid-connection-string? @code)
[qr-code-viewer/qr-code-view 331 @code]
[quo/qr-code
{:source (resources/get-image :qr-code)
:height 220
:width "100%"}])
(when-not (valid-cs? @code)
(when-not (sync-utils/valid-connection-string? @code)
[quo/button
{:on-press (fn []
;TODO https://github.com/status-im/status-mobile/issues/15570
@ -103,7 +95,7 @@
:size 40
:style style/generate-button
:before :i/reveal} (i18n/label :t/reveal-sync-code)])
(when (valid-cs? @code)
(when (sync-utils/valid-connection-string? @code)
[rn/view
{:style style/valid-cs-container}
[rn/view
@ -144,5 +136,5 @@
[quo/action-drawer
[[{:icon :i/scan
:override-theme :dark
:on-press #(js/alert "to be implemented")
:on-press #(rf/dispatch [:navigate-to :scan-sync-code-page])
:label (i18n/label :t/scan-or-enter-sync-code)}]]]]]])))

View File

@ -0,0 +1,10 @@
(ns status-im2.contexts.syncing.utils
(:require [clojure.string :as string]
[status-im2.constants :as constants]))
(defn valid-connection-string?
[connection-string]
(when connection-string
(string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)))

View File

@ -24,6 +24,7 @@
[status-im2.contexts.onboarding.profiles.view :as profiles]
[status-im2.contexts.quo-preview.main :as quo.preview]
[status-im2.contexts.shell.view :as shell]
[status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page]
[status-im2.contexts.syncing.syncing-devices-list.view :as settings-syncing]
[status-im2.contexts.syncing.how-to-pair.view :as how-to-pair]
[status-im2.navigation.options :as options]
@ -140,6 +141,8 @@
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}}
{:name :scan-sync-code-page
:component scan-sync-code-page/view}
{:name :sign-in
:options {:layout options/onboarding-layout}