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;