expo-dev-launcher 2.4.2 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ import { useWindowDimensions } from 'react-native';
16
16
  import { SafeAreaTop } from '../components/SafeAreaTop';
17
17
  import { useBuildInfo } from '../providers/BuildInfoProvider';
18
18
  import { useUser } from '../providers/UserContextProvider';
19
+ import { Avatar } from './Avatar';
19
20
 
20
21
  export function AppHeader({ navigation }) {
21
22
  const buildInfo = useBuildInfo();
@@ -29,7 +30,7 @@ export function AppHeader({ navigation }) {
29
30
  };
30
31
 
31
32
  const isAuthenticated = userData != null;
32
- const selectedUserImage = selectedAccount?.owner?.profilePhoto ?? null;
33
+ const selectedUserImage = selectedAccount?.ownerUserActor?.profilePhoto ?? null;
33
34
 
34
35
  return (
35
36
  <>
@@ -69,11 +70,16 @@ export function AppHeader({ navigation }) {
69
70
  <View>
70
71
  {isAuthenticated ? (
71
72
  <View rounded="full" padding="small">
72
- <View height="xl" width="xl" bg="secondary" rounded="full">
73
- {selectedUserImage && (
74
- <Image size="xl" rounded="full" source={{ uri: selectedUserImage }} />
75
- )}
76
- </View>
73
+ <Avatar
74
+ profilePhoto={selectedUserImage}
75
+ name={
76
+ selectedAccount?.ownerUserActor?.fullName
77
+ ? selectedAccount.ownerUserActor.fullName
78
+ : selectedAccount?.name
79
+ }
80
+ isOrganization={selectedAccount?.ownerUserActor === null}
81
+ size="xl"
82
+ />
77
83
  </View>
78
84
  ) : (
79
85
  <View mx="small">
@@ -84,20 +90,6 @@ export function AppHeader({ navigation }) {
84
90
  )}
85
91
  </View>
86
92
  </Button.HighlightOnPressContainer>
87
- {!selectedUserImage && (
88
- <Row
89
- style={{
90
- height: scale[2],
91
- flexWrap: 'wrap',
92
- maxWidth: scale[16],
93
- paddingRight: scale[2],
94
- transform: [{ translateY: -scale[2] }],
95
- }}>
96
- <Text numberOfLines={1} size="small" align="center" weight="medium">
97
- {selectedAccount?.name}
98
- </Text>
99
- </Row>
100
- )}
101
93
  </View>
102
94
  </Row>
103
95
  </View>
@@ -0,0 +1,96 @@
1
+ import { iconSize, palette } from '@expo/styleguide-native';
2
+ import { View, Image, scale, BuildingIcon, UserIcon } from 'expo-dev-client-components';
3
+ import React from 'react';
4
+ import { StyleSheet } from 'react-native';
5
+
6
+ type Props = {
7
+ name?: string;
8
+ profilePhoto?: string;
9
+ isOrganization?: boolean;
10
+ size?: React.ComponentProps<typeof Image>['size'];
11
+ };
12
+
13
+ export function Avatar({ profilePhoto, size = 'large', name = '', isOrganization = false }: Props) {
14
+ const firstLetter = name?.charAt(0).toLowerCase();
15
+ const viewSize = getViewSize(size);
16
+
17
+ if (isOrganization) {
18
+ const { backgroundColor, tintColor } = getOrganizationColor(firstLetter);
19
+ return (
20
+ <View
21
+ style={{
22
+ height: viewSize,
23
+ width: viewSize,
24
+ backgroundColor,
25
+ }}
26
+ rounded="full"
27
+ align="centered"
28
+ bg="secondary">
29
+ <BuildingIcon resizeMode="center" style={styles.icon} tintColor={tintColor} />
30
+ </View>
31
+ );
32
+ }
33
+
34
+ if (!profilePhoto || !firstLetter) {
35
+ return (
36
+ <View
37
+ style={{ height: viewSize, width: viewSize }}
38
+ rounded="full"
39
+ align="centered"
40
+ bg="secondary">
41
+ <UserIcon resizeMode="center" style={styles.icon} />
42
+ </View>
43
+ );
44
+ }
45
+
46
+ let _profilePhoto = profilePhoto;
47
+ if (profilePhoto.match('gravatar.com')) {
48
+ const defaultProfilePhoto = encodeURIComponent(
49
+ `https://storage.googleapis.com/expo-website-default-avatars-2023/${firstLetter}.png`
50
+ );
51
+ _profilePhoto = `${profilePhoto}&d=${defaultProfilePhoto}`;
52
+ }
53
+
54
+ return (
55
+ <View rounded="full" bg="secondary">
56
+ <Image rounded="full" source={{ uri: _profilePhoto }} size={size} />
57
+ </View>
58
+ );
59
+ }
60
+
61
+ function getOrganizationColor(firstLetter?: string) {
62
+ if (firstLetter?.match(/[a-d]/)) {
63
+ return { backgroundColor: palette.light.blue[200], tintColor: palette.light.blue[900] };
64
+ } else if (firstLetter?.match(/[e-h]/)) {
65
+ return { backgroundColor: palette.light.green[200], tintColor: palette.light.green[900] };
66
+ } else if (firstLetter?.match(/[i-l]/)) {
67
+ return { backgroundColor: palette.light.yellow[400], tintColor: palette.light.yellow[900] };
68
+ } else if (firstLetter?.match(/[m-p]/)) {
69
+ return { backgroundColor: palette.light.orange[200], tintColor: palette.light.orange[900] };
70
+ } else if (firstLetter?.match(/[q-t]/)) {
71
+ return { backgroundColor: palette.light.red[200], tintColor: palette.light.red[900] };
72
+ } else if (firstLetter?.match(/[u-z]/)) {
73
+ return { backgroundColor: palette.light.pink[200], tintColor: palette.light.pink[900] };
74
+ } else {
75
+ return { backgroundColor: palette.light.purple[200], tintColor: palette.light.purple[900] };
76
+ }
77
+ }
78
+
79
+ function getViewSize(size?: React.ComponentProps<typeof Image>['size']) {
80
+ switch (size) {
81
+ case 'tiny':
82
+ return scale.small;
83
+ case 'small':
84
+ return iconSize.small;
85
+ case 'large':
86
+ return iconSize.large;
87
+ case 'xl':
88
+ return scale.xl;
89
+ default:
90
+ return iconSize.large;
91
+ }
92
+ }
93
+
94
+ const styles = StyleSheet.create({
95
+ icon: { width: '45%', height: '45%' },
96
+ });
@@ -5,7 +5,6 @@ import { apiClient } from '../apiClient';
5
5
  export type UserData = {
6
6
  id: string;
7
7
  appCount: number;
8
- email: string;
9
8
  username: string;
10
9
  profilePhoto: string;
11
10
  accounts: UserAccount[];
@@ -15,19 +14,18 @@ export type UserData = {
15
14
  export type UserAccount = {
16
15
  id: string;
17
16
  name: string;
18
- owner?: {
17
+ ownerUserActor?: {
19
18
  username: string;
20
19
  fullName?: string;
21
- profilePhoto?: string;
20
+ profilePhoto: string;
22
21
  };
23
22
  };
24
23
 
25
24
  const query = gql`
26
25
  {
27
- me {
26
+ meUserActor {
28
27
  id
29
28
  appCount
30
- email
31
29
  profilePhoto
32
30
  username
33
31
  isExpoAdmin
@@ -35,7 +33,7 @@ const query = gql`
35
33
  accounts {
36
34
  id
37
35
  name
38
- owner {
36
+ ownerUserActor {
39
37
  username
40
38
  fullName
41
39
  profilePhoto
@@ -46,5 +44,6 @@ const query = gql`
46
44
  `;
47
45
 
48
46
  export async function getUserProfileAsync() {
49
- return apiClient.request(query).then((data) => data.me as UserData);
47
+ const data = await apiClient.request(query);
48
+ return data.meUserActor as UserData;
50
49
  }
@@ -14,6 +14,7 @@ import * as React from 'react';
14
14
  import { ActivityIndicator, ScrollView } from 'react-native';
15
15
  import { SafeAreaView } from 'react-native-safe-area-context';
16
16
 
17
+ import { Avatar } from '../components/Avatar';
17
18
  import { LogoutConfirmationModal } from '../components/LogoutConfirmationModal';
18
19
  import { UserAccount, UserData } from '../functions/getUserProfileAsync';
19
20
  import { useModalStack } from '../providers/ModalStackProvider';
@@ -186,8 +187,8 @@ function UserAccountSelector({
186
187
  const orgs: UserAccount[] = [];
187
188
 
188
189
  for (const account of userData.accounts) {
189
- if (account != null) {
190
- if (account.owner != null) {
190
+ if (account) {
191
+ if (account.ownerUserActor) {
191
192
  accounts.push(account);
192
193
  } else {
193
194
  orgs.push(account);
@@ -213,17 +214,20 @@ function UserAccountSelector({
213
214
  roundedBottom={isLast ? 'large' : 'none'}
214
215
  roundedTop={isFirst ? 'large' : 'none'}>
215
216
  <Row align="center" py="small" px="medium" bg="default">
216
- <View rounded="full" bg="secondary">
217
- <Image
218
- size="large"
219
- rounded="full"
220
- source={{ uri: account.owner?.profilePhoto }}
221
- />
222
- </View>
217
+ <Avatar
218
+ profilePhoto={account?.ownerUserActor?.profilePhoto}
219
+ name={
220
+ account?.ownerUserActor?.fullName
221
+ ? account.ownerUserActor.fullName
222
+ : account?.name
223
+ }
224
+ isOrganization={account?.ownerUserActor === null}
225
+ size="large"
226
+ />
223
227
  <Spacer.Horizontal size="small" />
224
228
 
225
229
  <View>
226
- <Heading>{account.owner?.username ?? account.name}</Heading>
230
+ <Heading>{account.ownerUserActor?.username ?? account.name}</Heading>
227
231
  </View>
228
232
 
229
233
  <Spacer.Vertical />