Skip to content

Commit

Permalink
feat: wallet screen detail screen (#148)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Aug 22, 2024
1 parent c16fbf4 commit d61d4b1
Show file tree
Hide file tree
Showing 48 changed files with 478 additions and 184 deletions.
2 changes: 1 addition & 1 deletion apps/ausweis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"react-native-get-random-values": "~1.11.0",
"react-native-keychain": "^8.2.0",
"react-native-mmkv": "^2.12.2",
"react-native-reanimated": "~3.10.1",
"react-native-reanimated": "~3.15.0",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "15.2.0"
Expand Down
6 changes: 1 addition & 5 deletions apps/ausweis/src/app/(app)/(home)/scan.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { QrScannerScreen } from '@package/app'

export default function Screen() {
return (
<>
<QrScannerScreen />
</>
)
return <QrScannerScreen allowedInvitationTypes={['openid-authorization-request']} />
}
59 changes: 52 additions & 7 deletions apps/ausweis/src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { Redirect, Stack } from 'expo-router'
import { Redirect, Stack, useRouter } from 'expo-router'

import { useSecureUnlock } from '@ausweis/agent'
import { useHasFinishedOnboarding } from '@ausweis/features/onboarding'
import { resetWallet, useResetWalletDevMenu } from '@ausweis/utils/resetWallet'
import { AgentProvider } from '@package/agent'
import { DeeplinkHandler, isAndroid } from '@package/app'
import { HeroIcons, XStack } from '@package/ui'
import { useEffect, useState } from 'react'
import Reanimated, { FadeIn } from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from 'tamagui'

export default function AppLayout() {
useResetWalletDevMenu()
const secureUnlock = useSecureUnlock()
const { top } = useSafeAreaInsets()
const theme = useTheme()
const router = useRouter()

// It could be that the onboarding is cut of mid-process, and e.g. the user closes the app
// if this is the case we will redo the onboarding
Expand Down Expand Up @@ -39,15 +46,53 @@ export default function AppLayout() {
return <Redirect href="/authenticate" />
}

// On Android, we push down the screen content when the presentation is a Modal
// This is because Android phones render Modals as full screen pages.
const headerModalOptions = isAndroid() && {
headerShown: true,
header: () => {
// Header is translucent by default. See configuration in app.json
return <XStack bg="$background" h={top} />
},
}

// Render the normal wallet, which is everything inside (app)
return (
<AgentProvider agent={secureUnlock.context.agent}>
<Reanimated.View
style={{ flex: 1 }}
entering={FadeIn.springify().damping(24).mass(0.8).stiffness(200).restSpeedThreshold(0.05).delay(200)}
>
<Stack />
</Reanimated.View>
<DeeplinkHandler allowedInvitationTypes={['openid-authorization-request']}>
<Reanimated.View
style={{ flex: 1 }}
entering={FadeIn.springify().damping(24).mass(0.8).stiffness(200).restSpeedThreshold(0.05).delay(200)}
>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: theme['primary-500'].val,
headerTitle: '',
headerLeft: () => (
<XStack onPress={() => router.back()}>
<HeroIcons.ArrowLeft size={32} color="$black" />
</XStack>
),
}}
name="credentials/pid"
/>
</Stack>
</Reanimated.View>
</DeeplinkHandler>
</AgentProvider>
)
}
15 changes: 0 additions & 15 deletions apps/ausweis/src/app/(app)/credentials/[id].tsx

This file was deleted.

17 changes: 17 additions & 0 deletions apps/ausweis/src/app/(app)/credentials/pid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FunkePidCredentialDetailScreen } from '@ausweis/features/wallet/FunkePidCredentialDetailScreen'
import { Stack } from 'expo-router'

export default function Screen() {
return (
<>
<Stack.Screen
options={{
title: 'Credential',
// TODO: would be nice if header dissapears when scrolling but can't get
// the header to not show... :(
}}
/>
<FunkePidCredentialDetailScreen />
</>
)
}
5 changes: 2 additions & 3 deletions apps/ausweis/src/app/(app)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { WalletScreen } from '@package/app'
import { FunkeWalletScreen } from '@ausweis/features/wallet/FunkeWalletScreen'
import { XStack } from '@package/ui'
import { Stack } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import inAppLogo from '../../../assets/in-app-logo.png'

export default function Screen() {
const { top } = useSafeAreaInsets()
Expand All @@ -18,7 +17,7 @@ export default function Screen() {
},
}}
/>
<WalletScreen logo={inAppLogo} showInbox={false} />
<FunkeWalletScreen />
</>
)
}
9 changes: 0 additions & 9 deletions apps/ausweis/src/app/(app)/notifications/inbox.tsx

