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

fix: person credential workflow pt. 2 #973

Merged
merged 15 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
58 changes: 26 additions & 32 deletions app/src/components/AddCredentialSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,26 @@ import { BCWalletEventTypes } from '../events/eventTypes'
import { showBCIDSelector, startFlow } from '../helpers/BCIDHelper'
import { BCState } from '../store'

import CredentialOfferTrigger from './CredentialOfferTrigger'
import LoadingIcon from './LoadingIcon'

const AddCredentialSlider: React.FC = () => {
const { ColorPallet, TextTheme } = useTheme()
const { agent } = useAgent()
const { t } = useTranslation()
const navigation = useNavigation()

const [store] = useStore<BCState>()
const [showGetFoundationCredential, setShowGetFoundationCredential] = useState<boolean>(false)
const [addCredentialPressed, setAddCredentialPressed] = useState<boolean>(false)
const [workflowInFlight, setWorkflowInFlight] = useState<boolean>(false)
const [showGetFoundationCredential, setShowGetFoundationCredential] = useState<boolean>(false)
const [workflowInProgress, setWorkflowInProgress] = useState<boolean>(false)
const [workflowConnectionId, setWorkflowConnectionId] = useState<string | undefined>()

const offers = useCredentialByState(CredentialState.OfferReceived)

const credentials = [
...useCredentialByState(CredentialState.CredentialReceived),
...useCredentialByState(CredentialState.Done),
]

const navigation = useNavigation()
const [canUseLSBCredential] = useState<boolean>(true)

const styles = StyleSheet.create({
centeredView: {
marginTop: 'auto',
Expand Down Expand Up @@ -79,39 +77,34 @@ const AddCredentialSlider: React.FC = () => {
DeviceEventEmitter.emit(BCWalletEventTypes.ADD_CREDENTIAL_PRESSED, false)
}, [])

const goToHomeScreen = (credentialId?: string) => {
navigation.getParent()?.navigate(Stacks.NotificationStack, {
screen: Screens.CredentialOffer,
params: { credentialId },
})
}

const goToScanScreen = useCallback(() => {
deactivateSlider()
navigation.getParent()?.navigate(Stacks.ConnectStack, { screen: Screens.Scan })
}, [])

const onBCIDPress = useCallback(() => {
setWorkflowInFlight(true)
startFlow(agent!, store, setWorkflowInFlight, t, (connectionId) => setWorkflowConnectionId(connectionId))
setWorkflowInProgress(true)
startFlow(agent!, store, setWorkflowInProgress, t, (connectionId) => setWorkflowConnectionId(connectionId))
}, [store])

useEffect(() => {
for (const credential of offers) {
if (credential.state == CredentialState.OfferReceived && credential.connectionId === workflowConnectionId) {
goToHomeScreen(credential.id)
deactivateSlider()
}
}
}, [offers, workflowConnectionId])

useEffect(() => {
const credentialDefinitionIDs = credentials.map(
(c) => c.metadata.data[CredentialMetadataKeys.IndyCredential].credentialDefinitionId as string
)

setShowGetFoundationCredential(showBCIDSelector(credentialDefinitionIDs, canUseLSBCredential))
}, [credentials, canUseLSBCredential])
setShowGetFoundationCredential(showBCIDSelector(credentialDefinitionIDs, true))
}, [credentials])

useEffect(() => {
const handle = DeviceEventEmitter.addListener(BCWalletEventTypes.ADD_CREDENTIAL_PRESSED, (value?: boolean) => {
const newVal = value === undefined ? !addCredentialPressed : value
setAddCredentialPressed(newVal)
})

return () => {
handle.remove()
}
}, [])

useEffect(() => {
const handle = DeviceEventEmitter.addListener(BCWalletEventTypes.ADD_CREDENTIAL_PRESSED, (value?: boolean) => {
amanji marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -125,7 +118,7 @@ const AddCredentialSlider: React.FC = () => {
}, [])

return (
<View>
<>
<Modal animationType="slide" transparent={true} visible={addCredentialPressed} onRequestClose={deactivateSlider}>
<TouchableOpacity style={styles.outsideListener} onPress={deactivateSlider} />
<View style={styles.centeredView}>
Expand All @@ -135,9 +128,9 @@ const AddCredentialSlider: React.FC = () => {
</TouchableOpacity>
<Text style={styles.drawerTitleText}>Choose</Text>
{showGetFoundationCredential && (
<TouchableOpacity style={styles.drawerRow} disabled={workflowInFlight} onPress={onBCIDPress}>
{workflowInFlight ? (
<LoadingIcon size={30} color={styles.drawerRowItem.color} active={workflowInFlight} />
<TouchableOpacity style={styles.drawerRow} disabled={workflowInProgress} onPress={onBCIDPress}>
{workflowInProgress ? (
<LoadingIcon size={30} color={styles.drawerRowItem.color} active={workflowInProgress} />
) : (
<Icon name="credit-card" size={30} style={styles.drawerRowItem}></Icon>
)}
Expand All @@ -152,7 +145,8 @@ const AddCredentialSlider: React.FC = () => {
</View>
</View>
</Modal>
</View>
<CredentialOfferTrigger workflowConnectionId={workflowConnectionId} />
</>
)
}

Expand Down
34 changes: 34 additions & 0 deletions app/src/components/CredentialOfferTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CredentialState } from '@aries-framework/core'
import { useCredentialByState } from '@aries-framework/react-hooks'
import { useNavigation } from '@react-navigation/core'
import { Screens, Stacks } from 'aries-bifold'
import React, { useEffect } from 'react'

interface CredentialOfferTriggerProps {
workflowConnectionId?: string
}

const CredentialOfferTrigger: React.FC<CredentialOfferTriggerProps> = ({ workflowConnectionId }) => {
const navigation = useNavigation()

const offers = useCredentialByState(CredentialState.OfferReceived)

const goToCredentialOffer = (credentialId?: string) => {
navigation.getParent()?.navigate(Stacks.NotificationStack, {
screen: Screens.CredentialOffer,
params: { credentialId },
})
}

useEffect(() => {
for (const credential of offers) {
if (credential.state == CredentialState.OfferReceived && credential.connectionId === workflowConnectionId) {
goToCredentialOffer(credential.id)
}
}
}, [offers, workflowConnectionId])

return null
}

export default CredentialOfferTrigger
amanji marked this conversation as resolved.
Show resolved Hide resolved
26 changes: 7 additions & 19 deletions app/src/helpers/BCIDHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const cleanupAfterServiceCardAuthentication = (status: AuthenticationResu

export const authenticateWithServiceCard = async (
store: BCState,
setWorkflow: React.Dispatch<React.SetStateAction<boolean>>,
setWorkflowInProgress: React.Dispatch<React.SetStateAction<boolean>>,
agentDetails: WellKnownAgentDetails,
t: TFunction<'translation', undefined>,
callback?: (connectionId?: string) => void
Expand All @@ -195,15 +195,14 @@ export const authenticateWithServiceCard = async (
result.type === AuthenticationResultType.Cancel &&
typeof (result as unknown as RedirectResult).url === 'undefined'
) {
setWorkflow(false)
setWorkflowInProgress(false)
return
}

if (
result.type === AuthenticationResultType.Dismiss &&
typeof (result as unknown as RedirectResult).url === 'undefined'
) {
setWorkflow(false)
callback && callback(agentDetails.connectionId)
}

Expand All @@ -214,7 +213,6 @@ export const authenticateWithServiceCard = async (
(result as unknown as RedirectResult).url.includes(did) &&
(result as unknown as RedirectResult).url.includes('success')
) {
setWorkflow(false)
callback && callback(agentDetails.connectionId)
}

Expand All @@ -226,15 +224,8 @@ export const authenticateWithServiceCard = async (
(result as unknown as RedirectResult).url.includes(did) &&
(result as unknown as RedirectResult).url.includes('cancel')
) {
setWorkflow(false)

// FIXME: This does nothing unlit the catch below is updated.
// throw new BifoldError(
// t('Error.Title2025'),
// t('Error.Message2025'),
// t('Error.NoMessage'),
// ErrorCodes.ServiceCardError
// )
setWorkflowInProgress(false)
return
}
} else {
await Linking.openURL(url)
Expand All @@ -243,19 +234,17 @@ export const authenticateWithServiceCard = async (
cleanupAfterServiceCardAuthentication(AuthenticationResultType.Success)
} catch (error: unknown) {
const code = (error as BifoldError).code

cleanupAfterServiceCardAuthentication(
code === ErrorCodes.CanceledByUser ? AuthenticationResultType.Cancel : AuthenticationResultType.Fail
)

DeviceEventEmitter.emit(BifoldEventTypes.ERROR_ADDED, error)
}
}

export const startFlow = async (
agent: Agent,
store: BCState,
setWorkflowInFlight: React.Dispatch<React.SetStateAction<boolean>>,
setWorkflowInProgress: React.Dispatch<React.SetStateAction<boolean>>,
t: TFunction<'translation', undefined>,
callback?: (connectionId?: string) => void
) => {
Expand All @@ -264,12 +253,11 @@ export const startFlow = async (

if (agentDetails.legacyConnectionDid !== undefined) {
setTimeout(async () => {
await authenticateWithServiceCard(store, setWorkflowInFlight, agentDetails, t, callback)
await authenticateWithServiceCard(store, setWorkflowInProgress, agentDetails, t, callback)
}, connectionDelayInMs)
}
} catch (error: unknown) {
setWorkflowInFlight(false)

setWorkflowInProgress(false)
DeviceEventEmitter.emit(BifoldEventTypes.ERROR_ADDED, error)
}
}
78 changes: 44 additions & 34 deletions app/src/screens/PersonCredential.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View, TouchableOpacity, Linking, FlatList } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'

import CredentialOfferTrigger from '../components/CredentialOfferTrigger'
import LoadingIcon from '../components/LoadingIcon'
import { startFlow } from '../helpers/BCIDHelper'
import { BCDispatchAction, BCState } from '../store'

const PersonCredential: React.FC = () => {
const [workflowInFlight, setWorkflowInFlight] = useState<boolean>(false)
const { agent } = useAgent()
const navigation = useNavigation()

const [store, dispatch] = useStore<BCState>()
const [workflowInProgress, setWorkflowInProgress] = useState<boolean>(false)
const [workflowConnectionId, setWorkflowConnectionId] = useState<string | undefined>()

const { ColorPallet, TextTheme } = useTheme()
const { t } = useTranslation()
Expand Down Expand Up @@ -45,9 +48,9 @@ const PersonCredential: React.FC = () => {
navigation.navigate(Screens.Home as never)
}, [])

const startGetBCIDCredentialWorkflow = useCallback(() => {
setWorkflowInFlight(true)
startFlow(agent!, store, setWorkflowInFlight, t, dismissPersonCredentialOffer)
const onBCIDPress = useCallback(() => {
setWorkflowInProgress(true)
startFlow(agent!, store, setWorkflowInProgress, t, (connectionId) => setWorkflowConnectionId(connectionId))
}, [])

const getBCServicesCardApp = useCallback(() => {
Expand All @@ -73,12 +76,12 @@ const PersonCredential: React.FC = () => {
<Button
title={t('PersonCredential.GetCredential')}
accessibilityLabel={t('PersonCredential.GetCredential')}
onPress={startGetBCIDCredentialWorkflow}
disabled={workflowInFlight}
onPress={onBCIDPress}
disabled={workflowInProgress}
buttonType={ButtonType.Primary}
>
{workflowInFlight && (
<LoadingIcon color={ColorPallet.grayscale.white} size={35} active={workflowInFlight} />
{workflowInProgress && (
<LoadingIcon color={ColorPallet.grayscale.white} size={35} active={workflowInProgress} />
)}
</Button>
</View>
Expand All @@ -94,33 +97,40 @@ const PersonCredential: React.FC = () => {
)
}
return (
<SafeAreaView style={styles.pageContainer} edges={['bottom', 'left', 'right']}>
<View style={styles.pageContent}>
<FlatList
data={[personCredentialAttributes]}
ListFooterComponent={personPageFooter}
contentContainerStyle={{ flexGrow: 1 }}
ListFooterComponentStyle={{ flex: 1, justifyContent: 'flex-end' }}
renderItem={({ item }) => {
return (
<View>
<View style={styles.credentialCardContainer}>
<CredentialCard credDefId={item.credDefId} schemaId={item.schemaId} displayItems={item.attributes} />
<>
<SafeAreaView style={styles.pageContainer} edges={['bottom', 'left', 'right']}>
<View style={styles.pageContent}>
<FlatList
data={[personCredentialAttributes]}
ListFooterComponent={personPageFooter}
contentContainerStyle={{ flexGrow: 1 }}
ListFooterComponentStyle={{ flex: 1, justifyContent: 'flex-end' }}
renderItem={({ item }) => {
return (
<View>
<View style={styles.credentialCardContainer}>
<CredentialCard
credDefId={item.credDefId}
schemaId={item.schemaId}
displayItems={item.attributes}
/>
</View>
<Text style={TextTheme.normal}>
{t('PersonCredential.Description') + ' '}
<TouchableOpacity onPress={getBCServicesCardApp}>
<Text style={{ ...TextTheme.normal, color: ColorPallet.brand.link }}>
{t('PersonCredential.LinkDescription')}
</Text>
</TouchableOpacity>
</Text>
</View>
<Text style={TextTheme.normal}>
{t('PersonCredential.Description') + ' '}
<TouchableOpacity onPress={getBCServicesCardApp}>
<Text style={{ ...TextTheme.normal, color: ColorPallet.brand.link }}>
{t('PersonCredential.LinkDescription')}
</Text>
</TouchableOpacity>
</Text>
</View>
)
}}
/>
</View>
</SafeAreaView>
)
}}
/>
</View>
</SafeAreaView>
<CredentialOfferTrigger workflowConnectionId={workflowConnectionId} />
</>
)
}

Expand Down