feat(onboarding): Switch profiles keeping biometrics valid (#21628)

* Rework login and logout flow to keep biometrics valid

- Move logout logic to a new namespace, the implementation no longer uses `rf/defn`

* Code style fixes

* Fix warning: "null is not an image" in login

* Fix biometric icons used all over the app

---------

Co-authored-by: Yevheniia Berdnyk <ie.berdnyk@gmail.com>
This commit is contained in:
Ulises Manuel 2024-11-20 13:51:06 -06:00 committed by GitHub
parent 58476a43da
commit ac0a115f1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 245 additions and 231 deletions

View File

@ -11,7 +11,6 @@
legacy.status-im.group-chats.core
legacy.status-im.log-level.core
legacy.status-im.multiaccounts.login.core
legacy.status-im.multiaccounts.logout.core
[legacy.status-im.multiaccounts.model :as multiaccounts.model]
legacy.status-im.multiaccounts.update.core
legacy.status-im.pairing.core
@ -110,14 +109,14 @@
(defn- on-biometric-auth-fail
[{:keys [code]}]
(if (= code "USER_FALLBACK")
(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
(re-frame/dispatch [:profile/logout])
(utils/show-confirmation
{:title (i18n/label :t/biometric-auth-confirm-title)
:content (i18n/label :t/biometric-auth-confirm-message)
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
:on-accept #(rf/dispatch [:biometric/authenticate {:on-fail on-biometric-auth-fail}])
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))
:on-cancel #(re-frame/dispatch [:profile/logout])})))
(rf/defn on-return-from-background
[{:keys [db now] :as cofx}]

View File

@ -91,5 +91,4 @@
cofx
:fleet
fleet
{:on-success
#(re-frame/dispatch [:logout])}))))
{:on-success #(re-frame/dispatch [:profile/logout])}))))

View File

@ -14,7 +14,7 @@
cofx
:log-level
log-level
{:on-success #(re-frame/dispatch [:logout])}))))
{:on-success #(re-frame/dispatch [:profile/logout])}))))
(rf/defn show-change-log-level-confirmation
{:events [:log-level.ui/log-level-selected]}

View File

@ -1,66 +0,0 @@
(ns legacy.status-im.multiaccounts.logout.core
(:require
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.common.keychain.events :as keychain]
[status-im.db :as db]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(re-frame/reg-fx
::logout
(fn []
(native-module/logout)))
(rf/defn initialize-app-db
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type status expensive?]
:centralized-metrics/keys [user-confirmed?]}
:db}]
{:db (assoc db/app-db
:centralized-metrics/user-confirmed? user-confirmed?
:network/type type
:network/status status
:network/expensive? expensive?
:initials-avatar-font-file initials-avatar-font-file
:keycard (dissoc keycard :secrets :pin :application-info)
:biometrics biometrics
:syncing nil)})
(rf/defn logout-method
{:events [::logout-method]}
[{:keys [db] :as cofx} {:keys [auth-method logout?]}]
(let [key-uid (get-in db [:profile/profile :key-uid])]
(rf/merge cofx
{:dispatch [:update-theme-and-init-root :progress]
:effects.shell/reset-state nil
:hide-popover nil
::logout nil
:profile.settings/webview-debug-changed false
:keychain/clear-user-password key-uid
:profile/get-profiles-overview #(rf/dispatch
[:profile/get-profiles-overview-success %])}
(keychain/save-auth-method key-uid auth-method)
(initialize-app-db))))
(rf/defn logout
{:events [:logout :multiaccounts.logout.ui/logout-confirmed
:multiaccounts.update.callback/save-settings-success]}
[_]
;; we need to disable notifications before starting the logout process
{:effects/push-notifications-disable nil
:dispatch [:alert-banners/remove-all]
:dispatch-later [{:ms 100
:dispatch [::logout-method
{:auth-method keychain/auth-method-none
:logout? true}]}]})
(rf/defn show-logout-confirmation
{:events [:multiaccounts.logout.ui/logout-pressed]}
[_]
{:ui/show-confirmation
{:title (i18n/label :t/logout-title)
:content (i18n/label :t/logout-are-you-sure)
:confirm-button-text (i18n/label :t/logout)
:on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
:on-cancel nil}})

View File

@ -125,6 +125,6 @@
(i18n/label :t/password-reset-success-message)])
[react/view {:align-items :center}
[quo/button
{:on-press #(re-frame/dispatch [:logout])
{:on-press #(re-frame/dispatch [:profile/logout])
:disabled resetting?}
(i18n/label :t/okay)]]]))

View File

@ -133,7 +133,7 @@
:params [{:enabled enabled?}]
:on-success (fn []
(log/info "light client set successfully" enabled?)
(re-frame/dispatch [:logout]))
(re-frame/dispatch [:profile/logout]))
:on-error #(log/error "failed to set light client"
{:error %
:enabled? enabled?})}]})
@ -149,7 +149,7 @@
:params [{:enabled enabled?}]
:on-success (fn []
(log/info "store confirmation set successfully" enabled?)
(re-frame/dispatch [:logout]))
(re-frame/dispatch [:profile/logout]))
:on-error #(log/error "failed to set store confirmation"
{:error %
:enabled? enabled?})}]})

