Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: onboarding mechanics and navigation #1464

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/legacy/core/App/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export enum EventTypes {
ERROR_REMOVED = 'ErrorRemoved',
BIOMETRY_UPDATE = 'BiometryUpdate',
BIOMETRY_ERROR = 'BiometryError',
DID_COMPLETE_ONBOARDING = 'DidCompleteOnboarding',
}

export const second = 1000
Expand Down
7 changes: 6 additions & 1 deletion packages/legacy/core/App/container-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { IHistoryManager } from './modules/history'
import Onboarding from './screens/Onboarding'
import { AttestationMonitor } from './types/attestation'
import { GenericFn } from './types/fn'
import { AuthenticateStackParams, ScreenLayoutConfig, ScreenOptionsType } from './types/navigators'
import { AuthenticateStackParams, ScreenLayoutConfig, ScreenOptionsType, OnboardingTask } from './types/navigators'
import { CustomNotification } from './types/notification'
import { Config, HistoryEventsLoggerConfig } from './types/config'
import { State } from './types/state'
import { NotificationReturnType, NotificationsInputProps } from './hooks/notifications'
import { NotificationListItemProps } from './components/listItems/NotificationListItem'
import { PINCreateHeaderProps } from './components/misc/PINCreateHeader'
Expand All @@ -32,6 +33,8 @@ export type FN_ONBOARDING_DONE = (

type LoadStateFn = (dispatch: React.Dispatch<ReducerAction<unknown>>) => Promise<void>

type GenerateOnboardingWorkflowStepsFn = (state: State, config: Config, termsVersion: number) => Array<OnboardingTask>

type ProofRequestTemplateFn = (useDevTemplates: boolean) => Array<ProofRequestTemplate>

export const PROOF_TOKENS = {
Expand Down Expand Up @@ -123,6 +126,7 @@ export const UTILITY_TOKENS = {
export const CONFIG_TOKENS = {
CONFIG: 'config',
INLINE_ERRORS: 'errors.inline',
ONBOARDING: 'utility.onboarding',
} as const

export const TOKENS = {
Expand Down Expand Up @@ -184,6 +188,7 @@ export type TokenMapping = {
[TOKENS.HISTORY_ENABLED]: boolean
[TOKENS.HISTORY_EVENTS_LOGGER]: HistoryEventsLoggerConfig
[TOKENS.CONFIG]: Config
[TOKENS.ONBOARDING]: GenerateOnboardingWorkflowStepsFn
[TOKENS.COMPONENT_CRED_LIST_HEADER_RIGHT]: React.FC
[TOKENS.COMPONENT_CRED_LIST_OPTIONS]: React.FC
[TOKENS.COMPONENT_CRED_LIST_FOOTER]: React.FC<CredentialListFooterProps>
Expand Down
23 changes: 10 additions & 13 deletions packages/legacy/core/App/container-impl.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Agent } from '@credo-ts/core'
import { getProofRequestTemplates } from '@hyperledger/aries-bifold-verifier'
import { DefaultOCABundleResolver } from '@hyperledger/aries-oca/build/legacy'
import { StackNavigationProp } from '@react-navigation/stack'
import { createContext, useContext } from 'react'
import { DependencyContainer } from 'tsyringe'

Expand All @@ -24,7 +23,6 @@ import Splash from './screens/Splash'
import ScreenTerms, { TermsVersion } from './screens/Terms'
import { loadLoginAttempt } from './services/keychain'
import { BifoldLogger } from './services/logger'
import { AuthenticateStackParams, Screens } from './types/navigators'
import {
Migration as MigrationState,
Preferences as PreferencesState,
Expand Down Expand Up @@ -52,6 +50,7 @@ import ContactCredentialListItem from './components/listItems/ContactCredentialL
import { InlineErrorPosition } from './types/error'
import { DefaultScreenLayoutOptions } from './navigators/defaultLayoutOptions'
import ConnectionAlert from './components/misc/ConnectionAlert'
import { generateOnboardingWorkflowSteps } from './onboarding'

export const defaultConfig: Config = {
PINSecurity: {
Expand Down Expand Up @@ -155,18 +154,13 @@ export class MainContainer implements Container {
hasErrorIcon: true,
position: InlineErrorPosition.Above,
})
this._container.registerInstance(
TOKENS.FN_ONBOARDING_DONE,
(dispatch: React.Dispatch<ReducerAction<unknown>>, navigation: StackNavigationProp<AuthenticateStackParams>) => {
return () => {
dispatch({
type: DispatchAction.DID_COMPLETE_TUTORIAL,
})

navigation.navigate(Screens.Terms)
}
this._container.registerInstance(TOKENS.FN_ONBOARDING_DONE, (dispatch: React.Dispatch<ReducerAction<unknown>>) => {
return () => {
dispatch({
type: DispatchAction.DID_COMPLETE_TUTORIAL,
})
}
)
})
this._container.registerInstance(TOKENS.FN_LOAD_HISTORY, (agent: Agent<any>): IHistoryManager => {
return new HistoryManager(agent)
})
Expand Down Expand Up @@ -209,12 +203,15 @@ export class MainContainer implements Container {
dispatch({ type: DispatchAction.STATE_DISPATCH, payload: [state] })
})

this._container.registerInstance(TOKENS.ONBOARDING, generateOnboardingWorkflowSteps)

return this
}

public resolve<K extends keyof TokenMapping>(token: K): TokenMapping[K] {
return this._container.resolve(token)
}

public resolveAll<K extends keyof TokenMapping, T extends K[]>(
tokens: [...T]
): { [I in keyof T]: TokenMapping[T[I]] } {
Expand Down
46 changes: 46 additions & 0 deletions packages/legacy/core/App/hooks/useOnboardingState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState, useEffect, useMemo } from 'react'
import { useNavigationState } from '@react-navigation/native'
import { State } from '../types/state'
import { OnboardingTask } from '../types/navigators'
import { Config } from '../types/config'

/**
* Hook to manage onboarding workflow state
* Tracks the onboarding tasks, their completion status.
*/
export const useOnboardingState = (
store: State,
config: Config,
termsVersion: number,
generateOnboardingWorkflowSteps: (store: State, config: Config, termsVersion: number) => OnboardingTask[]
): {
onboardingState: OnboardingTask[]
setOnboardingState: React.Dispatch<React.SetStateAction<OnboardingTask[]>>
activeScreen: string | undefined
currentRoute: any
isComplete: boolean
} => {
const [onboardingState, setOnboardingState] = useState<OnboardingTask[]>([])
const currentRoute = useNavigationState((state) => state?.routes[state?.index])

const activeScreen = useMemo(() => {
return onboardingState.find((task) => !task.completed)?.name
}, [onboardingState])

useEffect(() => {
if (!store.stateLoaded) {
return
}

const onboardingTasks = generateOnboardingWorkflowSteps(store, config, termsVersion)
setOnboardingState(onboardingTasks)
}, [store, config, termsVersion, generateOnboardingWorkflowSteps])

return {
onboardingState,
setOnboardingState,
activeScreen,
currentRoute,
isComplete: !activeScreen,
}
}
4 changes: 2 additions & 2 deletions packages/legacy/core/App/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ export type { CredentialListFooterProps }
export * from './container-api'
export { MainContainer } from './container-impl'

export type { ScreenLayoutConfig } from './types/navigators'
export type { HistoryEventsLoggerConfig } from './types/config'
export type { ScreenLayoutConfig, OnboardingTask } from './types/navigators'
export type { Config, HistoryEventsLoggerConfig } from './types/config'

export { BaseTourID } from './types/tour'

Expand Down
142 changes: 142 additions & 0 deletions packages/legacy/core/App/navigators/OnboardingScreens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { TFunction } from 'i18next'
import { TransitionPresets, StackNavigationOptions, StackScreenProps } from '@react-navigation/stack'
import { ParamListBase, RouteConfig, StackNavigationState } from '@react-navigation/native'
import { Screens, ScreenOptionsType } from '../types/navigators'
import type { StackNavigationEventMap } from '@react-navigation/stack/lib/typescript/src/types'

type ScreenOptions = RouteConfig<
ParamListBase,
Screens,
StackNavigationState<ParamListBase>,
StackNavigationOptions,
StackNavigationEventMap
>

interface ScreenComponents {
Splash: React.FC<StackScreenProps<ParamListBase>>
Preface: React.FC<StackScreenProps<ParamListBase>>
Terms: React.FC<StackScreenProps<ParamListBase>>
NameWallet: React.FC<StackScreenProps<ParamListBase>>
useBiometry: React.FC<StackScreenProps<ParamListBase>>
PushNotification: React.FC<StackScreenProps<ParamListBase, Screens.UsePushNotifications>>
Developer: React.FC<StackScreenProps<ParamListBase>>
AttemptLockout: React.FC<StackScreenProps<ParamListBase>>
OnboardingScreen: React.FC
CreatePINScreen: React.FC
EnterPINScreen: React.FC
}

export const getOnboardingScreens = (
t: TFunction,
ScreenOptionsDictionary: ScreenOptionsType,
components: ScreenComponents
): ScreenOptions[] => [
{
name: Screens.Splash,
component: components.Splash,
options: {
...TransitionPresets.ModalFadeTransition,
title: t('Screens.Splash'),
...ScreenOptionsDictionary[Screens.Splash],
},
},
{
name: Screens.Preface,
component: components.Preface,
options: {
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.Preface'),
...ScreenOptionsDictionary[Screens.Preface],
},
},
{
name: Screens.Onboarding,
children: components.OnboardingScreen,
options: () => ({
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.Onboarding'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.Onboarding],
}),
},
{
name: Screens.Terms,
options: () => ({
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.Terms'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.Terms],
}),
component: components.Terms,
},
{
name: Screens.CreatePIN,
children: components.CreatePINScreen,
initialParams: {},
options: () => ({
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.CreatePIN'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.CreatePIN],
}),
},
{
name: Screens.NameWallet,
options: () => ({
title: t('Screens.NameWallet'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.NameWallet],
}),
component: components.NameWallet,
},
{
name: Screens.UseBiometry,
options: () => ({
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.Biometry'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.UseBiometry],
}),
component: components.useBiometry,
},
{
name: Screens.UsePushNotifications,
component: components.PushNotification,
options: () => ({
...TransitionPresets.SlideFromRightIOS,
title: t('Screens.UsePushNotifications'),
headerLeft: () => false,
...ScreenOptionsDictionary[Screens.UsePushNotifications],
}),
},
{
name: Screens.Developer,
component: components.Developer,
options: () => ({
title: t('Screens.Developer'),
headerBackAccessibilityLabel: t('Global.Back'),
...ScreenOptionsDictionary[Screens.Developer],
}),
},
{
name: Screens.EnterPIN,
children: components.EnterPINScreen,
options: () => ({
title: t('Screens.EnterPIN'),
headerShown: true,
headerLeft: () => false,
rightLeft: () => false,
...ScreenOptionsDictionary[Screens.EnterPIN],
}),
},
{
name: Screens.AttemptLockout,
component: components.AttemptLockout,
options: () => ({
headerShown: true,
headerLeft: () => null,
title: t('Screens.AttemptLockout'),
...ScreenOptionsDictionary[Screens.AttemptLockout],
}),
},
]
Loading