Skip to content

Commit

Permalink
fix(navigation): Simplify initial route logic (#6223)
Browse files Browse the repository at this point in the history
### Description

This PR overhauls how we handle the initial route logic for the app. The
main difficulty in determining the initial route is onboarding.
Previously, we collected various bits and pieces of random information
and used them in aggregate, along with some custom logic, as a proxy for
what screen the user left off at during onboarding. With this PR, we
write state directly to redux during onboarding about the _first screen_
present in the user's current onboarding step. On startup, we set the
initial route to this screen, less a couple of edge cases. (This means
that a user will always be dropped back at the _beginning_ of their
current onboarding step.)

### Test plan

Unit and manual tested. See video below.


https://github.com/user-attachments/assets/a2312a02-a590-483e-953d-ebcebc25c509



### Related issues

- Fixes #[issue number here]

### Backwards compatibility

<!-- Brief explanation of why these changes are/are not backwards
compatible. -->

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [ ] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
jophish authored Nov 22, 2024
1 parent 9ddea5a commit 5853f2f
Show file tree
Hide file tree
Showing 23 changed files with 571 additions and 282 deletions.
1 change: 1 addition & 0 deletions __mocks__/@react-navigation/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ module.exports = {
navigate: jest.fn(),
reset: jest.fn(),
setParams: jest.fn(),
canGoBack: jest.fn(),
},
}
21 changes: 21 additions & 0 deletions src/account/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Actions, ActionTypes } from 'src/account/actions'
import { Actions as AppActions, ActionTypes as AppActionTypes } from 'src/app/actions'
import {
Actions as OnboardingActions,
ActionTypes as OnboardingActionTypes,
} from 'src/onboarding/actions'
import { DEV_SETTINGS_ACTIVE_INITIALLY } from 'src/config'
import { deleteKeylessBackupCompleted, keylessBackupCompleted } from 'src/keylessBackup/slice'
import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper'
import Logger from 'src/utils/Logger'
import { isE164NumberStrict } from 'src/utils/phoneNumbers'
import { Actions as Web3Actions, ActionTypes as Web3ActionTypes } from 'src/web3/actions'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'

