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, Linking, Platform, StyleSheet, TouchableOpacity } 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 // no properties for bare manifests 39 return { 40 iconUrl: undefined, 41 taskName: undefined, 42 sdkVersion: undefined, 43 runtimeVersion: undefined, 44 isVerified: undefined, 45 }; 46 } 47} 48 49export function DevMenuTaskInfo({ task }: Props) { 50 const theme = useExpoTheme(); 51 52 const manifest = task.manifestString 53 ? (JSON.parse(task.manifestString) as typeof Constants.manifest | typeof Constants.manifest2) 54 : null; 55 const manifestInfo = manifest ? getInfoFromManifest(manifest) : null; 56 57 return ( 58 <View> 59 <Row bg="default" padding="medium"> 60 {manifestInfo?.iconUrl ? ( 61 <Image source={{ uri: manifestInfo.iconUrl }} style={styles.taskIcon} /> 62 ) : null} 63 <View flex="1" style={{ justifyContent: 'center' }}> 64 <Text type="InterBold" color="default" size="medium" numberOfLines={1}> 65 {manifestInfo?.taskName ? manifestInfo.taskName : 'Untitled Experience'} 66 </Text> 67 {manifestInfo?.sdkVersion && ( 68 <Text size="small" type="InterRegular" color="secondary"> 69 SDK version:{' '} 70 <Text type="InterSemiBold" color="secondary" size="small"> 71 {manifestInfo.sdkVersion} 72 </Text> 73 </Text> 74 )} 75 {manifestInfo?.runtimeVersion && ( 76 <Text size="small" type="InterRegular" color="secondary"> 77 Runtime version:{' '} 78 <Text type="InterSemiBold" color="secondary" size="small"> 79 {manifestInfo.runtimeVersion} 80 </Text> 81 </Text> 82 )} 83 {!manifestInfo?.isVerified && ( 84 <TouchableOpacity 85 onPress={() => Linking.openURL('https://expo.fyi/unverified-app-expo-go')}> 86 <Row 87 bg="warning" 88 border="warning" 89 rounded="medium" 90 padding="0" 91 align="center" 92 style={{ 93 alignSelf: 'flex-start', 94 marginTop: 3, 95 }}> 96 <Ionicons 97 name={Platform.select({ ios: 'ios-warning', default: 'md-warning' })} 98 size={14} 99 color={theme.text.warning} 100 lightColor={theme.text.warning} 101 darkColor={theme.text.warning} 102 style={{ 103 marginHorizontal: 4, 104 }} 105 /> 106 <Text 107 color="warning" 108 type="InterSemiBold" 109 size="small" 110 style={{ 111 marginRight: 4, 112 }}> 113 Unverified 114 </Text> 115 </Row> 116 </TouchableOpacity> 117 )} 118 </View> 119 </Row> 120 </View> 121 ); 122} 123 124const styles = StyleSheet.create({ 125 taskIcon: { 126 width: 40, 127 height: 40, 128 marginRight: 8, 129 borderRadius: 8, 130 alignSelf: 'center', 131 backgroundColor: 'transparent', 132 }, 133}); 134