enable biometric when user signs in with syncing (#17627)
This commit is contained in:
parent
8f8c8dede2
commit
88dd5e04bb
|
@ -0,0 +1,7 @@
|
||||||
|
(ns status-im2.common.standard-authentication.core
|
||||||
|
(:require
|
||||||
|
status-im2.common.standard-authentication.standard-auth.button.view
|
||||||
|
status-im2.common.standard-authentication.standard-auth.slide-button.view))
|
||||||
|
|
||||||
|
(def button status-im2.common.standard-authentication.standard-auth.button.view/view)
|
||||||
|
(def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view)
|
|
@ -1,10 +1,6 @@
|
||||||
(ns status-im2.common.standard-authentication.standard-auth.view
|
(ns status-im2.common.standard-authentication.standard-auth.authorize
|
||||||
(:require
|
(:require
|
||||||
[quo.core :as quo]
|
|
||||||
[quo.theme :as quo.theme]
|
|
||||||
[react-native.core :as rn]
|
|
||||||
[react-native.touch-id :as biometric]
|
[react-native.touch-id :as biometric]
|
||||||
[reagent.core :as reagent]
|
|
||||||
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
|
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
|
@ -46,39 +42,3 @@
|
||||||
{:on-enter-password on-enter-password
|
{:on-enter-password on-enter-password
|
||||||
:button-icon-left auth-button-icon-left
|
:button-icon-left auth-button-icon-left
|
||||||
:button-label auth-button-label}])}]))))))
|
:button-label auth-button-label}])}]))))))
|
||||||
|
|
||||||
(defn- view-internal
|
|
||||||
[_]
|
|
||||||
(let [reset-slider? (reagent/atom false)
|
|
||||||
on-close #(reset! reset-slider? true)]
|
|
||||||
(fn [{:keys [biometric-auth?
|
|
||||||
track-text
|
|
||||||
customization-color
|
|
||||||
auth-button-label
|
|
||||||
on-enter-password
|
|
||||||
on-auth-success
|
|
||||||
on-auth-fail
|
|
||||||
auth-button-icon-left
|
|
||||||
size
|
|
||||||
theme
|
|
||||||
blur?
|
|
||||||
container-style]}]
|
|
||||||
[rn/view {:style {:flex 1}}
|
|
||||||
[quo/slide-button
|
|
||||||
{:size size
|
|
||||||
:container-style container-style
|
|
||||||
:customization-color customization-color
|
|
||||||
:on-reset (when @reset-slider? #(reset! reset-slider? false))
|
|
||||||
:on-complete #(authorize {:on-close on-close
|
|
||||||
:auth-button-icon-left auth-button-icon-left
|
|
||||||
:theme theme
|
|
||||||
:blur? blur?
|
|
||||||
:on-enter-password on-enter-password
|
|
||||||
:biometric-auth? biometric-auth?
|
|
||||||
:on-auth-success on-auth-success
|
|
||||||
:on-auth-fail on-auth-fail
|
|
||||||
:auth-button-label auth-button-label})
|
|
||||||
:track-icon (if biometric-auth? :i/face-id :password)
|
|
||||||
:track-text track-text}]])))
|
|
||||||
|
|
||||||
(def view (quo.theme/with-theme view-internal))
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
(ns status-im2.common.standard-authentication.standard-auth.button.view
|
||||||
|
(:require
|
||||||
|
[quo.core :as quo]
|
||||||
|
[quo.theme :as quo.theme]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
|
||||||
|
|
||||||
|
(defn- view-internal
|
||||||
|
[_]
|
||||||
|
(let [reset-slider? (reagent/atom false)
|
||||||
|
on-close #(reset! reset-slider? true)]
|
||||||
|
(fn [{:keys [biometric-auth?
|
||||||
|
customization-color
|
||||||
|
auth-button-label
|
||||||
|
on-enter-password
|
||||||
|
on-auth-success
|
||||||
|
on-press
|
||||||
|
on-auth-fail
|
||||||
|
auth-button-icon-left
|
||||||
|
size
|
||||||
|
button-label
|
||||||
|
theme
|
||||||
|
blur?
|
||||||
|
container-style
|
||||||
|
icon-left]}]
|
||||||
|
[quo/button
|
||||||
|
{:size size
|
||||||
|
:container-style container-style
|
||||||
|
:customization-color customization-color
|
||||||
|
:icon-left icon-left
|
||||||
|
:on-press (if on-press
|
||||||
|
on-press
|
||||||
|
#(authorize/authorize {:on-close on-close
|
||||||
|
:auth-button-icon-left auth-button-icon-left
|
||||||
|
:theme theme
|
||||||
|
:blur? blur?
|
||||||
|
:on-enter-password on-enter-password
|
||||||
|
:biometric-auth? biometric-auth?
|
||||||
|
:on-auth-success on-auth-success
|
||||||
|
:on-auth-fail on-auth-fail
|
||||||
|
:auth-button-label auth-button-label}))}
|
||||||
|
button-label])))
|
||||||
|
|
||||||
|
(def view (quo.theme/with-theme view-internal))
|
|
@ -0,0 +1,43 @@
|
||||||
|
(ns status-im2.common.standard-authentication.standard-auth.slide-button.view
|
||||||
|
(:require
|
||||||
|
[quo.core :as quo]
|
||||||
|
[quo.theme :as quo.theme]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
|
||||||
|
|
||||||
|
(defn- view-internal
|
||||||
|
[_]
|
||||||
|
(let [reset-slider? (reagent/atom false)
|
||||||
|
on-close #(reset! reset-slider? true)]
|
||||||
|
(fn [{:keys [biometric-auth?
|
||||||
|
track-text
|
||||||
|
customization-color
|
||||||
|
auth-button-label
|
||||||
|
on-enter-password
|
||||||
|
on-auth-success
|
||||||
|
on-auth-fail
|
||||||
|
auth-button-icon-left
|
||||||
|
size
|
||||||
|
theme
|
||||||
|
blur?
|
||||||
|
container-style]}]
|
||||||
|
[rn/view {:style {:flex 1}}
|
||||||
|
[quo/slide-button
|
||||||
|
{:size size
|
||||||
|
:container-style container-style
|
||||||
|
:customization-color customization-color
|
||||||
|
:on-reset (when @reset-slider? #(reset! reset-slider? false))
|
||||||
|
:on-complete #(authorize/authorize {:on-close on-close
|
||||||
|
:auth-button-icon-left auth-button-icon-left
|
||||||
|
:theme theme
|
||||||
|
:blur? blur?
|
||||||
|
:on-enter-password on-enter-password
|
||||||
|
:biometric-auth? biometric-auth?
|
||||||
|
:on-auth-success on-auth-success
|
||||||
|
:on-auth-fail on-auth-fail
|
||||||
|
:auth-button-label auth-button-label})
|
||||||
|
:track-icon (if biometric-auth? :i/face-id :password)
|
||||||
|
:track-text track-text}]])))
|
||||||
|
|
||||||
|
(def view (quo.theme/with-theme view-internal))
|
|
@ -1,15 +1,20 @@
|
||||||
(ns status-im2.contexts.onboarding.enable-biometrics.view
|
(ns status-im2.contexts.onboarding.enable-biometrics.view
|
||||||
(:require
|
(:require
|
||||||
[quo.core :as quo]
|
[quo.core :as quo]
|
||||||
|
[quo.theme :as quo.theme]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[react-native.safe-area :as safe-area]
|
[react-native.safe-area :as safe-area]
|
||||||
[status-im2.common.biometric.events :as biometric]
|
[status-im2.common.biometric.events :as biometric]
|
||||||
[status-im2.common.parallax.view :as parallax]
|
[status-im2.common.parallax.view :as parallax]
|
||||||
[status-im2.common.parallax.whitelist :as whitelist]
|
[status-im2.common.parallax.whitelist :as whitelist]
|
||||||
[status-im2.common.resources :as resources]
|
[status-im2.common.resources :as resources]
|
||||||
|
[status-im2.common.standard-authentication.core :as standard-auth]
|
||||||
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
|
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
|
||||||
|
[status-im2.navigation.state :as state]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]
|
||||||
|
[utils.security.core :as security]))
|
||||||
|
|
||||||
|
|
||||||
(defn page-title
|
(defn page-title
|
||||||
[]
|
[]
|
||||||
|
@ -21,22 +26,39 @@
|
||||||
:description-accessibility-label :enable-biometrics-sub-title}])
|
:description-accessibility-label :enable-biometrics-sub-title}])
|
||||||
|
|
||||||
(defn enable-biometrics-buttons
|
(defn enable-biometrics-buttons
|
||||||
[insets]
|
[insets theme]
|
||||||
(let [supported-biometric-type (rf/sub [:biometric/supported-type])
|
(let [supported-biometric-type (rf/sub [:biometric/supported-type])
|
||||||
bio-type-label (biometric/get-label-by-type supported-biometric-type)
|
bio-type-label (biometric/get-label-by-type supported-biometric-type)
|
||||||
profile-color (:color (rf/sub [:onboarding-2/profile]))]
|
profile-color (or (:color (rf/sub [:onboarding-2/profile]))
|
||||||
|
(rf/sub [:profile/customization-color]))
|
||||||
|
syncing-results? (= :syncing-results @state/root-id)]
|
||||||
[rn/view {:style (style/buttons insets)}
|
[rn/view {:style (style/buttons insets)}
|
||||||
[quo/button
|
[standard-auth/button
|
||||||
{:accessibility-label :enable-biometrics-button
|
(merge
|
||||||
:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])
|
{:size 40
|
||||||
|
:accessibility-label :enable-biometrics-button
|
||||||
:icon-left :i/face-id
|
:icon-left :i/face-id
|
||||||
:customization-color profile-color}
|
:customization-color profile-color
|
||||||
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
|
:button-label (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})}
|
||||||
|
(if syncing-results?
|
||||||
|
{:theme theme
|
||||||
|
:blur? true
|
||||||
|
:on-enter-password (fn [entered-password]
|
||||||
|
(rf/dispatch
|
||||||
|
[:onboarding-2/authenticate-enable-biometrics
|
||||||
|
(security/safe-unmask-data
|
||||||
|
entered-password)])
|
||||||
|
(rf/dispatch [:hide-bottom-sheet]))
|
||||||
|
:auth-button-label (i18n/label :t/confirm)}
|
||||||
|
{:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])}))]
|
||||||
[quo/button
|
[quo/button
|
||||||
{:accessibility-label :maybe-later-button
|
{:accessibility-label :maybe-later-button
|
||||||
:background :blur
|
:background :blur
|
||||||
:type :grey
|
:type :grey
|
||||||
:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
|
:on-press #(rf/dispatch (if syncing-results?
|
||||||
|
[:navigate-to-within-stack
|
||||||
|
[:enable-notifications :enable-biometrics]]
|
||||||
|
[:onboarding-2/create-account-and-login]))
|
||||||
:container-style {:margin-top 12}}
|
:container-style {:margin-top 12}}
|
||||||
(i18n/label :t/maybe-later)]]))
|
(i18n/label :t/maybe-later)]]))
|
||||||
|
|
||||||
|
@ -55,12 +77,19 @@
|
||||||
:style (style/page-illustration width)
|
:style (style/page-illustration width)
|
||||||
:source (resources/get-image :biometrics)}]))
|
:source (resources/get-image :biometrics)}]))
|
||||||
|
|
||||||
(defn enable-biometrics
|
(defn f-enable-biometrics
|
||||||
[]
|
[{:keys [theme]}]
|
||||||
(let [insets (safe-area/get-insets)]
|
(let [insets (safe-area/get-insets)]
|
||||||
[rn/view {:style (style/page-container insets)}
|
[rn/view {:style (style/page-container insets)}
|
||||||
[page-title]
|
[page-title]
|
||||||
(if whitelist/whitelisted?
|
(if whitelist/whitelisted?
|
||||||
[enable-biometrics-parallax]
|
[enable-biometrics-parallax]
|
||||||
[enable-biometrics-simple])
|
[enable-biometrics-simple])
|
||||||
[enable-biometrics-buttons insets]]))
|
[enable-biometrics-buttons insets theme]]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- internale-enable-biometrics
|
||||||
|
[params]
|
||||||
|
[:f> f-enable-biometrics params])
|
||||||
|
|
||||||
|
(def view (quo.theme/with-theme internale-enable-biometrics))
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
[quo/page-nav
|
[quo/page-nav
|
||||||
{:background :blur
|
{:background :blur
|
||||||
:icon-name :i/arrow-left
|
:icon-name :i/arrow-left
|
||||||
:on-press #(rf/dispatch [:navigate-back-within-stack :new-to-status])}]
|
:on-press #(rf/dispatch [:navigate-back-within-stack :enable-biometrics])}]
|
||||||
[page-title]
|
[page-title]
|
||||||
[rn/view {:style style/page-illustration}
|
[rn/view {:style style/page-illustration}
|
||||||
[quo/text
|
[quo/text
|
||||||
|
|
|
@ -35,11 +35,30 @@
|
||||||
{:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
|
{:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
|
||||||
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
|
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
|
||||||
|
|
||||||
|
(rf/defn authenticate-enable-biometrics
|
||||||
|
{:events [:onboarding-2/authenticate-enable-biometrics]}
|
||||||
|
[{:keys [db]} password]
|
||||||
|
{:db (-> db
|
||||||
|
(assoc-in [:onboarding-2/profile :password] password)
|
||||||
|
(assoc-in [:onboarding-2/profile :syncing?] true))
|
||||||
|
:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
|
||||||
|
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
|
||||||
|
|
||||||
|
(rf/defn navigate-to-enable-notifications
|
||||||
|
{:events [:onboarding-2/navigate-to-enable-notifications]}
|
||||||
|
[{:keys [db]}]
|
||||||
|
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||||
|
{:db (dissoc db :onboarding-2/profile)
|
||||||
|
:dispatch [:navigate-to-within-stack [:enable-notifications :enable-biometrics]]}))
|
||||||
|
|
||||||
(rf/defn biometrics-done
|
(rf/defn biometrics-done
|
||||||
{:events [:onboarding-2/biometrics-done]}
|
{:events [:onboarding-2/biometrics-done]}
|
||||||
[{:keys [db]}]
|
[{:keys [db]}]
|
||||||
|
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])]
|
||||||
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
|
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
|
||||||
:dispatch [:onboarding-2/create-account-and-login]})
|
:dispatch (if syncing?
|
||||||
|
[:onboarding-2/finalize-setup]
|
||||||
|
[:onboarding-2/create-account-and-login])}))
|
||||||
|
|
||||||
(rf/defn biometrics-fail
|
(rf/defn biometrics-fail
|
||||||
{:events [:onboarding-2/biometrics-fail]}
|
{:events [:onboarding-2/biometrics-fail]}
|
||||||
|
@ -83,6 +102,14 @@
|
||||||
[:navigate-to-within-stack [:enable-biometrics :new-to-status]]
|
[:navigate-to-within-stack [:enable-biometrics :new-to-status]]
|
||||||
[:onboarding-2/create-account-and-login])}))
|
[:onboarding-2/create-account-and-login])}))
|
||||||
|
|
||||||
|
(rf/defn navigate-to-enable-biometrics
|
||||||
|
{:events [:onboarding-2/navigate-to-enable-biometrics]}
|
||||||
|
[{:keys [db]}]
|
||||||
|
(let [supported-type (:biometric/supported-type db)]
|
||||||
|
{:dispatch (if supported-type
|
||||||
|
[:open-modal :enable-biometrics]
|
||||||
|
[:open-modal :enable-notifications])}))
|
||||||
|
|
||||||
(rf/defn seed-phrase-entered
|
(rf/defn seed-phrase-entered
|
||||||
{:events [:onboarding-2/seed-phrase-entered]}
|
{:events [:onboarding-2/seed-phrase-entered]}
|
||||||
[_ seed-phrase on-error]
|
[_ seed-phrase on-error]
|
||||||
|
@ -120,6 +147,7 @@
|
||||||
[{:keys [db]}]
|
[{:keys [db]}]
|
||||||
(let [masked-password (get-in db [:onboarding-2/profile :password])
|
(let [masked-password (get-in db [:onboarding-2/profile :password])
|
||||||
key-uid (get-in db [:profile/profile :key-uid])
|
key-uid (get-in db [:profile/profile :key-uid])
|
||||||
|
syncing? (get-in db [:onboarding-2/profile :syncing?])
|
||||||
biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method])
|
biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method])
|
||||||
constants/auth-method-biometric)]
|
constants/auth-method-biometric)]
|
||||||
(cond-> {:db (assoc db :onboarding-2/generated-keys? true)}
|
(cond-> {:db (assoc db :onboarding-2/generated-keys? true)}
|
||||||
|
@ -127,7 +155,10 @@
|
||||||
(assoc :keychain/save-password-and-auth-method
|
(assoc :keychain/save-password-and-auth-method
|
||||||
{:key-uid key-uid
|
{:key-uid key-uid
|
||||||
:masked-password masked-password
|
:masked-password masked-password
|
||||||
:on-success #(log/debug "successfully saved biometric")
|
:on-success (fn []
|
||||||
|
(if syncing?
|
||||||
|
(rf/dispatch [:onboarding-2/navigate-to-enable-notifications])
|
||||||
|
(log/error "successfully saved biometrics")))
|
||||||
:on-error #(log/error "failed to save biometrics"
|
:on-error #(log/error "failed to save biometrics"
|
||||||
{:key-uid key-uid
|
{:key-uid key-uid
|
||||||
:error %})}))))
|
:error %})}))))
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
{:on-press (fn []
|
{:on-press (fn []
|
||||||
(when on-press
|
(when on-press
|
||||||
(on-press))
|
(on-press))
|
||||||
(rf/dispatch [:open-modal :enable-notifications]))
|
(rf/dispatch [:onboarding-2/navigate-to-enable-biometrics]))
|
||||||
:accessibility-label :continue-button
|
:accessibility-label :continue-button
|
||||||
:customization-color profile-color
|
:customization-color profile-color
|
||||||
:container-style style/continue-button}
|
:container-style style/continue-button}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[status-im2.common.qr-codes.view :as qr-codes]
|
[status-im2.common.qr-codes.view :as qr-codes]
|
||||||
[status-im2.common.resources :as resources]
|
[status-im2.common.resources :as resources]
|
||||||
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
|
[status-im2.common.standard-authentication.core :as standard-auth]
|
||||||
[status-im2.contexts.syncing.setup-syncing.style :as style]
|
[status-im2.contexts.syncing.setup-syncing.style :as style]
|
||||||
[status-im2.contexts.syncing.utils :as sync-utils]
|
[status-im2.contexts.syncing.utils :as sync-utils]
|
||||||
[utils.datetime :as datetime]
|
[utils.datetime :as datetime]
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
(i18n/label :t/copy-qr)]])
|
(i18n/label :t/copy-qr)]])
|
||||||
(when-not (sync-utils/valid-connection-string? @code)
|
(when-not (sync-utils/valid-connection-string? @code)
|
||||||
[rn/view {:style style/standard-auth}
|
[rn/view {:style style/standard-auth}
|
||||||
[standard-auth/view
|
[standard-auth/slide-button
|
||||||
{:blur? true
|
{:blur? true
|
||||||
:size :size-40
|
:size :size-40
|
||||||
:track-text (i18n/label :t/slide-to-reveal-code)
|
:track-text (i18n/label :t/slide-to-reveal-code)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[react-native.safe-area :as safe-area]
|
[react-native.safe-area :as safe-area]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
|
[status-im2.common.standard-authentication.core :as standard-auth]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[status-im2.contexts.emoji-picker.utils :as emoji-picker.utils]
|
[status-im2.contexts.emoji-picker.utils :as emoji-picker.utils]
|
||||||
[status-im2.contexts.wallet.common.utils :as utils]
|
[status-im2.contexts.wallet.common.utils :as utils]
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
{:list-type :settings
|
{:list-type :settings
|
||||||
:label (i18n/label :t/origin)
|
:label (i18n/label :t/origin)
|
||||||
:data (get-keypair-data primary-name @derivation-path @account-color)}]
|
:data (get-keypair-data primary-name @derivation-path @account-color)}]
|
||||||
[standard-auth/view
|
[standard-auth/slide-button
|
||||||
{:size :size-48
|
{:size :size-48
|
||||||
:track-text (i18n/label :t/slide-to-create-account)
|
:track-text (i18n/label :t/slide-to-create-account)
|
||||||
:customization-color @account-color
|
:customization-color @account-color
|
||||||
|
|
|
@ -163,9 +163,13 @@
|
||||||
{:name :enable-biometrics
|
{:name :enable-biometrics
|
||||||
:options {:theme :dark
|
:options {:theme :dark
|
||||||
:layout options/onboarding-transparent-layout
|
:layout options/onboarding-transparent-layout
|
||||||
:animations transitions/push-animations-for-transparent-background
|
:animations (merge transitions/new-to-status-modal-animations
|
||||||
:popGesture false}
|
transitions/push-animations-for-transparent-background)
|
||||||
:component enable-biometrics/enable-biometrics}
|
:popGesture false
|
||||||
|
:modalPresentationStyle :overCurrentContext
|
||||||
|
:hardwareBackButton {:dismissModalOnPress false
|
||||||
|
:popStackOnPress false}}
|
||||||
|
:component enable-biometrics/view}
|
||||||
|
|
||||||
{:name :generating-keys
|
{:name :generating-keys
|
||||||
:options {:theme :dark
|
:options {:theme :dark
|
||||||
|
|
Loading…
Reference in New Issue