Page MenuHomePhabricator

D13996.id46372.diff
No OneTemporary

D13996.id46372.diff

diff --git a/native/components/community-joiner-modal.react.js b/native/components/community-joiner-modal.react.js
new file mode 100644
--- /dev/null
+++ b/native/components/community-joiner-modal.react.js
@@ -0,0 +1,208 @@
+// @flow
+
+import * as React from 'react';
+import { View, Text } from 'react-native';
+import {
+ TabView,
+ SceneMap,
+ TabBar,
+ type TabBarProps,
+} from 'react-native-tab-view';
+
+import { useThreadSearchIndex } from 'lib/selectors/nav-selectors.js';
+import { threadInfoFromRawThreadInfo } from 'lib/shared/thread-utils.js';
+import type { ClientCommunityInfoWithCommunityName } from 'lib/types/community-types.js';
+
+import Modal from './modal.react.js';
+import CommunityList from '../components/community-list.react.js';
+import type { RootNavigationProp } from '../navigation/root-navigator.react.js';
+import type { NavigationRoute } from '../navigation/route-names.js';
+import { useSelector } from '../redux/redux-utils.js';
+import { useColors, useStyles } from '../themes/colors.js';
+
+export type CommunityJoinerModalParams = {
+ +communities: $ReadOnlyArray<ClientCommunityInfoWithCommunityName>,
+};
+
+type Props = {
+ +navigation: RootNavigationProp<'CommunityJoinerModal'>,
+ +route: NavigationRoute<'CommunityJoinerModal'>,
+};
+
+const defaultCommunities: $ReadOnlyArray<ClientCommunityInfoWithCommunityName> =
+ [];
+
+// This should be updated with the names of the crypto communities on Comm
+const cryptoCommunityIDs: $ReadOnlyArray<string> = [];
+
+function CommunityJoinerModal(props: Props): React.Node {
+ const { params } = props.route;
+ const communities = params?.communities ?? defaultCommunities;
+
+ const viewerID = useSelector(
+ state => state.currentUserInfo && state.currentUserInfo.id,
+ );
+ const userInfos = useSelector(state => state.userStore.userInfos);
+ const styles = useStyles(unboundStyles);
+
+ const generalCommunities = React.useMemo(
+ () =>
+ communities.filter(
+ community => !cryptoCommunityIDs.includes(community.id),
+ ),
+ [communities],
+ );
+ const cryptoCommunities = React.useMemo(
+ () =>
+ communities.filter(community =>
+ cryptoCommunityIDs.includes(community.id),
+ ),
+ [communities],
+ );
+
+ const generateThreadInfos = React.useCallback(
+ (communityList: $ReadOnlyArray<ClientCommunityInfoWithCommunityName>) =>
+ communityList
+ .map(community =>
+ community.threadInfo
+ ? threadInfoFromRawThreadInfo(
+ community.threadInfo,
+ viewerID,
+ userInfos,
+ )
+ : null,
+ )
+ .filter(Boolean),
+ [userInfos, viewerID],
+ );
+
+ const generalThreadInfos = React.useMemo(
+ () => generateThreadInfos(generalCommunities),
+ [generateThreadInfos, generalCommunities],
+ );
+
+ const cryptoThreadInfos = React.useMemo(
+ () => generateThreadInfos(cryptoCommunities),
+ [generateThreadInfos, cryptoCommunities],
+ );
+
+ const generalIndex = useThreadSearchIndex(generalThreadInfos);
+ const cryptoIndex = useThreadSearchIndex(cryptoThreadInfos);
+
+ const renderGeneralTab = React.useCallback(
+ () => (
+ <CommunityList
+ threadInfos={generalThreadInfos}
+ itemStyle={styles.threadListItem}
+ searchIndex={generalIndex}
+ />
+ ),
+ [generalIndex, generalThreadInfos, styles.threadListItem],
+ );
+
+ const renderCryptoTab = React.useCallback(
+ () => (
+ <CommunityList
+ threadInfos={cryptoThreadInfos}
+ itemStyle={styles.threadListItem}
+ searchIndex={cryptoIndex}
+ />
+ ),
+ [cryptoIndex, cryptoThreadInfos, styles.threadListItem],
+ );
+
+ const [index, setIndex] = React.useState(0);
+ const [routes] = React.useState([
+ { key: 'general', title: 'General' },
+ { key: 'crypto', title: 'Crypto' },
+ ]);
+
+ const navigationState = React.useMemo(
+ () => ({ index, routes }),
+ [index, routes],
+ );
+
+ const renderScene = React.useMemo(() => {
+ return SceneMap({
+ general: renderGeneralTab,
+ crypto: renderCryptoTab,
+ });
+ }, [renderCryptoTab, renderGeneralTab]);
+
+ const colors = useColors();
+ const { tabBarBackground, tabBarAccent } = colors;
+
+ const screenOptions = React.useMemo(
+ () => ({
+ tabBarShowIcon: true,
+ tabBarStyle: {
+ backgroundColor: tabBarBackground,
+ },
+ tabBarItemStyle: {
+ flexDirection: 'row',
+ },
+ tabBarIndicatorStyle: {
+ borderColor: tabBarAccent,
+ borderBottomWidth: 2,
+ },
+ }),
+ [tabBarAccent, tabBarBackground],
+ );
+
+ const initialLayout = React.useMemo(() => ({ width: 400 }), []);
+
+ const renderTabBar = React.useCallback(
+ (tabBarProps: TabBarProps) => (
+ <View style={styles.tabBarContainer}>
+ <TabBar
+ {...tabBarProps}
+ style={screenOptions.tabBarStyle}
+ indicatorStyle={screenOptions.tabBarIndicatorStyle}
+ />
+ </View>
+ ),
+ [
+ screenOptions.tabBarIndicatorStyle,
+ screenOptions.tabBarStyle,
+ styles.tabBarContainer,
+ ],
+ );
+
+ return (
+ <Modal>
+ <View style={styles.headerContainer}>
+ <Text style={styles.headerText}>Discover communities</Text>
+ </View>
+ <TabView
+ navigationState={navigationState}
+ renderScene={renderScene}
+ onIndexChange={setIndex}
+ initialLayout={initialLayout}
+ renderTabBar={renderTabBar}
+ />
+ </Modal>
+ );
+}
+
+const unboundStyles = {
+ headerContainer: {
+ padding: 10,
+ paddingBottom: 0,
+ },
+ headerText: {
+ color: 'modalForegroundLabel',
+ fontSize: 20,
+ marginBottom: 8,
+ textAlign: 'center',
+ },
+ threadListItem: {
+ paddingLeft: 10,
+ paddingRight: 10,
+ paddingVertical: 2,
+ },
+ tabBarContainer: {
+ marginBottom: 15,
+ },
+};
+
+export default CommunityJoinerModal;
diff --git a/native/flow-typed/npm/react-native-tab-view_v3.x.x.js b/native/flow-typed/npm/react-native-tab-view_v3.x.x.js
new file mode 100644
--- /dev/null
+++ b/native/flow-typed/npm/react-native-tab-view_v3.x.x.js
@@ -0,0 +1,61 @@
+// flow-typed signature: a7123e367e1cd3cb25dcd811f56eea57
+// flow-typed version: <<STUB>>/react-native-tab-view_v3.3.0/flow_v0.202.1
+
+declare module 'react-native-tab-view' {
+ import type { Route } from '@react-navigation/core';
+ import type {
+ AnimatedValue,
+ ViewStyle,
+ } from 'react-native-gesture-handler/@react-native';
+
+ declare type Layout = {
+ +width: number,
+ +height: number,
+ };
+ declare type SceneRendererProps = {
+ +layout: Layout,
+ +jumpTo: (key: string) => void,
+ ...
+ };
+
+ declare export var TabBarItem: React$ComponentType<{
+ +position: AnimatedValue,
+ +route: Route<>,
+ +navigationState: { +index: number, ... },
+ +onPress: () => void,
+ +onLongPress: () => void,
+ +style: ViewStyle,
+ +getLabelText?: () => string,
+ +getAccessible?: () => void,
+ +getAccessibilityLabel?: () => void,
+ +renderIcon?: ({ color: string, ... }) => React$Node,
+ +getTestID?: () => void,
+ +activeColor?: string,
+ ...
+ }>;
+
+ declare export var TabView: React$ComponentType<{
+ +navigationState: { +index: number, ... },
+ +renderScene: (props: SceneRendererProps & { route: Route<> }) => React$Node,
+ +onIndexChange: (number) => void,
+ +initialLayout: Partial<Layout>,
+ +renderTabBar: (TabBarProps) => React$Node,
+ ...
+ }>;
+
+ declare export function SceneMap(scenes: {
+ [key: string]: () => React$Node,
+ }): (props: SceneRendererProps & { route: Route<> }) => React$Node;
+
+ declare export var TabBar: React$ComponentType<{
+ +style: ViewStyle,
+ +indicatorStyle: ViewStyle,
+ ...
+ }>;
+
+ declare export type TabBarProps = {
+ +style: ViewStyle,
+ +indicatorStyle: ViewStyle,
+ ...
+ }
+}
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -58,6 +58,7 @@
CreateMissingSIWEBackupMessageRouteName,
RestoreSIWEBackupRouteName,
LinkedDevicesBottomSheetRouteName,
+ CommunityJoinerModalRouteName,
} from './route-names.js';
import LoggedOutModal from '../account/logged-out-modal.react.js';
import CreateMissingSIWEBackupMessage from '../account/registration/missing-registration-data/missing-siwe-backup-message.react.js';
@@ -75,6 +76,7 @@
import SubchannelsListModal from '../chat/subchannels-list-modal.react.js';
import CommunityCreationNavigator from '../community-creation/community-creation-navigator.react.js';
import TagFarcasterChannelNavigator from '../community-settings/tag-farcaster-channel/tag-farcaster-channel-navigator.react.js';
+import CommunityJoinerModal from '../components/community-joiner-modal.react.js';
import ConnectFarcasterBottomSheet from '../components/connect-farcaster-bottom-sheet.react.js';
import DirectoryPromptBottomSheet from '../components/directory-prompt-bottom-sheet.react.js';
import InviteLinksNavigator from '../invite-links/invite-links-navigator.react.js';
@@ -311,6 +313,11 @@
component={DirectoryPromptBottomSheet}
options={modalOverlayScreenOptions}
/>
+ <Root.Screen
+ name={CommunityJoinerModalRouteName}
+ component={CommunityJoinerModal}
+ options={modalOverlayScreenOptions}
+ />
<Root.Screen
name={TagFarcasterChannelNavigatorRouteName}
component={TagFarcasterChannelNavigator}
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -40,6 +40,7 @@
import type { TogglePinModalParams } from '../chat/toggle-pin-modal.react.js';
import type { TagFarcasterChannelByNameParams } from '../community-settings/tag-farcaster-channel/tag-farcaster-channel-by-name.react.js';
import type { TagFarcasterChannelParams } from '../community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js';
+import type { CommunityJoinerModalParams } from '../components/community-joiner-modal.react.js';
import type { InviteLinksNavigatorParams } from '../invite-links/invite-links-navigator.react.js';
import type { ManagePublicLinkScreenParams } from '../invite-links/manage-public-link-screen.react.js';
import type { ViewInviteLinksScreenParams } from '../invite-links/view-invite-links-screen.react.js';
@@ -169,6 +170,7 @@
export const ConnectFarcasterBottomSheetRouteName =
'ConnectFarcasterBottomSheet';
export const DirectoryPromptBottomSheetRouteName = 'DirectoryPromptBottomSheet';
+export const CommunityJoinerModalRouteName = 'CommunityJoinerModal';
export const TagFarcasterChannelNavigatorRouteName =
'TagFarcasterChannelNavigator';
export const TagFarcasterChannelRouteName = 'TagFarcasterChannel';
@@ -206,6 +208,7 @@
+LinkedDevicesBottomSheet: LinkedDevicesBottomSheetParams,
+ConnectFarcasterBottomSheet: void,
+DirectoryPromptBottomSheet: void,
+ +CommunityJoinerModal: CommunityJoinerModalParams,
+TagFarcasterChannelNavigator: void,
+CreateMissingSIWEBackupMessage: void,
+RestoreSIWEBackup: RestoreSIWEBackupParams,

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 23, 6:18 PM (19 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2691153
Default Alt Text
D13996.id46372.diff (11 KB)

Event Timeline