interface State {
name: string | null
Expand All @@ -30,6 +36,8 @@ interface State {
celoEducationCompleted: boolean
recoveryPhraseInOnboardingStatus: RecoveryPhraseInOnboardingStatus
cloudBackupCompleted: boolean
onboardingCompleted: boolean
lastOnboardingStepScreen: keyof StackParamList
}

export enum PincodeType {
Expand Down Expand Up @@ -96,6 +104,8 @@ const initialState: State = {
celoEducationCompleted: false,
recoveryPhraseInOnboardingStatus: RecoveryPhraseInOnboardingStatus.NotStarted,
cloudBackupCompleted: false,
onboardingCompleted: false,
lastOnboardingStepScreen: Screens.Welcome,
}

export const reducer = (
Expand All @@ -105,6 +115,7 @@ export const reducer = (
| RehydrateAction
| Web3ActionTypes
| AppActionTypes
| OnboardingActionTypes
| typeof keylessBackupCompleted
| typeof deleteKeylessBackupCompleted
): State => {
Expand All @@ -118,6 +129,16 @@ export const reducer = (
dismissedGetVerified: false,
}
}
case OnboardingActions.UPDATE_LAST_ONBOARDING_SCREEN:
return {
...state,
lastOnboardingStepScreen: action.screen,
}
case OnboardingActions.ONBOARDING_COMPLETED:
return {
...state,
onboardingCompleted: true,
}
case Actions.CHOOSE_CREATE_ACCOUNT:
return {
...state,
Expand Down
12 changes: 0 additions & 12 deletions src/identity/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Recipient } from 'src/recipients/recipient'
import { type E164Number } from 'src/utils/io'

export enum Actions {
SET_SEEN_VERIFICATION_NUX = 'IDENTITY/SET_SEEN_VERIFICATION_NUX',
UPDATE_E164_PHONE_NUMBER_ADDRESSES = 'IDENTITY/UPDATE_E164_PHONE_NUMBER_ADDRESSES',
UPDATE_KNOWN_ADDRESSES = 'IDENTITY/UPDATE_KNOWN_ADDRESSES',
FETCH_ADDRESSES_AND_VALIDATION_STATUS = 'IDENTITY/FETCH_ADDRESSES_AND_VALIDATION_STATUS',
Expand All @@ -28,11 +27,6 @@ export enum Actions {
STORED_PASSWORD_REFRESHED = 'IDENTITY/STORED_PASSWORD_REFRESHED',
}

export interface SetHasSeenVerificationNux {
type: Actions.SET_SEEN_VERIFICATION_NUX
status: boolean
}

export interface UpdateE164PhoneNumberAddressesAction {
type: Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES
e164NumberToAddress: E164NumberToAddressType
Expand Down Expand Up @@ -118,7 +112,6 @@ interface StoredPasswordRefreshedAction {
}

export type ActionTypes =
| SetHasSeenVerificationNux
| UpdateE164PhoneNumberAddressesAction
| UpdateKnownAddressesAction
| ImportContactsAction
Expand All @@ -135,11 +128,6 @@ export type ActionTypes =
| ContactsSavedAction
| StoredPasswordRefreshedAction

export const setHasSeenVerificationNux = (status: boolean): SetHasSeenVerificationNux => ({
type: Actions.SET_SEEN_VERIFICATION_NUX,
status,
})

export const fetchAddressesAndValidate = (
e164Number: string,
requesterAddress?: string
Expand Down
7 changes: 0 additions & 7 deletions src/identity/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export interface AddressToVerificationStatus {
}

interface State {
hasSeenVerificationNux: boolean
addressToE164Number: AddressToE164NumberType
// Note: Do not access values in this directly, use the `getAddressFromPhoneNumber` helper in contactMapping
e164NumberToAddress: E164NumberToAddressType
Expand All @@ -73,7 +72,6 @@ interface State {
}

const initialState: State = {
hasSeenVerificationNux: false,
addressToE164Number: {},
e164NumberToAddress: {},
addressToDisplayName: {},
Expand Down Expand Up @@ -108,11 +106,6 @@ export const reducer = (
},
}
}
case Actions.SET_SEEN_VERIFICATION_NUX:
return {
...state,
hasSeenVerificationNux: action.status,
}
case Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES:
return {
...state,
Expand Down
3 changes: 0 additions & 3 deletions src/keylessBackup/LinkPhoneNumber.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ describe('LinkPhoneNumber', () => {
firstScreenInCurrentStep: Screens.VerificationStartScreen,
onboardingProps: mockOnboardingProps,
})
expect(store.getActions()).toEqual([
{ type: 'IDENTITY/SET_SEEN_VERIFICATION_NUX', status: true },
])
expect(AppAnalytics.track).toHaveBeenCalledWith(OnboardingEvents.link_phone_number_later)
})
})
5 changes: 0 additions & 5 deletions src/keylessBackup/LinkPhoneNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ import { OnboardingEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
import BackButton from 'src/components/BackButton'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
import { setHasSeenVerificationNux } from 'src/identity/actions'

import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
import { goToNextOnboardingScreen, onboardingPropsSelector } from 'src/onboarding/steps'
import { useDispatch } from 'src/redux/hooks'
import colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
Expand All @@ -23,7 +20,6 @@ type Props = NativeStackScreenProps<StackParamList, Screens.LinkPhoneNumber>

export default function LinkPhoneNumber({ navigation }: Props) {
const { t } = useTranslation()
const dispatch = useDispatch()
const onboardingProps = useSelector(onboardingPropsSelector)
useLayoutEffect(() => {
navigation.setOptions({
Expand All @@ -40,7 +36,6 @@ export default function LinkPhoneNumber({ navigation }: Props) {
}
const laterButtonOnPress = async () => {
AppAnalytics.track(OnboardingEvents.link_phone_number_later)
dispatch(setHasSeenVerificationNux(true))
goToNextOnboardingScreen({
firstScreenInCurrentStep: Screens.VerificationStartScreen,
onboardingProps,
Expand Down
17 changes: 13 additions & 4 deletions src/keylessBackup/SignInWithEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function SignInWithEmailBottomSheet({
type OAuthProvider = 'google-oauth2' | 'apple'
type Props = NativeStackScreenProps<StackParamList, Screens.SignInWithEmail>

function SignInWithEmail({ route }: Props) {
function SignInWithEmail({ route, navigation }: Props) {
const { t } = useTranslation()
const dispatch = useDispatch()
const showApple = getFeatureGate(StatsigFeatureGates.SHOW_APPLE_IN_CAB)
Expand All @@ -115,6 +115,14 @@ function SignInWithEmail({ route }: Props) {
}
const address = useSelector(walletAddressSelector)

// We check whether or not there is anything to go back to
// in case that this screen is the app's initial route, which can occur
// when restarting the app during onboarding.
// N.B. that a change in this value will /not/ trigger a re-render, but
// this should be fine since if this is true on the initial render, it should
// never change.
const canGoBack = navigation.canGoBack()

const isSetup = keylessBackupFlow === KeylessBackupFlow.Setup
const isSetupInOnboarding =
keylessBackupFlow === KeylessBackupFlow.Setup && origin === KeylessBackupOrigin.Onboarding
Expand Down Expand Up @@ -196,16 +204,17 @@ function SignInWithEmail({ route }: Props) {
origin={origin}
eventName={KeylessBackupEvents.cab_sign_in_with_email_screen_cancel}
/>
) : (
// This includes Onboarding and Restore
) : // This includes Onboarding and Restore
canGoBack ? (
<BackButton
testID="SignInWithEmail/BackButton"
eventName={KeylessBackupEvents.cab_sign_in_with_email_screen_cancel}
eventProperties={{
keylessBackupFlow,
origin,
}}
/>
)
) : undefined
}
title={
isSetupInOnboarding ? (
Expand Down
7 changes: 5 additions & 2 deletions src/navigator/NavigationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,13 @@ export const pushToStack: SafeNavigate = (...args) => {
})
}

export function navigate<RouteName extends keyof StackParamList>(
...args: undefined extends StackParamList[RouteName]
export type NavigateParams<RouteName extends keyof StackParamList> =
undefined extends StackParamList[RouteName]
? [RouteName] | [RouteName, StackParamList[RouteName]]
: [RouteName, StackParamList[RouteName]]

export function navigate<RouteName extends keyof StackParamList>(
...args: NavigateParams<RouteName>
) {
const [routeName, params] = args
ensureNavigator()
Expand Down
32 changes: 11 additions & 21 deletions src/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import VerificationStartScreen from 'src/verify/VerificationStartScreen'
import WalletConnectSessionsScreen from 'src/walletConnect/screens/Sessions'
import WalletConnectRequest from 'src/walletConnect/screens/WalletConnectRequest'
import WebViewScreen from 'src/webview/WebViewScreen'
import { KeylessBackupFlow, KeylessBackupOrigin } from 'src/keylessBackup/types'

const TAG = 'Navigator'

Expand Down Expand Up @@ -473,6 +474,10 @@ const settingsScreens = (Navigator: typeof Stack) => (
name={Screens.SignInWithEmail}
options={noHeader}
component={SignInWithEmail}
initialParams={{
keylessBackupFlow: KeylessBackupFlow.Setup,
origin: KeylessBackupOrigin.Onboarding,
}}
/>
<Navigator.Screen
name={Screens.KeylessBackupPhoneInput}
Expand Down Expand Up @@ -604,15 +609,11 @@ const pointsScreens = (Navigator: typeof Stack) => (
)
const mapStateToProps = (state: RootState) => {
return {
choseToRestoreAccount: state.account.choseToRestoreAccount,
language: currentLanguageSelector(state),
acceptedTerms: state.account.acceptedTerms,
pincodeType: state.account.pincodeType,
account: state.web3.account,
hasSeenVerificationNux: state.identity.hasSeenVerificationNux,
askedContactsPermission: state.identity.askedContactsPermission,
recoveryPhraseInOnboardingStatus: state.account.recoveryPhraseInOnboardingStatus,
multichainBetaStatus: state.app.multichainBetaStatus,
lastOnboardingStepScreen: state.account.lastOnboardingStepScreen as keyof StackParamList,
onboardingCompleted: state.account.onboardingCompleted,
}
}

Expand All @@ -622,26 +623,15 @@ function MainStackScreen() {
const [initialRouteName, setInitialRoute] = React.useState<InitialRouteName>(undefined)

React.useEffect(() => {
const {
choseToRestoreAccount,
language,
acceptedTerms,
pincodeType,
account,
hasSeenVerificationNux,
recoveryPhraseInOnboardingStatus,
multichainBetaStatus,
} = mapStateToProps(store.getState())
const { language, acceptedTerms, pincodeType, onboardingCompleted, lastOnboardingStepScreen } =
mapStateToProps(store.getState())

const initialRoute: InitialRouteName = getInitialRoute({
choseToRestoreAccount,
language,
acceptedTerms,
pincodeType,
account,
hasSeenVerificationNux,
recoveryPhraseInOnboardingStatus,
multichainBetaStatus,
onboardingCompleted,
lastOnboardingStepScreen,
})

setInitialRoute(initialRoute)
Expand Down
Loading

0 comments on commit 5853f2f

Please sign in to comment.