diff --git a/native/avatars/avatar.react.js b/native/avatars/avatar.react.js index 9e70c6aba..1da6ed296 100644 --- a/native/avatars/avatar.react.js +++ b/native/avatars/avatar.react.js @@ -1,127 +1,147 @@ // @flow import * as React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import type { ResolvedClientAvatar } from 'lib/types/avatar-types.js'; import Multimedia from '../media/multimedia.react.js'; +export type AvatarSize = + | 'micro' + | 'small' + | 'large' + | 'profile' + | 'profileLarge'; + type Props = { +avatarInfo: ResolvedClientAvatar, - +size: 'micro' | 'small' | 'large' | 'profile', + +size: AvatarSize, }; function Avatar(props: Props): React.Node { const { avatarInfo, size } = props; const containerSizeStyle = React.useMemo(() => { if (size === 'micro') { return styles.micro; } else if (size === 'small') { return styles.small; } else if (size === 'large') { return styles.large; + } else if (size === 'profile') { + return styles.profile; } - return styles.profile; + return styles.profileLarge; }, [size]); const emojiContainerStyle = React.useMemo(() => { const containerStyles = [styles.emojiContainer, containerSizeStyle]; if (avatarInfo.type === 'emoji') { const backgroundColor = { backgroundColor: `#${avatarInfo.color}` }; containerStyles.push(backgroundColor); } return containerStyles; }, [avatarInfo, containerSizeStyle]); const emojiSizeStyle = React.useMemo(() => { if (size === 'micro') { return styles.emojiMicro; } else if (size === 'small') { return styles.emojiSmall; } else if (size === 'large') { return styles.emojiLarge; + } else if (size === 'profile') { + return styles.emojiProfile; } - return styles.emojiProfile; + return styles.emojiProfileLarge; }, [size]); const avatar = React.useMemo(() => { if (avatarInfo.type === 'image') { const avatarMediaInfo = { type: 'photo', uri: avatarInfo.uri, }; return ( ); } return ( {avatarInfo.emoji} ); }, [ avatarInfo.emoji, avatarInfo.type, avatarInfo.uri, containerSizeStyle, emojiContainerStyle, emojiSizeStyle, ]); return avatar; } const styles = StyleSheet.create({ emojiContainer: { alignItems: 'center', justifyContent: 'center', }, emojiLarge: { fontSize: 28, textAlign: 'center', }, emojiMicro: { fontSize: 9, textAlign: 'center', }, emojiProfile: { + fontSize: 64, + textAlign: 'center', + }, + emojiProfileLarge: { fontSize: 80, textAlign: 'center', }, emojiSmall: { fontSize: 14, textAlign: 'center', }, imageContainer: { overflow: 'hidden', }, large: { borderRadius: 20, height: 40, width: 40, }, micro: { borderRadius: 8, height: 16, width: 16, }, profile: { + borderRadius: 45, + height: 90, + width: 90, + }, + profileLarge: { borderRadius: 56, height: 112, width: 112, }, small: { borderRadius: 12, height: 24, width: 24, }, }); export default Avatar; diff --git a/native/avatars/edit-thread-avatar.react.js b/native/avatars/edit-thread-avatar.react.js index 27c616ff9..979a216af 100644 --- a/native/avatars/edit-thread-avatar.react.js +++ b/native/avatars/edit-thread-avatar.react.js @@ -1,121 +1,121 @@ // @flow import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; import { EditThreadAvatarContext } from 'lib/components/base-edit-thread-avatar-provider.react.js'; import type { RawThreadInfo, ThreadInfo } from 'lib/types/thread-types.js'; import { useNativeSetThreadAvatar, useSelectFromGalleryAndUpdateThreadAvatar, useShowAvatarActionSheet, } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; import ThreadAvatar from './thread-avatar.react.js'; import { EmojiThreadAvatarCreationRouteName, ThreadAvatarCameraModalRouteName, } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; type Props = { +threadInfo: RawThreadInfo | ThreadInfo, +disabled?: boolean, }; function EditThreadAvatar(props: Props): React.Node { const styles = useStyles(unboundStyles); const { threadInfo, disabled } = props; const editThreadAvatarContext = React.useContext(EditThreadAvatarContext); invariant(editThreadAvatarContext, 'editThreadAvatarContext should be set'); const { threadAvatarSaveInProgress } = editThreadAvatarContext; const nativeSetThreadAvatar = useNativeSetThreadAvatar(); const selectFromGalleryAndUpdateThreadAvatar = useSelectFromGalleryAndUpdateThreadAvatar(); const { navigate } = useNavigation(); const navigateToThreadEmojiAvatarCreation = React.useCallback(() => { navigate<'EmojiThreadAvatarCreation'>({ name: EmojiThreadAvatarCreationRouteName, params: { threadInfo, }, }); }, [navigate, threadInfo]); const selectFromGallery = React.useCallback( () => selectFromGalleryAndUpdateThreadAvatar(threadInfo.id), [selectFromGalleryAndUpdateThreadAvatar, threadInfo.id], ); const navigateToCamera = React.useCallback(() => { navigate<'ThreadAvatarCameraModal'>({ name: ThreadAvatarCameraModalRouteName, params: { threadID: threadInfo.id }, }); }, [navigate, threadInfo.id]); const removeAvatar = React.useCallback( () => nativeSetThreadAvatar(threadInfo.id, { type: 'remove' }), [nativeSetThreadAvatar, threadInfo.id], ); const actionSheetConfig = React.useMemo(() => { const configOptions = [ { id: 'emoji', onPress: navigateToThreadEmojiAvatarCreation }, { id: 'image', onPress: selectFromGallery }, { id: 'camera', onPress: navigateToCamera }, ]; if (threadInfo.avatar) { configOptions.push({ id: 'remove', onPress: removeAvatar }); } return configOptions; }, [ navigateToCamera, navigateToThreadEmojiAvatarCreation, removeAvatar, selectFromGallery, threadInfo.avatar, ]); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig); let spinner; if (threadAvatarSaveInProgress) { spinner = ( ); } return ( - + {spinner} {!disabled ? : null} ); } const unboundStyles = { spinnerContainer: { position: 'absolute', alignItems: 'center', justifyContent: 'center', top: 0, bottom: 0, left: 0, right: 0, }, }; export default EditThreadAvatar; diff --git a/native/avatars/edit-user-avatar.react.js b/native/avatars/edit-user-avatar.react.js index d1880f8d3..89424f670 100644 --- a/native/avatars/edit-user-avatar.react.js +++ b/native/avatars/edit-user-avatar.react.js @@ -1,157 +1,157 @@ // @flow import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js'; import { useENSAvatar } from 'lib/hooks/ens-cache.js'; import { getETHAddressForUserInfo } from 'lib/shared/account-utils.js'; import type { GenericUserInfoWithAvatar } from 'lib/types/avatar-types.js'; import { useNativeSetUserAvatar, useSelectFromGalleryAndUpdateUserAvatar, useShowAvatarActionSheet, } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; import UserAvatar from './user-avatar.react.js'; import { EmojiUserAvatarCreationRouteName, UserAvatarCameraModalRouteName, EmojiAvatarSelectionRouteName, RegistrationUserAvatarCameraModalRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; type Props = | { +userID: ?string, +disabled?: boolean } | { +userInfo: ?GenericUserInfoWithAvatar, +disabled?: boolean, +prefetchedAvatarURI: ?string, }; function EditUserAvatar(props: Props): React.Node { const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); const { userAvatarSaveInProgress, getRegistrationModeEnabled } = editUserAvatarContext; const nativeSetUserAvatar = useNativeSetUserAvatar(); const selectFromGalleryAndUpdateUserAvatar = useSelectFromGalleryAndUpdateUserAvatar(); const currentUserInfo = useSelector(state => state.currentUserInfo); const userInfoProp = props.userInfo; const userInfo: ?GenericUserInfoWithAvatar = userInfoProp ?? currentUserInfo; const ethAddress = React.useMemo( () => getETHAddressForUserInfo(userInfo), [userInfo], ); const fetchedENSAvatarURI = useENSAvatar(ethAddress); const ensAvatarURI = fetchedENSAvatarURI ?? props.prefetchedAvatarURI; const { navigate } = useNavigation(); const usernameOrEthAddress = userInfo?.username; const navigateToEmojiSelection = React.useCallback(() => { if (!getRegistrationModeEnabled()) { navigate(EmojiUserAvatarCreationRouteName); return; } navigate<'EmojiAvatarSelection'>({ name: EmojiAvatarSelectionRouteName, params: { usernameOrEthAddress }, }); }, [navigate, getRegistrationModeEnabled, usernameOrEthAddress]); const navigateToCamera = React.useCallback(() => { navigate( getRegistrationModeEnabled() ? RegistrationUserAvatarCameraModalRouteName : UserAvatarCameraModalRouteName, ); }, [navigate, getRegistrationModeEnabled]); const setENSUserAvatar = React.useCallback(() => { nativeSetUserAvatar({ type: 'ens' }); }, [nativeSetUserAvatar]); const removeUserAvatar = React.useCallback(() => { nativeSetUserAvatar({ type: 'remove' }); }, [nativeSetUserAvatar]); const hasCurrentAvatar = !!userInfo?.avatar; const actionSheetConfig = React.useMemo(() => { const configOptions = [ { id: 'emoji', onPress: navigateToEmojiSelection }, { id: 'image', onPress: selectFromGalleryAndUpdateUserAvatar }, { id: 'camera', onPress: navigateToCamera }, ]; if (ensAvatarURI) { configOptions.push({ id: 'ens', onPress: setENSUserAvatar }); } if (hasCurrentAvatar) { configOptions.push({ id: 'remove', onPress: removeUserAvatar }); } return configOptions; }, [ hasCurrentAvatar, ensAvatarURI, navigateToCamera, navigateToEmojiSelection, removeUserAvatar, setENSUserAvatar, selectFromGalleryAndUpdateUserAvatar, ]); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig); const styles = useStyles(unboundStyles); let spinner; if (userAvatarSaveInProgress) { spinner = ( ); } const { userID } = props; const userAvatar = userID ? ( - + ) : ( - + ); const { disabled } = props; return ( {userAvatar} {spinner} {!disabled ? : null} ); } const unboundStyles = { spinnerContainer: { position: 'absolute', alignItems: 'center', justifyContent: 'center', top: 0, bottom: 0, left: 0, right: 0, }, }; export default EditUserAvatar; diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/avatars/emoji-avatar-creation.react.js index 90d9c8c0e..f160116a1 100644 --- a/native/avatars/emoji-avatar-creation.react.js +++ b/native/avatars/emoji-avatar-creation.react.js @@ -1,220 +1,220 @@ // @flow import * as React from 'react'; import { View, Text, TouchableWithoutFeedback, ActivityIndicator, } from 'react-native'; import type { UpdateUserAvatarRequest, ClientEmojiAvatar, } from 'lib/types/avatar-types'; import Avatar from './avatar.react.js'; import Button from '../components/button.react.js'; import ColorRows from '../components/color-rows.react.js'; import EmojiKeyboard from '../components/emoji-keyboard.react.js'; import { useStyles } from '../themes/colors.js'; type Props = { +saveAvatarCall: (newAvatarRequest: UpdateUserAvatarRequest) => mixed, +saveAvatarCallLoading: boolean, +savedEmojiAvatarFunc: () => ClientEmojiAvatar, }; function EmojiAvatarCreation(props: Props): React.Node { const { saveAvatarCall, saveAvatarCallLoading, savedEmojiAvatarFunc } = props; const [pendingEmoji, setPendingEmoji] = React.useState( () => savedEmojiAvatarFunc().emoji, ); const [pendingColor, setPendingColor] = React.useState( () => savedEmojiAvatarFunc().color, ); const [emojiKeyboardOpen, setEmojiKeyboardOpen] = React.useState(false); const styles = useStyles(unboundStyles); const onPressEditEmoji = React.useCallback(() => { setEmojiKeyboardOpen(true); }, []); const onPressSetAvatar = React.useCallback(() => { const newEmojiAvatarRequest = { type: 'emoji', emoji: pendingEmoji, color: pendingColor, }; saveAvatarCall(newEmojiAvatarRequest); }, [pendingColor, pendingEmoji, saveAvatarCall]); const onPressReset = React.useCallback(() => { const resetEmojiAvatar = savedEmojiAvatarFunc(); setPendingEmoji(resetEmojiAvatar.emoji); setPendingColor(resetEmojiAvatar.color); }, [savedEmojiAvatarFunc]); const onEmojiSelected = React.useCallback(emoji => { setPendingEmoji(emoji.emoji); }, []); const onEmojiKeyboardClose = React.useCallback( () => setEmojiKeyboardOpen(false), [], ); const stagedAvatarInfo: ClientEmojiAvatar = React.useMemo( () => ({ type: 'emoji', emoji: pendingEmoji, color: pendingColor, }), [pendingColor, pendingEmoji], ); const loadingContainer = React.useMemo(() => { if (!saveAvatarCallLoading) { return null; } return ( ); }, [saveAvatarCallLoading, styles.loadingContainer]); const alreadySelectedEmojis = React.useMemo( () => [pendingEmoji], [pendingEmoji], ); return ( - + {loadingContainer} Edit Emoji ); } const unboundStyles = { container: { flexGrow: 1, flex: 1, justifyContent: 'space-between', }, emojiAvatarCreationContainer: { paddingTop: 16, }, stagedAvatarSection: { backgroundColor: 'panelForeground', paddingVertical: 24, alignItems: 'center', }, editEmojiText: { color: 'purpleLink', marginTop: 16, fontWeight: '500', fontSize: 16, lineHeight: 24, textAlign: 'center', }, colorRowsSection: { paddingVertical: 24, marginTop: 24, backgroundColor: 'panelForeground', alignItems: 'center', }, selectedColorOuterRing: { backgroundColor: 'modalSubtext', }, buttonsContainer: { flexGrow: 1, paddingHorizontal: 16, paddingBottom: 8, justifyContent: 'flex-end', }, saveButton: { backgroundColor: 'purpleButton', paddingVertical: 12, borderRadius: 8, }, saveButtonText: { color: 'whiteText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, resetButton: { padding: 12, borderRadius: 8, marginTop: 8, alignSelf: 'center', }, resetButtonText: { color: 'redText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, loadingContainer: { position: 'absolute', backgroundColor: 'black', width: 112, height: 112, borderRadius: 56, opacity: 0.6, justifyContent: 'center', }, }; export default EmojiAvatarCreation; diff --git a/native/avatars/thread-avatar.react.js b/native/avatars/thread-avatar.react.js index f6258e407..690232fa5 100644 --- a/native/avatars/thread-avatar.react.js +++ b/native/avatars/thread-avatar.react.js @@ -1,48 +1,48 @@ // @flow import * as React from 'react'; import { useAvatarForThread, useENSResolvedAvatar, } from 'lib/shared/avatar-utils.js'; import { getSingleOtherUser } from 'lib/shared/thread-utils.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { type RawThreadInfo, type ThreadInfo } from 'lib/types/thread-types.js'; -import Avatar from './avatar.react.js'; +import Avatar, { type AvatarSize } from './avatar.react.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { +threadInfo: RawThreadInfo | ThreadInfo, - +size: 'micro' | 'small' | 'large' | 'profile', + +size: AvatarSize, }; function ThreadAvatar(props: Props): React.Node { const { threadInfo, size } = props; const avatarInfo = useAvatarForThread(threadInfo); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); let displayUserIDForThread; if (threadInfo.type === threadTypes.PRIVATE) { displayUserIDForThread = viewerID; } else if (threadInfo.type === threadTypes.PERSONAL) { displayUserIDForThread = getSingleOtherUser(threadInfo, viewerID); } const displayUser = useSelector(state => displayUserIDForThread ? state.userStore.userInfos[displayUserIDForThread] : null, ); const resolvedThreadAvatar = useENSResolvedAvatar(avatarInfo, displayUser); return ; } export default ThreadAvatar; diff --git a/native/avatars/user-avatar.react.js b/native/avatars/user-avatar.react.js index 29fc32909..f8ffd27c3 100644 --- a/native/avatars/user-avatar.react.js +++ b/native/avatars/user-avatar.react.js @@ -1,38 +1,37 @@ // @flow import * as React from 'react'; import { getAvatarForUser, useENSResolvedAvatar, } from 'lib/shared/avatar-utils.js'; import type { GenericUserInfoWithAvatar } from 'lib/types/avatar-types.js'; -import Avatar from './avatar.react.js'; +import Avatar, { type AvatarSize } from './avatar.react.js'; import { useSelector } from '../redux/redux-utils.js'; -type Size = 'micro' | 'small' | 'large' | 'profile'; type Props = - | { +userID: ?string, +size: Size } - | { +userInfo: ?GenericUserInfoWithAvatar, +size: Size }; + | { +userID: ?string, +size: AvatarSize } + | { +userInfo: ?GenericUserInfoWithAvatar, +size: AvatarSize }; function UserAvatar(props: Props): React.Node { const { userID, userInfo: userInfoProp, size } = props; const userInfo = useSelector(state => { if (!userID) { return userInfoProp; } else if (userID === state.currentUserInfo?.id) { return state.currentUserInfo; } else { return state.userStore.userInfos[userID]; } }); const avatarInfo = getAvatarForUser(userInfo); const resolvedUserAvatar = useENSResolvedAvatar(avatarInfo, userInfo); return ; } export default UserAvatar; diff --git a/native/roles/change-roles-screen.react.js b/native/roles/change-roles-screen.react.js index b709db070..51e2af2cf 100644 --- a/native/roles/change-roles-screen.react.js +++ b/native/roles/change-roles-screen.react.js @@ -1,303 +1,303 @@ // @flow import { useActionSheet } from '@expo/react-native-action-sheet'; import invariant from 'invariant'; import * as React from 'react'; import { View, Text, Platform, ActivityIndicator } from 'react-native'; import { TouchableOpacity } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { changeThreadMemberRolesActionTypes } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { otherUsersButNoOtherAdmins } from 'lib/selectors/thread-selectors.js'; import { roleIsAdminRole } from 'lib/shared/thread-utils.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { RelativeMemberInfo, ThreadInfo } from 'lib/types/thread-types.js'; import { values } from 'lib/utils/objects.js'; import ChangeRolesHeaderRightButton from './change-roles-header-right-button.react.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import type { ChatNavigationProp } from '../chat/chat.react'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; export type ChangeRolesScreenParams = { +threadInfo: ThreadInfo, +memberInfo: RelativeMemberInfo, +role: ?string, }; type Props = { +navigation: ChatNavigationProp<'ChangeRolesScreen'>, +route: NavigationRoute<'ChangeRolesScreen'>, }; const changeRolesLoadingStatusSelector = createLoadingStatusSelector( changeThreadMemberRolesActionTypes, ); function ChangeRolesScreen(props: Props): React.Node { const { navigation, route } = props; const { threadInfo, memberInfo, role } = props.route.params; invariant(role, 'Role must be defined'); const changeRolesLoadingStatus: LoadingStatus = useSelector( changeRolesLoadingStatusSelector, ); const styles = useStyles(unboundStyles); const [selectedRole, setSelectedRole] = React.useState(role); const roleOptions = React.useMemo( () => values(threadInfo.roles).map(threadRole => ({ id: threadRole.id, name: threadRole.name, })), [threadInfo.roles], ); const selectedRoleName = React.useMemo( () => roleOptions.find(roleOption => roleOption.id === selectedRole)?.name, [roleOptions, selectedRole], ); const onRoleChange = React.useCallback( (selectedIndex: ?number) => { if ( selectedIndex === undefined || selectedIndex === null || selectedIndex === roleOptions.length ) { return; } const newRole = roleOptions[selectedIndex].id; setSelectedRole(newRole); navigation.setParams({ threadInfo, memberInfo, role: newRole, }); }, [navigation, setSelectedRole, roleOptions, memberInfo, threadInfo], ); const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); const { showActionSheetWithOptions } = useActionSheet(); const insets = useSafeAreaInsets(); const showActionSheet = React.useCallback(() => { const options = Platform.OS === 'ios' ? [...roleOptions.map(roleOption => roleOption.name), 'Cancel'] : [...roleOptions.map(roleOption => roleOption.name)]; const cancelButtonIndex = Platform.OS === 'ios' ? options.length - 1 : -1; const containerStyle = { paddingBottom: insets.bottom, }; showActionSheetWithOptions( { options, cancelButtonIndex, containerStyle, userInterfaceStyle: activeTheme ?? 'dark', }, onRoleChange, ); }, [ roleOptions, onRoleChange, insets.bottom, activeTheme, showActionSheetWithOptions, ]); const otherUsersButNoOtherAdminsValue = useSelector( otherUsersButNoOtherAdmins(threadInfo.id), ); const memberIsAdmin = React.useMemo(() => { invariant(memberInfo.role, 'Expected member role to be defined'); return roleIsAdminRole(threadInfo.roles[memberInfo.role]); }, [threadInfo.roles, memberInfo.role]); const shouldRoleChangeBeDisabled = React.useMemo( () => otherUsersButNoOtherAdminsValue && memberIsAdmin, [otherUsersButNoOtherAdminsValue, memberIsAdmin], ); const roleSelector = React.useMemo(() => { if (shouldRoleChangeBeDisabled) { return ( {selectedRoleName} ); } return ( {selectedRoleName} ); }, [showActionSheet, styles, selectedRoleName, shouldRoleChangeBeDisabled]); const disabledRoleChangeMessage = React.useMemo(() => { if (!shouldRoleChangeBeDisabled) { return null; } return ( There must be at least one admin at any given time in a community. ); }, [ shouldRoleChangeBeDisabled, styles.disabledWarningBackground, styles.infoIcon, styles.disabledWarningText, ]); React.useEffect(() => { navigation.setOptions({ // eslint-disable-next-line react/display-name headerRight: () => { if (changeRolesLoadingStatus === 'loading') { return ( ); } return ( ); }, }); }, [ changeRolesLoadingStatus, navigation, styles.activityIndicator, route, shouldRoleChangeBeDisabled, ]); return ( Members can only be assigned one role at a time. Changing a member’s role will replace their previously assigned role. - + {memberInfo.username} {roleSelector} {disabledRoleChangeMessage} ); } const unboundStyles = { descriptionBackground: { backgroundColor: 'panelForeground', marginBottom: 20, }, descriptionText: { color: 'panelBackgroundLabel', padding: 16, fontSize: 14, }, memberInfo: { backgroundColor: 'panelForeground', padding: 16, marginBottom: 30, display: 'flex', flexDirection: 'column', alignItems: 'center', }, memberInfoUsername: { color: 'panelForegroundLabel', marginTop: 8, fontSize: 18, fontWeight: '500', }, roleSelectorLabel: { color: 'panelForegroundSecondaryLabel', marginLeft: 8, fontSize: 12, }, roleSelector: { backgroundColor: 'panelForeground', marginTop: 8, padding: 16, display: 'flex', alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between', }, currentRole: { color: 'panelForegroundSecondaryLabel', fontSize: 16, }, disabledCurrentRole: { color: 'disabledButton', fontSize: 16, }, pencilIcon: { color: 'panelInputSecondaryForeground', }, disabledPencilIcon: { color: 'disabledButton', }, disabledWarningBackground: { backgroundColor: 'disabledButton', padding: 16, display: 'flex', marginTop: 20, flexDirection: 'row', justifyContent: 'center', width: '75%', alignSelf: 'center', }, disabledWarningText: { color: 'panelForegroundSecondaryLabel', fontSize: 14, marginRight: 8, display: 'flex', }, infoIcon: { color: 'panelForegroundSecondaryLabel', marginRight: 8, marginLeft: 8, marginBottom: 12, }, activityIndicator: { paddingRight: 15, }, }; export default ChangeRolesScreen;