diff --git a/native/navigation/community-drawer-content.react.js b/native/navigation/community-drawer-content.react.js index 843c7ece8..1d7e996d0 100644 --- a/native/navigation/community-drawer-content.react.js +++ b/native/navigation/community-drawer-content.react.js @@ -1,195 +1,236 @@ // @flow import { useDrawerStatus } from '@react-navigation/drawer'; import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { FlatList, Platform, View, Text, TouchableOpacity } 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 { threadTypeIsCommunityRoot } from 'lib/types/thread-types-enum.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 CommunityDrawerItem from './community-drawer-item.react.js'; import { CommunityCreationRouteName } from './route-names.js'; import { useNavigateToThread } from '../chat/message-list-types.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { useStyles } from '../themes/colors.js'; +import { + flattenDrawerItemsData, + filterOutThreadAndDescendantIDs, +} from '../utils/drawer-utils.react.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 [expanded, setExpanded] = React.useState(() => { + if (communitiesSuffixed.length === 1) { + return new Set([communitiesSuffixed[0].id]); + } + return new Set(); + }); + + const setOpenCommunityOrClose = React.useCallback( + (id: string) => + expanded.has(id) ? setExpanded(new Set()) : setExpanded(new Set([id])), + [expanded], ); const labelStylesObj = useStyles(labelUnboundStyles); const labelStyles = React.useMemo( () => [ labelStylesObj.level0Label, labelStylesObj.level1Label, labelStylesObj.level2Label, ], [labelStylesObj], ); + const childThreadInfosMap = useSelector(childThreadInfos); const drawerItemsData = React.useMemo( () => createRecursiveDrawerItemsData( childThreadInfosMap, communitiesSuffixed, labelStyles, maxDepth, ), [childThreadInfosMap, communitiesSuffixed, labelStyles], ); + const toggleExpanded = React.useCallback( + (id: string) => + setExpanded(expandedState => { + if (expanded.has(id)) { + return new Set( + filterOutThreadAndDescendantIDs( + [...expandedState.values()], + drawerItemsData, + id, + ), + ); + } + return new Set([...expanded.values(), id]); + }), + [drawerItemsData, expanded], + ); + + const navigateToThread = useNavigateToThread(); + + const renderItem = React.useCallback( + ({ item }) => { + const isCommunity = threadTypeIsCommunityRoot(item.threadInfo.type); + return ( + + ); + }, + [expanded, navigateToThread, setOpenCommunityOrClose, toggleExpanded], + ); + const { navigate } = useNavigation(); const onPressCommunityCreation = React.useCallback(() => { navigate(CommunityCreationRouteName); }, [navigate]); const communityCreationButton = ( Create community ); + const flattenedDrawerItemsData = React.useMemo( + () => flattenDrawerItemsData(drawerItemsData, [...expanded.values()]), + [drawerItemsData, expanded], + ); + 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-community.react.js b/native/navigation/community-drawer-item-community.react.js deleted file mode 100644 index 70f811e09..000000000 --- a/native/navigation/community-drawer-item-community.react.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow - -import * as React from 'react'; -import { View } from 'react-native'; - -import CommunityDrawerItem from './community-drawer-item.react.js'; -import type { DrawerItemProps } from './community-drawer-item.react.js'; -import { useStyles } from '../themes/colors.js'; - -function CommunityDrawerItemCommunity(props: DrawerItemProps): React.Node { - const styles = useStyles(unboundStyles); - - const style = React.useMemo( - () => - props.expanded - ? [styles.communityExpanded, styles.communityBase] - : styles.communityBase, - [props.expanded, styles.communityBase, styles.communityExpanded], - ); - - return ( - - - - ); -} - -const unboundStyles = { - communityBase: { - paddingVertical: 2, - paddingRight: 24, - paddingLeft: 8, - overflow: 'hidden', - }, - communityExpanded: { - backgroundColor: 'drawerOpenCommunityBackground', - borderTopRightRadius: 8, - borderBottomRightRadius: 8, - }, -}; - -const MemoizedCommunityDrawerItemCommunity: React.ComponentType = - React.memo(CommunityDrawerItemCommunity); -export default MemoizedCommunityDrawerItemCommunity; diff --git a/native/navigation/community-drawer-item.react.js b/native/navigation/community-drawer-item.react.js index 9a9bc4cfc..b3bf0298d 100644 --- a/native/navigation/community-drawer-item.react.js +++ b/native/navigation/community-drawer-item.react.js @@ -1,171 +1,155 @@ // @flow import * as React from 'react'; -import { View, FlatList, TouchableOpacity } from 'react-native'; +import { View, 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'; +import type { CommunityDrawerItemDataFlattened } from '../utils/drawer-utils.react.js'; export type DrawerItemProps = { - +itemData: CommunityDrawerItemData, + +itemData: CommunityDrawerItemDataFlattened, +toggleExpanded: (threadID: string) => void, - +expanded: boolean, + +isExpanded: boolean, +navigateToThread: (params: MessageListParams) => void, }; function CommunityDrawerItem(props: DrawerItemProps): React.Node { const { - itemData: { threadInfo, itemChildren, labelStyle, hasSubchannelsButton }, + itemData: { + threadInfo, + labelStyle, + hasSubchannelsButton, + hasChildren, + itemStyle, + }, navigateToThread, - expanded, + isExpanded, toggleExpanded, } = props; const styles = useStyles(unboundStyles); - const renderItem = React.useCallback( - ({ item }) => ( - - ), - [navigateToThread], - ); - - const children = React.useMemo(() => { - if (!expanded) { - return null; - } - if (hasSubchannelsButton) { + const subchannelsButton = React.useMemo(() => { + if (isExpanded && hasSubchannelsButton) { return ( ); } - return ; - }, [ - expanded, - itemChildren, - renderItem, - hasSubchannelsButton, - styles.subchannelsButton, - threadInfo, - ]); + return null; + }, [isExpanded, hasSubchannelsButton, styles.subchannelsButton, threadInfo]); const onExpandToggled = React.useCallback(() => { toggleExpanded(threadInfo.id); }, [toggleExpanded, threadInfo.id]); const itemExpandButton = React.useMemo(() => { - if (!itemChildren?.length && !hasSubchannelsButton) { + if (!hasChildren && !hasSubchannelsButton) { return ; } - return ; - }, [itemChildren?.length, hasSubchannelsButton, onExpandToggled, expanded]); + return ; + }, [hasChildren, hasSubchannelsButton, onExpandToggled, isExpanded]); 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]); + const containerStyle = React.useMemo( + () => [ + styles.container, + { + paddingLeft: itemStyle.indentation, + }, + styles[itemStyle.background], + ], + [itemStyle.indentation, itemStyle.background, styles], + ); + return ( - + {itemExpandButton} {avatar} {uiName} - {children} + {subchannelsButton} ); } const unboundStyles = { + container: { + paddingRight: 24, + }, + none: { + paddingVertical: 2, + }, + beginning: { + backgroundColor: 'drawerOpenCommunityBackground', + borderTopRightRadius: 8, + paddingTop: 2, + }, + middle: { + backgroundColor: 'drawerOpenCommunityBackground', + paddingRight: 24, + }, + end: { + backgroundColor: 'drawerOpenCommunityBackground', + borderBottomRightRadius: 8, + paddingBottom: 2, + }, 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;