diff --git a/lib/utils/drawer-utils.react.js b/lib/utils/drawer-utils.react.js --- a/lib/utils/drawer-utils.react.js +++ b/lib/utils/drawer-utils.react.js @@ -1,6 +1,7 @@ // @flow import { threadIsChannel } from '../shared/thread-utils.js'; +import type { InviteLink } from '../types/link-types.js'; import { type ThreadInfo, type ResolvedThreadInfo, @@ -12,6 +13,7 @@ +itemChildren?: $ReadOnlyArray>, +hasSubchannelsButton: boolean, +labelStyle: T, + +inviteLink: ?InviteLink, }; function createRecursiveDrawerItemsData( @@ -19,12 +21,14 @@ communities: $ReadOnlyArray, labelStyles: $ReadOnlyArray, maxDepth: number, + inviteLinks: ?{ +[threadID: string]: InviteLink }, ): $ReadOnlyArray> { const result = communities.map(community => ({ threadInfo: community, itemChildren: [], labelStyle: labelStyles[0], hasSubchannelsButton: false, + inviteLink: inviteLinks?.[community.id], })); let queue = result.map(item => [item, 0]); @@ -42,6 +46,7 @@ hasSubchannelsButton: lvl + 1 === maxDepth && threadHasSubchannels(childItem, childThreadInfosMap), + inviteLink: inviteLinks?.[item.threadInfo.id], })); queue = queue.concat( item.itemChildren.map(childItem => [childItem, lvl + 1]), diff --git a/native/navigation/community-drawer-content.react.js b/native/navigation/community-drawer-content.react.js --- a/native/navigation/community-drawer-content.react.js +++ b/native/navigation/community-drawer-content.react.js @@ -1,14 +1,26 @@ // @flow +import { useDrawerStatus } from '@react-navigation/drawer'; +import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { FlatList, Platform } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useSelector } from 'react-redux'; +import { + fetchPrimaryInviteLinkActionTypes, + fetchPrimaryInviteLinks, +} from 'lib/actions/link-actions.js'; import { childThreadInfos, communityThreadSelector, } from 'lib/selectors/thread-selectors.js'; +import type { InviteLink } from 'lib/types/link-types'; +import type { ThreadInfo } from 'lib/types/thread-types'; +import { + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils.js'; import { createRecursiveDrawerItemsData, appendSuffix, @@ -16,6 +28,7 @@ import { useResolvedThreadInfos } from 'lib/utils/entity-helpers.js'; import CommunityDrawerItemCommunity from './community-drawer-item-community.react.js'; +import { ViewInviteLinksModalRouteName } from './route-names.js'; import { useNavigateToThread } from '../chat/message-list-types.js'; import { useStyles } from '../themes/colors.js'; @@ -34,6 +47,30 @@ ); const styles = useStyles(unboundStyles); + const [inviteLinks, setInviteLinks] = React.useState(null); + const callFetchPrimaryLinks = useServerCall(fetchPrimaryInviteLinks); + const dispatchActionPromise = useDispatchActionPromise(); + const drawerStatus = useDrawerStatus(); + React.useEffect(() => { + (async () => { + if (drawerStatus !== 'open') { + return; + } + const createFetchPrimaryLinksPromise = async () => { + const response = await callFetchPrimaryLinks(); + const links = {}; + for (const link of response.links) { + links[link.communityID] = link; + } + setInviteLinks(links); + }; + await dispatchActionPromise( + fetchPrimaryInviteLinkActionTypes, + createFetchPrimaryLinksPromise(), + ); + })(); + }, [callFetchPrimaryLinks, dispatchActionPromise, drawerStatus]); + const [openCommunity, setOpenCommunity] = React.useState( communitiesSuffixed.length === 1 ? communitiesSuffixed[0].id : null, ); @@ -45,6 +82,20 @@ setOpenCommunity(open => (open === index ? null : index)); }, []); + const { navigate } = useNavigation(); + const navigateToInviteLinksView = React.useCallback( + (community: ThreadInfo, inviteLink: InviteLink) => { + navigate<'ViewInviteLinksModal'>({ + name: ViewInviteLinksModalRouteName, + params: { + community, + inviteLink, + }, + }); + }, + [navigate], + ); + const renderItem = React.useCallback( ({ item }) => ( ), - [navigateToThread, openCommunity, setOpenCommunityOrClose], + [ + navigateToInviteLinksView, + navigateToThread, + openCommunity, + setOpenCommunityOrClose, + ], ); const labelStylesObj = useStyles(labelUnboundStyles); @@ -75,8 +132,9 @@ communitiesSuffixed, labelStyles, maxDepth, + inviteLinks, ), - [childThreadInfosMap, communitiesSuffixed, labelStyles], + [childThreadInfosMap, communitiesSuffixed, inviteLinks, labelStyles], ); return ( diff --git a/native/navigation/community-drawer-item.react.js b/native/navigation/community-drawer-item.react.js --- a/native/navigation/community-drawer-item.react.js +++ b/native/navigation/community-drawer-item.react.js @@ -1,8 +1,12 @@ // @flow +import { useActionSheet } from '@expo/react-native-action-sheet'; import * as React from 'react'; import { View, FlatList, TouchableOpacity } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import type { InviteLink } from 'lib/types/link-types'; +import type { ThreadInfo } from 'lib/types/thread-types'; import type { CommunityDrawerItemData } from 'lib/utils/drawer-utils.react.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; @@ -11,6 +15,7 @@ import ThreadAvatar from '../avatars/thread-avatar.react.js'; import type { MessageListParams } from '../chat/message-list-types.js'; import { SingleLine } from '../components/single-line.react.js'; +import SWMansionIcon from '../components/swmansion-icon.react.js'; import { useStyles } from '../themes/colors.js'; import type { TextStyle } from '../types/styles.js'; import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; @@ -20,14 +25,25 @@ +toggleExpanded: (threadID: string) => void, +expanded: boolean, +navigateToThread: (params: MessageListParams) => void, + +navigateToInviteLinksView: ( + community: ThreadInfo, + inviteLink: InviteLink, + ) => void, }; function CommunityDrawerItem(props: DrawerItemProps): React.Node { const { - itemData: { threadInfo, itemChildren, labelStyle, hasSubchannelsButton }, + itemData: { + threadInfo, + itemChildren, + labelStyle, + hasSubchannelsButton, + inviteLink, + }, navigateToThread, expanded, toggleExpanded, + navigateToInviteLinksView, } = props; const styles = useStyles(unboundStyles); @@ -38,9 +54,10 @@ key={item.threadInfo.id} itemData={item} navigateToThread={navigateToThread} + navigateToInviteLinksView={navigateToInviteLinksView} /> ), - [navigateToThread], + [navigateToInviteLinksView, navigateToThread], ); const children = React.useMemo(() => { @@ -95,6 +112,49 @@ ); }, [shouldRenderAvatars, styles.avatarContainer, threadInfo]); + const insets = useSafeAreaInsets(); + const { showActionSheetWithOptions } = useActionSheet(); + const inviteLinksButton = React.useMemo(() => { + if (!inviteLink) { + return null; + } + const options = ['Invite Link', 'Cancel']; + const containerStyle = { + paddingBottom: insets.bottom, + }; + return ( + + showActionSheetWithOptions( + { + options, + cancelButtonIndex: 1, + containerStyle, + }, + selectedIndex => { + if (selectedIndex === 0) { + navigateToInviteLinksView(threadInfo, inviteLink); + } + }, + ) + } + > + + + ); + }, [ + insets.bottom, + inviteLink, + navigateToInviteLinksView, + showActionSheetWithOptions, + styles.inviteLinksButton, + threadInfo, + ]); + return ( @@ -107,6 +167,7 @@ {avatar} {uiName} + {inviteLinksButton} {children} @@ -120,6 +181,9 @@ chatView: { marginLeft: 16, }, + inviteLinksButton: { + color: 'drawerItemLabelLevel0', + }, threadEntry: { flexDirection: 'row', marginVertical: 6, @@ -138,6 +202,7 @@ export type CommunityDrawerItemChatProps = { +itemData: CommunityDrawerItemData, +navigateToThread: (params: MessageListParams) => void, + +navigateToInviteLinksView: () => void, }; function CommunityDrawerItemChat(