Changeset View
Changeset View
Standalone View
Standalone View
native/navigation/community-drawer-content.react.js
// @flow | // @flow | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FlatList, Platform } from 'react-native'; | import { FlatList, Platform } from 'react-native'; | ||||
import { SafeAreaView } from 'react-native-safe-area-context'; | import { SafeAreaView } from 'react-native-safe-area-context'; | ||||
import { useSelector } from 'react-redux'; | import { useSelector } from 'react-redux'; | ||||
import { | import { | ||||
childThreadInfos, | childThreadInfos, | ||||
communityThreadSelector, | communityThreadSelector, | ||||
} from 'lib/selectors/thread-selectors.js'; | } from 'lib/selectors/thread-selectors.js'; | ||||
import { threadIsChannel } from 'lib/shared/thread-utils.js'; | |||||
import { | import { | ||||
type ThreadInfo, | createRecursiveDrawerItemsData, | ||||
type ResolvedThreadInfo, | appendSuffix, | ||||
communitySubthreads, | } from 'lib/utils/drawer-utils.react.js'; | ||||
} from 'lib/types/thread-types.js'; | |||||
import { useResolvedThreadInfos } from 'lib/utils/entity-helpers.js'; | import { useResolvedThreadInfos } from 'lib/utils/entity-helpers.js'; | ||||
import CommunityDrawerItemCommunity from './community-drawer-item-community.react.js'; | import CommunityDrawerItemCommunity from './community-drawer-item-community.react.js'; | ||||
import { useNavigateToThread } from '../chat/message-list-types.js'; | import { useNavigateToThread } from '../chat/message-list-types.js'; | ||||
import { useStyles } from '../themes/colors.js'; | import { useStyles } from '../themes/colors.js'; | ||||
import type { TextStyle } from '../types/styles.js'; | |||||
const maxDepth = 2; | const maxDepth = 2; | ||||
const safeAreaEdges = Platform.select({ | const safeAreaEdges = Platform.select({ | ||||
ios: ['top'], | ios: ['top'], | ||||
default: ['top', 'bottom'], | default: ['top', 'bottom'], | ||||
}); | }); | ||||
function CommunityDrawerContent(): React.Node { | function CommunityDrawerContent(): React.Node { | ||||
Show All 12 Lines | function CommunityDrawerContent(): React.Node { | ||||
const navigateToThread = useNavigateToThread(); | const navigateToThread = useNavigateToThread(); | ||||
const childThreadInfosMap = useSelector(childThreadInfos); | const childThreadInfosMap = useSelector(childThreadInfos); | ||||
const setOpenCommunityOrClose = React.useCallback((index: string) => { | const setOpenCommunityOrClose = React.useCallback((index: string) => { | ||||
setOpenCommunity(open => (open === index ? null : index)); | setOpenCommunity(open => (open === index ? null : index)); | ||||
}, []); | }, []); | ||||
const renderItem = React.useCallback( | const renderItem = React.useCallback( | ||||
({ item }) => { | ({ item }) => ( | ||||
const itemData = { | |||||
threadInfo: item.threadInfo, | |||||
itemChildren: item.itemChildren, | |||||
labelStyle: item.labelStyle, | |||||
hasSubchannelsButton: item.subchannelsButton, | |||||
}; | |||||
return ( | |||||
<CommunityDrawerItemCommunity | <CommunityDrawerItemCommunity | ||||
key={item.key} | key={item.threadInfo.id} | ||||
itemData={itemData} | itemData={item} | ||||
toggleExpanded={setOpenCommunityOrClose} | toggleExpanded={setOpenCommunityOrClose} | ||||
expanded={itemData.threadInfo.id === openCommunity} | expanded={item.threadInfo.id === openCommunity} | ||||
navigateToThread={navigateToThread} | navigateToThread={navigateToThread} | ||||
/> | /> | ||||
); | ), | ||||
}, | |||||
[navigateToThread, openCommunity, setOpenCommunityOrClose], | [navigateToThread, openCommunity, setOpenCommunityOrClose], | ||||
); | ); | ||||
const labelStylesObj = useStyles(labelUnboundStyles); | const labelStylesObj = useStyles(labelUnboundStyles); | ||||
const labelStyles = React.useMemo( | const labelStyles = React.useMemo( | ||||
() => [ | () => [ | ||||
labelStylesObj.level0Label, | labelStylesObj.level0Label, | ||||
labelStylesObj.level1Label, | labelStylesObj.level1Label, | ||||
labelStylesObj.level2Label, | labelStylesObj.level2Label, | ||||
], | ], | ||||
[labelStylesObj], | [labelStylesObj], | ||||
); | ); | ||||
const drawerItemsData = React.useMemo( | const drawerItemsData = React.useMemo( | ||||
() => | () => | ||||
createRecursiveDrawerItemsData( | createRecursiveDrawerItemsData( | ||||
childThreadInfosMap, | childThreadInfosMap, | ||||
communitiesSuffixed, | communitiesSuffixed, | ||||
labelStyles, | labelStyles, | ||||
maxDepth, | |||||
), | ), | ||||
[childThreadInfosMap, communitiesSuffixed, labelStyles], | [childThreadInfosMap, communitiesSuffixed, labelStyles], | ||||
); | ); | ||||
return ( | return ( | ||||
<SafeAreaView style={styles.drawerContent} edges={safeAreaEdges}> | <SafeAreaView style={styles.drawerContent} edges={safeAreaEdges}> | ||||
<FlatList data={drawerItemsData} renderItem={renderItem} /> | <FlatList data={drawerItemsData} renderItem={renderItem} /> | ||||
</SafeAreaView> | </SafeAreaView> | ||||
); | ); | ||||
} | } | ||||
function createRecursiveDrawerItemsData( | |||||
childThreadInfosMap: { +[id: string]: $ReadOnlyArray<ThreadInfo> }, | |||||
communities: $ReadOnlyArray<ResolvedThreadInfo>, | |||||
labelStyles: $ReadOnlyArray<TextStyle>, | |||||
) { | |||||
const result = communities.map(community => ({ | |||||
key: community.id, | |||||
threadInfo: community, | |||||
itemChildren: [], | |||||
labelStyle: labelStyles[0], | |||||
subchannelsButton: false, | |||||
})); | |||||
let queue = result.map(item => [item, 0]); | |||||
for (let i = 0; i < queue.length; i++) { | |||||
const [item, lvl] = queue[i]; | |||||
const itemChildThreadInfos = childThreadInfosMap[item.threadInfo.id] ?? []; | |||||
if (lvl < maxDepth) { | |||||
item.itemChildren = itemChildThreadInfos | |||||
.filter(childItem => communitySubthreads.includes(childItem.type)) | |||||
.map(childItem => ({ | |||||
threadInfo: childItem, | |||||
itemChildren: [], | |||||
labelStyle: labelStyles[Math.min(lvl + 1, labelStyles.length - 1)], | |||||
hasSubchannelsButton: | |||||
lvl + 1 === maxDepth && | |||||
threadHasSubchannels(childItem, childThreadInfosMap), | |||||
})); | |||||
queue = queue.concat( | |||||
item.itemChildren.map(childItem => [childItem, lvl + 1]), | |||||
); | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
function threadHasSubchannels( | |||||
threadInfo: ThreadInfo, | |||||
childThreadInfosMap: { +[id: string]: $ReadOnlyArray<ThreadInfo> }, | |||||
) { | |||||
if (!childThreadInfosMap[threadInfo.id]?.length) { | |||||
return false; | |||||
} | |||||
return childThreadInfosMap[threadInfo.id].some(thread => | |||||
threadIsChannel(thread), | |||||
); | |||||
} | |||||
function appendSuffix( | |||||
chats: $ReadOnlyArray<ResolvedThreadInfo>, | |||||
): ResolvedThreadInfo[] { | |||||
const result = []; | |||||
const names = new Map<string, number>(); | |||||
for (const chat of chats) { | |||||
let name = chat.uiName; | |||||
const numberOfOccurrences = names.get(name); | |||||
names.set(name, (numberOfOccurrences ?? 0) + 1); | |||||
if (numberOfOccurrences) { | |||||
name = `${name} (${numberOfOccurrences.toString()})`; | |||||
} | |||||
result.push({ ...chat, uiName: name }); | |||||
} | |||||
return result; | |||||
} | |||||
const unboundStyles = { | const unboundStyles = { | ||||
drawerContent: { | drawerContent: { | ||||
flex: 1, | flex: 1, | ||||
paddingRight: 8, | paddingRight: 8, | ||||
paddingTop: 8, | paddingTop: 8, | ||||
backgroundColor: 'drawerBackground', | backgroundColor: 'drawerBackground', | ||||
}, | }, | ||||
}; | }; | ||||
Show All 26 Lines |