This file was deleted.

9 changes: 0 additions & 9 deletions apps/ausweis/src/app/(app)/notifications/openIdCredential.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { OpenIdPresentationNotificationScreen } from '@package/app'
import { ModalProvider, OpenIdPresentationNotificationScreen } from '@package/app'

export default function Screen() {
return (
<>
<ModalProvider>
<OpenIdPresentationNotificationScreen />
</>
</ModalProvider>
)
}
2 changes: 1 addition & 1 deletion apps/ausweis/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ MIICeTCCAiCgAwIBAgIUB5E9QVZtmUYcDtCjKB/H3VQv72gwCgYIKoZIzj0EAwIwgYgxCzAJBgNVBAYT

// https://funke.animo.id
const animoFunkeRelyingPartyCertificate =
'MIIBBTCBq6ADAgECAhAar4TMiACrMSPwZ04DngKaMAoGCCqGSM49BAMCMAAwIBcNNzAwMTAxMDAwMDAwWhgPMjI4NjExMjAxNzQ2NDBaMAAwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMlMCMwIQYDVR0RBBowGIYWaHR0cHM6Ly9mdW5rZS5hbmltby5pZDAKBggqhkjOPQQDAgNJADBGAiEA1pSuByfgYpXmUjs+OGIJvqeHXCtTXiMxZDKe7bH7ySUCIQDyLR+EfdKPgj83FGUB9sERjfrCH9sH6QwI0TPAJSGicA=='
'MIH6MIGhoAMCAQICEDlbxpcN1V1PRbmc2TtPjNQwCgYIKoZIzj0EAwIwADAeFw03MDAxMDEwMDAwMDBaFw0yNTExMjIwODIyMTJaMAAwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMdMBswGQYDVR0RBBIwEIIOZnVua2UuYW5pbW8uaWQwCgYIKoZIzj0EAwIDSAAwRQIhAIFd2jlrZAzLTLsXdUE7O+CRuxuzk04lGo1eVYIbgT8iAiAQhR/FonhoLLTFjU/3tn5rPyB2DaOl3W18W5ugLWHjhQ=='

