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 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 <TouchableOpacity 84 onPress={() => Linking.openURL('https://expo.fyi/unverified-app-expo-go')}> 85 <Row 86 bg="warning" 87 border="warning" 88 rounded="medium" 89 padding="0" 90 align="center" 91 style={{ 92 alignSelf: 'flex-start', 93 marginTop: 3, 94 }}> 95 <Ionicons 96 name={Platform.select({ ios: 'ios-warning', default: 'md-warning' })} 97 size={14} 98 color={theme.text.warning} 99 lightColor={theme.text.warning} 100 darkColor={theme.text.warning} 101 style={{ 102 marginHorizontal: 4, 103 }} 104 /> 105 <Text 106 color="warning" 107 type="InterSemiBold" 108 size="small" 109 style={{ 110 marginRight: 4, 111 }}> 112 Unverified 113 </Text> 114 </Row> 115 </TouchableOpacity> 116 )} 117 </View> 118 </Row> 119 </View> 120 ); 121} 122 123const styles = StyleSheet.create({ 124 taskIcon: { 125 width: 40, 126 height: 40, 127 marginRight: 8, 128 borderRadius: 8, 129 alignSelf: 'center', 130 backgroundColor: 'transparent', 131 }, 132}); 133