diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js --- a/native/chat/chat.react.js +++ b/native/chat/chat.react.js @@ -69,7 +69,9 @@ type ChatParamList, type ChatTopTabsParamList, MessageSearchRouteName, + ChangeRolesScreenRouteName, } from '../navigation/route-names.js'; +import ChangeRolesScreen from '../roles/change-roles-screen.react.js'; import MessageSearch from '../search/message-search.react.js'; import SearchHeader from '../search/search-header.react.js'; import SearchMessagesButton from '../search/search-messages-button.react.js'; @@ -293,6 +295,10 @@ headerTitle: 'Pinned Messages', headerBackTitleVisible: false, }; +const changeRolesScreenOptions = { + headerTitle: 'Change Role', + presentation: 'modal', +}; export type ChatNavigationProp< RouteName: $Keys = $Keys, @@ -411,6 +417,11 @@ component={MessageSearch} options={messageSearchOptions} /> + diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js --- a/native/navigation/route-names.js +++ b/native/navigation/route-names.js @@ -42,6 +42,7 @@ import type { VideoPlaybackModalParams } from '../media/video-playback-modal.react.js'; import type { CustomServerModalParams } from '../profile/custom-server-modal.react.js'; import type { RelationshipListItemTooltipModalParams } from '../profile/relationship-list-item-tooltip-modal.react.js'; +import type { ChangeRolesScreenParams } from '../roles/change-roles-screen.react.js'; import type { MessageSearchParams } from '../search/message-search.react.js'; export const ActionResultModalRouteName = 'ActionResultModal'; @@ -53,6 +54,7 @@ export const BlockListRouteName = 'BlockList'; export const BuildInfoRouteName = 'BuildInfo'; export const CalendarRouteName = 'Calendar'; +export const ChangeRolesScreenRouteName = 'ChangeRolesScreen'; export const ChatCameraModalRouteName = 'ChatCameraModal'; export const ChatRouteName = 'Chat'; export const ChatThreadListRouteName = 'ChatThreadList'; @@ -187,6 +189,7 @@ +FullScreenThreadMediaGallery: FullScreenThreadMediaGalleryParams, +MessageResultsScreen: MessageResultsScreenParams, +MessageSearch: MessageSearchParams, + +ChangeRolesScreen: ChangeRolesScreenParams, }; export type ChatTopTabsParamList = { diff --git a/native/roles/change-roles-screen.react.js b/native/roles/change-roles-screen.react.js new file mode 100644 --- /dev/null +++ b/native/roles/change-roles-screen.react.js @@ -0,0 +1,177 @@ +// @flow + +import { useActionSheet } from '@expo/react-native-action-sheet'; +import invariant from 'invariant'; +import * as React from 'react'; +import { View, Text, Platform } from 'react-native'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import type { RelativeMemberInfo, ThreadInfo } from 'lib/types/thread-types.js'; +import { values } from 'lib/utils/objects.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'>, +}; + +function ChangeRolesScreen(props: Props): React.Node { + const { navigation } = props; + const { threadInfo, memberInfo, role } = props.route.params; + invariant(role, 'Role must be defined'); + + 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, + ]); + + return ( + + + + Members can only be assigned one role at a time. Changing a + member’s role will replace their previously assigned role. + + + + + {memberInfo.username} + + + ROLE + + {selectedRoleName} + + + + + ); +} + +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, + }, + pencilIcon: { + color: 'panelInputSecondaryForeground', + }, +}; + +export default ChangeRolesScreen;