add proof visualization and follow tsx best practices

This commit is contained in:
Rémi Colin
2024-02-05 20:37:01 +01:00
parent ffe3e9fba1
commit 412b7b29a8
14 changed files with 262 additions and 82 deletions

View File

@@ -80,7 +80,7 @@ function App(): JSX.Element {
const [dateOfExpiry, setDateOfExpiry] = useState(DEFAULT_DOE ?? '');
const [address, setAddress] = useState(DEFAULT_ADDRESS ?? '');
const [passportData, setPassportData] = useState(samplePassportData);
const [step, setStep] = useState(Steps.MRZ_SCAN);
const [step, setStep] = useState<number>(Steps.MRZ_SCAN);
const [testResult, setTestResult] = useState<any>(null);
const [error, setError] = useState<any>(null);
const [generatingProof, setGeneratingProof] = useState<boolean>(false);
@@ -224,7 +224,7 @@ function App(): JSX.Element {
console.log('encryptedDigest', passportData.encryptedDigest);
setPassportData(passportData);
setStep('scanCompleted');
setStep(Steps.NFC_SCAN_COMPLETED);
}
async function handleResponseAndroid(response: any) {

View File

@@ -555,13 +555,13 @@ SPEC CHECKSUMS:
React-Core: 8293312ad137ea82fd2c29deb163dbc24aa4e00e
React-CoreModules: 32fab1d62416849a3b6dac6feff9d54e5ddc2d1e
React-cxxreact: 55d0f7cb6b4cc09ba9190797f1da87182d1a2fb6
React-debug: 7e61555c8158126c6cd98c3154381ad3821aaaca
React-debug: 5dab69ef4d475ac86ea0dadf78f6453298e02d62
React-jsc: 0db8e8cc2074d979c37ffa7b8d7c914833960497
React-jsi: 58677ff4848ceb6aeb9118fe03448a843ea5e16a
React-jsiexecutor: 2c15ba1bace70177492368d5180b564f165870fd
React-jsinspector: b511447170f561157547bc0bef3f169663860be7
React-logger: c5b527272d5f22eaa09bb3c3a690fee8f237ae95
React-NativeModulesApple: 0438665fc7473be6edc496e823e6ea0b0537b46c
React-NativeModulesApple: 79820f35406d83c153c9ff6a3c774d87ac888b16
React-perflogger: 6bd153e776e6beed54c56b0847e1220a3ff92ba5
React-RCTActionSheet: c0b62af44e610e69d9a2049a682f5dba4e9dff17
React-RCTAnimation: fe7005136b58f58871cab2f70732343b6e330d30
@@ -575,13 +575,13 @@ SPEC CHECKSUMS:
React-RCTVibration: ea3a68a49873a54ced927c90923fc6932baf344a
React-rncore: 9672a017af4a7da7495d911f0b690cbcae9dd18d
React-runtimeexecutor: 369ae9bb3f83b65201c0c8f7d50b72280b5a1dbc
React-runtimescheduler: ec1066a4f2d1152eb1bc3fb61d69376b3bc0dde0
React-utils: d55ba834beb39f01b0b470ae43478c0a3a024abe
ReactCommon: 68e3a815fbb69af3bb4196e04c6ae7abb306e7a8
React-runtimescheduler: 749452b413819c5903d575089edcf0eda74eded9
React-utils: f37c388aaf8b292278d2dd83d0aa36ac579d8057
ReactCommon: ad66a3a68f98db6309f30f4e4ebd0751a9548876
RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce
PODFILE CHECKSUM: d401e6efe0c933985349c8c115c7edca8fef3182
COCOAPODS: 1.14.3
COCOAPODS: 1.15.0

View File

@@ -490,8 +490,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
DEVELOPMENT_TEAM = X53ZR86F22;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = ProofOfPassport/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -512,8 +514,9 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassport;
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassportdev;
PRODUCT_NAME = ProofOfPassport;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -528,7 +531,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
DEVELOPMENT_TEAM = X53ZR86F22;
INFOPLIST_FILE = ProofOfPassport/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -547,7 +550,7 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassport;
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassportdev;
PRODUCT_NAME = ProofOfPassport;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -25,6 +25,7 @@
"pvutils": "^1.1.3",
"react": "18.2.0",
"react-native": "0.72.3",
"react-native-canvas": "^0.1.39",
"react-native-passport-reader": "^1.0.3",
"react-native-svg": "13.4.0",
"react-native-toast-message": "^2.2.0",

View File

@@ -2,13 +2,32 @@ import React from 'react';
import { Text, YStack, XStack, Card, H3, Image } from 'tamagui';
import { ChevronRight } from '@tamagui/lucide-icons';
const AppCard = ({ title, description, colorOfTheText, background, id, onTouchStart, eleva }) => {
interface AppCardProps {
title: string;
description: string;
colorOfTheText: string;
background: string | undefined;
id: string | number;
onTouchStart?: () => void;
eleva?: string;
}
const AppCard: React.FC<AppCardProps> = ({
title,
description,
colorOfTheText,
background,
id,
onTouchStart,
eleva
}) => {
return (
<Card
key={id}
borderRadius="$10"
elevation={eleva}
onTouchStart={onTouchStart}
shadowColor="black"
>
<XStack
>

View File

@@ -0,0 +1,74 @@
import React, { useMemo } from 'react';
import { Svg, Rect } from 'react-native-svg';
import { YStack } from 'tamagui';
interface ProofGridProps {
proof: { proof: string; inputs: string } | null;
}
const ProofGrid: React.FC<ProofGridProps> = ({ proof }) => {
const gridSize = 8;
const pixelSize = 15;
const sumAndScaleDigits = useMemo(() => (values: string[]) => {
const sum = values.reduce((acc, val) => acc + BigInt(val), BigInt(0));
const digits = sum.toString().split('').map(Number);
return digits.map(digit => Math.round(digit * (256 / 9)));
}, []);
// Prepare the RGB values
const { rValues, gValues, bValues } = useMemo(() => {
if (!proof) {
return { rValues: [], gValues: [], bValues: [] };
}
const parsedProof = JSON.parse(proof.proof);
return {
rValues: sumAndScaleDigits(parsedProof.a),
gValues: sumAndScaleDigits(parsedProof.b.flat()),
bValues: sumAndScaleDigits(parsedProof.c)
};
}, [proof, sumAndScaleDigits]);
// Generate the grid data
const gridData = useMemo(() =>
Array.from({ length: gridSize }, (_, rowIndex) =>
Array.from({ length: gridSize }, (_, colIndex) => {
const index = rowIndex * gridSize + colIndex;
const r = index < rValues.length ? rValues[index] : 0;
const g = index < gValues.length ? gValues[index] : 0;
const b = index < bValues.length ? bValues[index] : 0;
return `rgb(${r}, ${g}, ${b})`;
})
)
, [gridSize, rValues, gValues, bValues]);
// Render the grid using SVG and Rect
return (
<YStack
width={gridSize * pixelSize}
borderRadius={40}
overflow="hidden"
elevation="$4"
style={{ backdropFilter: 'blur(10px)' }}
>
<Svg height={gridSize * pixelSize} width={gridSize * pixelSize} >
{gridData.map((row, i) =>
row.map((fill, j) => (
<Rect
key={`${i}-${j}`}
x={j * pixelSize}
y={i * pixelSize}
width={pixelSize}
height={pixelSize}
fill={fill}
/>
))
)}
</Svg>
</YStack>
);
};
export default ProofGrid;

View File

@@ -4,7 +4,13 @@ import GITCOIN from '../images/gitcoin.png';
import { YStack } from 'tamagui';
import AppCard from '../components/AppCard';
import { App, gitcoin, soulbond, zuzalu } from '../utils/AppClass';
const AppScreen = ({ selectedApp, setSelectedApp }) => {
interface AppScreenProps {
selectedApp: App | null;
setSelectedApp: (app: App | null) => void;
}
const AppScreen: React.FC<AppScreenProps> = ({ selectedApp, setSelectedApp }) => {
const handleCardSelect = (app: App) => {
setSelectedApp(app);

View File

@@ -1,36 +1,63 @@
import React, { useState, useEffect } from 'react';
import { YStack, XStack, Text, Button, SizableText, Tabs, styled, Dialog, Adapt, Sheet, Label, Fieldset, Input, Switch, ThemeableStack } from 'tamagui'
import { YStack, XStack, Text, Button, Tabs, styled, Dialog, Adapt, Sheet, Label, Fieldset, Input, Switch, ThemeableStack } from 'tamagui'
import { Scan, UserCheck, HelpCircle, XCircle, IterationCw, LayoutGrid, Sparkles } from '@tamagui/lucide-icons';
import ScanScreen from './ScanScreen';
import ProveScreen from './ProveScreen';
import { Steps } from '../utils/utils';
import AppScreen from './AppScreen';
import { App } from '../utils/AppClass';
const MainScreen = (
{ onStartCameraScan,
nfcScan,
passportData,
disclosure,
handleDisclosureChange,
address,
setAddress,
generatingProof,
handleProve,
step,
mintText,
proof,
proofTime,
handleMint,
totalTime,
setStep,
passportNumber,
setPassportNumber,
dateOfBirth,
setDateOfBirth,
dateOfExpiry,
setDateOfExpiry
}
) => {
interface MainScreenProps {
onStartCameraScan: () => void;
nfcScan: () => void;
passportData: any;
disclosure: boolean;
handleDisclosureChange: (disclosure: boolean) => void;
address: string;
setAddress: (address: string) => void;
generatingProof: boolean;
handleProve: () => void;
step: number;
mintText: string;
proof: any;
proofTime: number;
handleMint: () => void;
totalTime: number;
setStep: (step: number) => void;
passportNumber: string;
setPassportNumber: (number: string) => void;
dateOfBirth: string;
setDateOfBirth: (date: string) => void;
dateOfExpiry: string;
setDateOfExpiry: (date: string) => void;
}
const MainScreen: React.FC<MainScreenProps> = ({
onStartCameraScan,
nfcScan,
passportData,
disclosure,
handleDisclosureChange,
address,
setAddress,
generatingProof,
handleProve,
step,
mintText,
proof,
proofTime,
handleMint,
totalTime,
setStep,
passportNumber,
setPassportNumber,
dateOfBirth,
setDateOfBirth,
dateOfExpiry,
setDateOfExpiry
}) => {
const [selectedTab, setSelectedTab] = useState("scan");
const [selectedApp, setSelectedApp] = useState<App | null>(null);
const [brokenCamera, setBrokenCamera] = useState(false);
@@ -70,7 +97,7 @@ const MainScreen = (
<XStack w="100%" jc="space-between" ai="center" ph="$4" pv="$2" bc="#fff" p="$3">
<XStack></XStack>
<Text>
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "App" : "Prove")}
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "Apps" : "Prove")}
</Text>
<Dialog
@@ -224,7 +251,6 @@ const MainScreen = (
proofTime={proofTime}
handleMint={handleMint}
totalTime={totalTime}
setStep={setStep}
/>
</Tabs.Content>
@@ -235,7 +261,7 @@ const MainScreen = (
<Tabs.Tab unstyled value="scan" w="33%" backgroundColor="transparent" >
<YStack ai="center">
<Scan color={selectedTab === "scan" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "scan" ? '#3185FC' : 'black'}>Scan</SizableText>
<Text color={selectedTab === "scan" ? '#3185FC' : 'black'}>Scan</Text>
</YStack>
</Tabs.Tab>
@@ -243,14 +269,14 @@ const MainScreen = (
<Tabs.Tab unstyled value="scan" w="33%" backgroundColor="transparent" >
<YStack ai="center">
<LayoutGrid color="#eeeeee" />
<SizableText color="#eeeeee">App</SizableText>
<Text color="#eeeeee">Apps</Text>
</YStack>
</Tabs.Tab>
:
<Tabs.Tab unstyled value="app" w="33%" backgroundColor="transparent" >
<YStack ai="center">
<LayoutGrid color={selectedTab === "app" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "app" ? '#3185FC' : 'black'}>App</SizableText>
<Text color={selectedTab === "app" ? '#3185FC' : 'black'}>Apps</Text>
</YStack>
</Tabs.Tab>
}
@@ -261,14 +287,14 @@ const MainScreen = (
<Tabs.Tab unstyled value={step < Steps.NFC_SCAN_COMPLETED ? "scan" : "app"} w="33%" backgroundColor="transparent">
<YStack ai="center">
<UserCheck color="#eeeeee" />
<SizableText color="#eeeeee">Prove</SizableText>
<Text color="#eeeeee">Prove</Text>
</YStack>
</Tabs.Tab>
:
<Tabs.Tab unstyled value="generate" w="33%" backgroundColor="transparent">
<YStack ai="center">
<UserCheck color={selectedTab === "generate" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "generate" ? '#3185FC' : 'black'}>Prove</SizableText>
<Text color={selectedTab === "generate" ? '#3185FC' : 'black'}>Prove</Text>
</YStack>
</Tabs.Tab>
}

View File

@@ -1,12 +1,32 @@
import React from 'react';
import { YStack, XStack, Text, Checkbox, Input, Button, Spinner, SizableText, Image } from 'tamagui';
import { YStack, XStack, Text, Checkbox, Input, Button, Spinner, Image } from 'tamagui';
import { Check } from '@tamagui/lucide-icons';
import { getFirstName, formatDuration } from '../../utils/utils';
import { attributeToPosition } from '../../../common/src/constants/constants';
import { Steps } from '../utils/utils';
import USER from '../images/user.png'
import ProofGrid from '../components/ProofGrid';
import { App } from '../utils/AppClass';
const ProveScreen = ({
interface ProveScreenProps {
selectedApp: App | null;
passportData: any;
disclosure: any;
handleDisclosureChange: (disclosure: boolean) => void;
address: string;
setAddress: (address: string) => void;
generatingProof: boolean;
handleProve: () => void;
handleMint: () => void;
step: number;
mintText: string;
proof: { proof: string, inputs: string } | null;
proofTime: number;
totalTime: number;
}
const ProveScreen: React.FC<ProveScreenProps> = ({
passportData,
disclosure,
selectedApp,
@@ -16,13 +36,13 @@ const ProveScreen = ({
generatingProof,
handleProve,
step,
setStep,
mintText,
proof,
proofTime,
handleMint,
totalTime
}) => {
return (
<YStack space="$4" p="$4" >
{
@@ -40,12 +60,12 @@ const ProveScreen = ({
/>
</YStack>
<YStack mt="$12">
<SizableText mb="$1">Hi {getFirstName(passportData.mrz)},</SizableText>
<Text mb="$2">{selectedApp.name} is asking for the following information:</Text>
{Object.keys(selectedApp.disclosure).map((key) => {
const keyy = key as keyof typeof disclosure;
const indexes = attributeToPosition[keyy];
const keyFormatted = keyy.replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
<Text mb="$1">Hi {getFirstName(passportData.mrz)},</Text>
<Text mb="$2">{selectedApp?.name} is asking for the following information:</Text>
{selectedApp && Object.keys(selectedApp.disclosure).map((key) => {
const key_ = key as keyof typeof disclosure;
const indexes = attributeToPosition[key_];
const keyFormatted = key_.replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1]);
const mrzAttributeFormatted = mrzAttribute.replace(/</g, ' ');
@@ -54,8 +74,8 @@ const ProveScreen = ({
<Checkbox
value={key}
checked={disclosure[keyy]}
onCheckedChange={() => handleDisclosureChange(keyy)}
checked={disclosure[key_]}
onCheckedChange={() => handleDisclosureChange(key_)}
aria-label={keyFormatted}
size="$5"
>
@@ -91,13 +111,11 @@ const ProveScreen = ({
</Button>
</YStack>
) : (
<YStack m="$2">
<Text>Zero-knowledge proof generated</Text>
<YStack m="$2" justifyContent='center' alignItems='center' gap="$5">
<Text fontWeight="bold">Zero-knowledge proof generated:</Text>
<ProofGrid proof={proof} />
<Text fontWeight="bold">Proof:</Text>
<Text>{JSON.stringify(proof)}</Text>
<Text fontWeight="bold">Proof Duration: {formatDuration(proofTime)}</Text>
<Text fontWeight="bold" mt="$2">Proof Duration: {formatDuration(proofTime)}</Text>
<Text fontWeight="bold">Total Duration: {formatDuration(totalTime)}</Text>
<Button borderRadius={100} onPress={handleMint} marginTop="$4" mb="$4" backgroundColor="#3185FC">

View File

@@ -1,11 +1,17 @@
import React from 'react';
import { YStack, Text, Spinner, Circle, ZStack, XStack, SizableText } from 'tamagui'; // Ensure correct import paths based on your project setup
import { YStack, Text, Spinner, Circle, ZStack, XStack } from 'tamagui'; // Ensure correct import paths based on your project setup
import { Steps } from '../utils/utils';
const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
interface ScanScreenProps {
onStartCameraScan: () => void;
nfcScan: () => void;
step: number;
}
const ScanScreen: React.FC<ScanScreenProps> = ({ onStartCameraScan, nfcScan, step }) => {
return (
<YStack >
<ZStack alignSelf='center' maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<YStack gap="$1" >
<ZStack alignSelf='center' maxWidth={50} maxHeight={50} width={50} flex={0} space="$0">
<Circle
alignSelf='center'
h={22}
@@ -13,20 +19,20 @@ const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
borderWidth={1.6}
p={0}
/>
<SizableText
<Text
alignSelf='center'
h={22}
w={22}
y={-1}
y={1}
x={7}
color="black"
fow="bold"
>1</SizableText>
>1</Text>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={step === Steps.MRZ_SCAN ? "bold" : "normal"} >Scan the machine readable zone on the main page of your passport</Text>
<Text textAlign='center' mt="$5" px="$4" fow={step === Steps.MRZ_SCAN ? "bold" : "normal"} >Scan the machine readable zone on the main page of your passport</Text>
<ZStack alignSelf='center' mt="$8" maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<ZStack alignSelf='center' mt="$5" maxWidth={50} maxHeight={50} width={50} flex={0} space="$0">
<Circle
alignSelf='center'
h={22}
@@ -34,19 +40,19 @@ const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
borderWidth={1.6}
p={0}
/>
<SizableText
<Text
alignSelf='center'
h={22}
w={22}
y={-1}
y={1}
x={7}
color="black"
fow="bold"
>2</SizableText>
>2</Text>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={(step === Steps.MRZ_SCAN_COMPLETED) || (step === Steps.NFC_SCANNING) ? "bold" : "normal"}>Hold your passport against your device to read the biometric chip</Text>
<Text textAlign='center' mt="$5" px="$4" fow={(step === Steps.MRZ_SCAN_COMPLETED) || (step === Steps.NFC_SCANNING) ? "bold" : "normal"}>Hold your passport against your device to read the biometric chip</Text>
<ZStack alignSelf='center' mt="$8" maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<ZStack alignSelf='center' mt="$5" maxWidth={50} maxHeight={50} width={50} flex={0} space="$0">
<Circle
alignSelf='center'
h={22}
@@ -54,17 +60,17 @@ const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
borderWidth={1.6}
p={0}
/>
<SizableText
<Text
alignSelf='center'
h={22}
w={22}
y={-1}
y={1}
x={7}
color="black"
fow="bold"
>3</SizableText>
>3</Text>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={step >= Steps.NFC_SCAN_COMPLETED ? "bold" : "normal"}>Select App</Text>
<Text textAlign='center' mt="$5" px="$4" fow={step >= Steps.NFC_SCAN_COMPLETED ? "bold" : "normal"}>Select App</Text>
<YStack w="100%" ai="center">
{

View File

@@ -4153,6 +4153,11 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
ctx-polyfill@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ctx-polyfill/-/ctx-polyfill-1.1.4.tgz#08420bc5c540d08ac36d05720ca503c65e302d65"
integrity sha512-tz65F3/zmZ2+CMtn4MhNhYi/yIN9dKnItMKzSkH2GE6dBpAIbUR0K5pSHWqUL3OuNBCA70DKlWZYUClHfydIXg==
dayjs@^1.8.15:
version "1.11.9"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
@@ -7184,6 +7189,13 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-native-canvas@^0.1.39:
version "0.1.39"
resolved "https://registry.yarnpkg.com/react-native-canvas/-/react-native-canvas-0.1.39.tgz#2f9d14d7cda77cdc0c9cc13d3b6d12d4ce777c9c"
integrity sha512-UYAMoNvmGgBU+gk2WVg+GVDotMH+tujlsw9TR8FFLlcCqAzhdEnh5tQRA0G12DkusjwfJNiEiDagRX/MhKLLMA==
dependencies:
ctx-polyfill "^1.1.4"
react-native-dotenv@^3.4.9:
version "3.4.9"
resolved "https://registry.yarnpkg.com/react-native-dotenv/-/react-native-dotenv-3.4.9.tgz#621c5b0c1d0c5c7f569bfe5a1d804bec7885c010"