Skip to content

Commit ef36815

Browse files
committed
refactor: consolidate ACCOUNT_INIT_COMPLETE into LOGIN action
Eliminate the ACCOUNT_INIT_COMPLETE action by loading all settings upfront and dispatching them in the LOGIN action. This enables immediate navigation after login since all required state is available in Redux right away. - Load synced and local settings in parallel before LOGIN dispatch - Expand LOGIN action payload to include syncedSettings and localSettings - Remove ACCOUNT_INIT_COMPLETE action type and AccountInitPayload - Remove pinLoginEnabled from Redux, load locally in SettingsScene - Remove togglePinLoginEnabled thunk from SettingsActions - Update WalletsReducer to remove ACCOUNT_INIT_COMPLETE handling - Update SpendingLimitsReducer to read from LOGIN payload - Update PasswordReminderReducer to read from LOGIN payload - Remove getFirstActiveWalletInfo (walletId/currencyCode no longer set at login)
1 parent cbab933 commit ef36815

File tree

10 files changed

+107
-220
lines changed

10 files changed

+107
-220
lines changed

eslint.config.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,7 @@ export default [
491491
'src/plugins/stake-plugins/util/builder.ts',
492492
'src/reducers/ExchangeInfoReducer.ts',
493493
'src/reducers/NetworkReducer.ts',
494-
'src/reducers/PasswordReminderReducer.ts',
495494

496-
'src/reducers/SpendingLimitsReducer.ts',
497495
'src/selectors/getCreateWalletList.ts',
498496
'src/selectors/SettingsSelectors.ts',
499497
'src/state/createStateProvider.tsx',

src/__tests__/reducers/__snapshots__/RootReducer.test.ts.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ exports[`initialState 1`] = `
134134
"nonPasswordLoginsLimit": 4,
135135
"passwordUseCount": 0,
136136
},
137-
"pinLoginEnabled": false,
138137
"preferredSwapPluginId": undefined,
139138
"preferredSwapPluginType": undefined,
140139
"rampLastCryptoSelection": undefined,

src/__tests__/spendingLimits.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ describe('spendingLimits', () => {
1414
describe('when logging in', () => {
1515
it('should update', () => {
1616
const actual = spendingLimits(initialState, {
17-
type: 'ACCOUNT_INIT_COMPLETE',
17+
type: 'LOGIN',
1818
data: {
19-
spendingLimits: {
20-
transaction: {
21-
isEnabled: false,
22-
amount: 150
19+
localSettings: {
20+
spendingLimits: {
21+
transaction: {
22+
isEnabled: false,
23+
amount: 150
24+
}
2325
}
2426
}
2527
} as any

src/actions/LoginActions.tsx

Lines changed: 46 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import type {
2-
EdgeAccount,
3-
EdgeCreateCurrencyWallet,
4-
EdgeTokenId
5-
} from 'edge-core-js/types'
1+
import type { EdgeAccount, EdgeCreateCurrencyWallet } from 'edge-core-js/types'
62
import {
73
hasSecurityAlerts,
84
refreshTouchId,
@@ -26,10 +22,6 @@ import { Airship, showError } from '../components/services/AirshipInstance'
2622
import { ENV } from '../env'
2723
import { getExperimentConfig } from '../experimentConfig'
2824
import { lstrings } from '../locales/strings'
29-
import {
30-
type AccountInitPayload,
31-
initialState
32-
} from '../reducers/scenes/SettingsReducer'
3325
import type { WalletCreateItem } from '../selectors/getCreateWalletList'
3426
import { config } from '../theme/appConfig'
3527
import type { Dispatch, GetState, ThunkAction } from '../types/reduxTypes'
@@ -56,28 +48,6 @@ import {
5648
const PER_WALLET_TIMEOUT = 5000
5749
const MIN_CREATE_WALLET_TIMEOUT = 20000
5850

59-
function getFirstActiveWalletInfo(account: EdgeAccount): {
60-
walletId: string
61-
tokenId: EdgeTokenId
62-
} {
63-
// Find the first wallet:
64-
const [walletId] = account.activeWalletIds
65-
const walletKey = account.allKeys.find(key => key.id === walletId)
66-
67-
// Find the matching currency code:
68-
if (walletKey != null) {
69-
for (const pluginId of Object.keys(account.currencyConfig)) {
70-
const { currencyInfo } = account.currencyConfig[pluginId]
71-
if (currencyInfo.walletType === walletKey.type) {
72-
return { walletId, tokenId: null }
73-
}
74-
}
75-
}
76-
77-
// The user has no wallets:
78-
return { walletId: '', tokenId: null }
79-
}
80-
8151
export function initializeAccount(
8252
navigation: NavigationBase,
8353
account: EdgeAccount
@@ -86,17 +56,25 @@ export function initializeAccount(
8656
const { newAccount } = account
8757
const rootNavigation = getRootNavigation(navigation)
8858

89-
// Log in as quickly as possible, but we do need the sort order:
90-
const syncedSettings = await readSyncedSettings(account)
91-
const { walletsSort } = syncedSettings
92-
dispatch({ type: 'LOGIN', data: { account, walletSort: walletsSort } })
93-
const referralPromise = dispatch(loadAccountReferral(account))
59+
// Load all settings upfront so we can navigate immediately after LOGIN
60+
const [syncedSettings, localSettings] = await Promise.all([
61+
readSyncedSettings(account),
62+
readLocalAccountSettings(account)
63+
])
9464

95-
// Track whether we showed a non-survey modal or some other interrupting UX.
96-
// We don't want to pester the user with too many interrupting flows.
97-
let hideSurvey = false
65+
// Dispatch LOGIN with all settings - this enables immediate navigation
66+
dispatch({
67+
type: 'LOGIN',
68+
data: {
69+
account,
70+
syncedSettings,
71+
localSettings
72+
}
73+
})
9874

99-
// Account-type specific navigation and setup
75+
const referralPromise = dispatch(loadAccountReferral(account))
76+
77+
// Navigate immediately - all settings are now in Redux
10078
if (newAccount) {
10179
await navigateToNewAccountFlow(
10280
rootNavigation,
@@ -112,6 +90,10 @@ export function initializeAccount(
11290

11391
performance.mark('loginEnd', { detail: { isNewAccount: newAccount } })
11492

93+
// Track whether we showed a non-survey modal or some other interrupting UX.
94+
// We don't want to pester the user with too many interrupting flows.
95+
let hideSurvey = false
96+
11597
// Show a notice for deprecated electrum server settings
11698
const pluginIdsNeedingUserAction: string[] = []
11799
for (const pluginId in account.currencyConfig) {
@@ -162,9 +144,6 @@ export function initializeAccount(
162144
hideSurvey = true
163145
}
164146

165-
const state = getState()
166-
const { context } = state.core
167-
168147
// Sign up for push notifications:
169148
dispatch(registerNotificationsV2()).catch((error: unknown) => {
170149
console.error(error)
@@ -174,77 +153,36 @@ export function initializeAccount(
174153
const filteredWalletInfos = walletInfos.map(({ keys, id, ...info }) => info)
175154
console.log('Wallet Infos:', filteredWalletInfos)
176155

177-
// Merge and prepare settings files:
178-
const walletInfo = newAccount
179-
? undefined
180-
: getFirstActiveWalletInfo(account)
181-
let accountInitObject: AccountInitPayload = {
182-
...initialState,
183-
account,
184-
tokenId: walletInfo?.tokenId ?? null,
185-
pinLoginEnabled: false,
186-
walletId: walletInfo?.walletId ?? '',
187-
walletsSort: 'manual'
188-
}
189-
190-
try {
191-
accountInitObject = { ...accountInitObject, ...syncedSettings }
192-
193-
const loadedLocalSettings = await readLocalAccountSettings(account)
194-
accountInitObject = { ...accountInitObject, ...loadedLocalSettings }
195-
196-
for (const userInfo of context.localUsers) {
197-
if (
198-
userInfo.loginId === account.rootLoginId &&
199-
userInfo.pinLoginEnabled
200-
) {
201-
accountInitObject.pinLoginEnabled = true
202-
}
156+
// Run one-time migration to clean up denomination settings in background
157+
migrateDenominationSettings(account, syncedSettings).catch(
158+
(error: unknown) => {
159+
console.log('Failed to migrate denomination settings:', error)
203160
}
161+
)
204162

205-
// Use synced denomination settings directly (user customizations only).
206-
// Default denominations are derived on-demand from currencyInfo via selectors.
207-
accountInitObject.denominationSettings =
208-
syncedSettings?.denominationSettings ?? {}
163+
await dispatch(refreshAccountReferral())
209164

210-
dispatch({
211-
type: 'ACCOUNT_INIT_COMPLETE',
212-
data: { ...accountInitObject }
213-
})
165+
refreshTouchId(account).catch(() => {
166+
// We have always failed silently here
167+
})
214168

215-
// Run one-time migration to clean up denomination settings in background
216-
migrateDenominationSettings(account, syncedSettings).catch(
217-
(error: unknown) => {
218-
console.log('Failed to migrate denomination settings:', error)
169+
if (
170+
await showNotificationPermissionReminder({
171+
appName: config.appName,
172+
onLogEvent(event, values) {
173+
dispatch(logEvent(event, values))
174+
},
175+
onNotificationPermit(info) {
176+
dispatch(updateNotificationSettings(info.notificationOptIns)).catch(
177+
(error: unknown) => {
178+
trackError(error, 'LoginScene:onLogin:setDeviceSettings')
179+
console.error(error)
180+
}
181+
)
219182
}
220-
)
221-
222-
await dispatch(refreshAccountReferral())
223-
224-
refreshTouchId(account).catch(() => {
225-
// We have always failed silently here
226183
})
227-
228-
if (
229-
await showNotificationPermissionReminder({
230-
appName: config.appName,
231-
onLogEvent(event, values) {
232-
dispatch(logEvent(event, values))
233-
},
234-
onNotificationPermit(info) {
235-
dispatch(updateNotificationSettings(info.notificationOptIns)).catch(
236-
(error: unknown) => {
237-
trackError(error, 'LoginScene:onLogin:setDeviceSettings')
238-
console.error(error)
239-
}
240-
)
241-
}
242-
})
243-
) {
244-
hideSurvey = true
245-
}
246-
} catch (error: unknown) {
247-
showError(error)
184+
) {
185+
hideSurvey = true
248186
}
249187

250188
// Post login stuff: Survey modal (existing accounts only)

src/actions/SettingsActions.tsx

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -224,40 +224,6 @@ export function setDenominationKeyRequest(
224224
}
225225
}
226226

227-
export function togglePinLoginEnabled(
228-
pinLoginEnabled: boolean
229-
): ThunkAction<Promise<unknown>> {
230-
return async (dispatch, getState) => {
231-
const state = getState()
232-
const { context, account } = state.core
233-
234-
dispatch({
235-
type: 'UI/SETTINGS/TOGGLE_PIN_LOGIN_ENABLED',
236-
data: { pinLoginEnabled }
237-
})
238-
return await account
239-
.changePin({ enableLogin: pinLoginEnabled })
240-
.catch(async (error: unknown) => {
241-
showError(error)
242-
243-
let pinLoginEnabled = false
244-
for (const userInfo of context.localUsers) {
245-
if (
246-
userInfo.loginId === account.rootLoginId &&
247-
userInfo.pinLoginEnabled
248-
) {
249-
pinLoginEnabled = true
250-
}
251-
}
252-
253-
dispatch({
254-
type: 'UI/SETTINGS/TOGGLE_PIN_LOGIN_ENABLED',
255-
data: { pinLoginEnabled }
256-
})
257-
})
258-
}
259-
}
260-
261227
export async function showReEnableOtpModal(
262228
account: EdgeAccount
263229
): Promise<void> {

src/components/scenes/SettingsScene.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import { logoutRequest } from '../../actions/LoginActions'
2828
import {
2929
setAutoLogoutTimeInSecondsRequest,
3030
showReEnableOtpModal,
31-
showUnlockSettingsModal,
32-
togglePinLoginEnabled
31+
showUnlockSettingsModal
3332
} from '../../actions/SettingsActions'
3433
import { ENV } from '../../env'
3534
import { useAsyncEffect } from '../../hooks/useAsyncEffect'
@@ -75,9 +74,6 @@ export const SettingsScene: React.FC<Props> = props => {
7574
state => state.ui.settings.developerModeOn
7675
)
7776
const isLocked = useSelector(state => state.ui.settings.changesLocked)
78-
const pinLoginEnabled = useSelector(
79-
state => state.ui.settings.pinLoginEnabled
80-
)
8177
const spamFilterOn = useSelector(state => state.ui.settings.spamFilterOn)
8278

8379
const account = useSelector(state => state.core.account)
@@ -121,6 +117,15 @@ export const SettingsScene: React.FC<Props> = props => {
121117
const context = useSelector(state => state.core.context)
122118
const logSettings = useWatch(context, 'logSettings')
123119

120+
// Load pin login state locally (not from Redux) and make it mutable
121+
const [pinLoginEnabled, setPinLoginEnabled] = React.useState<boolean>(
122+
() =>
123+
context?.localUsers?.some(
124+
userInfo =>
125+
userInfo.loginId === account.rootLoginId && userInfo.pinLoginEnabled
126+
) ?? false
127+
)
128+
124129
const [localContactPermissionOn, setLocalContactsPermissionOn] =
125130
React.useState(false)
126131
const [isDarkTheme, setIsDarkTheme] = React.useState(
@@ -222,7 +227,15 @@ export const SettingsScene: React.FC<Props> = props => {
222227
})
223228

224229
const handleTogglePinLoginEnabled = useHandler(async () => {
225-
await dispatch(togglePinLoginEnabled(!pinLoginEnabled))
230+
const newValue = !pinLoginEnabled
231+
setPinLoginEnabled(newValue)
232+
try {
233+
await account.changePin({ enableLogin: newValue })
234+
} catch (error: unknown) {
235+
// Revert on error
236+
setPinLoginEnabled(!newValue)
237+
showError(error)
238+
}
226239
})
227240

228241
const handleToggleDarkTheme = useHandler(async () => {
@@ -554,22 +567,22 @@ export const SettingsScene: React.FC<Props> = props => {
554567
onPress={handleDefaultFiat}
555568
/>
556569

557-
{isLightAccount ? null : (
570+
{!isLightAccount ? (
558571
<SettingsSwitchRow
559572
key="pinRelogin"
560573
label={lstrings.settings_title_pin_login}
561574
value={pinLoginEnabled}
562575
onPress={handleTogglePinLoginEnabled}
563576
/>
564-
)}
565-
{supportsTouchId && !isLightAccount && touchIdEnabled != null && (
577+
) : null}
578+
{supportsTouchId && !isLightAccount && touchIdEnabled != null ? (
566579
<SettingsSwitchRow
567580
key="useTouchID"
568581
label={touchIdText}
569582
value={touchIdEnabled}
570583
onPress={handleUpdateTouchId}
571584
/>
572-
)}
585+
) : null}
573586

574587
<SettingsSwitchRow
575588
label={lstrings.settings_button_contacts_access_permission}

0 commit comments

Comments
 (0)