1import Ionicons from '@expo/vector-icons/build/Ionicons'; 2import Constants from 'expo-constants'; 3import { Row, View, Text, useExpoTheme } from 'expo-dev-client-components'; 4import React from 'react'; 5import { Image, Platform, StyleSheet } from 'react-native'; 6 7type Props = { 8 task: { manifestUrl: string; manifestString: string }; 9}; 10 11function stringOrUndefined<T>(anything: T): string | undefined { 12 if (typeof anything === 'string') { 13 return anything; 14 } 15 16 return undefined; 17} 18 19function getInfoFromManifest( 20 manifest: NonNullable<typeof Constants.manifest | typeof Constants.manifest2> 21): { 22 iconUrl?: string; 23 taskName?: string; 24 sdkVersion?: string; 25 runtimeVersion?: string; 26 isVerified?: boolean; 27} { 28 if ('metadata' in manifest) { 29 // modern manifest 30 return { 31 iconUrl: undefined, // no icon for modern manifests 32 taskName: manifest.extra?.expoClient?.name, 33 sdkVersion: manifest.extra?.expoClient?.sdkVersion, 34 runtimeVersion: stringOrUndefined(manifest.runtimeVersion), 35 isVerified: (manifest as any).isVerified, 36 }; 37 } else { 38 return { 39 iconUrl: manifest.iconUrl, 40 taskName: manifest.name, 41 sdkVersion: manifest.sdkVersion, 42 runtimeVersion: stringOrUndefined(manifest.runtimeVersion), 43 isVerified: manifest.isVerified, 44 }; 45 } 46} 47 48export function DevMenuTaskInfo({ task }: Props) { 49 const theme = useExpoTheme(); 50 51 const manifest = task.manifestString 52 ? (JSON.parse(task.manifestString) as typeof Constants.manifest | typeof Constants.manifest2) 53 : null; 54 const manifestInfo = manifest ? getInfoFromManifest(manifest) : null; 55 56 return ( 57 <View> 58 <Row bg="default" padding="medium"> 59 {manifestInfo?.iconUrl ? ( 60 <Image source={{ uri: manifestInfo.iconUrl }} style={styles.taskIcon} /> 61 ) : null} 62 <View flex="1" style={{ justifyContent: 'center' }}> 63 <Text type="InterBold" color="default" size="medium" numberOfLines={1}> 64 {manifestInfo?.taskName ? manifestInfo.taskName : 'Untitled Experience'} 65 </Text> 66 {manifestInfo?.sdkVersion && ( 67 <Text size="small" type="InterRegular" color="secondary"> 68 SDK version:{' '} 69 <Text type="InterSemiBold" color="secondary" size="small"> 70 {manifestInfo.sdkVersion} 71 </Text> 72 </Text> 73 )} 74 {manifestInfo?.runtimeVersion && ( 75 <Text size="small" type="InterRegular" color="secondary"> 76 Runtime version:{' '} 77 <Text type="InterSemiBold" color="secondary" size="small"> 78 {manifestInfo.runtimeVersion} 79 </Text> 80 </Text> 81 )} 82 {!manifestInfo?.isVerified && ( 83 <Row 84 bg="warning" 85 border="warning" 86 rounded="medium" 87 padding="0" 88 align="center" 89 style={{ 90 alignSelf: 'flex-start', 91 marginTop: 3, 92 }}> 93 <Ionicons 94 name={Platform.select({ ios: 'ios-warning', default: 'md-warning' })} 95 size={14} 96 color={theme.text.warning} 97 lightColor={theme.text.warning} 98 darkColor={theme.text.warning} 99 style={{ 100 marginHorizontal: 4, 101 }} 102 /> 103 <Text 104 color="warning" 105 type="InterSemiBold" 106 size="small" 107 style={{ 108 marginRight: 4, 109 }}> 110 Unverified 111 </Text> 112 </Row> 113 )} 114 </View> 115 </Row> 116 </View> 117 ); 118} 119 120const styles = StyleSheet.create({ 121 taskIcon: { 122 width: 40, 123 height: 40, 124 marginRight: 8, 125 borderRadius: 8, 126 alignSelf: 'center', 127 backgroundColor: 'transparent', 128 }, 129}); 130