diff --git a/native/invite-links/invite-links-button.react.js b/native/invite-links/invite-links-button.react.js new file mode 100644 index 000000000..bc96b0005 --- /dev/null +++ b/native/invite-links/invite-links-button.react.js @@ -0,0 +1,88 @@ +// @flow + +import { useActionSheet } from '@expo/react-native-action-sheet'; +import { useNavigation } from '@react-navigation/native'; +import * as React from 'react'; +import { TouchableOpacity } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; +import type { ThreadInfo } from 'lib/types/thread-types.js'; + +import SWMansionIcon from '../components/swmansion-icon.react.js'; +import { + InviteLinkNavigatorRouteName, + ViewInviteLinksRouteName, +} from '../navigation/route-names.js'; +import { useSelector } from '../redux/redux-utils.js'; +import { useStyles } from '../themes/colors.js'; + +type Props = { + +community: ThreadInfo, +}; + +function InviteLinksButton(props: Props): React.Node { + const { community } = props; + const inviteLink = useSelector(primaryInviteLinksSelector)[community.id]; + + const { navigate } = useNavigation(); + const navigateToInviteLinksView = React.useCallback(() => { + if (!inviteLink || !community) { + return; + } + navigate<'InviteLinkNavigator'>(InviteLinkNavigatorRouteName, { + screen: ViewInviteLinksRouteName, + params: { + community, + }, + }); + }, [community, inviteLink, navigate]); + + const insets = useSafeAreaInsets(); + const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); + const styles = useStyles(unboundStyles); + + const { showActionSheetWithOptions } = useActionSheet(); + const options = React.useMemo(() => ['Invite Link', 'Cancel'], []); + + const openActionSheet = React.useCallback(() => { + showActionSheetWithOptions( + { + options, + cancelButtonIndex: 1, + containerStyle: { + paddingBottom: insets.bottom, + }, + userInterfaceStyle: activeTheme ?? 'dark', + }, + selectedIndex => { + if (selectedIndex === 0) { + navigateToInviteLinksView(); + } + }, + ); + }, [ + activeTheme, + insets.bottom, + navigateToInviteLinksView, + options, + showActionSheetWithOptions, + ]); + + if (!inviteLink) { + return null; + } + return ( + + + + ); +} + +const unboundStyles = { + button: { + color: 'drawerItemLabelLevel0', + }, +}; + +export default InviteLinksButton; diff --git a/native/navigation/community-drawer-content.react.js b/native/navigation/community-drawer-content.react.js index 7b79c6460..771d522c1 100644 --- a/native/navigation/community-drawer-content.react.js +++ b/native/navigation/community-drawer-content.react.js @@ -1,166 +1,190 @@ // @flow +import { useDrawerStatus } from '@react-navigation/drawer'; import * as React from 'react'; import { FlatList, Platform, View, Text } 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 { + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils.js'; import { createRecursiveDrawerItemsData, appendSuffix, } from 'lib/utils/drawer-utils.react.js'; import { useResolvedThreadInfos } from 'lib/utils/entity-helpers.js'; import CommunityDrawerItemCommunity from './community-drawer-item-community.react.js'; import { useNavigateToThread } from '../chat/message-list-types.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { useStyles } from '../themes/colors.js'; const maxDepth = 2; const safeAreaEdges = Platform.select({ ios: ['top'], default: ['top', 'bottom'], }); function CommunityDrawerContent(): React.Node { const communities = useSelector(communityThreadSelector); const resolvedCommunities = useResolvedThreadInfos(communities); const communitiesSuffixed = React.useMemo( () => appendSuffix(resolvedCommunities), [resolvedCommunities], ); const styles = useStyles(unboundStyles); + const callFetchPrimaryLinks = useServerCall(fetchPrimaryInviteLinks); + const dispatchActionPromise = useDispatchActionPromise(); + const drawerStatus = useDrawerStatus(); + React.useEffect(() => { + (async () => { + if (drawerStatus !== 'open') { + return; + } + await dispatchActionPromise( + fetchPrimaryInviteLinkActionTypes, + callFetchPrimaryLinks(), + ); + })(); + }, [callFetchPrimaryLinks, dispatchActionPromise, drawerStatus]); + const [openCommunity, setOpenCommunity] = React.useState( communitiesSuffixed.length === 1 ? communitiesSuffixed[0].id : null, ); const navigateToThread = useNavigateToThread(); const childThreadInfosMap = useSelector(childThreadInfos); const setOpenCommunityOrClose = React.useCallback((index: string) => { setOpenCommunity(open => (open === index ? null : index)); }, []); const renderItem = React.useCallback( ({ item }) => ( ), [navigateToThread, openCommunity, setOpenCommunityOrClose], ); const labelStylesObj = useStyles(labelUnboundStyles); const labelStyles = React.useMemo( () => [ labelStylesObj.level0Label, labelStylesObj.level1Label, labelStylesObj.level2Label, ], [labelStylesObj], ); const drawerItemsData = React.useMemo( () => createRecursiveDrawerItemsData( childThreadInfosMap, communitiesSuffixed, labelStyles, maxDepth, ), [childThreadInfosMap, communitiesSuffixed, labelStyles], ); const isCommunityCreationButtonEnabled = false; let communityCreationButton; if (isCommunityCreationButtonEnabled) { communityCreationButton = ( Create community ); } return ( {communityCreationButton} ); } const unboundStyles = { drawerContent: { flex: 1, paddingRight: 8, paddingTop: 8, paddingBottom: 8, }, communityCreationContainer: { flexDirection: 'row', padding: 24, alignItems: 'center', borderTopWidth: 1, borderColor: 'panelSeparator', }, communityCreationText: { color: 'panelForegroundLabel', fontSize: 16, lineHeight: 24, fontWeight: '500', }, communityCreationIconContainer: { height: 28, width: 28, justifyContent: 'center', alignItems: 'center', borderRadius: 16, marginRight: 12, backgroundColor: 'panelSecondaryForeground', }, communityCreationIcon: { color: 'panelForegroundLabel', }, }; const labelUnboundStyles = { level0Label: { color: 'drawerItemLabelLevel0', fontSize: 16, lineHeight: 24, fontWeight: '500', }, level1Label: { color: 'drawerItemLabelLevel1', fontSize: 14, lineHeight: 22, fontWeight: '500', }, level2Label: { color: 'drawerItemLabelLevel2', fontSize: 14, lineHeight: 22, fontWeight: '400', }, }; const MemoizedCommunityDrawerContent: React.ComponentType<{}> = React.memo( CommunityDrawerContent, ); export default MemoizedCommunityDrawerContent; diff --git a/native/navigation/community-drawer-item.react.js b/native/navigation/community-drawer-item.react.js index 293b7d454..9a9bc4cfc 100644 --- a/native/navigation/community-drawer-item.react.js +++ b/native/navigation/community-drawer-item.react.js @@ -1,169 +1,171 @@ // @flow import * as React from 'react'; import { View, FlatList, TouchableOpacity } from 'react-native'; import type { CommunityDrawerItemData } from 'lib/utils/drawer-utils.react.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import { ExpandButton, ExpandButtonDisabled } from './expand-buttons.react.js'; import SubchannelsButton from './subchannels-button.react.js'; 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 InviteLinksButton from '../invite-links/invite-links-button.react.js'; import { useStyles } from '../themes/colors.js'; import type { TextStyle } from '../types/styles.js'; import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; export type DrawerItemProps = { +itemData: CommunityDrawerItemData, +toggleExpanded: (threadID: string) => void, +expanded: boolean, +navigateToThread: (params: MessageListParams) => void, }; function CommunityDrawerItem(props: DrawerItemProps): React.Node { const { itemData: { threadInfo, itemChildren, labelStyle, hasSubchannelsButton }, navigateToThread, expanded, toggleExpanded, } = props; const styles = useStyles(unboundStyles); const renderItem = React.useCallback( ({ item }) => ( ), [navigateToThread], ); const children = React.useMemo(() => { if (!expanded) { return null; } if (hasSubchannelsButton) { return ( ); } return ; }, [ expanded, itemChildren, renderItem, hasSubchannelsButton, styles.subchannelsButton, threadInfo, ]); const onExpandToggled = React.useCallback(() => { toggleExpanded(threadInfo.id); }, [toggleExpanded, threadInfo.id]); const itemExpandButton = React.useMemo(() => { if (!itemChildren?.length && !hasSubchannelsButton) { return ; } return ; }, [itemChildren?.length, hasSubchannelsButton, onExpandToggled, expanded]); const onPress = React.useCallback(() => { navigateToThread({ threadInfo }); }, [navigateToThread, threadInfo]); const { uiName } = useResolvedThreadInfo(threadInfo); const shouldRenderAvatars = useShouldRenderAvatars(); const avatar = React.useMemo(() => { if (!shouldRenderAvatars) { return null; } return ( ); }, [shouldRenderAvatars, styles.avatarContainer, threadInfo]); return ( {itemExpandButton} {avatar} {uiName} + {children} ); } const unboundStyles = { avatarContainer: { marginRight: 8, }, chatView: { marginLeft: 16, }, threadEntry: { flexDirection: 'row', marginVertical: 6, }, textTouchableWrapper: { flex: 1, flexDirection: 'row', alignItems: 'center', }, subchannelsButton: { marginLeft: 24, marginBottom: 6, }, }; export type CommunityDrawerItemChatProps = { +itemData: CommunityDrawerItemData, +navigateToThread: (params: MessageListParams) => void, }; function CommunityDrawerItemChat( props: CommunityDrawerItemChatProps, ): React.Node { const [expanded, setExpanded] = React.useState(false); const styles = useStyles(unboundStyles); const toggleExpanded = React.useCallback(() => { setExpanded(isExpanded => !isExpanded); }, []); return ( ); } const MemoizedCommunityDrawerItemChat: React.ComponentType = React.memo(CommunityDrawerItemChat); const MemoizedCommunityDrawerItem: React.ComponentType = React.memo(CommunityDrawerItem); export default MemoizedCommunityDrawerItem;