[Feature] Sign in by scanning sync QR code (#15416)

This commit is contained in:
Mohamed Javid 2023-03-24 23:06:25 +08:00 committed by GitHub
parent 2f19badc6c
commit 7d4be37111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 475 additions and 100 deletions

View File

@ -19,7 +19,7 @@
"@babel/preset-typescript": "^7.17.12",
"@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.1-status",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.2-status",
"@react-native-community/cameraroll": "git+https://github.com/status-im/react-native-cameraroll.git#refs/tags/v4.0.4-status.0",
"@react-native-community/clipboard": "^1.2.2",
"@react-native-community/hooks": "^2.5.1",

View File

@ -5,6 +5,10 @@
[react-native.core :as rn]
[reagent.core :as reagent]))
(def themes-for-blur
{:light {:background-color colors/neutral-80-opa-5}
:dark {:background-color colors/white-opa-5}})
(def themes
{:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
@ -12,11 +16,12 @@
(defn segmented-control
[{:keys [default-active on-change]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size]}]
(fn [{:keys [data size override-theme blur?]}]
(let [active-id @active-tab-id]
[rn/view
{:flex-direction :row
:background-color (get-in themes [(theme/get-theme) :background-color])
:background-color (get-in (if blur? themes-for-blur themes)
[(or override-theme (theme/get-theme)) :background-color])
:border-radius (case size
32 10
28 8
@ -32,6 +37,8 @@
{:id id
:segmented? true
:size size
:override-theme override-theme
:blur? blur?
:active (= id active-id)
:on-press (fn [tab-id]
(reset! active-tab-id tab-id)

View File

@ -79,10 +79,11 @@
[notification-dot/notification-dot
{:style style/notification-dot}])
[rn/view
{:style (style/tab {:size size
{:style (style/tab
{:size size
:disabled disabled
:segmented? segmented?
:background-color background-color
:background-color (if (and segmented? (not active)) :transparent background-color)
:show-notification-dot? show-notification-dot?})}
(when before
[rn/view

View File

@ -60,6 +60,7 @@
quo2.components.settings.accounts.view
quo2.components.settings.privacy-option
quo2.components.onboarding.small-option-card.view
quo2.components.tabs.segmented-tab
quo2.components.tabs.account-selector
quo2.components.tabs.tabs
quo2.components.tags.context-tags
@ -91,6 +92,7 @@
(def audio-tag quo2.components.tags.context-tags/audio-tag)
(def community-tag quo2.components.tags.context-tags/community-tag)
(def tabs quo2.components.tabs.tabs/tabs)
(def segmented-control quo2.components.tabs.segmented-tab/segmented-control)
(def account-selector quo2.components.tabs.account-selector/account-selector)
(def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button)
(def page-nav quo2.components.navigation.page-nav/page-nav)

View File

@ -0,0 +1,5 @@
(ns react-native.camera-kit
(:require ["react-native-camera-kit" :refer (CameraKitCamera)]
[reagent.core :as reagent]))
(def camera (reagent/adapt-react-class CameraKitCamera))

View File

@ -481,6 +481,9 @@
"Decides which root should be initialised depending on user and app state"
[db]
(cond
(get db :local-pairing/completed-pairing?)
(re-frame/dispatch [:syncing/pairing-completed])
(get db :onboarding-2/new-account?)
(re-frame/dispatch [:navigate-to :enable-notifications])

View File

@ -9,7 +9,10 @@
[status-im.visibility-status-updates.core :as visibility-status-updates]
[utils.re-frame :as rf]
[status-im2.contexts.chat.messages.link-preview.events :as link-preview]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[status-im2.constants :as constants]
[quo2.foundations.colors :as colors]
[status-im.multiaccounts.model :as multiaccounts.model]))
(rf/defn status-node-started
[{db :db :as cofx} {:keys [error]}]
@ -53,10 +56,40 @@
:peer-stats peer-stats
:peers-count (count (:peers peer-stats)))}))
(defn handle-local-pairing-signals
[event]
(log/debug "local pairing signal received"
{:event event}))
(rf/defn handle-local-pairing-signals
[{:keys [db] :as cofx} event]
(log/info "local pairing signal received"
{:event event})
(let [connection-success? (= (:type event)
constants/local-pairing-event-connection-success)
error-on-pairing? (contains? constants/local-pairing-event-errors (:type event))
completed-pairing? (and (= (:type event)
constants/local-pairing-event-process-success)
(= (:action event)
constants/local-pairing-action-pairing-account))
logged-in? (multiaccounts.model/logged-in? cofx)
;; since `connection-success` event is received on both sender and receiver devices
;; we check the `logged-in?` status to identify the receiver and take the user to next screen
navigate-to-syncing-devices? (and connection-success? (not logged-in?))
user-in-syncing-devices-screen? (= (:view-id db) :syncing-devices)]
(merge {:db (cond-> db
connection-success?
(assoc :local-pairing/completed-pairing? false)
error-on-pairing?
(dissoc :local-pairing/completed-pairing?)
completed-pairing?
(assoc :local-pairing/completed-pairing? true))}
(when navigate-to-syncing-devices?
{:dispatch [:navigate-to :syncing-devices]})
(when (and error-on-pairing? user-in-syncing-devices-screen?)
{:dispatch-n [[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-syncing-connection-failed)}]
[:navigate-back]]}))))
(rf/defn process
{:events [:signals/signal-received]}
@ -110,5 +143,6 @@
"status.updates.timedout" (visibility-status-updates/handle-visibility-status-updates
cofx
(js->clj event-js :keywordize-keys true))
"localPairing" (handle-local-pairing-signals (js->clj event-js :keywordize-keys true))
"localPairing" (handle-local-pairing-signals cofx
(js->clj event-js :keywordize-keys true))
(log/debug "Event " type " not handled"))))

View File

@ -263,6 +263,27 @@
An example of a connection string is -> cs2:5vd6J6:Jfc:27xMmHKEYwzRGXcvTtuiLZFfXscMx4Mz8d9wEHUxDj4p7:EG7Z13QScfWBJNJ5cprszzDQ5fBVsYMirXo8MaQFJvpF:3 "
"cs")
;; sender and receiver events
(def ^:const local-pairing-event-connection-success "connection-success")
(def ^:const local-pairing-event-connection-error "connection-error")
(def ^:const local-pairing-event-transfer-success "transfer-success")
(def ^:const local-pairing-event-transfer-error "transfer-error")
;; receiver events
(def ^:const local-pairing-event-received-amount "received-account")
(def ^:const local-pairing-event-process-success "process-success")
(def ^:const local-pairing-event-process-error "process-error")
(def ^:const local-pairing-event-errors
#{local-pairing-event-connection-error
local-pairing-event-transfer-error
local-pairing-event-process-error})
(def ^:const local-pairing-action-connect 1)
(def ^:const local-pairing-action-pairing-account 2)
(def ^:const local-pairing-action-sync-device 3)
(def ^:const local-pairing-action-pairing-installation 4)
(def ^:const serialization-key
"We pass this serialization key as a parameter to MultiformatSerializePublicKey
function at status-go, This key determines the output base of the serialization.

View File

@ -1,14 +1,92 @@
(ns status-im2.contexts.onboarding.sign-in.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(:require [quo2.foundations.colors :as colors]))
(def navigation-bar {:height 56})
(def screen-padding 20)
(def page-container
{:padding-top (if platform/ios? 44 0)
:position :absolute
(def flex-spacer {:flex 1})
(def absolute-fill
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-80-opa-80-blur})
:right 0})
(defn root-container
[padding-top]
{: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)})
(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]
{: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})

View File

@ -1,42 +1,234 @@
(ns status-im2.contexts.onboarding.sign-in.view
(:require [quo2.core :as quo]
(: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.common.resources :as resources]
[status-im2.constants :as constants]
[status-im2.contexts.onboarding.sign-in.style :as style]
[utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf]))
(defn navigation-bar
[]
[rn/view {:style style/navigation-bar}
[quo/page-nav
{:align-mid? true
:mid-section {:type :text-only :main-text ""}
:left-section {:type :blur-bg
:icon :i/arrow-left
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:right-section-buttons [{:type :blur-bg
:icon :i/info
:icon-override-theme :dark
:on-press #(js/alert "Pending")}]}]])
(defonce camera-permission-granted? (reagent/atom false))
(defn page
[]
[rn/view {:style style/page-container}
[navigation-bar]
[rn/view {:style {:padding-horizontal 20}}
(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 {:color colors/white}} "Sign in by syncing"]
[quo/button
{:on-press #(rf/dispatch [:navigate-to :syncing-devices])
:style {}} (i18n/label :t/continue)]]])
: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 sign-in
(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 {:flex 1}}
[background/view true]
[page]])
[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 view
[]
(let [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)}])}]))]
[:f>
(fn []
(let [insets (safe-area/use-safe-area)
camera-ref (atom nil)
read-qr-once? (atom false)
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}]
[rn/image
{:style (merge style/absolute-fill {:height height :width width})
:source (resources/get-image :intro-4)}])
(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]]))]))

