From cbbfb87b7893d875dc6203693159f546ba85e37e Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Nov 2024 15:00:39 +0100 Subject: [PATCH 1/5] feat: new home Signed-off-by: Jan --- apps/easypid/src/app/(app)/_layout.tsx | 1 + .../src/app/(app)/credentials/index.tsx | 5 + .../src/features/activity/activityRecord.ts | 5 + .../wallet/FunkeCredentialsScreen.tsx | 168 ++++++++++ .../src/features/wallet/FunkeWalletScreen.tsx | 288 +++++++++--------- .../wallet/components/LatestActivityCard.tsx | 39 +++ packages/app/src/components/PinDotsInput.tsx | 2 +- packages/ui/src/components/InfoButton.tsx | 10 +- packages/ui/src/constants.ts | 2 +- packages/ui/src/content/Icon.tsx | 2 + packages/ui/src/index.ts | 2 +- 11 files changed, 379 insertions(+), 145 deletions(-) create mode 100644 apps/easypid/src/app/(app)/credentials/index.tsx create mode 100644 apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx create mode 100644 apps/easypid/src/features/wallet/components/LatestActivityCard.tsx diff --git a/apps/easypid/src/app/(app)/_layout.tsx b/apps/easypid/src/app/(app)/_layout.tsx index 75ad832c..c2c94ac8 100644 --- a/apps/easypid/src/app/(app)/_layout.tsx +++ b/apps/easypid/src/app/(app)/_layout.tsx @@ -86,6 +86,7 @@ export default function AppLayout() { gestureEnabled: false, }} /> + diff --git a/apps/easypid/src/app/(app)/credentials/index.tsx b/apps/easypid/src/app/(app)/credentials/index.tsx new file mode 100644 index 00000000..03d93679 --- /dev/null +++ b/apps/easypid/src/app/(app)/credentials/index.tsx @@ -0,0 +1,5 @@ +import { FunkeCredentialsScreen } from '@easypid/features/wallet/FunkeCredentialsScreen' + +export default function Screen() { + return +} diff --git a/apps/easypid/src/features/activity/activityRecord.ts b/apps/easypid/src/features/activity/activityRecord.ts index 05232b73..007ab56f 100644 --- a/apps/easypid/src/features/activity/activityRecord.ts +++ b/apps/easypid/src/features/activity/activityRecord.ts @@ -83,6 +83,11 @@ export const useActivities = ({ filters }: { filters?: { host?: string; name?: s } } +export const useLatestActivity = () => { + const { activities } = useActivities() + return activities[0] +} + export const addReceivedActivity = async ( agent: EasyPIDAppAgent, input: { diff --git a/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx new file mode 100644 index 00000000..d7f8fb7b --- /dev/null +++ b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx @@ -0,0 +1,168 @@ +import { useCredentialsWithCustomDisplay } from '@easypid/hooks/useCredentialsWithCustomDisplay' +import { useHaptics, useScrollViewPosition } from '@package/app/src/hooks' +import { + AnimatedStack, + FlexPage, + Heading, + HeroIcons, + IconContainer, + Image, + Input, + Loader, + LucideIcons, + Paragraph, + ScrollView, + Spacer, + Stack, + XStack, + YStack, + useScaleAnimation, +} from '@package/ui' +import { useRouter } from 'expo-router' +import type { DisplayImage } from 'packages/agent/src' +import { TextBackButton } from 'packages/app/src' +import { formatDate } from 'packages/utils/src' +import React, { useMemo, useState } from 'react' +import { FadeInDown } from 'react-native-reanimated' + +export function FunkeCredentialsScreen() { + const { credentials, isLoading: isLoadingCredentials } = useCredentialsWithCustomDisplay() + + const [searchQuery, setSearchQuery] = useState('') + const filteredCredentials = useMemo(() => { + return credentials.filter((credential) => credential.display.name.toLowerCase().includes(searchQuery.toLowerCase())) + }, [credentials, searchQuery]) + + const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition() + const { push } = useRouter() + const { withHaptics } = useHaptics() + + const pushToCredential = withHaptics((id: string) => push(`/credentials/${id}`)) + + return ( + + + + + Cards + + + {credentials.length === 0 ? ( + + + There's nothing here, yet + + Credentials will appear here once you receive them. + + ) : isLoadingCredentials ? ( + + + + + ) : ( + + + + + + + {filteredCredentials.length > 0 ? ( + filteredCredentials.map((credential) => ( + { + pushToCredential(credential.id) + }} + /> + )) + ) : ( + + No cards found for "{searchQuery}" + + )} + + + )} + + + + + ) +} + +interface FunkeCredentialRowCardProps { + name: string + backgroundColor: string + textColor: string + issuer: string + logo: DisplayImage | undefined + onPress: () => void +} + +function FunkeCredentialRowCard({ name, backgroundColor, textColor, logo, onPress }: FunkeCredentialRowCardProps) { + const { pressStyle, handlePressIn, handlePressOut } = useScaleAnimation({ scaleInValue: 0.99 }) + + const icon = logo?.url ? ( + + ) : ( + + + + ) + + return ( + + {icon} + + + {name.toLocaleUpperCase()} + + + Issued on {formatDate(new Date(), { includeTime: false })} + + + } /> + + ) +} diff --git a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx index 680c074a..ff2f2abf 100644 --- a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx @@ -1,13 +1,11 @@ import { AnimatedStack, - BASE_CREDENTIAL_CARD_HEIGHT, Button, FlexPage, Heading, HeroIcons, IconContainer, Loader, - LucideIcons, Paragraph, ScrollView, Spacer, @@ -20,10 +18,12 @@ import { useRouter } from 'solito/router' import { useCredentialsWithCustomDisplay } from '@easypid/hooks/useCredentialsWithCustomDisplay' import { useWalletReset } from '@easypid/hooks/useWalletReset' -import { useHaptics, useNetworkCallback } from '@package/app/src/hooks' -import type { CredentialDisplay } from 'packages/agent/src' +import { useHaptics, useNetworkCallback, useScrollViewPosition } from '@package/app/src/hooks' import { FunkeCredentialCard } from 'packages/app' -import { FadeIn, FadeInDown, LinearTransition, ZoomIn, useAnimatedStyle } from 'react-native-reanimated' +import { useState } from 'react' +import { FadeInDown, ZoomIn } from 'react-native-reanimated' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { LatestActivityCard } from './components/LatestActivityCard' export function FunkeWalletScreen() { const { push } = useRouter() @@ -32,8 +32,8 @@ export function FunkeWalletScreen() { const { withHaptics } = useHaptics() const pushToMenu = withHaptics(() => push('/menu')) - const pushToActivity = withHaptics(() => push('/activity')) const pushToScanner = withHaptics(() => push('/scan')) + const pushToCards = withHaptics(() => push('/credentials')) const { pressStyle: qrPressStyle, @@ -41,150 +41,160 @@ export function FunkeWalletScreen() { handlePressOut: qrHandlePressOut, } = useScaleAnimation({ scaleInValue: 0.95 }) + const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition() + const { bottom } = useSafeAreaInsets() + const [scrollViewHeight, setScrollViewHeight] = useState(0) + return ( - - - - } onPress={pushToMenu} /> - } onPress={pushToActivity} /> - - - - + {/* Header */} + + } onPress={pushToMenu} /> + + + {/* Body */} + 0} + onScroll={handleScroll} + scrollEventThrottle={scrollEventThrottle} + px="$4" + onLayout={(e) => { + setScrollViewHeight(e.nativeEvent.layout.height) + }} + contentContainerStyle={{ + minHeight: credentials.length <= 1 ? scrollViewHeight : '100%', + justifyContent: 'space-between', + paddingBottom: bottom, + }} + > + + + + + + + + + Scan QR-Code + + + {credentials.length === 0 && !isLoading ? ( + - + + + There's nothing here, yet + + Setup your ID or use the QR scanner to receive credentials. + + + + Setup ID + + + + ) : isLoading ? ( + + + + + ) : ( + + + + + Recently used + + + {credentials.slice(0, 2).map((credential) => ( + push(`/credentials/${credential.id}`))} + /> + ))} + + {credentials.length > 1 && ( + + See all cards + + + )} + - - - Scan QR-Code - - - - {credentials.length === 0 ? ( - - - - There's nothing here, yet - - Setup your ID or use the QR scanner to receive credentials. - - - - Setup ID - - - - ) : isLoading ? ( - - - + )} - ) : ( - - - {credentials.map((credential, idx) => ( - - ))} - - - )} - - - Learn more about{' '} + + push('/menu/about')} - variant="annotation" + variant="sub" fontSize={13} - fontWeight="$semiBold" - color="$primary-500" + fontWeight="$medium" + ta="center" + px="$4" > - using this wallet + Learn more about{' '} + + using this wallet + + . - . - - + + ) } - -function AnimatedCredentialCard({ - display, - id, - index, -}: { - display: CredentialDisplay - id: string - index: number -}) { - const { push } = useRouter() - const { withHaptics } = useHaptics() - - const animatedStyle = useAnimatedStyle(() => { - const baseMargin = index * 72 - - return { - marginTop: baseMargin, - } - }) - - return ( - - push(`/credentials/${id}`))} - /> - - ) -} diff --git a/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx new file mode 100644 index 00000000..2c47615e --- /dev/null +++ b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx @@ -0,0 +1,39 @@ +import { useLatestActivity } from '@easypid/features/activity/activityRecord' +import { useCredentialsWithCustomDisplay } from '@easypid/hooks/useCredentialsWithCustomDisplay' +import { InfoButton } from '@package/ui/src' +import { useRouter } from 'expo-router' +import { useHaptics } from 'packages/app/src/hooks' +import { formatRelativeDate } from 'packages/utils/src' +import { useMemo } from 'react' + +export function LatestActivityCard() { + const { push } = useRouter() + const { withHaptics } = useHaptics() + const latestActivity = useLatestActivity() + const { credentials } = useCredentialsWithCustomDisplay() + + const pushToActivity = withHaptics(() => push('/activity')) + + const content = useMemo(() => { + if (!latestActivity) return null + if (latestActivity.type === 'shared') { + const isPlural = latestActivity.request.credentials.length > 1 + return { + title: formatRelativeDate(new Date(latestActivity.date)), + description: `Shared ${isPlural ? 'cards' : 'card'}`, + } + } + if (latestActivity.type === 'received') { + const credential = credentials.find((c) => c.id.includes(latestActivity.credentialIds[0])) + return { + title: formatRelativeDate(new Date(latestActivity.date)), + description: `Added ${credential?.display.name ?? '1 card'}`, + } + } + return null + }, [latestActivity, credentials]) + + if (!content) return null + + return +} diff --git a/packages/app/src/components/PinDotsInput.tsx b/packages/app/src/components/PinDotsInput.tsx index 668e3774..521d9c68 100644 --- a/packages/app/src/components/PinDotsInput.tsx +++ b/packages/app/src/components/PinDotsInput.tsx @@ -146,7 +146,7 @@ export const PinDotsInput = forwardRef( size="$1.5" backgroundColor={filled ? '$primary-500' : '$background'} borderColor="$primary-500" - borderWidth="$0.5" + borderWidth="$1" /> ))} diff --git a/packages/ui/src/components/InfoButton.tsx b/packages/ui/src/components/InfoButton.tsx index 88a78209..26a02b26 100644 --- a/packages/ui/src/components/InfoButton.tsx +++ b/packages/ui/src/components/InfoButton.tsx @@ -55,6 +55,7 @@ interface InfoButtonProps { description: string onPress?: () => void routingType?: 'push' | 'modal' + noIcon?: boolean } export function InfoButton({ @@ -64,6 +65,7 @@ export function InfoButton({ description, onPress, routingType = 'push', + noIcon, }: InfoButtonProps) { const isPressable = !!onPress const { pressStyle, handlePressIn, handlePressOut } = useScaleAnimation() @@ -85,9 +87,11 @@ export function InfoButton({ borderColor="$grey-100" onPress={onPress} > - - {image ? {image.alt} : infoButtonVariants[variant].icon} - + {!noIcon && ( + + {image ? {image.alt} : infoButtonVariants[variant].icon} + + )} {title} diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts index 6629bb6b..ed02888f 100644 --- a/packages/ui/src/constants.ts +++ b/packages/ui/src/constants.ts @@ -1,4 +1,4 @@ -export const BASE_CREDENTIAL_CARD_HEIGHT = 212 +export const BASE_CREDENTIAL_CARD_HEIGHT = 196 export const CREDENTIAL_TOP_INFO_OFFSET = 56 export const CREDENTIAL_TOP_INFO_HEIGHT = 64 export const HEADER_TITLE_TEXT_HEIGHT = 38 diff --git a/packages/ui/src/content/Icon.tsx b/packages/ui/src/content/Icon.tsx index 3d1772a7..5b6bd21b 100644 --- a/packages/ui/src/content/Icon.tsx +++ b/packages/ui/src/content/Icon.tsx @@ -67,6 +67,7 @@ import { HandRaisedIcon as HandRaisedFilledIcon, IdentificationIcon as IdentificationFilledIcon, InformationCircleIcon as InformationCircleFilledIcon, + MagnifyingGlassIcon, QueueListIcon as QueueListFilledIcon, ShieldCheckIcon as ShieldCheckFilledIcon, TrashIcon as TrashFilledIcon, @@ -164,6 +165,7 @@ export const HeroIcons = { ChatBubbleBottomCenterTextFilled: wrapHeroIcon(ChatBubbleBottomCenterTextFilledIcon), Cog8ToothFilled: wrapHeroIcon(Cog8ToothFilledIcon), IdentificationFilled: wrapHeroIcon(IdentificationFilledIcon), + MagnifyingGlass: wrapHeroIcon(MagnifyingGlassIcon), } as const export const CustomIcons = { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 66cf143d..35ec57fd 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,6 +1,6 @@ export { tokens, config, absoluteFill } from './config/tamagui.config' export * from './constants' -export { TamaguiProviderProps, TamaguiProvider, Spacer, AnimatePresence, Circle } from 'tamagui' +export { TamaguiProviderProps, TamaguiProvider, Spacer, Input, AnimatePresence, Circle } from 'tamagui' export { ToastProvider, useToastController, ToastViewport, useToastState } from '@tamagui/toast' export * from './panels' export * from './base' From 831f21ec661d8ffbd5baf59c23b27717e3ce3850 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Nov 2024 15:09:19 +0100 Subject: [PATCH 2/5] chore: final improv --- apps/easypid/src/features/activity/activityRecord.ts | 5 ----- apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx | 4 ++-- apps/easypid/src/features/wallet/FunkeWalletScreen.tsx | 2 +- .../src/features/wallet/components/LatestActivityCard.tsx | 5 +++-- packages/ui/src/constants.ts | 2 +- packages/ui/src/content/Icon.tsx | 2 +- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/easypid/src/features/activity/activityRecord.ts b/apps/easypid/src/features/activity/activityRecord.ts index 007ab56f..05232b73 100644 --- a/apps/easypid/src/features/activity/activityRecord.ts +++ b/apps/easypid/src/features/activity/activityRecord.ts @@ -83,11 +83,6 @@ export const useActivities = ({ filters }: { filters?: { host?: string; name?: s } } -export const useLatestActivity = () => { - const { activities } = useActivities() - return activities[0] -} - export const addReceivedActivity = async ( agent: EasyPIDAppAgent, input: { diff --git a/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx index d7f8fb7b..c94e3189 100644 --- a/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx @@ -155,10 +155,10 @@ function FunkeCredentialRowCard({ name, backgroundColor, textColor, logo, onPres > {icon} - + {name.toLocaleUpperCase()} - + Issued on {formatDate(new Date(), { includeTime: false })} diff --git a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx index ff2f2abf..074ba2d3 100644 --- a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx @@ -75,7 +75,7 @@ export function FunkeWalletScreen() { paddingBottom: bottom, }} > - + push('/activity')) diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts index ed02888f..6629bb6b 100644 --- a/packages/ui/src/constants.ts +++ b/packages/ui/src/constants.ts @@ -1,4 +1,4 @@ -export const BASE_CREDENTIAL_CARD_HEIGHT = 196 +export const BASE_CREDENTIAL_CARD_HEIGHT = 212 export const CREDENTIAL_TOP_INFO_OFFSET = 56 export const CREDENTIAL_TOP_INFO_HEIGHT = 64 export const HEADER_TITLE_TEXT_HEIGHT = 38 diff --git a/packages/ui/src/content/Icon.tsx b/packages/ui/src/content/Icon.tsx index 5b6bd21b..0155e60f 100644 --- a/packages/ui/src/content/Icon.tsx +++ b/packages/ui/src/content/Icon.tsx @@ -40,6 +40,7 @@ import { InformationCircleIcon, KeyIcon, LockClosedIcon, + MagnifyingGlassIcon, NoSymbolIcon, PlusIcon, QrCodeIcon, @@ -67,7 +68,6 @@ import { HandRaisedIcon as HandRaisedFilledIcon, IdentificationIcon as IdentificationFilledIcon, InformationCircleIcon as InformationCircleFilledIcon, - MagnifyingGlassIcon, QueueListIcon as QueueListFilledIcon, ShieldCheckIcon as ShieldCheckFilledIcon, TrashIcon as TrashFilledIcon, From 1f6c208986b1772ba8ef4e43bbc11cdde3963f40 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Nov 2024 15:13:21 +0100 Subject: [PATCH 3/5] chore: number --- apps/easypid/src/features/wallet/FunkeWalletScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx index 074ba2d3..ea3c0741 100644 --- a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx @@ -160,7 +160,7 @@ export function FunkeWalletScreen() { /> ))} - {credentials.length > 1 && ( + {credentials.length > 2 && ( Date: Tue, 12 Nov 2024 16:08:01 +0100 Subject: [PATCH 4/5] chore: keyboard nav fix --- .../features/wallet/components/LatestActivityCard.tsx | 10 +++++++++- packages/app/src/components/FunkeCredentialCard.tsx | 2 +- packages/ui/src/components/InfoButton.tsx | 4 +++- packages/ui/src/index.ts | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx index d3352298..1e107566 100644 --- a/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx +++ b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx @@ -36,5 +36,13 @@ export function LatestActivityCard() { if (!content) return null - return + return ( + + ) } diff --git a/packages/app/src/components/FunkeCredentialCard.tsx b/packages/app/src/components/FunkeCredentialCard.tsx index eac34117..4f4b4e8d 100644 --- a/packages/app/src/components/FunkeCredentialCard.tsx +++ b/packages/app/src/components/FunkeCredentialCard.tsx @@ -92,7 +92,7 @@ export function FunkeCredentialCard({ overflow="hidden" accessible={true} accessibilityRole={onPress ? 'button' : undefined} - aria-label={`${name.toLocaleUpperCase()} credential`} + aria-label="Credential" > diff --git a/packages/ui/src/components/InfoButton.tsx b/packages/ui/src/components/InfoButton.tsx index 26a02b26..cc069ab4 100644 --- a/packages/ui/src/components/InfoButton.tsx +++ b/packages/ui/src/components/InfoButton.tsx @@ -56,6 +56,7 @@ interface InfoButtonProps { onPress?: () => void routingType?: 'push' | 'modal' noIcon?: boolean + ariaLabel?: string } export function InfoButton({ @@ -66,6 +67,7 @@ export function InfoButton({ onPress, routingType = 'push', noIcon, + ariaLabel, }: InfoButtonProps) { const isPressable = !!onPress const { pressStyle, handlePressIn, handlePressOut } = useScaleAnimation() @@ -83,7 +85,7 @@ export function InfoButton({ bw="$0.5" accessible={true} accessibilityRole={onPress ? 'button' : undefined} - aria-label={`${title} ${description}`} + aria-label={ariaLabel ?? `${title} ${description}`} borderColor="$grey-100" onPress={onPress} > diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 35ec57fd..af1d2d57 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,6 +1,6 @@ export { tokens, config, absoluteFill } from './config/tamagui.config' export * from './constants' -export { TamaguiProviderProps, TamaguiProvider, Spacer, Input, AnimatePresence, Circle } from 'tamagui' +export { TamaguiProviderProps, TamaguiProvider, Spacer, Input, AnimatePresence, Circle, VisuallyHidden } from 'tamagui' export { ToastProvider, useToastController, ToastViewport, useToastState } from '@tamagui/toast' export * from './panels' export * from './base' From 9057759a5687b35684d6169d4da8ca1319797d43 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Nov 2024 16:09:05 +0100 Subject: [PATCH 5/5] fix: aria --- packages/ui/src/components/InfoButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/InfoButton.tsx b/packages/ui/src/components/InfoButton.tsx index cc069ab4..c923d85c 100644 --- a/packages/ui/src/components/InfoButton.tsx +++ b/packages/ui/src/components/InfoButton.tsx @@ -85,7 +85,7 @@ export function InfoButton({ bw="$0.5" accessible={true} accessibilityRole={onPress ? 'button' : undefined} - aria-label={ariaLabel ?? `${title} ${description}`} + aria-label={ariaLabel ?? `${title}. ${description}`} borderColor="$grey-100" onPress={onPress} >