View File

@ -36,10 +36,11 @@
"Same as rn/image but cache the image source in a js/Set, so the image won't
flicker when re-render on android"
[props]
(let [[loaded-source set-loaded-source] (rn/use-state nil)
on-source-loaded (rn/use-callback #(set-loaded-source %))]
(let [[loaded-source set-loaded-source] (rn/use-state nil)]
(if platform/ios?
[rn/image props]
[:<>
[rn/image (assoc props :source loaded-source)]
[caching-image props on-source-loaded]])))
[rn/image
(cond-> props
loaded-source (assoc :source loaded-source))]
[caching-image props set-loaded-source]])))

View File

@ -75,7 +75,9 @@
(-> (str key-uid "-auth")
(keychain/get-credentials)
(.then (fn [value]
(if value (oops/oget value "password") auth-method-none)))
(if value
(oops/oget value "password")
auth-method-none)))
(.catch (constantly auth-method-none))))
(defn save-user-password!
@ -86,10 +88,10 @@
[key-uid callback]
(keychain/get-credentials key-uid
(fn [value]
(callback (when value
(-> value
(some-> value
(oops/oget "password")
(security/mask-data)))))))
(security/mask-data)
(callback)))))
(re-frame/reg-fx
:keychain/get-user-password

View File

@ -4,6 +4,7 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[status-im.common.biometric.utils :as biometric]
[status-im.common.standard-authentication.forgot-password-doc.view :as forgot-password-doc]
[status-im.common.standard-authentication.password-input.style :as style]
[utils.debounce :as debounce]
@ -54,7 +55,8 @@
{:password (security/mask-data
entered-password)
:error ""}]
100)))]
100)))
biometric-type (rf/sub [:biometrics/supported-type])]
[:<>
[rn/view {:style {:flex-direction :row}}
[quo/input
@ -76,6 +78,6 @@
:icon-only? true
:background (when blur? :blur)
:type :outline}
:i/face-id])]
(biometric/get-icon-by-type biometric-type)])]
(when error?
[error-info error-message processing shell?])]))

View File

@ -3,6 +3,7 @@
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[status-im.common.biometric.utils :as biometric]
[status-im.constants :as constants]
[utils.re-frame :as rf]))
@ -26,12 +27,15 @@
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}]))
(vec (conj dependencies on-auth-success on-auth-fail)))]
(vec (conj dependencies on-auth-success on-auth-fail)))
biometric-type (rf/sub [:biometrics/supported-type])]
[quo/slide-button
{:container-style container-style
:size size
:customization-color customization-color
:on-complete on-complete
:track-icon (if biometric-auth? :i/face-id :password)
:track-icon (if biometric-auth?
(biometric/get-icon-by-type biometric-type)
:password)
:track-text track-text
:disabled? disabled?}]))

View File

@ -29,4 +29,4 @@
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/log-out-remove)
:button-one-props {:on-press #(rf/dispatch [:logout])}}]]))
:button-one-props {:on-press #(rf/dispatch [:profile/logout])}}]]))

View File

@ -26,6 +26,6 @@
{:resize-mode :contain
:source (resources/get-image :keycard-migration-succeeded)}]]
[quo/button
{:on-press #(rf/dispatch [:logout])
{:on-press #(rf/dispatch [:profile/logout])
:container-style {:margin-horizontal 20}}
(i18n/label :t/logout)]]))

View File