View File

@ -3,9 +3,7 @@
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[status-im2.contexts.onboarding.syncing.syncing-devices.style :as style]
[utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf]))
[status-im2.contexts.onboarding.common.background.view :as background]))
(defn navigation-bar
@ -14,10 +12,6 @@
[quo/page-nav
{:align-mid? true
:mid-section {:type :text-only :main-text ""}
:left-section {:type :blur-bg
:icon :i/arrow-left
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:right-section-buttons [{:type :blur-bg
:icon :i/info
:icon-override-theme :dark
@ -39,10 +33,7 @@
[quo/text
{:size :heading-2
:weight :semi-bold
:style {:color colors/white}} "will show sync failed if unsuccessful"]
[quo/button
{:on-press #(rf/dispatch [:navigate-to :enable-notifications])
:style {}} (i18n/label :t/continue)]]])
:style {:color colors/white}} "will show sync failed if unsuccessful"]]])
(defn syncing-devices
[]

View File

@ -1,6 +1,7 @@
(ns status-im2.contexts.quo-preview.tabs.segmented-tab
(:require [quo2.components.tabs.segmented-tab :as quo2]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.preview :as preview]))
@ -12,17 +13,26 @@
:options [{:key 28
:value "28"}
{:key 20
:value "20"}]}])
:value "20"}]}
{:label "Blur?"
:key :blur?
:type :boolean}])
(defn cool-preview
[]
(let [state (reagent/atom {:size 32})]
(let [state (reagent/atom {:size 32
:blur? false})]
(fn []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[rn/view {:flex 1}
[preview/customizer state descriptor]]
[rn/view {:padding-vertical 60}
[preview/blur-view
{:show-blur-background? (:blur? @state)
:height 200
:style {:width "100%"}
:blur-view-props {:blur-type (theme/get-theme)}}
[:<>
[quo2/segmented-control
(merge @state
{:default-active 1
@ -30,14 +40,14 @@
{:id 2 :label "Tab 2"}
{:id 3 :label "Tab 3"}
{:id 4 :label "Tab 4"}]
:on-change #(println "Active tab" %)})]]
[rn/view {:padding-vertical 60}
:on-change #(println "Active tab" %)})]
[rn/view {:style {:padding-top 24}}
[quo2/segmented-control
(merge @state
{:default-active 1
:data [{:id 1 :label "Tab 1"}
{:id 2 :label "Tab 2"}]
:on-change #(println "Active tab" %)})]]]])))
:on-change #(println "Active tab" %)})]]]]]])))
(defn preview-segmented
[]

View File

@ -10,6 +10,13 @@
[status-im.data-store.settings :as data-store.settings]
[status-im.utils.platform :as utils.platform]))
(rf/defn local-pairing-completed
{:events [:syncing/pairing-completed]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (dissoc db :local-pairing/completed-pairing?)
:dispatch [:init-root :enable-notifications]}))
(defn- get-default-node-config
[installation-id]
(let [db {:networks/current-network config/default-network
@ -24,7 +31,7 @@
(rf/defn initiate-local-pairing-with-connection-string
{:events [:syncing/input-connection-string-for-bootstrapping]
:interceptors [(re-frame/inject-cofx :random-guid-generator)]}
[{:keys [random-guid-generator db]} {connection-string :data}]
[{:keys [random-guid-generator db]} connection-string]
(let [installation-id (random-guid-generator)
default-node-config (get-default-node-config installation-id)
default-node-config-string (.stringify js/JSON (clj->js default-node-config))

View File

@ -160,6 +160,14 @@
{:stack {:id :profiles
:children [{:component {:name :profiles
:id :profiles
:options (merge
(status-bar-options)
{:topBar {:visible false}})}}]}}}
:enable-notifications
{:root
{:stack {:id :enable-notifications
:children [{:component {:name :enable-notifications
:id :enable-notifications
:options (merge
(status-bar-options)
{:topBar {:visible false}})}}]}}}}))

View File

@ -184,11 +184,13 @@
:component enable-notifications/enable-notifications}
{:name :sign-in
:options {:statusBar {:style :light}
:options {:statusBar {:style :light
:backgroundColor :transparent
:translucent true}
:topBar {:visible false}
:navigationBar {:backgroundColor colors/black}}
:insets {:top false}
:component sign-in/sign-in}
:component sign-in/view}
{:name :syncing-devices
:options {:statusBar {:style :light}

View File

@ -2020,5 +2020,19 @@
"remove-profile-message": "Remove profile from this device",
"remove-profile-confirm-message": "All profile data will removed from device.",
"create-new-profile": "Create new profile",
"add-existing-status-profile": "Add existing Status profile"
"add-existing-status-profile": "Add existing Status profile",
"find-sync-code": "Find sync code",
"sign-in-by-syncing": "Sign in by syncing",
"synchronise-your-data-across-your-devices": "Synchronise your data across your devices",
"scan-sync-qr-code": "Scan QR code",
"enter-sync-code": "Enter sync code",
"enable-access-to-camera": "Enable access to camera",
"to-scan-a-qr-enable-your-camera": "To scan a QR, enable your camera",
"enable-camera": "Enable camera",
"ensure-qr-code-in-focus-to-scan": "Ensure that the QR code is in focus to scan",
"i-dont-have-status-on-another-device": "I dont have Status on another device",
"ensure-qr-code-is-in-focus-to-scan":"Ensure that the QR code is in focus to scan",
"error-this-is-not-a-sync-qr-code": "Oops! This is not a sync QR code",
"error-syncing-connection-failed": "Oops! Connection failed. Try again",
"camera-permission-denied": "Permission denied"
}

View File

@ -1786,9 +1786,9 @@
eventemitter3 "^1.2.0"
lodash "^4.17.15"
"@react-native-community/blur@git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.1-status":
version "4.3.0"
resolved "git+https://github.com/status-im/react-native-blur#7317898ee7c824d2d5501aec72a509baced2b253"
"@react-native-community/blur@git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.2-status":
version "4.3.2"
resolved "git+https://github.com/status-im/react-native-blur#722940e19c7ac8f90d3fccc5e9d34e0fd2342d51"
"@react-native-community/cameraroll@git+https://github.com/status-im/react-native-cameraroll.git#refs/tags/v4.0.4-status.0":
version "4.0.4"