From a65f84c1dd329b8ed19aa9587ab9cb8829b7b727 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 19 Dec 2024 17:15:34 +0100 Subject: [PATCH] fix: offline permissions Signed-off-by: Jan --- .../features/activity/FunkeActivityScreen.tsx | 2 +- .../wallet/FunkeCredentialsScreen.tsx | 4 +- .../features/wallet/FunkeOfflineQrScreen.tsx | 82 ++++++++++++++++--- .../src/features/wallet/FunkeWalletScreen.tsx | 28 +++++-- .../features/wallet/components/ActionCard.tsx | 24 ++++-- .../wallet/components/LatestActivityCard.tsx | 6 +- packages/ui/src/components/ProgressHeader.tsx | 4 +- packages/ui/src/content/IconContainer.tsx | 11 ++- 8 files changed, 121 insertions(+), 40 deletions(-) diff --git a/apps/easypid/src/features/activity/FunkeActivityScreen.tsx b/apps/easypid/src/features/activity/FunkeActivityScreen.tsx index 3bf6be35..120e1d79 100644 --- a/apps/easypid/src/features/activity/FunkeActivityScreen.tsx +++ b/apps/easypid/src/features/activity/FunkeActivityScreen.tsx @@ -29,7 +29,7 @@ export function FunkeActivityScreen({ entityId }: { entityId?: string }) { return ( - + Activity diff --git a/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx index 366a17ce..fbceb1c3 100644 --- a/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeCredentialsScreen.tsx @@ -41,7 +41,7 @@ export function FunkeCredentialsScreen() { return ( - + Cards @@ -161,7 +161,7 @@ function FunkeCredentialRowCard({ name, backgroundColor, textColor, logo, onPres Issued on {formatDate(new Date(), { includeTime: false })} - } /> + } /> ) } diff --git a/apps/easypid/src/features/wallet/FunkeOfflineQrScreen.tsx b/apps/easypid/src/features/wallet/FunkeOfflineQrScreen.tsx index dc13a194..01a2a30b 100644 --- a/apps/easypid/src/features/wallet/FunkeOfflineQrScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeOfflineQrScreen.tsx @@ -1,3 +1,4 @@ +import { mmkv } from '@easypid/storage/mmkv' import { AnimatedStack, Button, @@ -14,27 +15,94 @@ import { import { useRouter } from 'expo-router' import { useHaptics } from 'packages/app/src' import { useEffect, useState } from 'react' -import { Platform, useWindowDimensions } from 'react-native' +import { Alert, Linking, useWindowDimensions } from 'react-native' +import { useMMKVBoolean } from 'react-native-mmkv' import QRCode from 'react-native-qrcode-svg' import easypidLogo from '../../../assets/icon-rounded.png' -import { checkMdocPermissions, shutdownDataTransfer, waitForDeviceRequest } from '../proximity' +import { + checkMdocPermissions, + getMdocQrCode, + requestMdocPermissions, + shutdownDataTransfer, + waitForDeviceRequest, +} from '../proximity' export function FunkeOfflineQrScreen() { const { withHaptics } = useHaptics() const { replace, back } = useRouter() const { width } = useWindowDimensions() const toast = useToastController() - const [isProcessing, setIsProcessing] = useState(false) - const [isLoading, setIsLoading] = useState(false) + const [qrCodeData, setQrCodeData] = useState() const [arePermissionsGranted, setArePermissionsGranted] = useState(false) + const [arePermissionsRequested, setArePermissionsRequested] = useMMKVBoolean('arePermissionsRequested', mmkv) useEffect(() => { void checkMdocPermissions().then((result) => { setArePermissionsGranted(!!result) + if (!result) { + void requestPermissions() + } }) }, []) + useEffect(() => { + if (arePermissionsGranted) { + void getMdocQrCode().then(setQrCodeData) + } else { + setQrCodeData(undefined) + } + }, [arePermissionsGranted]) + + const handlePermissions = async () => { + const permissions = await requestMdocPermissions() + + if (!permissions) { + toast.show('Failed to request permissions.', { customData: { preset: 'danger' } }) + return { granted: false, shouldShowSettings: false } + } + + // Check if any permission is in 'never_ask_again' state + const hasNeverAskAgain = Object.values(permissions).some((status) => status === 'never_ask_again') + + if (hasNeverAskAgain) { + return { granted: false, shouldShowSettings: true } + } + + const permissionStatus = await checkMdocPermissions() + return { granted: !!permissionStatus, shouldShowSettings: false } + } + + const requestPermissions = async () => { + // First request without checking the never_ask_again state + if (!arePermissionsRequested) { + const { granted } = await handlePermissions() + setArePermissionsRequested(true) + setArePermissionsGranted(granted) + return + } + + // Subsequent requests need to check for the never_ask_again state + const { granted, shouldShowSettings } = await handlePermissions() + + if (shouldShowSettings) { + back() + Alert.alert( + 'Please enable required permissions in your phone settings', + 'Sharing with QR-Code needs access to Bluetooth and Location.', + [ + { + text: 'Open Settings', + onPress: () => Linking.openSettings(), + }, + ] + ) + return + } + + setArePermissionsGranted(granted) + } + useEffect(() => { if (qrCodeData) { void waitForDeviceRequest().then((data) => { @@ -69,12 +137,6 @@ export function FunkeOfflineQrScreen() { shutdownDataTransfer() } - if (Platform.OS === 'ios') { - toast.show('This feature is not supported on your OS yet.', { customData: { preset: 'warning' } }) - back() - return - } - return ( diff --git a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx index 68839034..ead50a26 100644 --- a/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx +++ b/apps/easypid/src/features/wallet/FunkeWalletScreen.tsx @@ -12,11 +12,13 @@ import { XStack, YStack, useSpringify, + useToastController, } from '@package/ui' import { useRouter } from 'expo-router' import { useFirstNameFromPidCredential } from '@easypid/hooks' import { useHaptics } from '@package/app/src/hooks' +import { Platform } from 'react-native' import { FadeIn } from 'react-native-reanimated' import { Blob } from '../../../assets/Blob' import { ActionCard } from './components/ActionCard' @@ -26,6 +28,7 @@ import { LatestActivityCard } from './components/LatestActivityCard' export function FunkeWalletScreen() { const { push } = useRouter() const { withHaptics } = useHaptics() + const toast = useToastController() const { userName, isLoading } = useFirstNameFromPidCredential() @@ -33,20 +36,27 @@ export function FunkeWalletScreen() { const pushToScanner = withHaptics(() => push('/scan')) const pushToPidSetup = withHaptics(() => push('/pidSetup')) const pushToAbout = withHaptics(() => push('/menu/about')) - const pushToOffline = withHaptics(() => push('/offline')) + const pushToOffline = () => { + if (Platform.OS === 'ios') { + toast.show('This feature is not supported on your OS yet.', { customData: { preset: 'warning' } }) + return + } + + withHaptics(() => push('/offline'))() + } return ( - + - } onPress={pushToMenu} /> + } onPress={pushToMenu} /> - + @@ -58,19 +68,19 @@ export function FunkeWalletScreen() { > {userName ? `Hello, ${userName}!` : 'Hello!'} - Select what you want to do + Receive or share from your wallet - + } - title="QR-code" + title="Scan QR-code" onPress={pushToScanner} /> } - title="In-person" + title="Present In-person" onPress={pushToOffline} /> @@ -81,7 +91,7 @@ export function FunkeWalletScreen() { ) : ( - Setup your ID + Setup your ID )} diff --git a/apps/easypid/src/features/wallet/components/ActionCard.tsx b/apps/easypid/src/features/wallet/components/ActionCard.tsx index d6db9f5b..5439edfb 100644 --- a/apps/easypid/src/features/wallet/components/ActionCard.tsx +++ b/apps/easypid/src/features/wallet/components/ActionCard.tsx @@ -1,4 +1,4 @@ -import { AnimatedStack, Heading, Stack, useScaleAnimation } from '@package/ui' +import { AnimatedStack, Heading, Stack, XStack, YStack, useScaleAnimation } from '@package/ui' import type { ReactNode } from 'react' interface ActionCardProps { @@ -21,18 +21,24 @@ export function ActionCard({ icon, title, onPress, variant = 'primary' }: Action onPressIn={qrHandlePressIn} onPressOut={qrHandlePressOut} onPress={onPress} - ai="center" jc="center" bg={variant === 'primary' ? '$grey-900' : '$white'} - py="$4" - px="$6" - gap="$4" + p="$3" + fg={1} + gap="$3" br="$6" > - {icon} - - {title} - + + + {icon} + + + {title.split(' ').map((word) => ( + + {word} + + ))} + ) } diff --git a/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx index bc8dea22..e2829188 100644 --- a/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx +++ b/apps/easypid/src/features/wallet/components/LatestActivityCard.tsx @@ -16,7 +16,11 @@ export function LatestActivityCard() { const pushToActivity = withHaptics(() => push('/activity')) const content = useMemo(() => { - if (!latestActivity) return null + if (!latestActivity) + return { + title: 'Recent activity', + description: 'No activity yet', + } if (latestActivity.type === 'shared') { const isPlural = latestActivity.request.credentials.length > 1 return { diff --git a/packages/ui/src/components/ProgressHeader.tsx b/packages/ui/src/components/ProgressHeader.tsx index 18023c45..92bc42a4 100644 --- a/packages/ui/src/components/ProgressHeader.tsx +++ b/packages/ui/src/components/ProgressHeader.tsx @@ -55,9 +55,9 @@ export function ProgressHeader({ mx={variant === 'small' ? '$-4' : '$0'} > {variant === 'small' ? ( - + ) : ( - + )} { icon: React.ReactElement scaleOnPress?: boolean - bg?: boolean + bg?: 'white' | 'grey' | 'transparent' 'aria-label'?: string } export function IconContainer({ icon, scaleOnPress = true, - bg = false, + bg = 'grey', 'aria-label': ariaLabel, ...props }: IconContainerProps) { @@ -23,16 +23,15 @@ export function IconContainer({ - {bg && } {cloneElement(icon, { strokeWidth: icon.props.strokeWidth ?? 2, size: icon.props.size ?? 24,