@ -10,7 +10,6 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn page-title
[]
[quo/text-combinations
@ -26,12 +25,13 @@
bio-type-label (biometric/get-label-by-type supported-biometric-type)
profile-color (or (:color (rf/sub [:onboarding/profile]))
(rf/sub [:profile/customization-color]))
syncing-results? (= :screen/onboarding.syncing-results @state/root-id)]
syncing-results? (= :screen/onboarding.syncing-results @state/root-id)
biometric-type (rf/sub [:biometrics/supported-type])]
[rn/view {:style (style/buttons insets)}
[quo/button
{:size 40
:accessibility-label :enable-biometrics-button
:icon-left :i/face-id
:icon-left (biometric/get-icon-by-type biometric-type)
:customization-color profile-color
:on-press #(rf/dispatch [:onboarding/enable-biometrics])}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]

View File

@ -81,8 +81,8 @@
(rf/defn create-account-and-login
{:events [:onboarding/create-account-and-login]}
[{:keys [db] :as cofx}]
(let [{:keys [display-name seed-phrase password image-path color] :as profile}
(:onboarding/profile db)
(let [{:keys [seed-phrase]
:as profile} (:onboarding/profile db)
syncing-account-recovered? (and (seq (:syncing/key-uid db))
(= (:syncing/key-uid db)
(get-in db [:onboarding/profile :key-uid])))]
@ -112,20 +112,21 @@
(when-not (seq multiaccounts)
{:dispatch [:update-theme-and-init-root :screen/onboarding.intro]}))))
(rf/defn password-set
{:events [:onboarding/password-set]}
[{:keys [db]} password]
(let [supported-type (get-in db [:biometrics :supported-type])]
(rf/reg-event-fx
:onboarding/password-set
(fn [{:keys [db]} [masked-password]]
(let [biometric-supported-type (get-in db [:biometrics :supported-type])]
{:db (-> db
(assoc-in [:onboarding/profile :password] password)
(assoc-in [:onboarding/profile :password] masked-password)
(assoc-in [:onboarding/profile :auth-method] constants/auth-method-password))
:dispatch (if supported-type
:fx [[:dispatch
(if biometric-supported-type
[:navigate-to-within-stack
[:screen/onboarding.enable-biometrics
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]
[:onboarding/create-account-and-login])}))
[:onboarding/create-account-and-login])]]})))
(rf/defn navigate-to-enable-biometrics
{:events [:onboarding/navigate-to-enable-biometrics]}
@ -190,8 +191,7 @@
key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding/profile :syncing?])
auth-method (get-in db [:onboarding/profile :auth-method])
biometric-enabled? (= auth-method
constants/auth-method-biometric)]
biometric-enabled? (= auth-method constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding/generated-keys? true)}
biometric-enabled?
(assoc :keychain/save-password-and-auth-method

View File

@ -11,6 +11,7 @@
[status-im.contexts.profile.edit.name.events]
status-im.contexts.profile.effects
status-im.contexts.profile.login.events
status-im.contexts.profile.logout.events
[status-im.contexts.profile.rpc :as profile.rpc]
[utils.re-frame :as rf]))
@ -39,32 +40,54 @@
(rf/reg-event-fx
:profile/profile-selected
(fn [{:keys [db]} [key-uid]]
{:db (update db :profile/login #(select-profile % key-uid))}))
{:db (update db :profile/login select-profile key-uid)}))
(rf/reg-event-fx
:profile/get-profiles-overview-success
(fn [{:keys [db]} [{:keys [accounts] {:keys [userConfirmed enabled]} :centralizedMetricsInfo}]]
(let [db-with-settings (assoc db
:centralized-metrics/user-confirmed? userConfirmed
:centralized-metrics/enabled? enabled)
profiles (reduce-profiles accounts)
{:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))
new-db (cond-> db-with-settings
(seq profiles)
(assoc :profile/profiles-overview profiles)
:profile/set-profile-overview-auth-method
(fn [{db :db} [key-uid auth-method]]
{:db (assoc-in db [:profile/profiles-overview key-uid :auth-method] auth-method)}))
key-uid
(update :profile/login #(select-profile % key-uid)))]
{:db new-db
:fx (if (profile.data-store/accepted-terms? accounts)
[[:dispatch [:update-theme-and-init-root :screen/profile.profiles]]
(when (and key-uid userConfirmed)
(rf/reg-event-fx
:profile/get-profiles-auth-method
(fn [_ [key-uids]]
(let [auth-method-fx (fn [key-uid]
[:effects.biometric/check-if-available
{:key-uid key-uid
:on-success (fn [auth-method]
(rf/dispatch [:profile.login/check-biometric-success key-uid
(rf/dispatch
[:profile/set-profile-overview-auth-method
key-uid
auth-method]))}])]
[[:dispatch [:update-theme-and-init-root :screen/onboarding.intro]]])})))
{:fx (map auth-method-fx key-uids)})))
(rf/reg-event-fx
:profile/set-already-logged-out
(fn [{:keys [db]}]
{:db (dissoc db :profile/logging-out?)}))
(rf/reg-event-fx
:profile/get-profiles-overview-success
(fn [{:keys [db]}
[{accounts :accounts
{:keys [userConfirmed enabled]} :centralizedMetricsInfo}]]
(let [profiles (reduce-profiles accounts)
profiles-key-uids (keys profiles)
new-db (cond-> db
:always
(assoc :centralized-metrics/user-confirmed? userConfirmed
:centralized-metrics/enabled? enabled)
(seq profiles)
(assoc :profile/profiles-overview profiles))]
{:db new-db
:fx [[:dispatch [:profile/get-profiles-auth-method profiles-key-uids]]
(if (profile.data-store/accepted-terms? accounts)
[:dispatch [:update-theme-and-init-root :screen/profile.profiles]]
[:dispatch [:update-theme-and-init-root :screen/onboarding.intro]])
;; dispatch-later makes sure that the logout button subscribed is always disabled
[:dispatch-later
{:ms 100
:dispatch [:profile/set-already-logged-out]}]]})))
(rf/reg-event-fx
:profile/update-setting-from-backup

View File

@ -35,22 +35,20 @@
;; login phase 1: we want to load and show chats faster, so we split login into 2 phases
(rf/reg-event-fx :profile.login/login-existing-profile
(fn [{:keys [db]} [settings account]]
(let [{:networks/keys [_current-network _networks]
:as settings}
(data-store.settings/rpc->settings settings)
(fn [{:keys [db]} [settings-data account]]
(let [settings (data-store.settings/rpc->settings settings-data)
profile-overview (profile.rpc/rpc->profiles-overview account)
log-level (or (:log-level settings) config/log-level)
pairing-completed? (= (get-in db [:syncing :pairing-status]) :completed)]
{:db (cond-> (-> db
pairing-completed? (= (get-in db [:syncing :pairing-status]) :completed)
new-db (-> db
(assoc :chats/loading? true
:profile/profile (merge profile-overview
settings
{:log-level log-level}))
(assoc-in [:activity-center :loading?] true)
(dissoc :centralized-metrics/onboarding-enabled?))
pairing-completed?
(dissoc :syncing))
(dissoc :centralized-metrics/onboarding-enabled?))]
{:db (cond-> new-db
pairing-completed? (dissoc :syncing))
:fx (into [[:json-rpc/call
[{:method "wakuext_startMessenger"
:js-response true
@ -201,11 +199,11 @@
(fn [{:keys [db]}]
(let [key-uid (get-in db [:profile/login :key-uid])
keycard? (get-in db [:profile/profiles-overview key-uid :keycard-pairing])]
(if keycard?
{:keychain/get-keycard-keys
[key-uid #(rf/dispatch [:keycard.login/on-get-keys-from-keychain-success key-uid %])]}
{:keychain/get-user-password
[key-uid #(rf/dispatch [:profile.login/get-user-password-success %])]}))))
{:fx [(if keycard?
[:keychain/get-keycard-keys
[key-uid #(rf/dispatch [:keycard.login/on-get-keys-from-keychain-success key-uid %])]]
[:keychain/get-user-password
[key-uid #(rf/dispatch [:profile.login/get-user-password-success %])]])]})))
(rf/reg-event-fx
:profile.login/biometric-auth-fail

View File

@ -0,0 +1,5 @@
(ns status-im.contexts.profile.logout.effects
(:require [native-module.core :as native-module]
[re-frame.core :as rf]))
(rf/reg-fx :effects.profile/logout native-module/logout)

View File

@ -0,0 +1,47 @@
(ns status-im.contexts.profile.logout.events
(:require [status-im.contexts.profile.logout.effects]
[status-im.db :as db]
[utils.re-frame :as rf]))
(rf/reg-event-fx
:profile.logout/disable-notifications
(fn [_]
{:fx [[:effects/push-notifications-disable nil]
[:dispatch [:alert-banners/remove-all]]]}))
(defn- restart-app-db
[{:keys [initials-avatar-font-file keycard biometrics
network/status network/expensive?
centralized-metrics/user-confirmed?]
network-type :network/network-type
:as _db}]
(assoc db/app-db
:profile/logging-out? true
:centralized-metrics/user-confirmed? user-confirmed?
:network/type network-type
:network/status status
:network/expensive? expensive?
:initials-avatar-font-file initials-avatar-font-file
:keycard (dissoc keycard :secrets :pin :application-info)
:biometrics biometrics
:syncing nil))
(rf/reg-event-fx
:profile.logout/reset-state
(fn [{db :db}]
{:db (restart-app-db db)
:fx [[:hide-popover nil]
[:effects.profile/logout nil]
[:profile.settings/webview-debug-changed false]
[:profile/get-profiles-overview #(rf/dispatch [:profile/get-profiles-overview-success %])]]}))
(rf/reg-event-fx
:profile/logout
(fn [{db :db}]
{:db (assoc db :profile/logging-out? true)
;; We need to disable notifications before starting the logout process
:fx [[:dispatch [:profile.logout/disable-notifications]]
[:dispatch-later
{:ms 100
:dispatch [:profile.logout/reset-state]}]]}))

View File

@ -138,8 +138,7 @@
:color customization-color
:profile-picture profile-picture})
:on-card-press (fn []
(rf/dispatch
[:profile/profile-selected key-uid])
(rf/dispatch-sync [:profile/profile-selected key-uid])
(rf/dispatch
[:profile.login/login-with-biometric-if-available key-uid])
(set-hide-profiles))}]))
@ -157,10 +156,8 @@
(pop-animation-fn)))
(reset! push-animation-fn-atom nil)
(reset! pop-animation-fn-atom nil))))
[reanimated/view
{:style (style/profiles-container translate-x)}
[rn/view
{:style style/profiles-header}
[reanimated/view {:style (style/profiles-container translate-x)}
[rn/view {:style style/profiles-header}
[quo/text
{:size :heading-1
:weight :semi-bold
@ -183,22 +180,21 @@
:render-fn profile-card}]]))
(defn password-input
[processing error]
(let [auth-method (rf/sub [:auth-method])
on-press-biometrics (fn []
(rf/dispatch [:biometric/authenticate
[processing error auth-method]
(let [on-press-biometrics (fn []
(rf/dispatch
[:biometric/authenticate
{:on-success #(rf/dispatch
[:profile.login/biometric-success])
:on-fail #(rf/dispatch
[:profile.login/biometric-auth-fail
%])}]))]
[:profile.login/biometric-auth-fail %])}]))]
[standard-authentication/password-input
{:processing processing
:error error
:shell? true
:blur? true
:on-press-biometrics (when (= auth-method constants/auth-method-biometric) on-press-biometrics)}]))
:on-press-biometrics (when (= auth-method constants/auth-method-biometric)
on-press-biometrics)}]))
(defn- get-error-message
[error]
@ -210,7 +206,7 @@
(defn login-section
[{:keys [show-profiles]}]
(let [{:keys [key-uid name keycard-pairing
(let [{:keys [key-uid name keycard-pairing auth-method
customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
@ -266,7 +262,7 @@
{:pin pin
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]))}]
[password-input processing error])]
[password-input processing error auth-method])]
(when-not keycard-pairing
[quo/button
{:size 40
@ -282,7 +278,7 @@
(defn view
[]
(rn/use-mount #(rf/dispatch [:centralized-metrics/check-modal metrics-modal/view]))
(let [[show-profiles? set-show-profiles] (rn/use-state false)
(let [[show-profiles? set-show-profiles] (rn/use-state true)
show-profiles (rn/use-callback #(set-show-profiles true))
hide-profiles (rn/use-callback #(set-show-profiles false))]
[:<>

View File

@ -15,8 +15,7 @@
(rf/reg-event-fx :profile.settings/profile-update
(fn [{:keys [db]} [setting setting-value {:keys [dont-sync? on-success]}]]
{:db (-> db
(set-setting-value setting setting-value))
{:db (set-setting-value db setting setting-value)
:fx [[:json-rpc/call
[{:method "settings_saveSetting"
:params [setting setting-value]
@ -40,14 +39,14 @@
(rf/reg-event-fx :profile.settings/toggle-test-networks
(fn [{:keys [db]}]
(let [value (get-in db [:profile/profile :test-networks-enabled?])
on-success #(rf/dispatch [:logout])]
(let [test-networks-disabled? (not (get-in db [:profile/profile :test-networks-enabled?]))]
{:fx [[:ui/show-confirmation
{:title (i18n/label :t/testnet-mode-prompt-title)
:content (i18n/label :t/testnet-mode-prompt-content)
:on-accept #(rf/dispatch [:profile.settings/profile-update :test-networks-enabled?
(not value)
{:on-success on-success}])
:on-accept (fn []
(rf/dispatch [:profile.settings/profile-update :test-networks-enabled?
test-networks-disabled?
{:on-success #(rf/dispatch [:profile/logout])}]))
:on-cancel nil}]]})))
(rf/reg-event-fx :profile.settings/change-preview-privacy
@ -58,8 +57,7 @@
(rf/reg-event-fx :profile.settings/change-profile-pictures-show-to
(fn [{:keys [db]} [id]]
{:db (-> db
(assoc-in [:profile/profile :profile-pictures-show-to] id))
{:db (assoc-in db [:profile/profile :profile-pictures-show-to] id)
:fx [[:json-rpc/call
[{:method "wakuext_changeIdentityImageShowTo"
:params [id]
@ -97,8 +95,7 @@
(rf/reg-event-fx :profile.settings/save-profile-picture
(fn [{:keys [db]} [path ax ay bx by]]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:db (-> db
(assoc :bottom-sheet/show? false))
{:db (assoc db :bottom-sheet/show? false)
:fx [[:json-rpc/call
[{:method "multiaccounts_storeIdentityImage"
:params [key-uid (string/replace-first path #"file://" "") ax ay bx
@ -109,8 +106,7 @@
(rf/reg-event-fx :profile.settings/save-profile-picture-from-url
(fn [{:keys [db]} [url]]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:db (-> db
(assoc :bottom-sheet/show? false))
{:db (assoc db :bottom-sheet/show? false)
:fx [[:json-rpc/call
[{:method "multiaccounts_storeIdentityImageFromURL"
:params [key-uid url]
@ -147,3 +143,14 @@
(fn [_ [currency]]
{:fx [[:dispatch [:profile.settings/profile-update :currency currency]]
[:dispatch [:wallet/get-wallet-token-for-all-accounts]]]}))
;; Logout process
(rf/reg-event-fx
:profile.settings/ask-logout
(fn [_ _]
{:fx [[:ui/show-confirmation
{:title (i18n/label :t/logout-title)
:content (i18n/label :t/logout-are-you-sure)
:confirm-button-text (i18n/label :t/logout)
:on-accept #(rf/dispatch [:profile/logout])
:on-cancel nil}]]}))

View File

@ -12,7 +12,7 @@
(defn- handle-logout
[]
(rf/dispatch [:multiaccounts.logout.ui/logout-pressed])
(rf/dispatch [:profile.settings/ask-logout])
(rf/dispatch [:change-password/reset]))
(defn view
@ -21,6 +21,7 @@
[minimum-loading-timeout-done?
set-minimum-loading-timeout-done] (rn/use-state false)
loading? (rf/sub [:settings/change-password-loading])
logging-out? (rf/sub [:profile/logging-out?])
done? (and (not loading?) minimum-loading-timeout-done?)]
(rn/use-mount (fn []
(js/setTimeout
@ -39,8 +40,7 @@
:description-text (if done?
(i18n/label :t/change-password-done-description)
(i18n/label :t/change-password-loading-description))}]
[rn/view
{:style style/loading-content}
[rn/view {:style style/loading-content}
(when-not done?
[quo/information-box
{:type :error
@ -50,5 +50,6 @@
(when done?
[quo/logout-button
{:container-style style/logout-container
:disabled? logging-out?
:on-press handle-logout}])]]))

View File

@ -32,20 +32,23 @@
(let [current-y (oops/oget event "nativeEvent.contentOffset.y")]
(reanimated/set-shared-value scroll-y current-y)))
(defn- logout-press
[]
(rf/dispatch [:profile.settings/ask-logout]))
(defn- footer
[{:keys [bottom]} logout-press]
[{:keys [bottom]}]
(rn/delay-render
(let [logging-out? (rf/sub [:profile/logging-out?])]
[rn/view {:style (style/footer-container bottom)}
[quo/logout-button {:on-press logout-press}]]))
[quo/logout-button
{:on-press logout-press
:disabled? logging-out?}]])))
(defn- get-item-layout
[_ index]
#js {:length 100 :offset (* 100 index) :index index})
(defn logout-press
[]
(rf/dispatch [:multiaccounts.logout.ui/logout-pressed]))
(defn view
[]
(let [theme (quo.theme/use-theme)
@ -83,7 +86,7 @@
:shows-vertical-scroll-indicator false
:render-fn settings-category-view
:get-item-layout get-item-layout
:footer [footer insets logout-press]
:footer [footer insets]
:scroll-event-throttle 16
:on-scroll on-scroll
:bounces false

View File

@ -11,7 +11,7 @@
(defn logout
[]
(rf/dispatch [:logout]))
(rf/dispatch [:profile/logout]))
(defn on-confirm-change
[enable?]

View File

@ -88,6 +88,7 @@
(reg-root-key-sub :profile/profiles-overview :profile/profiles-overview)
(reg-root-key-sub :profile/login :profile/login)
(reg-root-key-sub :profile/profile :profile/profile)
(reg-root-key-sub :profile/logging-out? :profile/logging-out?)
(reg-root-key-sub :profile/wallet-accounts :profile/wallet-accounts)
(reg-root-key-sub :multiaccount/reset-password-form-vals :multiaccount/reset-password-form-vals)

View File

@ -2,7 +2,6 @@
(:require
[cljs.test :refer [is] :as test]
legacy.status-im.events
[legacy.status-im.multiaccounts.logout.core :as logout]
legacy.status-im.subs.root
[native-module.core :as native-module]
[promesa.core :as promesa]
@ -96,7 +95,7 @@
(defn logout
[]
(log/info (str "==== before dispatch logout ===="))
(rf/dispatch [:logout]))
(rf/dispatch [:profile/logout]))
(defn log-headline
[test-name]
@ -259,7 +258,7 @@
(setup-account)
(logout)
(log/info (str "==== before wait-for logout ===="))
(wait-for [::logout/logout-method])
(wait-for [:profile.logout/reset-state])
(log/info (str "==== after wait-for logout ===="))))))
;;;; Fixtures
@ -287,7 +286,7 @@
(test/async done
(promesa/do (logout)
(log/info (str "==== before wait-for logout ===="))
(wait-for [::logout/logout-method])
(wait-for [:profile.logout/reset-state])
(log/info (str "==== after wait-for logout ===="))
(done))))})
([] (fixture-session [:new-account])))

View File

@ -170,8 +170,7 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
self.home_2.just_fyi("Device 2 sign in, user name is " + self.username_2)
self.home_2.reopen_app(sign_in=False)
self.device_2.show_profiles_button.wait_and_click()
self.device_2.get_user_profile_by_name(username=self.username_2).click()
self.device_2.sign_in()
self.device_2.sign_in(user_name=self.username_2)
self.loop.run_until_complete(run_in_parallel(((_device_1_creates_user, {}),
(_device_2_sign_in, {}))))
@ -466,7 +465,7 @@ class TestActivityMultipleDevicePRTwo(MultipleSharedDeviceTestCase):
community_name = 'closed community'
self.channel_name = "dogs"
self.home_1.create_community(community_type="closed")
self.home_1.reopen_app()
self.home_1.reopen_app(user_name=self.username_1)
self.community_1.invite_to_community(community_name, self.username_2)
self.home_2.just_fyi("Request access to community")

View File

@ -687,7 +687,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUiTwo(MultipleSharedDeviceTestCase
chat = self.home_2.get_chat_from_home_view(self.username_1)
if chat.is_element_displayed():
self.errors.append("Deleted '%s' chat is shown, but the chat has been deleted" % self.username_1)
self.home_2.reopen_app()
self.home_2.reopen_app(user_name=self.username_2)
if chat.is_element_displayed(15):
self.errors.append(
"Deleted chat '%s' is shown after re-login, but the chat has been deleted" % self.username_1)

View File

@ -186,7 +186,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
)))
self.chats[0].just_fyi("Admin relogins")
self.chats[0].reopen_app()
self.chats[0].reopen_app(user_name=self.usernames[0])
self.homes[0].get_chat(self.chat_name).click()
self.chats[0].just_fyi("Admin checks reactions count after relogin")
@ -277,7 +277,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
# workaround for app closed after navigating back from gallery
if not self.chats[2].chat_message_input.is_element_displayed():
self.drivers[2].activate_app(app_package)
SignInView(self.drivers[2]).sign_in()
SignInView(self.drivers[2]).sign_in(user_name=self.usernames[2])
self.homes[2].chats_tab.click()
self.homes[2].get_chat(self.chat_name).click()
@ -291,7 +291,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
# workaround for app closed after navigating back from gallery
if not self.chats[2].chats_tab.is_element_displayed():
self.drivers[2].activate_app(app_package)
SignInView(self.drivers[2]).sign_in()
SignInView(self.drivers[2]).sign_in(user_name=self.usernames[2])
self.homes[2].chats_tab.click()
self.errors.verify_no_errors()
@ -331,7 +331,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
# workaround for app closed after opening notifications
if not self.homes[0].chats_tab.is_element_displayed():
self.drivers[0].activate_app(app_package)
SignInView(self.drivers[0]).sign_in()
SignInView(self.drivers[0]).sign_in(user_name=self.usernames[0])
self.homes[0].chats_tab.click()
self.homes[0].get_chat(self.chat_name).click()

View File

@ -42,7 +42,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.channel.send_message(text_message)
self.channel.chat_element_by_text(text_message).wait_for_visibility_of_element()
self.channel.reopen_app()
self.channel.reopen_app(user_name=self.username)
if not self.channel.chat_element_by_text(text_message).is_element_displayed(30):
self.drivers[0].fail("Not navigated to channel view after reopening app")
@ -158,7 +158,8 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
def test_restore_multiaccount_with_waku_backup_remove_profile_switch(self):
self.home.reopen_app(sign_in=False)
self.home.just_fyi("Restore user with predefined communities and contacts")
self.sign_in.recover_access(passphrase=waku_user.seed, second_user=True)
recover_user_name = 'Recover user'
self.sign_in.recover_access(passphrase=waku_user.seed, second_user=True, username=recover_user_name)
self.home.just_fyi("Check contacts/blocked users")
self.home.chats_tab.click()
@ -216,10 +217,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
profile.perform_backup_button.click()
self.home.just_fyi("Check that can login with different user")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.wait_and_click()
self.sign_in.element_by_text(self.username).click()
self.sign_in.sign_in()
self.home.reopen_app(user_name=self.username)
self.home.navigate_back_to_home_view()
self.home.communities_tab.click()
if self.home.element_by_text(waku_user.communities['admin_open']).is_element_displayed(30):
@ -227,7 +225,6 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that can remove user from logged out state")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.wait_and_click()
user_card = self.sign_in.get_user_profile_by_name(username=self.username)
user_card.open_user_options()
self.sign_in.remove_profile_button.click()
@ -237,8 +234,6 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that removed user is not shown in the list anymore")
self.home.reopen_app(sign_in=False)
self.sign_in.explore_new_status_button.click_until_presence_of_element(self.sign_in.show_profiles_button)
self.sign_in.show_profiles_button.wait_and_click()
if self.sign_in.element_by_text(self.username).is_element_displayed():
self.errors.append("Removed user is re-appeared after relogin!")
@ -248,15 +243,14 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
def test_community_discovery(self):
try:
# workaround for case if a user is logged out in the previous test
self.sign_in.get_user_profile_by_index(index=1).click()
self.sign_in.sign_in()
self.sign_in.sign_in(user_name=self.username)
except NoSuchElementException:
pass
self.home.navigate_back_to_home_view()
self.home.just_fyi("Turn off testnet in the profile settings")
profile = self.home.profile_button.click()
profile.switch_network()
self.sign_in.sign_in()
self.sign_in.sign_in(user_name=self.username)
self.home.just_fyi("Check Discover Communities content")
self.home.communities_tab.click()
@ -1192,7 +1186,7 @@ class TestCommunityMultipleDeviceMergedTwo(MultipleSharedDeviceTestCase):
self.home_1.just_fyi("Device 1 goes back online")
self.home_1.driver.activate_app(app_package)
self.device_1.sign_in()
self.device_1.sign_in(user_name=self.username_1)
self.home_2.just_fyi("Device 2 checks that he's joined the community")
exp_text = "You joined “%s" % community_name

View File

@ -131,7 +131,7 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase):
self.driver.terminate_app(app_package)
community_url = "https://status.app/c/Ow==#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK"
self.home.open_link_from_google_search_app(community_url, app_package)
self.sign_in.sign_in()
self.sign_in.sign_in(user_name=self.username)
if not self.community_view.join_button.is_element_displayed(10):
self.errors.append("Closed community was not requested to join by the url %s" % community_url)

View File

@ -83,8 +83,8 @@ class TestWalletMultipleDevice(MultipleSharedDeviceTestCase):
self.wallet_2.wallet_tab.is_element_displayed()
time.sleep(10)
self.loop.run_until_complete(
run_in_parallel(((self.home_1.reopen_app,),
(self.home_2.reopen_app,))))
run_in_parallel(((self.home_1.reopen_app, {'user_name': self.sender_username}),
(self.home_2.reopen_app, {'user_name': self.receiver_username}))))
self.wallet_1.wallet_tab.wait_and_click()
self.wallet_2.wallet_tab.wait_and_click()
self.wallet_1.select_network(network_name='Arbitrum')
@ -216,7 +216,7 @@ class TestWalletOneDevice(MultipleSharedDeviceTestCase):
def test_wallet_balance_mainnet(self):
self.profile_view = self.home_view.profile_button.click()
self.profile_view.switch_network()
self.sign_in_view.sign_in()
self.sign_in_view.sign_in(user_name=self.sender_username)
self.sign_in_view.wallet_tab.click()
self.wallet_view.just_fyi("Checking total balance")

View File

@ -703,14 +703,14 @@ class BaseView(object):
time.sleep(1)
raise TimeoutException(msg="Status app is not terminated after %s sec" % wait_time)
def reopen_app(self, password=common_password, sign_in=True):
def reopen_app(self, password=common_password, sign_in=True, user_name=None):
app_package = self.driver.current_package
self.driver.terminate_app(app_package)
self.wait_for_application_to_not_run(app_package=app_package)
self.driver.activate_app(app_package)
if sign_in:
sign_in_view = self.get_sign_in_view()
sign_in_view.sign_in(password)
sign_in_view.sign_in(user_name, password)
def close_share_popup(self):
self.driver.info("Closing share popup")

View File

@ -269,7 +269,6 @@ class SignInView(BaseView):
self.create_profile_button.click_until_presence_of_element(self.generate_keys_button)
self.not_now_button.wait_and_click()
else:
self.show_profiles_button.wait_and_click(20)
self.plus_profiles_button.click()
self.create_new_profile_button.click()
self.use_recovery_phrase_button.click()
@ -302,8 +301,9 @@ class SignInView(BaseView):
self.enter_sync_code_input.send_keys(sync_code)
self.confirm_button.click()
def sign_in(self, password=common_password):
def sign_in(self, user_name, password=common_password):
self.driver.info("## Sign in (password: %s)" % password, device=False)
self.get_user_profile_by_name(user_name).click()
self.password_input.wait_for_visibility_of_element(10)
self.password_input.send_keys(password)
self.login_button.click()