diff --git a/native/community-creation/community-configuration.react.js b/native/community-creation/community-configuration.react.js index ca177ca02..910c2904c 100644 --- a/native/community-creation/community-configuration.react.js +++ b/native/community-creation/community-configuration.react.js @@ -1,215 +1,216 @@ // @flow import * as React from 'react'; import { Text, View } from 'react-native'; import { useNewThinThread, newThreadActionTypes, } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { NewThreadResult } from 'lib/types/thread-types.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import CommunityCreationKeyserverLabel from './community-creation-keyserver-label.react.js'; import type { CommunityCreationNavigationProp } from './community-creation-navigator.react.js'; import RegistrationButtonContainer from '../account/registration/registration-button-container.react.js'; import RegistrationButton from '../account/registration/registration-button.react.js'; import RegistrationContainer from '../account/registration/registration-container.react.js'; import RegistrationContentContainer from '../account/registration/registration-content-container.react.js'; import { useNavigateToThread } from '../chat/message-list-types.js'; import { ThreadSettingsCategoryFooter, ThreadSettingsCategoryHeader, } from '../chat/settings/thread-settings-category.react.js'; import EnumSettingsOption from '../components/enum-settings-option.react.js'; import TextInput from '../components/text-input.react.js'; import { useCalendarQuery } from '../navigation/nav-selectors.js'; import { type NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, useStyles } from '../themes/colors.js'; type Props = { +navigation: CommunityCreationNavigationProp<'CommunityConfiguration'>, +route: NavigationRoute<'CommunityConfiguration'>, }; const createNewCommunityLoadingStatusSelector = createLoadingStatusSelector(newThreadActionTypes); // eslint-disable-next-line no-unused-vars function CommunityConfiguration(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const dispatchActionPromise = useDispatchActionPromise(); const callNewThinThread = useNewThinThread(); const calendarQueryFunc = useCalendarQuery(); const createNewCommunityLoadingStatus: LoadingStatus = useSelector( createNewCommunityLoadingStatusSelector, ); const [pendingCommunityName, setPendingCommunityName] = React.useState(''); const [announcementSetting, setAnnouncementSetting] = React.useState(false); const [errorMessage, setErrorMessage] = React.useState(); const onChangePendingCommunityName = React.useCallback((newValue: string) => { setErrorMessage(); setPendingCommunityName(newValue); }, []); const callCreateNewCommunity = React.useCallback(async () => { const calendarQuery = calendarQueryFunc(); try { const newThreadResult: NewThreadResult = await callNewThinThread({ name: pendingCommunityName, type: announcementSetting ? threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT : threadTypes.COMMUNITY_ROOT, calendarQuery, }); return newThreadResult; } catch (e) { setErrorMessage('Community creation failed. Please try again.'); throw e; } }, [ announcementSetting, calendarQueryFunc, callNewThinThread, pendingCommunityName, ]); const [newCommunityID, setNewCommunityID] = React.useState(null); const createNewCommunity = React.useCallback(async () => { setErrorMessage(); const newThreadResultPromise = callCreateNewCommunity(); void dispatchActionPromise(newThreadActionTypes, newThreadResultPromise); const newThreadResult = await newThreadResultPromise; setNewCommunityID(newThreadResult.newThreadID); }, [callCreateNewCommunity, dispatchActionPromise]); const navigateToThread = useNavigateToThread(); const threadInfos = useSelector(threadInfoSelector); React.useEffect(() => { if (!newCommunityID) { return; } const communityThreadInfo = threadInfos[newCommunityID]; if (communityThreadInfo) { navigateToThread({ threadInfo: communityThreadInfo }); } }, [navigateToThread, newCommunityID, threadInfos]); const onCheckBoxPress = React.useCallback(() => { setErrorMessage(); setAnnouncementSetting(!announcementSetting); }, [announcementSetting]); const enumSettingsOptionDescription = 'Make it so only admins can post to ' + 'the root channel of the community.'; return ( Name You may edit your community’s image and name later. {errorMessage} ); } const unboundStyles = { containerPaddingOverride: { padding: 0, }, communityNameRow: { backgroundColor: 'panelForeground', flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 8, }, communityNameLabel: { color: 'panelForegroundTertiaryLabel', fontSize: 16, width: 96, }, communityNamePendingValue: { color: 'panelForegroundSecondaryLabel', flex: 1, fontFamily: 'Arial', fontSize: 16, margin: 0, paddingLeft: 4, paddingRight: 0, paddingVertical: 0, borderBottomColor: 'transparent', }, communityNameNoticeContainer: { display: 'flex', flexDirection: 'row', justifyContent: 'center', }, communityNameNoticeText: { color: 'panelForegroundTertiaryLabel', }, errorMessageContainer: { alignItems: 'center', }, errorMessageText: { color: 'redText', }, }; export default CommunityConfiguration; diff --git a/native/components/enum-settings-option.react.js b/native/components/enum-settings-option.react.js index 2eac60c2a..869387eca 100644 --- a/native/components/enum-settings-option.react.js +++ b/native/components/enum-settings-option.react.js @@ -1,110 +1,155 @@ // @flow import * as React from 'react'; import { Text, TouchableOpacity, View } from 'react-native'; import CommIcon from '../components/comm-icon.react.js'; import { useColors, useStyles } from '../themes/colors.js'; +type InputType = 'radio' | 'checkbox'; + type EnumSettingsOptionProps = { +icon?: string, +name: string, +description: string, +enumValue: boolean, +onEnumValuePress: () => mixed, + +type?: InputType, }; function EnumSettingsOption(props: EnumSettingsOptionProps): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); - const { icon, name, description, enumValue, onEnumValuePress } = props; + const { + icon, + name, + description, + enumValue, + onEnumValuePress, + type = 'radio', + } = props; const enumIcon = React.useMemo(() => { if (!icon) { return null; } return ( ); }, [icon, styles.enumIcon, colors.purpleButton]); - const checkBoxFill = enumValue ? ( - - ) : null; - const infoContainerStyle = React.useMemo( () => props.icon ? styles.enumInfoContainer : { ...styles.enumInfoContainer, marginLeft: 10 }, [props.icon, styles.enumInfoContainer], ); + const enumInputStyles = React.useMemo(() => { + const style = [styles.enumInput]; + + if (type === 'radio') { + style.push(styles.radio); + } else { + style.push(styles.checkBox); + } + + return style; + }, [styles.checkBox, styles.enumInput, styles.radio, type]); + + const enumInputFilledStyles = React.useMemo(() => { + const style = [styles.enumInputFill]; + + if (type === 'radio') { + style.push(styles.radioFill); + } else { + style.push(styles.checkBoxFill); + } + + return style; + }, [styles.checkBoxFill, styles.enumInputFill, styles.radioFill, type]); + + const enumInputFill = React.useMemo( + () => (enumValue ? : null), + [enumValue, enumInputFilledStyles], + ); + return ( {enumIcon} {name} {description} - {checkBoxFill} + {enumInputFill} ); } const unboundStyles = { enumCell: { flexDirection: 'row', height: 96, backgroundColor: 'panelForeground', }, enumIcon: { padding: 16, }, enumInfoContainer: { flex: 1, flexDirection: 'column', justifyContent: 'space-evenly', padding: 8, }, enumInfoName: { color: 'panelForegroundLabel', fontSize: 18, lineHeight: 24, }, enumInfoDescription: { color: 'panelForegroundSecondaryLabel', lineHeight: 18, }, - enumCheckBoxContainer: { + enumInputContainer: { padding: 22, justifyContent: 'center', alignItems: 'center', }, - enumCheckBox: { + enumInput: { height: 32, width: 32, - borderRadius: 3.5, borderWidth: 1, borderColor: 'panelSecondaryForegroundBorder', justifyContent: 'center', alignItems: 'center', }, - enumCheckBoxFill: { + checkBox: { + borderRadius: 3.5, + }, + radio: { + borderRadius: 16, + }, + enumInputFill: { height: 20, width: 20, - borderRadius: 2.1875, backgroundColor: 'panelForegroundSecondaryLabel', }, + checkBoxFill: { + borderRadius: 2.1875, + }, + radioFill: { + borderRadius: 10, + }, }; export default EnumSettingsOption; diff --git a/native/roles/create-roles-screen.react.js b/native/roles/create-roles-screen.react.js index f85dd140c..85f489ae2 100644 --- a/native/roles/create-roles-screen.react.js +++ b/native/roles/create-roles-screen.react.js @@ -1,318 +1,319 @@ // @flow import * as React from 'react'; import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { modifyCommunityRoleActionTypes } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import { type UserSurfacedPermission, type UserSurfacedPermissionOption, userSurfacedPermissionOptions, } from 'lib/types/thread-permission-types.js'; import CreateRolesHeaderRightButton from './create-roles-header-right-button.react.js'; import type { RolesNavigationProp } from './roles-navigator.react.js'; import EnumSettingsOption from '../components/enum-settings-option.react.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import TextInput from '../components/text-input.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 CreateRolesScreenParams = { +threadInfo: ThreadInfo, +action: 'create_role' | 'edit_role', +existingRoleID?: string, +roleName: string, +rolePermissions: $ReadOnlySet, }; type CreateRolesScreenProps = { +navigation: RolesNavigationProp<'CreateRolesScreen'>, +route: NavigationRoute<'CreateRolesScreen'>, }; const createRolesLoadingStatusSelector = createLoadingStatusSelector( modifyCommunityRoleActionTypes, ); function CreateRolesScreen(props: CreateRolesScreenProps): React.Node { const { threadInfo, action, existingRoleID, roleName: defaultRoleName, rolePermissions: defaultRolePermissions, } = props.route.params; const createRolesLoadingStatus: LoadingStatus = useSelector( createRolesLoadingStatusSelector, ); const [customRoleName, setCustomRoleName] = React.useState(defaultRoleName); const [selectedPermissions, setSelectedPermissions] = React.useState< $ReadOnlySet, >(defaultRolePermissions); const [roleCreationFailed, setRoleCreationFailed] = React.useState(false); const styles = useStyles(unboundStyles); const errorStyles = React.useMemo( () => roleCreationFailed ? [styles.errorContainer, styles.errorContainerVisible] : styles.errorContainer, [roleCreationFailed, styles.errorContainer, styles.errorContainerVisible], ); const onClearPermissions = React.useCallback(() => { setSelectedPermissions(new Set()); }, []); const isSelectedPermissionsEmpty = selectedPermissions.size === 0; const clearPermissionsText = React.useMemo(() => { const textStyle = isSelectedPermissionsEmpty ? styles.clearPermissionsTextDisabled : styles.clearPermissionsText; return ( Clear permissions ); }, [ isSelectedPermissionsEmpty, onClearPermissions, styles.clearPermissionsText, styles.clearPermissionsTextDisabled, ]); const isUserSurfacedPermissionSelected = React.useCallback( (option: UserSurfacedPermissionOption) => selectedPermissions.has(option.userSurfacedPermission), [selectedPermissions], ); const onEnumValuePress = React.useCallback( (option: UserSurfacedPermissionOption) => setSelectedPermissions(currentPermissions => { if (currentPermissions.has(option.userSurfacedPermission)) { const newPermissions = new Set(currentPermissions); newPermissions.delete(option.userSurfacedPermission); return newPermissions; } else { return new Set([ ...currentPermissions, option.userSurfacedPermission, ]); } }), [], ); React.useEffect( () => props.navigation.setParams({ threadInfo, action, existingRoleID, roleName: customRoleName, rolePermissions: selectedPermissions, }), [ props.navigation, threadInfo, action, existingRoleID, customRoleName, selectedPermissions, ], ); const permissionsList = React.useMemo( () => [...userSurfacedPermissionOptions].map(permission => ( onEnumValuePress(permission)} + type="checkbox" /> )), [isUserSurfacedPermissionSelected, onEnumValuePress], ); const onChangeRoleNameInput = React.useCallback((roleName: string) => { setRoleCreationFailed(false); setCustomRoleName(roleName); }, []); React.useEffect( () => props.navigation.setOptions({ headerRight: () => { if (createRolesLoadingStatus === 'loading') { return ( ); } return ( ); }, }), [ createRolesLoadingStatus, props.navigation, styles.activityIndicator, props.route, ], ); const createRolesScreen = React.useMemo( () => ( ROLE NAME There is already a role with this name in the community PERMISSIONS {clearPermissionsText} {permissionsList} ), [ clearPermissionsText, customRoleName, errorStyles, onChangeRoleNameInput, permissionsList, styles.errorText, styles.pencilIcon, styles.permissionsContainer, styles.permissionsHeader, styles.permissionsListContainer, styles.permissionsListContentContainer, styles.permissionsText, styles.roleInput, styles.roleInputComponent, styles.roleNameContainer, styles.roleNameText, ], ); return createRolesScreen; } const unboundStyles = { roleNameContainer: { marginTop: 30, }, roleNameText: { color: 'panelBackgroundLabel', fontSize: 12, marginBottom: 5, marginLeft: 10, }, roleInput: { backgroundColor: 'panelForeground', padding: 12, flexDirection: 'row', justifyContent: 'space-between', }, roleInputComponent: { color: 'panelForegroundLabel', fontSize: 16, }, pencilIcon: { color: 'panelInputSecondaryForeground', }, errorContainer: { marginTop: 10, alignItems: 'center', opacity: 0, }, errorContainerVisible: { opacity: 1, }, errorText: { color: 'redText', fontSize: 14, }, permissionsContainer: { marginTop: 20, paddingBottom: 220, }, permissionsHeader: { flexDirection: 'row', justifyContent: 'space-between', }, permissionsText: { color: 'panelBackgroundLabel', fontSize: 12, marginLeft: 10, }, clearPermissionsText: { color: 'purpleLink', fontSize: 12, marginRight: 15, }, clearPermissionsTextDisabled: { color: 'disabledButton', fontSize: 12, marginRight: 15, }, permissionsListContainer: { backgroundColor: 'panelForeground', marginTop: 10, }, permissionsListContentContainer: { paddingBottom: 48, }, activityIndicator: { paddingRight: 15, }, }; export default CreateRolesScreen;