diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -1533,6 +1533,41 @@ return threadInfo.community === communityID || threadInfo.id === communityID; } +type RoleAndMemberCount = { + [roleName: string]: number, +}; + +function useRoleMemberCountsForCommunity( + threadInfo: ThreadInfo, +): RoleAndMemberCount { + return React.useMemo(() => { + const roleIDsToNames = {}; + + Object.keys(threadInfo.roles).forEach(roleID => { + roleIDsToNames[roleID] = threadInfo.roles[roleID].name; + }); + + const roleNamesToMemberCount: RoleAndMemberCount = {}; + + threadInfo.members.forEach(({ role: roleID }) => { + invariant(roleID, 'Community member should have a role'); + const roleName = roleIDsToNames[roleID]; + + roleNamesToMemberCount[roleName] = + (roleNamesToMemberCount[roleName] ?? 0) + 1; + }); + + // For all community roles with no members, add them to the list with 0 + Object.keys(roleIDsToNames).forEach(roleName => { + if (roleNamesToMemberCount[roleIDsToNames[roleName]] === undefined) { + roleNamesToMemberCount[roleIDsToNames[roleName]] = 0; + } + }); + + return roleNamesToMemberCount; + }, [threadInfo]); +} + export { threadHasPermission, viewerIsMember, @@ -1598,4 +1633,5 @@ chatNameMaxLength, patchThreadInfoToIncludeMentionedMembersOfParent, threadInfoInsideCommunity, + useRoleMemberCountsForCommunity, }; diff --git a/native/roles/community-roles-screen.react.js b/native/roles/community-roles-screen.react.js --- a/native/roles/community-roles-screen.react.js +++ b/native/roles/community-roles-screen.react.js @@ -1,11 +1,17 @@ // @flow import * as React from 'react'; +import { View, Text } from 'react-native'; +import { ScrollView } from 'react-native-gesture-handler'; +import { useRoleMemberCountsForCommunity } from 'lib/shared/thread-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; +import RolePanelEntry from './role-panel-entry.react.js'; import type { RolesNavigationProp } from './roles-navigator.react.js'; +import Button from '../components/button.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; +import { useStyles } from '../themes/colors.js'; export type CommunityRolesScreenParams = { +threadInfo: ThreadInfo, @@ -16,9 +22,117 @@ +route: NavigationRoute<'CommunityRolesScreen'>, }; -// eslint-disable-next-line no-unused-vars function CommunityRolesScreen(props: CommunityRolesScreenProps): React.Node { - return null; + const { threadInfo } = props.route.params; + const styles = useStyles(unboundStyles); + + const roleNamesToMembers = useRoleMemberCountsForCommunity(threadInfo); + + const rolePanelList = React.useMemo(() => { + const rolePanelEntries = []; + + Object.keys(roleNamesToMembers).forEach(roleName => { + rolePanelEntries.push( + , + ); + }); + + return rolePanelEntries; + }, [roleNamesToMembers]); + + const navigateToCreateRole = React.useCallback(() => {}, []); + + return ( + + + + Roles help you group community members together and assign them + certain permissions. The Admins and Members roles are set by default + and cannot be edited or deleted. + + + When people join the community, they are automatically assigned the + Members role. + + + + + ROLES + MEMBERS + + {rolePanelList} + + + + + + ); } +const unboundStyles = { + rolesInfoContainer: { + backgroundColor: 'panelForeground', + padding: 16, + }, + rolesInfoTextFirstLine: { + color: 'panelBackgroundLabel', + fontSize: 14, + marginBottom: 14, + }, + rolesInfoTextSecondLine: { + color: 'panelBackgroundLabel', + fontSize: 14, + }, + rolesPanel: { + marginTop: 30, + }, + rolePanelHeadersContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 8, + }, + rolePanelHeaderLeft: { + color: 'panelBackgroundLabel', + fontSize: 14, + }, + rolePanelHeaderRight: { + color: 'panelBackgroundLabel', + fontSize: 14, + marginRight: 72, + }, + rolePanelList: { + backgroundColor: 'panelForeground', + marginTop: 8, + padding: 4, + maxHeight: 325, + }, + buttonContainer: { + backgroundColor: 'panelForeground', + padding: 2, + }, + createRoleButton: { + justifyContent: 'center', + alignItems: 'center', + margin: 10, + backgroundColor: 'purpleButton', + height: 48, + borderRadius: 10, + }, + createRoleButtonText: { + color: 'whiteText', + fontSize: 16, + fontWeight: '500', + }, +}; + export default CommunityRolesScreen; diff --git a/native/roles/role-panel-entry.react.js b/native/roles/role-panel-entry.react.js new file mode 100644 --- /dev/null +++ b/native/roles/role-panel-entry.react.js @@ -0,0 +1,50 @@ +// @flow + +import * as React from 'react'; +import { View, Text } from 'react-native'; + +import CommIcon from '../components/comm-icon.react.js'; +import { useStyles } from '../themes/colors.js'; + +type RolePanelEntryProps = { + +roleName: string, + +memberCount: number, +}; + +function RolePanelEntry(props: RolePanelEntryProps): React.Node { + const { roleName, memberCount } = props; + const styles = useStyles(unboundStyles); + + return ( + + {roleName} + + {memberCount} + + + + ); +} + +const unboundStyles = { + rolePanelEntry: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 8, + }, + rolePanelNameEntry: { + color: 'panelForegroundLabel', + fontWeight: '600', + fontSize: 14, + }, + rolePanelCountEntry: { + color: 'panelForegroundLabel', + fontWeight: '600', + fontSize: 14, + marginRight: 72, + padding: 8, + }, +}; + +export default RolePanelEntry;