export const trustedX509Certificates = [bdrPidIssuerCertificate, animoFunkeRelyingPartyCertificate]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export interface OnboardingIdCardFetchProps {
export function OnboardingIdCardFetch({ goToNextStep, userName }: OnboardingIdCardFetchProps) {
return (
<YStack justifyContent="space-between" flex-1>
<IdCard icon={userName ? 'complete' : 'loading'} issuerImage={germanIssuerImage} userName={userName} />
<IdCard
icon={userName ? 'complete' : 'loading'}
hideUserName
issuerImage={germanIssuerImage}
userName={userName}
/>
<Stack>
{userName && (
<Animated.View entering={FadeIn} layout={LinearTransition}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const OnboardingIdCardPinEnter = forwardRef(({ goToNextStep }: Onboarding
return (
<YStack fg={1} jc="space-between" gap="$6">
<YStack gap="$6">
<IdCard icon={isLoading ? 'loading' : 'locked'} issuerImage={germanIssuerImage} />
<IdCard icon={isLoading ? 'loading' : 'locked'} issuerImage={germanIssuerImage} hideUserName />
<XStack gap="$3" justifyContent="center">
{pinValues.map((digit, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: index is the correct key here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface OnboardingIdCardFetchProps {
export function OnboardingIdCardVerify({ goToNextStep }: OnboardingIdCardFetchProps) {
return (
<YStack jc="space-between" fg={1}>
<IdCard icon="biometric" issuerImage={germanIssuerImage} />
<IdCard hideUserName icon="biometric" issuerImage={germanIssuerImage} />
<Button.Solid onPress={goToNextStep}>Unlock with biometrics</Button.Solid>
</YStack>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button, Heading, HeroIcons, IdCard, ScrollView, Spacer, Stack, YStack } from '@package/ui'
import React from 'react'
import { useRouter } from 'solito/router'

import { CredentialAttributes } from '@package/app/src/components'
import { useScrollViewPosition } from '@package/app/src/hooks'

import germanIssuerImage from '../../../assets/german-issuer-image.png'
import { usePidCredential } from '../../hooks'

export function FunkePidCredentialDetailScreen() {
const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition()
const router = useRouter()

const { isLoading, credential } = usePidCredential()
if (isLoading) {
return null
}

return (
<YStack bg="$background" height="100%">
<Spacer size="$13" />
<YStack borderWidth={isScrolledByOffset ? 0.5 : 0} borderColor="$grey-300" />
<ScrollView onScroll={handleScroll} scrollEventThrottle={scrollEventThrottle}>
<YStack g="xl" pad="lg" py="$4">
<IdCard issuerImage={germanIssuerImage} small />
<Stack g="md">
<Heading variant="title">Personalausweis</Heading>
<CredentialAttributes subject={credential.attributes} headerTitle="Attributes" />
<Button.Text onPress={() => router.back()} icon={<HeroIcons.ArrowLeft size={20} />}>
Back
</Button.Text>
</Stack>
</YStack>
</ScrollView>
</YStack>
)
}
59 changes: 59 additions & 0 deletions apps/ausweis/src/features/wallet/FunkeWalletScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Heading, HeroIcons, IdCard, Page, ScrollView, Spacer, Spinner, Stack, XStack, YStack } from '@package/ui'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useRouter } from 'solito/router'

import { usePidCredential } from '@ausweis/hooks'
import { useNetworkCallback } from '@package/app/src/hooks'
import { capitalizeFirstLetter } from '@package/utils'
import Reanimated from 'react-native-reanimated'
import germanIssuerImage from '../../../assets/german-issuer-image.png'

export function FunkeWalletScreen() {
const { push } = useRouter()
const { isLoading, credential } = usePidCredential()
const { bottom } = useSafeAreaInsets()
const navigateToPidDetail = () => push('/credentials/pid')
const navigateToScanner = useNetworkCallback(() => push('/scan'))

if (isLoading) {
return (
<Page jc="center" ai="center">
<Spinner />
</Page>
)
}

return (
<>
<XStack position="absolute" width="100%" zIndex={5} justifyContent="center" bottom={bottom ?? '$6'}>
<YStack
bg="$grey-900"
br="$12"
borderWidth="$2"
borderColor="#e9e9eb"
p="$3.5"
pressStyle={{ backgroundColor: '$grey-800' }}
shadowOffset={{ width: 5, height: 5 }}
shadowColor="$grey-500"
shadowOpacity={0.5}
shadowRadius={10}
onPress={() => navigateToScanner()}
>
<HeroIcons.QrCode color="$grey-100" size={48} />
</YStack>
</XStack>
<YStack bg="$background" py="$4" height="100%" position="relative">
<ScrollView px="$4" gap="$2">
<YStack gap="$4">
<Heading variant="title" fontWeight="$bold">
{capitalizeFirstLetter(credential.attributes.given_name.toLowerCase())}'s Wallet
</Heading>
<IdCard issuerImage={germanIssuerImage} onPress={navigateToPidDetail} hideUserName />
<Spacer />
<Heading variant="h1">Recent Activity</Heading>
</YStack>
</ScrollView>
</YStack>
</>
)
}
1 change: 1 addition & 0 deletions apps/ausweis/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './usePidCredential'
45 changes: 45 additions & 0 deletions apps/ausweis/src/hooks/usePidCredential.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useCredentialsForDisplay } from '@package/agent'
import { capitalizeFirstLetter } from '@package/utils'
import { useMemo } from 'react'

export function usePidCredential() {
const { isLoading, credentials } = useCredentialsForDisplay()
const credential = credentials[0]

const pidCredential = useMemo(() => {
if (!credential) return undefined

const attributes = credential.attributes as {
given_name: string
family_name: string
birth_family_name: string
place_of_birth: {
locality: string
}
address: {
locality: string
street_address: string
country: string
}
[key: string]: unknown
}

return {
id: credential.id,
attributes,
userName: `${capitalizeFirstLetter(attributes.given_name.toLowerCase())} ${capitalizeFirstLetter(attributes.family_name.toLowerCase())}`,
}
}, [credential])

if (isLoading || !pidCredential) {
return {
credential: undefined,
isLoading: true,
} as const
}

return {
isLoading,
credential: pidCredential,
} as const
}
2 changes: 1 addition & 1 deletion apps/ausweis/src/use-cases/ReceivePidUseCaseCFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class ReceivePidUseCaseCFlow {
}
},
onCardAttachedChanged: (options) => this.options.onCardAttachedChanged?.(options),
debug: false,
debug: __DEV__,
onStatusProgress: (options) => this.options.onStatusProgress?.(options),
onAttachCard: () => this.options.onAttachCard?.(),
})
Expand Down
Loading

0 comments on commit d61d4b1

Please sign in to comment.