diff --git a/native/chat/chat-list.react.js b/native/chat/chat-list.react.js index df491dc38..bdf8ef07c 100644 --- a/native/chat/chat-list.react.js +++ b/native/chat/chat-list.react.js @@ -1,333 +1,351 @@ // @flow +import type { + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, +} from '@react-navigation/core'; import invariant from 'invariant'; import _sum from 'lodash/fp/sum.js'; import * as React from 'react'; import { Animated, Easing, StyleSheet, TouchableWithoutFeedback, View, FlatList as ReactNativeFlatList, } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { localIDPrefix } from 'lib/shared/message-utils.js'; import type { ChatNavigationProp } from './chat.react.js'; import NewMessagesPill from './new-messages-pill.react.js'; import { chatMessageItemHeight, chatMessageItemKey } from './utils.js'; import { InputStateContext } from '../input/input-state.js'; import type { InputState } from '../input/input-state.js'; import { type KeyboardState, KeyboardContext, } from '../keyboard/keyboard-state.js'; +import type { ScreenParamList } from '../navigation/route-names.js'; import type { TabNavigationProp } from '../navigation/tab-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import type { ChatMessageItemWithHeight } from '../types/chat-types.js'; import type { ScrollEvent } from '../types/react-native.js'; import type { ViewStyle } from '../types/styles.js'; type FlatListElementRef = React.ElementRef; type FlatListProps = React.ElementConfig; const animationSpec = { duration: 150, useNativeDriver: true, }; type BaseProps = { ...FlatListProps, +navigation: ChatNavigationProp<'MessageList'>, +data: $ReadOnlyArray, ... }; type Props = { ...BaseProps, // Redux state +viewerID: ?string, // withKeyboardState +keyboardState: ?KeyboardState, +inputState: ?InputState, ... }; type State = { +newMessageCount: number, }; class ChatList extends React.PureComponent { state: State = { newMessageCount: 0, }; flatList: ?FlatListElementRef; scrollPos = 0; newMessagesPillProgress = new Animated.Value(0); newMessagesPillStyle: ViewStyle; constructor(props: Props) { super(props); const sendButtonTranslateY = this.newMessagesPillProgress.interpolate({ inputRange: [0, 1], outputRange: ([10, 0]: number[]), // Flow... }); this.newMessagesPillStyle = { opacity: this.newMessagesPillProgress, transform: [{ translateY: sendButtonTranslateY }], }; } componentDidMount() { - const tabNavigation: ?TabNavigationProp<'Chat'> = - this.props.navigation.getParent(); + const tabNavigation = this.props.navigation.getParent< + ScreenParamList, + 'Chat', + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, + TabNavigationProp<'Chat'>, + >(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); tabNavigation.addListener('tabPress', this.onTabPress); this.props.inputState?.addScrollToMessageListener(this.scrollToMessage); } componentWillUnmount() { - const tabNavigation: ?TabNavigationProp<'Chat'> = - this.props.navigation.getParent(); + const tabNavigation = this.props.navigation.getParent< + ScreenParamList, + 'Chat', + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, + TabNavigationProp<'Chat'>, + >(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); tabNavigation.removeListener('tabPress', this.onTabPress); this.props.inputState?.removeScrollToMessageListener(this.scrollToMessage); } onTabPress = () => { const { flatList } = this; if (!this.props.navigation.isFocused() || !flatList) { return; } if (this.scrollPos > 0) { flatList.scrollToOffset({ offset: 0 }); } else { this.props.navigation.popToTop(); } }; get scrolledToBottom() { return this.scrollPos <= 0; } componentDidUpdate(prevProps: Props) { const { flatList } = this; if (!flatList || this.props.data === prevProps.data) { return; } if (this.props.data.length < prevProps.data.length) { // This should only happen due to MessageStorePruner, // which will only prune a thread when it is off-screen flatList.scrollToOffset({ offset: 0, animated: false }); return; } const { scrollPos } = this; let curDataIndex = 0, prevDataIndex = 0, heightSoFar = 0; let adjustScrollPos = 0, newLocalMessage = false, newRemoteMessageCount = 0; while (prevDataIndex < prevProps.data.length && heightSoFar <= scrollPos) { const prevItem = prevProps.data[prevDataIndex]; invariant(prevItem, 'prevItem should exist'); const prevItemKey = chatMessageItemKey(prevItem); const prevItemHeight = chatMessageItemHeight(prevItem); let curItem = this.props.data[curDataIndex]; while (curItem) { const curItemKey = chatMessageItemKey(curItem); if (curItemKey === prevItemKey) { break; } if (curItemKey.startsWith(localIDPrefix)) { newLocalMessage = true; } else if ( curItem.itemType === 'message' && curItem.messageInfo.creator.id !== this.props.viewerID ) { newRemoteMessageCount++; } adjustScrollPos += chatMessageItemHeight(curItem); curDataIndex++; curItem = this.props.data[curDataIndex]; } if (!curItem) { // The only case in which we would expect the length of data to // decrease, but find that an item was removed, is if the start // of the chat is reached. In that case, the spinner at the top // will no longer be rendered. We break here as we expect the // spinner to be the last item. if (prevItemKey === 'loader') { break; } console.log( `items not removed from ChatList, but ${prevItemKey} now missing`, ); return; } const curItemHeight = chatMessageItemHeight(curItem); adjustScrollPos += curItemHeight - prevItemHeight; heightSoFar += prevItemHeight; prevDataIndex++; curDataIndex++; } if (adjustScrollPos === 0) { return; } flatList.scrollToOffset({ offset: scrollPos + adjustScrollPos, animated: false, }); if (newLocalMessage || scrollPos <= 0) { flatList.scrollToOffset({ offset: 0 }); } else if (newRemoteMessageCount > 0) { this.setState(prevState => ({ newMessageCount: prevState.newMessageCount + newRemoteMessageCount, })); this.toggleNewMessagesPill(true); } } render() { const { navigation, viewerID, ...rest } = this.props; const { newMessageCount } = this.state; return ( 0 ? 'auto' : 'none'} containerStyle={styles.newMessagesPillContainer} style={this.newMessagesPillStyle} /> ); } flatListRef = (flatList: any) => { this.flatList = flatList; }; static getItemLayout = ( data: ?$ReadOnlyArray, index: number, ) => { if (!data) { return { length: 0, offset: 0, index }; } const offset = ChatList.heightOfItems(data.filter((_, i) => i < index)); const item = data[index]; const length = item ? chatMessageItemHeight(item) : 0; return { length, offset, index }; }; static heightOfItems( data: $ReadOnlyArray, ): number { return _sum(data.map(chatMessageItemHeight)); } toggleNewMessagesPill(show: boolean) { Animated.timing(this.newMessagesPillProgress, { ...animationSpec, easing: show ? Easing.ease : Easing.out(Easing.ease), toValue: show ? 1 : 0, }).start(({ finished }) => { if (finished && !show) { this.setState({ newMessageCount: 0 }); } }); } onScroll = (event: ScrollEvent) => { this.scrollPos = event.nativeEvent.contentOffset.y; if (this.scrollPos <= 0) { this.toggleNewMessagesPill(false); } this.props.onScroll && this.props.onScroll(event); }; onPressNewMessagesPill = () => { const { flatList } = this; if (!flatList) { return; } flatList.scrollToOffset({ offset: 0 }); this.toggleNewMessagesPill(false); }; scrollToMessage = (key?: string) => { const { flatList } = this; const { data } = this.props; if (!flatList || !key) { return; } const index = data.findIndex(item => chatMessageItemKey(item) === key); if (index < 0) { console.warn("Couldn't find message to scroll to"); return; } flatList.scrollToIndex({ index, animated: true, viewPosition: 0.5, }); }; onPressBackground = () => { const { keyboardState } = this.props; keyboardState && keyboardState.dismissKeyboard(); }; } const styles = StyleSheet.create({ container: { flex: 1, }, newMessagesPillContainer: { bottom: 30, position: 'absolute', right: 30, }, }); const ConnectedChatList: React.ComponentType = React.memo( function ConnectedChatList(props: BaseProps) { const keyboardState = React.useContext(KeyboardContext); const inputState = React.useContext(InputStateContext); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); return ( ); }, ); export default ConnectedChatList; diff --git a/native/chat/chat-thread-list.react.js b/native/chat/chat-thread-list.react.js index ca230b705..464e530ca 100644 --- a/native/chat/chat-thread-list.react.js +++ b/native/chat/chat-thread-list.react.js @@ -1,474 +1,496 @@ // @flow import IonIcon from '@expo/vector-icons/Ionicons.js'; +import type { + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, + StackNavigationState, + StackOptions, + StackNavigationEventMap, +} from '@react-navigation/core'; import invariant from 'invariant'; import * as React from 'react'; import { View, FlatList, Platform, TouchableWithoutFeedback, BackHandler, } from 'react-native'; import { FloatingAction } from 'react-native-floating-action'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; import { type ChatThreadItem, useFlattenedChatListData, } from 'lib/selectors/chat-selectors.js'; import { createPendingThread, getThreadListSearchResults, useThreadListSearch, } from 'lib/shared/thread-utils.js'; import type { MinimallyEncodedThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import type { UserInfo } from 'lib/types/user-types.js'; import { ChatThreadListItem } from './chat-thread-list-item.react.js'; import ChatThreadListSearch from './chat-thread-list-search.react.js'; import { getItemLayout, keyExtractor } from './chat-thread-list-utils.js'; import type { ChatTopTabsNavigationProp, ChatNavigationProp, } from './chat.react.js'; import { useNavigateToThread } from './message-list-types.js'; import { SidebarListModalRouteName, HomeChatThreadListRouteName, BackgroundChatThreadListRouteName, type NavigationRoute, + type ScreenParamList, } from '../navigation/route-names.js'; import type { TabNavigationProp } from '../navigation/tab-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { indicatorStyleSelector, useStyles } from '../themes/colors.js'; import type { ScrollEvent } from '../types/react-native.js'; const floatingActions = [ { text: 'Compose', icon: , name: 'compose', position: 1, }, ]; export type Item = | ChatThreadItem | { +type: 'search', +searchText: string } | { +type: 'empty', +emptyItem: React.ComponentType<{}> }; type BaseProps = { +navigation: | ChatTopTabsNavigationProp<'HomeChatThreadList'> | ChatTopTabsNavigationProp<'BackgroundChatThreadList'>, +route: | NavigationRoute<'HomeChatThreadList'> | NavigationRoute<'BackgroundChatThreadList'>, +filterThreads: ( threadItem: ThreadInfo | MinimallyEncodedThreadInfo, ) => boolean, +emptyItem?: React.ComponentType<{}>, }; export type SearchStatus = 'inactive' | 'activating' | 'active'; function ChatThreadList(props: BaseProps): React.Node { const boundChatListData = useFlattenedChatListData(); const loggedInUserInfo = useLoggedInUserInfo(); const styles = useStyles(unboundStyles); const indicatorStyle = useSelector(indicatorStyleSelector); const navigateToThread = useNavigateToThread(); const { navigation, route, filterThreads, emptyItem } = props; const [searchText, setSearchText] = React.useState(''); const [searchStatus, setSearchStatus] = React.useState('inactive'); const { threadSearchResults, usersSearchResults } = useThreadListSearch( searchText, loggedInUserInfo?.id, ); const [openedSwipeableID, setOpenedSwipeableID] = React.useState(''); const [numItemsToDisplay, setNumItemsToDisplay] = React.useState(25); const onChangeSearchText = React.useCallback((updatedSearchText: string) => { setSearchText(updatedSearchText); setNumItemsToDisplay(25); }, []); const scrollPos = React.useRef(0); const flatListRef = React.useRef(); const onScroll = React.useCallback( (event: ScrollEvent) => { const oldScrollPos = scrollPos.current; scrollPos.current = event.nativeEvent.contentOffset.y; if (scrollPos.current !== 0 || oldScrollPos === 0) { return; } if (searchStatus === 'activating') { setSearchStatus('active'); } }, [searchStatus], ); const onSwipeableWillOpen = React.useCallback( (threadInfo: ThreadInfo) => setOpenedSwipeableID(threadInfo.id), [], ); const composeThread = React.useCallback(() => { if (!loggedInUserInfo) { return; } const threadInfo = createPendingThread({ viewerID: loggedInUserInfo.id, threadType: threadTypes.PRIVATE, members: [loggedInUserInfo], }); navigateToThread({ threadInfo, searching: true }); }, [loggedInUserInfo, navigateToThread]); const onSearchFocus = React.useCallback(() => { if (searchStatus !== 'inactive') { return; } if (scrollPos.current === 0) { setSearchStatus('active'); } else { setSearchStatus('activating'); } }, [searchStatus]); const clearSearch = React.useCallback(() => { if (scrollPos.current > 0 && flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: false }); } setSearchStatus('inactive'); }, []); const onSearchBlur = React.useCallback(() => { if (searchStatus !== 'active') { return; } clearSearch(); }, [clearSearch, searchStatus]); const onSearchCancel = React.useCallback(() => { onChangeSearchText(''); clearSearch(); }, [clearSearch, onChangeSearchText]); const searchInputRef = React.useRef(); const onPressItem = React.useCallback( (threadInfo: ThreadInfo, pendingPersonalThreadUserInfo?: UserInfo) => { onChangeSearchText(''); if (searchInputRef.current) { searchInputRef.current.blur(); } navigateToThread({ threadInfo, pendingPersonalThreadUserInfo }); }, [navigateToThread, onChangeSearchText], ); const onPressSeeMoreSidebars = React.useCallback( (threadInfo: ThreadInfo) => { onChangeSearchText(''); if (searchInputRef.current) { this.searchInputRef.current.blur(); } navigation.navigate<'SidebarListModal'>({ name: SidebarListModalRouteName, params: { threadInfo }, }); }, [navigation, onChangeSearchText], ); const hardwareBack = React.useCallback(() => { if (!navigation.isFocused()) { return false; } const isActiveOrActivating = searchStatus === 'active' || searchStatus === 'activating'; if (!isActiveOrActivating) { return false; } onSearchCancel(); return true; }, [navigation, onSearchCancel, searchStatus]); const searchItem = React.useMemo( () => ( ), [ onChangeSearchText, onSearchBlur, onSearchCancel, onSearchFocus, searchStatus, searchText, styles.searchContainer, ], ); const renderItem = React.useCallback( (row: { item: Item, ... }) => { const item = row.item; if (item.type === 'search') { return searchItem; } if (item.type === 'empty') { const EmptyItem = item.emptyItem; return ; } return ( ); }, [ onPressItem, onPressSeeMoreSidebars, onSwipeableWillOpen, openedSwipeableID, searchItem, ], ); const listData: $ReadOnlyArray = React.useMemo(() => { const chatThreadItems = getThreadListSearchResults( boundChatListData, searchText, filterThreads, threadSearchResults, usersSearchResults, loggedInUserInfo, ); const chatItems: Item[] = [...chatThreadItems]; if (emptyItem && chatItems.length === 0) { chatItems.push({ type: 'empty', emptyItem }); } if (searchStatus === 'inactive' || searchStatus === 'activating') { chatItems.unshift({ type: 'search', searchText }); } return chatItems; }, [ boundChatListData, emptyItem, filterThreads, loggedInUserInfo, searchStatus, searchText, threadSearchResults, usersSearchResults, ]); const partialListData: $ReadOnlyArray = React.useMemo( () => listData.slice(0, numItemsToDisplay), [listData, numItemsToDisplay], ); const onEndReached = React.useCallback(() => { if (partialListData.length === listData.length) { return; } setNumItemsToDisplay(prevNumItems => prevNumItems + 25); }, [listData.length, partialListData.length]); const floatingAction = React.useMemo(() => { if (Platform.OS !== 'android') { return null; } return ( ); }, [composeThread]); const fixedSearch = React.useMemo(() => { if (searchStatus !== 'active') { return null; } return ( ); }, [ onChangeSearchText, onSearchBlur, onSearchCancel, searchStatus, searchText, styles.searchContainer, ]); const scrollEnabled = searchStatus === 'inactive' || searchStatus === 'active'; // viewerID is in extraData since it's used by MessagePreview // within ChatThreadListItem const viewerID = loggedInUserInfo?.id; const extraData = `${viewerID || ''} ${openedSwipeableID}`; const chatThreadList = React.useMemo( () => ( {fixedSearch} {floatingAction} ), [ extraData, fixedSearch, floatingAction, indicatorStyle, onEndReached, onScroll, partialListData, renderItem, scrollEnabled, styles.container, styles.flatList, ], ); const onTabPress = React.useCallback(() => { if (!navigation.isFocused()) { return; } if (scrollPos.current > 0 && flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: true }); } else if (route.name === BackgroundChatThreadListRouteName) { navigation.navigate({ name: HomeChatThreadListRouteName }); } }, [navigation, route.name]); React.useEffect(() => { const clearNavigationBlurListener = navigation.addListener('blur', () => { setNumItemsToDisplay(25); }); return () => { // `.addListener` returns function that can be called to unsubscribe. // https://reactnavigation.org/docs/navigation-events/#navigationaddlistener clearNavigationBlurListener(); }; }, [navigation]); React.useEffect(() => { - const chatNavigation: ?ChatNavigationProp<'ChatThreadList'> = - navigation.getParent(); + const chatNavigation = navigation.getParent< + ScreenParamList, + 'ChatThreadList', + StackNavigationState, + StackOptions, + StackNavigationEventMap, + ChatNavigationProp<'ChatThreadList'>, + >(); invariant(chatNavigation, 'ChatNavigator should be within TabNavigator'); - const tabNavigation: ?TabNavigationProp<'Chat'> = - chatNavigation.getParent(); + + const tabNavigation = chatNavigation.getParent< + ScreenParamList, + 'Chat', + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, + TabNavigationProp<'Chat'>, + >(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); tabNavigation.addListener('tabPress', onTabPress); return () => { tabNavigation.removeListener('tabPress', onTabPress); }; }, [navigation, onTabPress]); React.useEffect(() => { BackHandler.addEventListener('hardwareBackPress', hardwareBack); return () => { BackHandler.removeEventListener('hardwareBackPress', hardwareBack); }; }, [hardwareBack]); React.useEffect(() => { if (scrollPos.current > 0 && flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: false }); } }, [searchText]); const isSearchActivating = searchStatus === 'activating'; React.useEffect(() => { if (isSearchActivating && scrollPos.current > 0 && flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: true }); } }, [isSearchActivating]); return chatThreadList; } const unboundStyles = { icon: { fontSize: 28, }, container: { flex: 1, }, searchContainer: { backgroundColor: 'listBackground', display: 'flex', justifyContent: 'center', flexDirection: 'row', }, flatList: { flex: 1, backgroundColor: 'listBackground', }, }; export default ChatThreadList; diff --git a/native/chat/settings/thread-settings.react.js b/native/chat/settings/thread-settings.react.js index 0e4fa9b0c..f15cf7ede 100644 --- a/native/chat/settings/thread-settings.react.js +++ b/native/chat/settings/thread-settings.react.js @@ -1,1279 +1,1292 @@ // @flow +import type { + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, +} from '@react-navigation/core'; import invariant from 'invariant'; import * as React from 'react'; import { View, Platform } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { createSelector } from 'reselect'; import tinycolor from 'tinycolor2'; import { changeThreadSettingsActionTypes, leaveThreadActionTypes, removeUsersFromThreadActionTypes, changeThreadMemberRolesActionTypes, } from 'lib/actions/thread-actions.js'; import { usePromoteSidebar } from 'lib/hooks/promote-sidebar.react.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector, childThreadInfos, } from 'lib/selectors/thread-selectors.js'; import { getAvailableRelationshipButtons } from 'lib/shared/relationship-utils.js'; import { threadHasPermission, viewerIsMember, threadInChatList, getSingleOtherUser, threadIsChannel, } from 'lib/shared/thread-utils.js'; import threadWatcher from 'lib/shared/thread-watcher.js'; import type { MinimallyEncodedRelativeMemberInfo, MinimallyEncodedResolvedThreadInfo, MinimallyEncodedThreadInfo, } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { RelationshipButton } from 'lib/types/relationship-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { type ThreadInfo, type ResolvedThreadInfo, type RelativeMemberInfo, } from 'lib/types/thread-types.js'; import type { UserInfos } from 'lib/types/user-types.js'; import { useResolvedThreadInfo, useResolvedOptionalThreadInfo, useResolvedOptionalThreadInfos, } from 'lib/utils/entity-helpers.js'; import ThreadSettingsAvatar from './thread-settings-avatar.react.js'; import type { CategoryType } from './thread-settings-category.react.js'; import { ThreadSettingsCategoryHeader, ThreadSettingsCategoryActionHeader, ThreadSettingsCategoryFooter, } from './thread-settings-category.react.js'; import ThreadSettingsChildThread from './thread-settings-child-thread.react.js'; import ThreadSettingsColor from './thread-settings-color.react.js'; import ThreadSettingsDeleteThread from './thread-settings-delete-thread.react.js'; import ThreadSettingsDescription from './thread-settings-description.react.js'; import ThreadSettingsEditRelationship from './thread-settings-edit-relationship.react.js'; import ThreadSettingsHomeNotifs from './thread-settings-home-notifs.react.js'; import ThreadSettingsLeaveThread from './thread-settings-leave-thread.react.js'; import { ThreadSettingsSeeMore, ThreadSettingsAddMember, ThreadSettingsAddSubchannel, } from './thread-settings-list-action.react.js'; import ThreadSettingsMediaGallery from './thread-settings-media-gallery.react.js'; import ThreadSettingsMember from './thread-settings-member.react.js'; import ThreadSettingsName from './thread-settings-name.react.js'; import ThreadSettingsParent from './thread-settings-parent.react.js'; import ThreadSettingsPromoteSidebar from './thread-settings-promote-sidebar.react.js'; import ThreadSettingsPushNotifs from './thread-settings-push-notifs.react.js'; import ThreadSettingsVisibility from './thread-settings-visibility.react.js'; import ThreadAncestors from '../../components/thread-ancestors.react.js'; import { type KeyboardState, KeyboardContext, } from '../../keyboard/keyboard-state.js'; import { defaultStackScreenOptions } from '../../navigation/options.js'; import { OverlayContext, type OverlayContextType, } from '../../navigation/overlay-context.js'; import { AddUsersModalRouteName, ComposeSubchannelModalRouteName, FullScreenThreadMediaGalleryRouteName, + type ScreenParamList, + type NavigationRoute, } from '../../navigation/route-names.js'; -import type { NavigationRoute } from '../../navigation/route-names.js'; import type { TabNavigationProp } from '../../navigation/tab-navigator.react.js'; import { useSelector } from '../../redux/redux-utils.js'; import type { AppState } from '../../redux/state-types.js'; import { useStyles, type IndicatorStyle, useIndicatorStyle, } from '../../themes/colors.js'; import type { VerticalBounds } from '../../types/layout-types.js'; import type { ViewStyle } from '../../types/styles.js'; import type { ChatNavigationProp } from '../chat.react.js'; const itemPageLength = 5; export type ThreadSettingsParams = { +threadInfo: ThreadInfo | MinimallyEncodedThreadInfo, }; export type ThreadSettingsNavigate = $PropertyType< ChatNavigationProp<'ThreadSettings'>, 'navigate', >; type ChatSettingsItem = | { +itemType: 'header', +key: string, +title: string, +categoryType: CategoryType, } | { +itemType: 'actionHeader', +key: string, +title: string, +actionText: string, +onPress: () => void, } | { +itemType: 'footer', +key: string, +categoryType: CategoryType, } | { +itemType: 'avatar', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +canChangeSettings: boolean, } | { +itemType: 'name', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +nameEditValue: ?string, +canChangeSettings: boolean, } | { +itemType: 'color', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +colorEditValue: string, +canChangeSettings: boolean, +navigate: ThreadSettingsNavigate, +threadSettingsRouteKey: string, } | { +itemType: 'description', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +descriptionEditValue: ?string, +descriptionTextHeight: ?number, +canChangeSettings: boolean, } | { +itemType: 'parent', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +parentThreadInfo: | ?ResolvedThreadInfo | ?MinimallyEncodedResolvedThreadInfo, } | { +itemType: 'visibility', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, } | { +itemType: 'pushNotifs', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, } | { +itemType: 'homeNotifs', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, } | { +itemType: 'seeMore', +key: string, +onPress: () => void, } | { +itemType: 'childThread', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +firstListItem: boolean, +lastListItem: boolean, } | { +itemType: 'addSubchannel', +key: string, } | { +itemType: 'member', +key: string, +memberInfo: RelativeMemberInfo | MinimallyEncodedRelativeMemberInfo, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +canEdit: boolean, +navigate: ThreadSettingsNavigate, +firstListItem: boolean, +lastListItem: boolean, +verticalBounds: ?VerticalBounds, +threadSettingsRouteKey: string, } | { +itemType: 'addMember', +key: string, } | { +itemType: 'mediaGallery', +key: string, +threadInfo: ThreadInfo | MinimallyEncodedThreadInfo, +limit: number, +verticalBounds: ?VerticalBounds, } | { +itemType: 'promoteSidebar' | 'leaveThread' | 'deleteThread', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +navigate: ThreadSettingsNavigate, +buttonStyle: ViewStyle, } | { +itemType: 'editRelationship', +key: string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +navigate: ThreadSettingsNavigate, +buttonStyle: ViewStyle, +relationshipButton: RelationshipButton, }; type BaseProps = { +navigation: ChatNavigationProp<'ThreadSettings'>, +route: NavigationRoute<'ThreadSettings'>, }; type Props = { ...BaseProps, // Redux state +userInfos: UserInfos, +viewerID: ?string, +threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, +parentThreadInfo: ?ResolvedThreadInfo | ?MinimallyEncodedResolvedThreadInfo, +childThreadInfos: ?$ReadOnlyArray, +somethingIsSaving: boolean, +styles: typeof unboundStyles, +indicatorStyle: IndicatorStyle, // withOverlayContext +overlayContext: ?OverlayContextType, // withKeyboardState +keyboardState: ?KeyboardState, +canPromoteSidebar: boolean, }; type State = { +numMembersShowing: number, +numSubchannelsShowing: number, +numSidebarsShowing: number, +nameEditValue: ?string, +descriptionEditValue: ?string, +descriptionTextHeight: ?number, +colorEditValue: string, +verticalBounds: ?VerticalBounds, }; type PropsAndState = { ...Props, ...State }; class ThreadSettings extends React.PureComponent { flatListContainer: ?React.ElementRef; constructor(props: Props) { super(props); this.state = { numMembersShowing: itemPageLength, numSubchannelsShowing: itemPageLength, numSidebarsShowing: itemPageLength, nameEditValue: null, descriptionEditValue: null, descriptionTextHeight: null, colorEditValue: props.threadInfo.color, verticalBounds: null, }; } static scrollDisabled(props: Props) { const { overlayContext } = props; invariant(overlayContext, 'ThreadSettings should have OverlayContext'); return overlayContext.scrollBlockingModalStatus !== 'closed'; } componentDidUpdate(prevProps: Props) { const prevThreadInfo = prevProps.threadInfo; const newThreadInfo = this.props.threadInfo; if ( !tinycolor.equals(newThreadInfo.color, prevThreadInfo.color) && tinycolor.equals(this.state.colorEditValue, prevThreadInfo.color) ) { this.setState({ colorEditValue: newThreadInfo.color }); } if (defaultStackScreenOptions.gestureEnabled) { const scrollIsDisabled = ThreadSettings.scrollDisabled(this.props); const scrollWasDisabled = ThreadSettings.scrollDisabled(prevProps); if (!scrollWasDisabled && scrollIsDisabled) { this.props.navigation.setOptions({ gestureEnabled: false }); } else if (scrollWasDisabled && !scrollIsDisabled) { this.props.navigation.setOptions({ gestureEnabled: true }); } } } threadBasicsListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.threadInfo, (propsAndState: PropsAndState) => propsAndState.parentThreadInfo, (propsAndState: PropsAndState) => propsAndState.nameEditValue, (propsAndState: PropsAndState) => propsAndState.colorEditValue, (propsAndState: PropsAndState) => propsAndState.descriptionEditValue, (propsAndState: PropsAndState) => propsAndState.descriptionTextHeight, (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving, (propsAndState: PropsAndState) => propsAndState.navigation.navigate, (propsAndState: PropsAndState) => propsAndState.route.key, ( threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, parentThreadInfo: | ?ResolvedThreadInfo | ?MinimallyEncodedResolvedThreadInfo, nameEditValue: ?string, colorEditValue: string, descriptionEditValue: ?string, descriptionTextHeight: ?number, canStartEditing: boolean, navigate: ThreadSettingsNavigate, routeKey: string, ) => { const canEditThreadAvatar = threadHasPermission( threadInfo, threadPermissions.EDIT_THREAD_AVATAR, ); const canEditThreadName = threadHasPermission( threadInfo, threadPermissions.EDIT_THREAD_NAME, ); const canEditThreadDescription = threadHasPermission( threadInfo, threadPermissions.EDIT_THREAD_DESCRIPTION, ); const canEditThreadColor = threadHasPermission( threadInfo, threadPermissions.EDIT_THREAD_COLOR, ); const canChangeAvatar = canEditThreadAvatar && canStartEditing; const canChangeName = canEditThreadName && canStartEditing; const canChangeDescription = canEditThreadDescription && canStartEditing; const canChangeColor = canEditThreadColor && canStartEditing; const listData: ChatSettingsItem[] = []; listData.push({ itemType: 'header', key: 'avatarHeader', title: 'Channel Avatar', categoryType: 'unpadded', }); listData.push({ itemType: 'avatar', key: 'avatar', threadInfo, canChangeSettings: canChangeAvatar, }); listData.push({ itemType: 'footer', key: 'avatarFooter', categoryType: 'outline', }); listData.push({ itemType: 'header', key: 'basicsHeader', title: 'Basics', categoryType: 'full', }); listData.push({ itemType: 'name', key: 'name', threadInfo, nameEditValue, canChangeSettings: canChangeName, }); listData.push({ itemType: 'color', key: 'color', threadInfo, colorEditValue, canChangeSettings: canChangeColor, navigate, threadSettingsRouteKey: routeKey, }); listData.push({ itemType: 'footer', key: 'basicsFooter', categoryType: 'full', }); if ( (descriptionEditValue !== null && descriptionEditValue !== undefined) || threadInfo.description || canEditThreadDescription ) { listData.push({ itemType: 'description', key: 'description', threadInfo, descriptionEditValue, descriptionTextHeight, canChangeSettings: canChangeDescription, }); } const isMember = viewerIsMember(threadInfo); if (isMember) { listData.push({ itemType: 'header', key: 'subscriptionHeader', title: 'Subscription', categoryType: 'full', }); listData.push({ itemType: 'pushNotifs', key: 'pushNotifs', threadInfo, }); if (threadInfo.type !== threadTypes.SIDEBAR) { listData.push({ itemType: 'homeNotifs', key: 'homeNotifs', threadInfo, }); } listData.push({ itemType: 'footer', key: 'subscriptionFooter', categoryType: 'full', }); } listData.push({ itemType: 'header', key: 'privacyHeader', title: 'Privacy', categoryType: 'full', }); listData.push({ itemType: 'visibility', key: 'visibility', threadInfo, }); listData.push({ itemType: 'parent', key: 'parent', threadInfo, parentThreadInfo, }); listData.push({ itemType: 'footer', key: 'privacyFooter', categoryType: 'full', }); return listData; }, ); subchannelsListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.threadInfo, (propsAndState: PropsAndState) => propsAndState.navigation.navigate, (propsAndState: PropsAndState) => propsAndState.childThreadInfos, (propsAndState: PropsAndState) => propsAndState.numSubchannelsShowing, ( threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, navigate: ThreadSettingsNavigate, childThreads: ?$ReadOnlyArray< ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, >, numSubchannelsShowing: number, ) => { const listData: ChatSettingsItem[] = []; const subchannels = childThreads?.filter(threadIsChannel) ?? []; const canCreateSubchannels = threadHasPermission( threadInfo, threadPermissions.CREATE_SUBCHANNELS, ); if (subchannels.length === 0 && !canCreateSubchannels) { return listData; } listData.push({ itemType: 'header', key: 'subchannelHeader', title: 'Subchannels', categoryType: 'unpadded', }); if (canCreateSubchannels) { listData.push({ itemType: 'addSubchannel', key: 'addSubchannel', }); } const numItems = Math.min(numSubchannelsShowing, subchannels.length); for (let i = 0; i < numItems; i++) { const subchannelInfo = subchannels[i]; listData.push({ itemType: 'childThread', key: `childThread${subchannelInfo.id}`, threadInfo: subchannelInfo, firstListItem: i === 0 && !canCreateSubchannels, lastListItem: i === numItems - 1 && numItems === subchannels.length, }); } if (numItems < subchannels.length) { listData.push({ itemType: 'seeMore', key: 'seeMoreSubchannels', onPress: this.onPressSeeMoreSubchannels, }); } listData.push({ itemType: 'footer', key: 'subchannelFooter', categoryType: 'unpadded', }); return listData; }, ); sidebarsListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.navigation.navigate, (propsAndState: PropsAndState) => propsAndState.childThreadInfos, (propsAndState: PropsAndState) => propsAndState.numSidebarsShowing, ( navigate: ThreadSettingsNavigate, childThreads: ?$ReadOnlyArray< ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, >, numSidebarsShowing: number, ) => { const listData: ChatSettingsItem[] = []; const sidebars = childThreads?.filter( childThreadInfo => childThreadInfo.type === threadTypes.SIDEBAR, ) ?? []; if (sidebars.length === 0) { return listData; } listData.push({ itemType: 'header', key: 'sidebarHeader', title: 'Threads', categoryType: 'unpadded', }); const numItems = Math.min(numSidebarsShowing, sidebars.length); for (let i = 0; i < numItems; i++) { const sidebarInfo = sidebars[i]; listData.push({ itemType: 'childThread', key: `childThread${sidebarInfo.id}`, threadInfo: sidebarInfo, firstListItem: i === 0, lastListItem: i === numItems - 1 && numItems === sidebars.length, }); } if (numItems < sidebars.length) { listData.push({ itemType: 'seeMore', key: 'seeMoreSidebars', onPress: this.onPressSeeMoreSidebars, }); } listData.push({ itemType: 'footer', key: 'sidebarFooter', categoryType: 'unpadded', }); return listData; }, ); threadMembersListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.threadInfo, (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving, (propsAndState: PropsAndState) => propsAndState.navigation.navigate, (propsAndState: PropsAndState) => propsAndState.route.key, (propsAndState: PropsAndState) => propsAndState.numMembersShowing, (propsAndState: PropsAndState) => propsAndState.verticalBounds, ( threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, canStartEditing: boolean, navigate: ThreadSettingsNavigate, routeKey: string, numMembersShowing: number, verticalBounds: ?VerticalBounds, ) => { const listData: ChatSettingsItem[] = []; const canAddMembers = threadHasPermission( threadInfo, threadPermissions.ADD_MEMBERS, ); if (threadInfo.members.length === 0 && !canAddMembers) { return listData; } listData.push({ itemType: 'header', key: 'memberHeader', title: 'Members', categoryType: 'unpadded', }); if (canAddMembers) { listData.push({ itemType: 'addMember', key: 'addMember', }); } const numItems = Math.min(numMembersShowing, threadInfo.members.length); for (let i = 0; i < numItems; i++) { const memberInfo = threadInfo.members[i]; listData.push({ itemType: 'member', key: `member${memberInfo.id}`, memberInfo, threadInfo, canEdit: canStartEditing, navigate, firstListItem: i === 0 && !canAddMembers, lastListItem: i === numItems - 1 && numItems === threadInfo.members.length, verticalBounds, threadSettingsRouteKey: routeKey, }); } if (numItems < threadInfo.members.length) { listData.push({ itemType: 'seeMore', key: 'seeMoreMembers', onPress: this.onPressSeeMoreMembers, }); } listData.push({ itemType: 'footer', key: 'memberFooter', categoryType: 'unpadded', }); return listData; }, ); mediaGalleryListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.threadInfo, (propsAndState: PropsAndState) => propsAndState.verticalBounds, ( threadInfo: ThreadInfo | MinimallyEncodedThreadInfo, verticalBounds: ?VerticalBounds, ) => { const listData: ChatSettingsItem[] = []; const limit = 6; listData.push({ itemType: 'actionHeader', key: 'mediaGalleryHeader', title: 'Media Gallery', actionText: 'See more', onPress: this.onPressSeeMoreMediaGallery, }); listData.push({ itemType: 'mediaGallery', key: 'mediaGallery', threadInfo, limit, verticalBounds, }); listData.push({ itemType: 'footer', key: 'mediaGalleryFooter', categoryType: 'outline', }); return listData; }, ); actionsListDataSelector = createSelector( (propsAndState: PropsAndState) => propsAndState.threadInfo, (propsAndState: PropsAndState) => propsAndState.parentThreadInfo, (propsAndState: PropsAndState) => propsAndState.navigation.navigate, (propsAndState: PropsAndState) => propsAndState.styles, (propsAndState: PropsAndState) => propsAndState.userInfos, (propsAndState: PropsAndState) => propsAndState.viewerID, ( threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo, parentThreadInfo: | ?ResolvedThreadInfo | ?MinimallyEncodedResolvedThreadInfo, navigate: ThreadSettingsNavigate, styles: typeof unboundStyles, userInfos: UserInfos, viewerID: ?string, ) => { const buttons = []; if (this.props.canPromoteSidebar) { buttons.push({ itemType: 'promoteSidebar', key: 'promoteSidebar', threadInfo, navigate, }); } const canLeaveThread = threadHasPermission( threadInfo, threadPermissions.LEAVE_THREAD, ); if (viewerIsMember(threadInfo) && canLeaveThread) { buttons.push({ itemType: 'leaveThread', key: 'leaveThread', threadInfo, navigate, }); } const canDeleteThread = threadHasPermission( threadInfo, threadPermissions.DELETE_THREAD, ); if (canDeleteThread) { buttons.push({ itemType: 'deleteThread', key: 'deleteThread', threadInfo, navigate, }); } const threadIsPersonal = threadInfo.type === threadTypes.PERSONAL; if (threadIsPersonal && viewerID) { const otherMemberID = getSingleOtherUser(threadInfo, viewerID); if (otherMemberID) { const otherUserInfo = userInfos[otherMemberID]; const availableRelationshipActions = getAvailableRelationshipButtons(otherUserInfo); for (const action of availableRelationshipActions) { buttons.push({ itemType: 'editRelationship', key: action, threadInfo, navigate, relationshipButton: action, }); } } } const listData: ChatSettingsItem[] = []; if (buttons.length === 0) { return listData; } listData.push({ itemType: 'header', key: 'actionsHeader', title: 'Actions', categoryType: 'unpadded', }); for (let i = 0; i < buttons.length; i++) { // Necessary for Flow... if (buttons[i].itemType === 'editRelationship') { listData.push({ ...buttons[i], buttonStyle: [ i === 0 ? null : styles.nonTopButton, i === buttons.length - 1 ? styles.lastButton : null, ], }); } else { listData.push({ ...buttons[i], buttonStyle: [ i === 0 ? null : styles.nonTopButton, i === buttons.length - 1 ? styles.lastButton : null, ], }); } } listData.push({ itemType: 'footer', key: 'actionsFooter', categoryType: 'unpadded', }); return listData; }, ); listDataSelector = createSelector( this.threadBasicsListDataSelector, this.subchannelsListDataSelector, this.sidebarsListDataSelector, this.threadMembersListDataSelector, this.mediaGalleryListDataSelector, this.actionsListDataSelector, ( threadBasicsListData: ChatSettingsItem[], subchannelsListData: ChatSettingsItem[], sidebarsListData: ChatSettingsItem[], threadMembersListData: ChatSettingsItem[], mediaGalleryListData: ChatSettingsItem[], actionsListData: ChatSettingsItem[], ) => [ ...threadBasicsListData, ...subchannelsListData, ...sidebarsListData, ...threadMembersListData, ...mediaGalleryListData, ...actionsListData, ], ); get listData() { return this.listDataSelector({ ...this.props, ...this.state }); } render() { return ( ); } flatListContainerRef = ( flatListContainer: ?React.ElementRef, ) => { this.flatListContainer = flatListContainer; }; onFlatListContainerLayout = () => { const { flatListContainer } = this; if (!flatListContainer) { return; } const { keyboardState } = this.props; if (!keyboardState || keyboardState.keyboardShowing) { return; } flatListContainer.measure((x, y, width, height, pageX, pageY) => { if ( height === null || height === undefined || pageY === null || pageY === undefined ) { return; } this.setState({ verticalBounds: { height, y: pageY } }); }); }; // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return renderItem = (row: { item: ChatSettingsItem, ... }) => { const item = row.item; if (item.itemType === 'header') { return ( ); } else if (item.itemType === 'actionHeader') { return ( ); } else if (item.itemType === 'footer') { return ; } else if (item.itemType === 'avatar') { return ( ); } else if (item.itemType === 'name') { return ( ); } else if (item.itemType === 'color') { return ( ); } else if (item.itemType === 'description') { return ( ); } else if (item.itemType === 'parent') { return ( ); } else if (item.itemType === 'visibility') { return ; } else if (item.itemType === 'pushNotifs') { return ; } else if (item.itemType === 'homeNotifs') { return ; } else if (item.itemType === 'seeMore') { return ; } else if (item.itemType === 'childThread') { return ( ); } else if (item.itemType === 'addSubchannel') { return ( ); } else if (item.itemType === 'member') { return ( ); } else if (item.itemType === 'addMember') { return ; } else if (item.itemType === 'mediaGallery') { return ( ); } else if (item.itemType === 'leaveThread') { return ( ); } else if (item.itemType === 'deleteThread') { return ( ); } else if (item.itemType === 'promoteSidebar') { return ( ); } else if (item.itemType === 'editRelationship') { return ( ); } else { invariant(false, `unexpected ThreadSettings item type ${item.itemType}`); } }; setNameEditValue = (value: ?string, callback?: () => void) => { this.setState({ nameEditValue: value }, callback); }; setColorEditValue = (color: string) => { this.setState({ colorEditValue: color }); }; setDescriptionEditValue = (value: ?string, callback?: () => void) => { this.setState({ descriptionEditValue: value }, callback); }; setDescriptionTextHeight = (height: number) => { this.setState({ descriptionTextHeight: height }); }; onPressComposeSubchannel = () => { this.props.navigation.navigate(ComposeSubchannelModalRouteName, { presentedFrom: this.props.route.key, threadInfo: this.props.threadInfo, }); }; onPressAddMember = () => { this.props.navigation.navigate(AddUsersModalRouteName, { presentedFrom: this.props.route.key, threadInfo: this.props.threadInfo, }); }; onPressSeeMoreMembers = () => { this.setState(prevState => ({ numMembersShowing: prevState.numMembersShowing + itemPageLength, })); }; onPressSeeMoreSubchannels = () => { this.setState(prevState => ({ numSubchannelsShowing: prevState.numSubchannelsShowing + itemPageLength, })); }; onPressSeeMoreSidebars = () => { this.setState(prevState => ({ numSidebarsShowing: prevState.numSidebarsShowing + itemPageLength, })); }; onPressSeeMoreMediaGallery = () => { this.props.navigation.navigate(FullScreenThreadMediaGalleryRouteName, { threadInfo: this.props.threadInfo, }); }; } const unboundStyles = { container: { backgroundColor: 'panelBackground', flex: 1, }, flatList: { paddingVertical: 16, }, nonTopButton: { borderColor: 'panelForegroundBorder', borderTopWidth: 1, }, lastButton: { paddingBottom: Platform.OS === 'ios' ? 14 : 12, }, }; const threadMembersChangeIsSaving = ( state: AppState, threadMembers: $ReadOnlyArray< RelativeMemberInfo | MinimallyEncodedRelativeMemberInfo, >, ) => { for (const threadMember of threadMembers) { const removeUserLoadingStatus = createLoadingStatusSelector( removeUsersFromThreadActionTypes, `${removeUsersFromThreadActionTypes.started}:${threadMember.id}`, )(state); if (removeUserLoadingStatus === 'loading') { return true; } const changeRoleLoadingStatus = createLoadingStatusSelector( changeThreadMemberRolesActionTypes, `${changeThreadMemberRolesActionTypes.started}:${threadMember.id}`, )(state); if (changeRoleLoadingStatus === 'loading') { return true; } } return false; }; const ConnectedThreadSettings: React.ComponentType = React.memo(function ConnectedThreadSettings(props: BaseProps) { const userInfos = useSelector(state => state.userStore.userInfos); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const threadID = props.route.params.threadInfo.id; const reduxThreadInfo: ?ThreadInfo = useSelector( state => threadInfoSelector(state)[threadID], ); React.useEffect(() => { invariant( reduxThreadInfo, 'ReduxThreadInfo should exist when ThreadSettings is opened', ); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const { setParams } = props.navigation; React.useEffect(() => { if (reduxThreadInfo) { setParams({ threadInfo: reduxThreadInfo }); } }, [reduxThreadInfo, setParams]); const threadInfo: ThreadInfo | MinimallyEncodedThreadInfo = reduxThreadInfo ?? props.route.params.threadInfo; const resolvedThreadInfo = useResolvedThreadInfo(threadInfo); React.useEffect(() => { if (threadInChatList(threadInfo)) { return undefined; } threadWatcher.watchID(threadInfo.id); return () => { threadWatcher.removeID(threadInfo.id); }; }, [threadInfo]); const parentThreadID = threadInfo.parentThreadID; const parentThreadInfo: ?ThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); const resolvedParentThreadInfo = useResolvedOptionalThreadInfo(parentThreadInfo); const threadMembers = threadInfo.members; const boundChildThreadInfos = useSelector( state => childThreadInfos(state)[threadID], ); const resolvedChildThreadInfos = useResolvedOptionalThreadInfos( boundChildThreadInfos, ); const somethingIsSaving = useSelector(state => { const editNameLoadingStatus = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:${threadID}:name`, )(state); const editColorLoadingStatus = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:${threadID}:color`, )(state); const editDescriptionLoadingStatus = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:${threadID}:description`, )(state); const leaveThreadLoadingStatus = createLoadingStatusSelector( leaveThreadActionTypes, `${leaveThreadActionTypes.started}:${threadID}`, )(state); const boundThreadMembersChangeIsSaving = threadMembersChangeIsSaving( state, threadMembers, ); return ( boundThreadMembersChangeIsSaving || editNameLoadingStatus === 'loading' || editColorLoadingStatus === 'loading' || editDescriptionLoadingStatus === 'loading' || leaveThreadLoadingStatus === 'loading' ); }); const { navigation } = props; React.useEffect(() => { - const tabNavigation: ?TabNavigationProp<'Chat'> = navigation.getParent(); + const tabNavigation = navigation.getParent< + ScreenParamList, + 'Chat', + TabNavigationState, + BottomTabOptions, + BottomTabNavigationEventMap, + TabNavigationProp<'Chat'>, + >(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); const onTabPress = () => { if (navigation.isFocused() && !somethingIsSaving) { navigation.popToTop(); } }; tabNavigation.addListener('tabPress', onTabPress); return () => tabNavigation.removeListener('tabPress', onTabPress); }, [navigation, somethingIsSaving]); const styles = useStyles(unboundStyles); const indicatorStyle = useIndicatorStyle(); const overlayContext = React.useContext(OverlayContext); const keyboardState = React.useContext(KeyboardContext); const { canPromoteSidebar } = usePromoteSidebar(threadInfo); return ( ); }); export default ConnectedThreadSettings; diff --git a/native/flow-typed/npm/@react-navigation/core_v6.x.x.js b/native/flow-typed/npm/@react-navigation/core_v6.x.x.js index 7072c959c..d898ea30c 100644 --- a/native/flow-typed/npm/@react-navigation/core_v6.x.x.js +++ b/native/flow-typed/npm/@react-navigation/core_v6.x.x.js @@ -1,2345 +1,2358 @@ // flow-typed signature: 080655bdcaa9b716925932d980dfc4fb // flow-typed version: 9a968c602c/@react-navigation/core_v5.x.x/flow_>=v0.201.x declare module '@react-navigation/core' { //--------------------------------------------------------------------------- // SECTION 1: IDENTICAL TYPE DEFINITIONS //--------------------------------------------------------------------------- /** * We start with some definitions that we have copy-pasted from React Native * source files. */ // This is a bastardization of the true StyleObj type located in // react-native/Libraries/StyleSheet/StyleSheetTypes. We unfortunately can't // import that here, and it's too lengthy (and consequently too brittle) to // copy-paste here either. declare type StyleObj = | null | void | number | false | '' | $ReadOnlyArray | { [name: string]: any, ... }; declare export type ViewStyleProp = StyleObj; declare export type TextStyleProp = StyleObj; declare export type AnimatedViewStyleProp = StyleObj; declare type AnimatedTextStyleProp = StyleObj; // Vaguely copied from // react-native/Libraries/Animated/src/animations/Animation.js declare type EndResult = { finished: boolean, ... }; declare type EndCallback = (result: EndResult) => void; declare interface Animation { start( fromValue: number, onUpdate: (value: number) => void, onEnd: ?EndCallback, previousAnimation: ?Animation, animatedValue: AnimatedValue, ): void; stop(): void; } declare type AnimationConfig = { isInteraction?: boolean, useNativeDriver: boolean, onComplete?: ?EndCallback, iterations?: number, ... }; // Vaguely copied from // react-native/Libraries/Animated/src/nodes/AnimatedTracking.js declare interface AnimatedTracking { constructor( value: AnimatedValue, parent: any, animationClass: any, animationConfig: Object, callback?: ?EndCallback, ): void; update(): void; } // Vaguely copied from // react-native/Libraries/Animated/src/nodes/AnimatedValue.js declare type ValueListenerCallback = (state: { value: number, ... }) => void; declare interface AnimatedValue { constructor(value: number): void; setValue(value: number): void; setOffset(offset: number): void; flattenOffset(): void; extractOffset(): void; addListener(callback: ValueListenerCallback): string; removeListener(id: string): void; removeAllListeners(): void; stopAnimation(callback?: ?(value: number) => void): void; resetAnimation(callback?: ?(value: number) => void): void; interpolate(config: InterpolationConfigType): AnimatedInterpolation; animate(animation: Animation, callback: ?EndCallback): void; stopTracking(): void; track(tracking: AnimatedTracking): void; } // Copied from // react-native/Libraries/Animated/src/animations/TimingAnimation.js declare type TimingAnimationConfigSingle = AnimationConfig & { toValue: number | AnimatedValue, easing?: (value: number) => number, duration?: number, delay?: number, ... }; // Copied from // react-native/Libraries/Animated/src/animations/SpringAnimation.js declare type SpringAnimationConfigSingle = AnimationConfig & { toValue: number | AnimatedValue, overshootClamping?: boolean, restDisplacementThreshold?: number, restSpeedThreshold?: number, velocity?: number, bounciness?: number, speed?: number, tension?: number, friction?: number, stiffness?: number, damping?: number, mass?: number, delay?: number, ... }; // Copied from react-native/Libraries/Types/CoreEventTypes.js declare type SyntheticEvent = $ReadOnly<{| bubbles: ?boolean, cancelable: ?boolean, currentTarget: number, defaultPrevented: ?boolean, dispatchConfig: $ReadOnly<{| registrationName: string, |}>, eventPhase: ?number, preventDefault: () => void, isDefaultPrevented: () => boolean, stopPropagation: () => void, isPropagationStopped: () => boolean, isTrusted: ?boolean, nativeEvent: T, persist: () => void, target: ?number, timeStamp: number, type: ?string, |}>; declare type Layout = $ReadOnly<{| x: number, y: number, width: number, height: number, |}>; declare type LayoutEvent = SyntheticEvent< $ReadOnly<{| layout: Layout, |}>, >; declare type BlurEvent = SyntheticEvent< $ReadOnly<{| target: number, |}>, >; declare type FocusEvent = SyntheticEvent< $ReadOnly<{| target: number, |}>, >; declare type ResponderSyntheticEvent = $ReadOnly<{| ...SyntheticEvent, touchHistory: $ReadOnly<{| indexOfSingleActiveTouch: number, mostRecentTimeStamp: number, numberActiveTouches: number, touchBank: $ReadOnlyArray< $ReadOnly<{| touchActive: boolean, startPageX: number, startPageY: number, startTimeStamp: number, currentPageX: number, currentPageY: number, currentTimeStamp: number, previousPageX: number, previousPageY: number, previousTimeStamp: number, |}>, >, |}>, |}>; declare export type PressEvent = ResponderSyntheticEvent< $ReadOnly<{| changedTouches: $ReadOnlyArray<$PropertyType>, force: number, identifier: number, locationX: number, locationY: number, pageX: number, pageY: number, target: ?number, timestamp: number, touches: $ReadOnlyArray<$PropertyType>, |}>, >; // Vaguely copied from // react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js declare type ExtrapolateType = 'extend' | 'identity' | 'clamp'; declare type InterpolationConfigType = { inputRange: Array, outputRange: Array | Array, easing?: (input: number) => number, extrapolate?: ExtrapolateType, extrapolateLeft?: ExtrapolateType, extrapolateRight?: ExtrapolateType, ... }; declare interface AnimatedInterpolation { interpolate(config: InterpolationConfigType): AnimatedInterpolation; } // Copied from react-native/Libraries/Components/View/ViewAccessibility.js declare type AccessibilityRole = | 'none' | 'button' | 'link' | 'search' | 'image' | 'keyboardkey' | 'text' | 'adjustable' | 'imagebutton' | 'header' | 'summary' | 'alert' | 'checkbox' | 'combobox' | 'menu' | 'menubar' | 'menuitem' | 'progressbar' | 'radio' | 'radiogroup' | 'scrollbar' | 'spinbutton' | 'switch' | 'tab' | 'tablist' | 'timer' | 'toolbar'; declare type AccessibilityActionInfo = $ReadOnly<{ name: string, label?: string, ... }>; declare type AccessibilityActionEvent = SyntheticEvent< $ReadOnly<{actionName: string, ...}>, >; declare type AccessibilityState = { disabled?: boolean, selected?: boolean, checked?: ?boolean | 'mixed', busy?: boolean, expanded?: boolean, ... }; declare type AccessibilityValue = $ReadOnly<{| min?: number, max?: number, now?: number, text?: string, |}>; // Copied from // react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js declare type Stringish = string; declare type EdgeInsetsProp = $ReadOnly<$Partial>; declare type TouchableWithoutFeedbackProps = $ReadOnly<{| accessibilityActions?: ?$ReadOnlyArray, accessibilityElementsHidden?: ?boolean, accessibilityHint?: ?Stringish, accessibilityIgnoresInvertColors?: ?boolean, accessibilityLabel?: ?Stringish, accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'), accessibilityRole?: ?AccessibilityRole, accessibilityState?: ?AccessibilityState, accessibilityValue?: ?AccessibilityValue, accessibilityViewIsModal?: ?boolean, accessible?: ?boolean, children?: ?React$Node, delayLongPress?: ?number, delayPressIn?: ?number, delayPressOut?: ?number, disabled?: ?boolean, focusable?: ?boolean, hitSlop?: ?EdgeInsetsProp, importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'), nativeID?: ?string, onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed, onBlur?: ?(event: BlurEvent) => mixed, onFocus?: ?(event: FocusEvent) => mixed, onLayout?: ?(event: LayoutEvent) => mixed, onLongPress?: ?(event: PressEvent) => mixed, onPress?: ?(event: PressEvent) => mixed, onPressIn?: ?(event: PressEvent) => mixed, onPressOut?: ?(event: PressEvent) => mixed, pressRetentionOffset?: ?EdgeInsetsProp, rejectResponderTermination?: ?boolean, testID?: ?string, touchSoundDisabled?: ?boolean, |}>; // Copied from react-native/Libraries/Image/ImageSource.js declare export type ImageURISource = $ReadOnly<{ uri?: ?string, bundle?: ?string, method?: ?string, headers?: ?Object, body?: ?string, cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached'), width?: ?number, height?: ?number, scale?: ?number, ... }>; /** * The following is copied from react-native-gesture-handler's libdef */ declare type StateUndetermined = 0; declare type StateFailed = 1; declare type StateBegan = 2; declare type StateCancelled = 3; declare type StateActive = 4; declare type StateEnd = 5; declare type GestureHandlerState = | StateUndetermined | StateFailed | StateBegan | StateCancelled | StateActive | StateEnd; declare type $SyntheticEvent = { +nativeEvent: $ReadOnly<$Exact>, ... }; declare type $Event = $SyntheticEvent<{ handlerTag: number, numberOfPointers: number, state: GestureHandlerState, oldState: GestureHandlerState, ...$Exact, ... }>; declare type $EventHandlers = {| onGestureEvent?: ($Event) => mixed, onHandlerStateChange?: ($Event) => mixed, onBegan?: ($Event) => mixed, onFailed?: ($Event) => mixed, onCancelled?: ($Event) => mixed, onActivated?: ($Event) => mixed, onEnded?: ($Event) => mixed, |}; declare type HitSlop = | number | {| left?: number, top?: number, right?: number, bottom?: number, vertical?: number, horizontal?: number, width?: number, height?: number, |} | {| width: number, left: number, |} | {| width: number, right: number, |} | {| height: number, top: number, |} | {| height: number, bottom: number, |}; declare type $GestureHandlerProps< AdditionalProps: {...}, ExtraEventsProps: {...} > = $ReadOnly<{| ...$Exact, ...$EventHandlers, id?: string, enabled?: boolean, waitFor?: React$Ref | Array>, simultaneousHandlers?: React$Ref | Array>, shouldCancelWhenOutside?: boolean, minPointers?: number, hitSlop?: HitSlop, children?: React$Node, |}>; declare export type PanGestureHandlerProps = $GestureHandlerProps< { activeOffsetY?: number | [number, number], activeOffsetX?: number | [number, number], failOffsetY?: number | [number, number], failOffsetX?: number | [number, number], minDist?: number, minVelocity?: number, minVelocityX?: number, minVelocityY?: number, minPointers?: number, maxPointers?: number, avgTouches?: boolean, ... }, { x: number, y: number, absoluteX: number, absoluteY: number, translationX: number, translationY: number, velocityX: number, velocityY: number, ... } >; /** * MAGIC */ declare type $If = $Call< ((true, Then, Else) => Then) & ((false, Then, Else) => Else), Test, Then, Else, >; declare type $IsA = $Call< (Y => true) & (mixed => false), X, >; declare type $IsUndefined = $IsA; declare type $Partial = $ReadOnly<$Rest>; // If { ...T, ... } counts as a T, then we're inexact declare type $IsExact = $Call< (T => false) & (mixed => true), { ...T, ... }, >; /** * Actions, state, etc. */ declare export type ScreenParams = { +[key: string]: mixed, ... }; declare export type BackAction = {| +type: 'GO_BACK', +source?: string, +target?: string, |}; declare export type NavigateAction = {| +type: 'NAVIGATE', +payload: | {| +key: string, +params?: ScreenParams |} | {| +name: string, +key?: string, +params?: ScreenParams |}, +source?: string, +target?: string, |}; declare export type ResetAction = {| +type: 'RESET', +payload: StaleNavigationState, +source?: string, +target?: string, |}; declare export type SetParamsAction = {| +type: 'SET_PARAMS', +payload: {| +params?: ScreenParams |}, +source?: string, +target?: string, |}; declare export type CommonAction = | BackAction | NavigateAction | ResetAction | SetParamsAction; declare type NavigateActionCreator = {| (routeName: string, params?: ScreenParams): NavigateAction, ( | {| +key: string, +params?: ScreenParams |} | {| +name: string, +key?: string, +params?: ScreenParams |}, ): NavigateAction, |}; declare export type CommonActionsType = {| +navigate: NavigateActionCreator, +goBack: () => BackAction, +reset: (state: PossiblyStaleNavigationState) => ResetAction, +setParams: (params: ScreenParams) => SetParamsAction, |}; declare export type GenericNavigationAction = {| +type: string, +payload?: { +[key: string]: mixed, ... }, +source?: string, +target?: string, |}; declare export type LeafRoute = {| +key: string, +name: RouteName, +params?: ScreenParams, |}; declare export type StateRoute = {| ...LeafRoute, +state: NavigationState | StaleNavigationState, |}; declare export type Route = | LeafRoute | StateRoute; declare export type NavigationState = {| +key: string, +index: number, +routeNames: $ReadOnlyArray, +history?: $ReadOnlyArray, +routes: $ReadOnlyArray>, +type: string, +stale: false, |}; declare export type StaleLeafRoute = {| +key?: string, +name: RouteName, +params?: ScreenParams, |}; declare export type StaleStateRoute = {| ...StaleLeafRoute, +state: StaleNavigationState, |}; declare export type StaleRoute = | StaleLeafRoute | StaleStateRoute; declare export type StaleNavigationState = {| // It's possible to pass React Nav a StaleNavigationState with an undefined // index, but React Nav will always return one with the index set. This is // the same as for the type property below, but in the case of index we tend // to rely on it being set more... +index: number, +history?: $ReadOnlyArray, +routes: $ReadOnlyArray>, +type?: string, +stale?: true, |}; declare export type PossiblyStaleNavigationState = | NavigationState | StaleNavigationState; declare export type PossiblyStaleRoute = | Route | StaleRoute; /** * Routers */ declare type ActionCreators< State: NavigationState, Action: GenericNavigationAction, > = { +[key: string]: (...args: any) => (Action | State => Action), ... }; declare export type DefaultRouterOptions = { +initialRouteName?: string, ... }; declare export type RouterFactory< State: NavigationState, Action: GenericNavigationAction, RouterOptions: DefaultRouterOptions, > = (options: RouterOptions) => Router; declare export type ParamListBase = { +[key: string]: ?ScreenParams, ... }; declare export type RouterConfigOptions = {| +routeNames: $ReadOnlyArray, +routeParamList: ParamListBase, |}; declare export type Router< State: NavigationState, Action: GenericNavigationAction, > = {| +type: $PropertyType, +getInitialState: (options: RouterConfigOptions) => State, +getRehydratedState: ( partialState: PossiblyStaleNavigationState, options: RouterConfigOptions, ) => State, +getStateForRouteNamesChange: ( state: State, options: RouterConfigOptions, ) => State, +getStateForRouteFocus: (state: State, key: string) => State, +getStateForAction: ( state: State, action: Action, options: RouterConfigOptions, ) => ?PossiblyStaleNavigationState; +shouldActionChangeFocus: (action: GenericNavigationAction) => boolean, +actionCreators?: ActionCreators, |}; /** * Stack actions and router */ declare export type StackNavigationState = {| ...NavigationState, +type: 'stack', |}; declare export type ReplaceAction = {| +type: 'REPLACE', +payload: {| +name: string, +key?: ?string, +params?: ScreenParams |}, +source?: string, +target?: string, |}; declare export type PushAction = {| +type: 'PUSH', +payload: {| +name: string, +key?: ?string, +params?: ScreenParams |}, +source?: string, +target?: string, |}; declare export type PopAction = {| +type: 'POP', +payload: {| +count: number |}, +source?: string, +target?: string, |}; declare export type PopToTopAction = {| +type: 'POP_TO_TOP', +source?: string, +target?: string, |}; declare export type StackAction = | CommonAction | ReplaceAction | PushAction | PopAction | PopToTopAction; declare export type StackActionsType = {| +replace: (routeName: string, params?: ScreenParams) => ReplaceAction, +push: (routeName: string, params?: ScreenParams) => PushAction, +pop: (count?: number) => PopAction, +popToTop: () => PopToTopAction, |}; declare export type StackRouterOptions = $Exact; /** * Tab actions and router */ declare export type TabNavigationState = {| ...NavigationState, +type: 'tab', +history: $ReadOnlyArray<{| type: 'route', key: string |}>, |}; declare export type JumpToAction = {| +type: 'JUMP_TO', +payload: {| +name: string, +params?: ScreenParams |}, +source?: string, +target?: string, |}; declare export type TabAction = | CommonAction | JumpToAction; declare export type TabActionsType = {| +jumpTo: string => JumpToAction, |}; declare export type TabRouterOptions = {| ...$Exact, +backBehavior?: 'initialRoute' | 'order' | 'history' | 'none', |}; /** * Drawer actions and router */ declare type DrawerHistoryEntry = | {| +type: 'route', +key: string |} | {| +type: 'drawer' |}; declare export type DrawerNavigationState = {| ...NavigationState, +type: 'drawer', +history: $ReadOnlyArray, |}; declare export type OpenDrawerAction = {| +type: 'OPEN_DRAWER', +source?: string, +target?: string, |}; declare export type CloseDrawerAction = {| +type: 'CLOSE_DRAWER', +source?: string, +target?: string, |}; declare export type ToggleDrawerAction = {| +type: 'TOGGLE_DRAWER', +source?: string, +target?: string, |}; declare export type DrawerAction = | TabAction | OpenDrawerAction | CloseDrawerAction | ToggleDrawerAction; declare export type DrawerActionsType = {| ...TabActionsType, +openDrawer: () => OpenDrawerAction, +closeDrawer: () => CloseDrawerAction, +toggleDrawer: () => ToggleDrawerAction, |}; declare export type DrawerRouterOptions = {| ...TabRouterOptions, +defaultStatus?: 'open' | 'closed', |}; /** * Events */ declare export type EventMapBase = { +[name: string]: {| +data?: mixed, +canPreventDefault?: boolean, |}, ... }; declare type EventPreventDefaultProperties = $If< Test, {| +defaultPrevented: boolean, +preventDefault: () => void |}, {| |}, >; declare type EventDataProperties = $If< $IsUndefined, {| |}, {| +data: Data |}, >; declare type EventArg< EventName: string, CanPreventDefault: ?boolean = false, Data = void, > = {| ...EventPreventDefaultProperties, ...EventDataProperties, +type: EventName, +target?: string, |}; declare type GlobalEventMap = {| +state: {| +data: {| +state: State |}, +canPreventDefault: false |}, |}; declare type EventMapCore = {| ...GlobalEventMap, +focus: {| +data: void, +canPreventDefault: false |}, +blur: {| +data: void, +canPreventDefault: false |}, +beforeRemove: {| +data: {| +action: GenericNavigationAction |}, +canPreventDefault: true, |}, |}; declare type EventListenerCallback< EventName: string, State: PossiblyStaleNavigationState = NavigationState, EventMap: EventMapBase = EventMapCore, > = (e: EventArg< EventName, $PropertyType< $ElementType< {| ...EventMap, ...EventMapCore |}, EventName, >, 'canPreventDefault', >, $PropertyType< $ElementType< {| ...EventMap, ...EventMapCore |}, EventName, >, 'data', >, >) => mixed; /** * Navigation prop */ declare type PartialWithMergeProperty = $If< $IsExact, { ...$Partial, +merge: true }, { ...$Partial, +merge: true, ... }, >; declare type EitherExactOrPartialWithMergeProperty = | ParamsType | PartialWithMergeProperty; declare export type SimpleNavigate = >( routeName: DestinationRouteName, params: EitherExactOrPartialWithMergeProperty< $ElementType, >, ) => void; declare export type Navigate = & SimpleNavigate & >( route: $If< $IsUndefined<$ElementType>, | {| +key: string |} | {| +name: DestinationRouteName, +key?: string |}, | {| +key: string, +params?: EitherExactOrPartialWithMergeProperty< $ElementType, >, |} | {| +name: DestinationRouteName, +key?: string, +params?: EitherExactOrPartialWithMergeProperty< $ElementType, >, |}, >, ) => void; declare type CoreNavigationHelpers< ParamList: ParamListBase, State: PossiblyStaleNavigationState = PossiblyStaleNavigationState, EventMap: EventMapBase = EventMapCore, > = { +navigate: Navigate, +dispatch: ( action: | GenericNavigationAction | (State => GenericNavigationAction), ) => void, +reset: PossiblyStaleNavigationState => void, +goBack: () => void, +isFocused: () => boolean, +canGoBack: () => boolean, +getId: () => string | void, - +getParent: >(id?: string) => ?Parent, + +getParent: < + ParamList: ParamListBase, + RouteName: $Keys, + State: PossiblyStaleNavigationState, + ScreenOptions: {...}, + EventMap: EventMapBase, + Parent: NavigationProp< + ParamList, + RouteName, + State, + ScreenOptions, + EventMap, + >, + >(id?: string) => ?Parent, +getState: () => NavigationState, +addListener: |}, >>( name: EventName, callback: EventListenerCallback, ) => () => void, +removeListener: |}, >>( name: EventName, callback: EventListenerCallback, ) => void, ... }; declare export type NavigationHelpers< ParamList: ParamListBase, State: PossiblyStaleNavigationState = PossiblyStaleNavigationState, EventMap: EventMapBase = EventMapCore, > = { ...$Exact>, +setParams: (params: ScreenParams) => void, ... }; declare type SetParamsInput< ParamList: ParamListBase, RouteName: $Keys = $Keys, > = $If< $IsUndefined<$ElementType>, empty, $Partial<$NonMaybeType<$ElementType>>, >; declare export type NavigationProp< ParamList: ParamListBase, RouteName: $Keys = $Keys, State: PossiblyStaleNavigationState = PossiblyStaleNavigationState, ScreenOptions: {...} = {...}, EventMap: EventMapBase = EventMapCore, > = { ...$Exact>, +setOptions: (options: $Partial) => void, +setParams: (params: SetParamsInput) => void, ... }; /** * CreateNavigator */ declare export type RouteProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, > = {| ...LeafRoute, +params: $ElementType, +path?: string, |}; declare type ScreenOptionsProp< ScreenOptions: {...}, RouteParam, NavHelpers, > = | ScreenOptions | ({| +route: RouteParam, +navigation: NavHelpers |}) => ScreenOptions; declare export type ScreenListeners< State: NavigationState = NavigationState, EventMap: EventMapBase = EventMapCore, > = $ObjMapi< {| [name: $Keys]: empty |}, >(K, empty) => EventListenerCallback, >; declare type ScreenListenersProp< ScreenListenersParam: {...}, RouteParam, NavHelpers, > = | ScreenListenersParam | ({| +route: RouteParam, +navigation: NavHelpers |}) => ScreenListenersParam; declare type BaseScreenProps< ParamList: ParamListBase, NavProp, RouteName: $Keys = $Keys, State: NavigationState = NavigationState, ScreenOptions: {...} = {...}, EventMap: EventMapBase = EventMapCore, > = {| +name: RouteName, +options?: ScreenOptionsProp< ScreenOptions, RouteProp, NavProp, >, +listeners?: ScreenListenersProp< ScreenListeners, RouteProp, NavProp, >, +initialParams?: $Partial<$ElementType>, +getId?: ({ +params: $ElementType, }) => string | void, +navigationKey?: string, |}; declare export type ScreenProps< ParamList: ParamListBase, NavProp, RouteName: $Keys = $Keys, State: NavigationState = NavigationState, ScreenOptions: {...} = {...}, EventMap: EventMapBase = EventMapCore, > = | {| ...BaseScreenProps< ParamList, NavProp, RouteName, State, ScreenOptions, EventMap, >, +component: React$ComponentType<{| +route: RouteProp, +navigation: NavProp, |}>, |} | {| ...BaseScreenProps< ParamList, NavProp, RouteName, State, ScreenOptions, EventMap, >, +getComponent: () => React$ComponentType<{| +route: RouteProp, +navigation: NavProp, |}>, |} | {| ...BaseScreenProps< ParamList, NavProp, RouteName, State, ScreenOptions, EventMap, >, +children: ({| +route: RouteProp, +navigation: NavProp, |}) => React$Node, |}; declare export type ScreenComponent< GlobalParamList: ParamListBase, ParamList: ParamListBase, State: NavigationState = NavigationState, ScreenOptions: {...} = {...}, EventMap: EventMapBase = EventMapCore, > = < RouteName: $Keys, NavProp: NavigationProp< GlobalParamList, RouteName, State, ScreenOptions, EventMap, >, >(props: ScreenProps< ParamList, NavProp, RouteName, State, ScreenOptions, EventMap, >) => React$Node; declare type ScreenOptionsProps< ScreenOptions: {...}, RouteParam, NavHelpers, > = {| +screenOptions?: ScreenOptionsProp, |}; declare type ScreenListenersProps< ScreenListenersParam: {...}, RouteParam, NavHelpers, > = {| +screenListeners?: ScreenListenersProp< ScreenListenersParam, RouteParam, NavHelpers, >, |}; declare export type ExtraNavigatorPropsBase = { ...$Exact, +id?: string, +children?: React$Node, ... }; declare export type NavigatorProps< ScreenOptions: {...}, ScreenListenersParam, RouteParam, NavHelpers, ExtraNavigatorProps: ExtraNavigatorPropsBase, > = { ...$Exact, ...ScreenOptionsProps, ...ScreenListenersProps, +defaultScreenOptions?: | ScreenOptions | ({| +route: RouteParam, +navigation: NavHelpers, +options: ScreenOptions, |}) => ScreenOptions, ... }; declare export type NavigatorPropsBase< ScreenOptions: {...}, ScreenListenersParam: {...}, NavHelpers, > = NavigatorProps< ScreenOptions, ScreenListenersParam, RouteProp<>, NavHelpers, ExtraNavigatorPropsBase, >; declare export type CreateNavigator< State: NavigationState, ScreenOptions: {...}, EventMap: EventMapBase, ExtraNavigatorProps: ExtraNavigatorPropsBase, > = < GlobalParamList: ParamListBase, ParamList: ParamListBase, NavHelpers: NavigationHelpers< GlobalParamList, State, EventMap, >, >() => {| +Screen: ScreenComponent< GlobalParamList, ParamList, State, ScreenOptions, EventMap, >, +Navigator: React$ComponentType<$Exact, RouteProp, NavHelpers, ExtraNavigatorProps, >>>, +Group: React$ComponentType<{| ...ScreenOptionsProps, NavHelpers>, +children: React$Node, +navigationKey?: string, |}>, |}; declare export type CreateNavigatorFactory = < State: NavigationState, ScreenOptions: {...}, EventMap: EventMapBase, NavHelpers: NavigationHelpers< ParamListBase, State, EventMap, >, ExtraNavigatorProps: ExtraNavigatorPropsBase, >( navigator: React$ComponentType<$Exact, RouteProp<>, NavHelpers, ExtraNavigatorProps, >>>, ) => CreateNavigator; /** * useNavigationBuilder */ declare export type Descriptor< NavHelpers, ScreenOptions: {...} = {...}, > = {| +render: () => React$Node, +options: $ReadOnly, +navigation: NavHelpers, |}; declare export type UseNavigationBuilder = < State: NavigationState, Action: GenericNavigationAction, ScreenOptions: {...}, RouterOptions: DefaultRouterOptions, NavHelpers, EventMap: EventMapBase, ExtraNavigatorProps: ExtraNavigatorPropsBase, >( routerFactory: RouterFactory, options: $Exact, RouteProp<>, NavHelpers, ExtraNavigatorProps, >>, ) => {| +id?: string, +state: State, +descriptors: {| +[key: string]: Descriptor |}, +navigation: NavHelpers, |}; /** * EdgeInsets */ declare type EdgeInsets = {| +top: number, +right: number, +bottom: number, +left: number, |}; /** * TransitionPreset */ declare export type TransitionSpec = | {| animation: 'spring', config: $Diff< SpringAnimationConfigSingle, { toValue: number | AnimatedValue, ... }, >, |} | {| animation: 'timing', config: $Diff< TimingAnimationConfigSingle, { toValue: number | AnimatedValue, ... }, >, |}; declare export type StackCardInterpolationProps = {| +current: {| +progress: AnimatedInterpolation, |}, +next?: {| +progress: AnimatedInterpolation, |}, +index: number, +closing: AnimatedInterpolation, +swiping: AnimatedInterpolation, +inverted: AnimatedInterpolation, +layouts: {| +screen: {| +width: number, +height: number |}, |}, +insets: EdgeInsets, |}; declare export type StackCardInterpolatedStyle = {| containerStyle?: AnimatedViewStyleProp, cardStyle?: AnimatedViewStyleProp, overlayStyle?: AnimatedViewStyleProp, shadowStyle?: AnimatedViewStyleProp, |}; declare export type StackCardStyleInterpolator = ( props: StackCardInterpolationProps, ) => StackCardInterpolatedStyle; declare export type StackHeaderInterpolationProps = {| +current: {| +progress: AnimatedInterpolation, |}, +next?: {| +progress: AnimatedInterpolation, |}, +layouts: {| +header: {| +width: number, +height: number |}, +screen: {| +width: number, +height: number |}, +title?: {| +width: number, +height: number |}, +leftLabel?: {| +width: number, +height: number |}, |}, |}; declare export type StackHeaderInterpolatedStyle = {| leftLabelStyle?: AnimatedViewStyleProp, leftButtonStyle?: AnimatedViewStyleProp, rightButtonStyle?: AnimatedViewStyleProp, titleStyle?: AnimatedViewStyleProp, backgroundStyle?: AnimatedViewStyleProp, |}; declare export type StackHeaderStyleInterpolator = ( props: StackHeaderInterpolationProps, ) => StackHeaderInterpolatedStyle; declare type GestureDirection = | 'horizontal' | 'horizontal-inverted' | 'vertical' | 'vertical-inverted'; declare export type TransitionPreset = {| +gestureDirection: GestureDirection, +transitionSpec: {| +open: TransitionSpec, +close: TransitionSpec, |}, +cardStyleInterpolator: StackCardStyleInterpolator, +headerStyleInterpolator: StackHeaderStyleInterpolator, |}; /** * Header common options */ declare export type SceneProgress = {| +current: AnimatedInterpolation, +next?: AnimatedInterpolation, +previous?: AnimatedInterpolation, |}; declare export type HeaderProps = {| +navigation: NavProp, +route: RouteProp<>, +options: ScreenOptions, +layout: {| +width: number, +height: number |}, |}; declare export type HeaderButtonProps = $Partial<{| +tintColor: string, +pressColor: string, +pressOpacity: number, |}>; declare export type HeaderLeftButtonProps = $Partial<{| ...HeaderButtonProps, +labelVisible: boolean, |}>; declare export type HeaderTitleInputBase = { +onLayout: LayoutEvent => void, +children: string, +allowFontScaling: ?boolean, +tintColor: ?string, +style: ?AnimatedTextStyleProp, ... }; declare export type HeaderTitleInputProps = $Exact; declare export type HeaderCommonOptions< NavHeaderProps, NavHeaderLeftProps, NavHeaderRightProps, > = $Partial<{| +header: NavHeaderProps => React$Node, +headerShown: boolean, +headerTitle: string | ( HeaderTitleInputProps => React$Node), +headerTitleAlign: 'left' | 'center', +headerTitleStyle: AnimatedTextStyleProp, +headerTitleContainerStyle: AnimatedViewStyleProp, +headerTintColor: string, +headerTitleAllowFontScaling: boolean, +headerLeft: NavHeaderLeftProps => React$Node, +headerLeftContainerStyle: AnimatedViewStyleProp, +headerRight: NavHeaderRightProps => React$Node, +headerRightContainerStyle: AnimatedViewStyleProp, +headerBackground: ({| style: AnimatedViewStyleProp |}) => React$Node, +headerStyle: AnimatedViewStyleProp, +headerTransparent: boolean, +headerStatusBarHeight: number, +headerShadowVisible: boolean, +headerBackgroundContainerStyle: AnimatedViewStyleProp, +headerPressColor: string, +headerPressOpacity: number, |}>; /** * Stack options */ declare export type StackDescriptor = Descriptor< StackNavigationHelpers<>, StackOptions, >; declare type Scene = {| +route: T, +descriptor: StackDescriptor, +progress: SceneProgress, |}; declare export type StackHeaderProps = {| ...HeaderProps, StackOptions>, +progress: SceneProgress, +back?: {| +title: string |}, +styleInterpolator: StackHeaderStyleInterpolator, |}; declare export type StackHeaderButtonProps = $Partial<{| ...HeaderButtonProps, +canGoBack: boolean, |}>; declare export type StackHeaderLeftButtonProps = $Partial<{| ...StackHeaderButtonProps, +onPress: (() => void), +backImage: (props: {| tintColor: string |}) => React$Node, +label: string, +truncatedLabel: string, +labelVisible: boolean, +labelStyle: AnimatedTextStyleProp, +allowFontScaling: boolean, +onLabelLayout: LayoutEvent => void, +screenLayout: {| +width: number, +height: number |}, +titleLayout: {| +width: number, +height: number |}, +disabled: boolean, +accessibilityLabel: string, +style: ViewStyleProp, |}>; declare export type StackOptions = $Partial<{| +title: string, +cardShadowEnabled: boolean, +cardOverlayEnabled: boolean, +cardOverlay: {| style: ViewStyleProp |} => React$Node, +cardStyle: ViewStyleProp, +animationEnabled: boolean, +animationTypeForReplace: 'push' | 'pop', +gestureEnabled: boolean, +gestureResponseDistance: number, +gestureVelocityImpact: number, +safeAreaInsets: $Partial, +keyboardHandlingEnabled: boolean, +presentation: 'card' | 'modal' | 'transparentModal', // Transition ...TransitionPreset, // Header ...HeaderCommonOptions< StackHeaderProps, StackHeaderLeftButtonProps, StackHeaderButtonProps, >, +headerMode: 'float' | 'screen', +headerBackAllowFontScaling: boolean, +headerBackTitle: string | null, +headerBackTitleStyle: TextStyleProp, +headerBackTitleVisible: boolean, +headerTruncatedBackTitle: string, +headerBackImage: $PropertyType, +headerBackAccessibilityLabel: string, |}>; /** * Stack navigation prop */ declare export type StackNavigationEventMap = {| ...EventMapCore, +transitionStart: {| +data: {| +closing: boolean |}, +canPreventDefault: false, |}, +transitionEnd: {| +data: {| +closing: boolean |}, +canPreventDefault: false, |}, +gestureStart: {| +data: void, +canPreventDefault: false |}, +gestureEnd: {| +data: void, +canPreventDefault: false |}, +gestureCancel: {| +data: void, +canPreventDefault: false |}, |}; declare type StackExtraNavigationHelpers< ParamList: ParamListBase = ParamListBase, > = {| +replace: SimpleNavigate, +push: SimpleNavigate, +pop: (count?: number) => void, +popToTop: () => void, |}; declare export type StackNavigationHelpers< ParamList: ParamListBase = ParamListBase, EventMap: EventMapBase = StackNavigationEventMap, > = { ...$Exact>, ...StackExtraNavigationHelpers, ... }; declare export type StackNavigationProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, Options: {...} = StackOptions, EventMap: EventMapBase = StackNavigationEventMap, > = {| ...$Exact>, ...StackExtraNavigationHelpers, |}; /** * Miscellaneous stack exports */ declare export type StackNavigationConfig = {| +detachInactiveScreens?: boolean, |}; declare export type ExtraStackNavigatorProps = {| ...$Exact, ...StackRouterOptions, ...StackNavigationConfig, |}; declare export type StackNavigatorProps< NavHelpers: StackNavigationHelpers<> = StackNavigationHelpers<>, > = $Exact, RouteProp<>, NavHelpers, ExtraStackNavigatorProps, >>; /** * Bottom tab options */ declare export type BottomTabBarButtonProps = {| ...$Diff< TouchableWithoutFeedbackProps, {| onPress?: ?(event: PressEvent) => mixed |}, >, +to?: string, +children: React$Node, +onPress?: (MouseEvent | PressEvent) => void, |}; declare export type TabBarVisibilityAnimationConfig = | {| +animation: 'spring', +config?: $Diff< SpringAnimationConfigSingle, { toValue: number | AnimatedValue, useNativeDriver: boolean, ... }, >, |} | {| +animation: 'timing', +config?: $Diff< TimingAnimationConfigSingle, { toValue: number | AnimatedValue, useNativeDriver: boolean, ... }, >, |}; declare export type BottomTabOptions = $Partial<{| +title: string, +tabBarLabel: | string | ({| focused: boolean, color: string |}) => React$Node, +tabBarIcon: ({| focused: boolean, color: string, size: number, |}) => React$Node, +tabBarBadge: number | string, +tabBarBadgeStyle: TextStyleProp, +tabBarAccessibilityLabel: string, +tabBarTestID: string, +tabBarVisibilityAnimationConfig: $Partial<{| +show: TabBarVisibilityAnimationConfig, +hide: TabBarVisibilityAnimationConfig, |}>, +tabBarButton: BottomTabBarButtonProps => React$Node, +tabBarHideOnKeyboard: boolean, +tabBarActiveTintColor: string, +tabBarInactiveTintColor: string, +tabBarActiveBackgroundColor: string, +tabBarInactiveBackgroundColor: string, +tabBarAllowFontScaling: boolean, +tabBarShowLabel: boolean, +tabBarLabelStyle: TextStyleProp, +tabBarIconStyle: TextStyleProp, +tabBarItemStyle: ViewStyleProp, +tabBarLabelPosition: 'beside-icon' | 'below-icon', +tabBarStyle: ViewStyleProp, +unmountOnBlur: boolean, +lazy: boolean, ...HeaderCommonOptions< HeaderProps, BottomTabOptions>, HeaderLeftButtonProps, HeaderButtonProps, >, |}>; /** * Bottom tab navigation prop */ declare export type BottomTabNavigationEventMap = {| ...EventMapCore, +tabPress: {| +data: void, +canPreventDefault: true |}, +tabLongPress: {| +data: void, +canPreventDefault: false |}, |}; declare type TabExtraNavigationHelpers< ParamList: ParamListBase = ParamListBase, > = {| +jumpTo: SimpleNavigate, |}; declare export type BottomTabNavigationHelpers< ParamList: ParamListBase = ParamListBase, EventMap: EventMapBase = BottomTabNavigationEventMap, > = { ...$Exact>, ...TabExtraNavigationHelpers, ... }; declare export type BottomTabNavigationProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, Options: {...} = BottomTabOptions, EventMap: EventMapBase = BottomTabNavigationEventMap, > = {| ...$Exact>, ...TabExtraNavigationHelpers, |}; /** * Miscellaneous bottom tab exports */ declare export type BottomTabDescriptor = Descriptor< BottomTabNavigationHelpers<>, BottomTabOptions, >; declare export type BottomTabNavigationBuilderResult = {| +state: TabNavigationState, +navigation: BottomTabNavigationHelpers<>, +descriptors: {| +[key: string]: BottomTabDescriptor |}, |}; declare export type BottomTabBarProps = BottomTabNavigationBuilderResult; declare export type BottomTabNavigationConfig = {| +tabBar?: BottomTabBarProps => React$Node, +safeAreaInsets?: $Partial, +detachInactiveScreens?: boolean, |}; declare export type ExtraBottomTabNavigatorProps = {| ...$Exact, ...TabRouterOptions, ...BottomTabNavigationConfig, |}; declare export type BottomTabNavigatorProps< NavHelpers: BottomTabNavigationHelpers<> = BottomTabNavigationHelpers<>, > = $Exact, RouteProp<>, NavHelpers, ExtraBottomTabNavigatorProps, >>; /** * Material bottom tab options */ declare export type MaterialBottomTabOptions = $Partial<{| +title: string, +tabBarColor: string, +tabBarLabel: string, +tabBarIcon: | string | ({| +focused: boolean, +color: string |}) => React$Node, +tabBarBadge: boolean | number | string, +tabBarAccessibilityLabel: string, +tabBarTestID: string, |}>; /** * Material bottom tab navigation prop */ declare export type MaterialBottomTabNavigationEventMap = {| ...EventMapCore, +tabPress: {| +data: void, +canPreventDefault: true |}, |}; declare export type MaterialBottomTabNavigationHelpers< ParamList: ParamListBase = ParamListBase, EventMap: EventMapBase = MaterialBottomTabNavigationEventMap, > = { ...$Exact>, ...TabExtraNavigationHelpers, ... }; declare export type MaterialBottomTabNavigationProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, Options: {...} = MaterialBottomTabOptions, EventMap: EventMapBase = MaterialBottomTabNavigationEventMap, > = {| ...$Exact>, ...TabExtraNavigationHelpers, |}; /** * Miscellaneous material bottom tab exports */ declare export type PaperFont = {| +fontFamily: string, +fontWeight?: | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900', |}; declare export type PaperFonts = {| +regular: PaperFont, +medium: PaperFont, +light: PaperFont, +thin: PaperFont, |}; declare export type PaperTheme = {| +dark: boolean, +mode?: 'adaptive' | 'exact', +roundness: number, +colors: {| +primary: string, +background: string, +surface: string, +accent: string, +error: string, +text: string, +onSurface: string, +onBackground: string, +disabled: string, +placeholder: string, +backdrop: string, +notification: string, |}, +fonts: PaperFonts, +animation: {| +scale: number, |}, |}; declare export type PaperRoute = {| +key: string, +title?: string, +icon?: any, +badge?: string | number | boolean, +color?: string, +accessibilityLabel?: string, +testID?: string, |}; declare export type PaperTouchableProps = {| ...TouchableWithoutFeedbackProps, +key: string, +route: PaperRoute, +children: React$Node, +borderless?: boolean, +centered?: boolean, +rippleColor?: string, |}; declare export type MaterialBottomTabNavigationConfig = {| +shifting?: boolean, +labeled?: boolean, +renderTouchable?: PaperTouchableProps => React$Node, +activeColor?: string, +inactiveColor?: string, +sceneAnimationEnabled?: boolean, +keyboardHidesNavigationBar?: boolean, +barStyle?: ViewStyleProp, +style?: ViewStyleProp, +theme?: PaperTheme, |}; declare export type ExtraMaterialBottomTabNavigatorProps = {| ...$Exact, ...TabRouterOptions, ...MaterialBottomTabNavigationConfig, |}; declare export type MaterialBottomTabNavigatorProps< NavHelpers: MaterialBottomTabNavigationHelpers<> = MaterialBottomTabNavigationHelpers<>, > = $Exact, RouteProp<>, NavHelpers, ExtraMaterialBottomTabNavigatorProps, >>; /** * Material top tab options */ declare export type MaterialTopTabOptions = $Partial<{| +title: string, +tabBarLabel: | string | ({| +focused: boolean, +color: string |}) => React$Node, +tabBarIcon: ({| +focused: boolean, +color: string |}) => React$Node, +tabBarAccessibilityLabel: string, +tabBarTestID: string, +tabBarActiveTintColor: string, +tabBarInactiveTintColor: string, +tabBarPressColor: string, +tabBarPressOpacity: number, +tabBarShowLabel: boolean, +tabBarShowIcon: boolean, +tabBarAllowFontScaling: boolean, +tabBarBounces: boolean, +tabBarScrollEnabled: boolean, +tabBarIconStyle: ViewStyleProp, +tabBarLabelStyle: TextStyleProp, +tabBarItemStyle: ViewStyleProp, +tabBarIndicatorStyle: ViewStyleProp, +tabBarIndicatorContainerStyle: ViewStyleProp, +tabBarContentContainerStyle: ViewStyleProp, +tabBarStyle: ViewStyleProp, +tabBarBadge: () => React$Node, +tabBarIndicator: MaterialTopTabBarIndicatorProps => React$Node, +lazy: boolean, +lazyPlaceholder: ({| +route: Route<> |}) => React$Node, |}>; /** * Material top tab navigation prop */ declare export type MaterialTopTabNavigationEventMap = {| ...EventMapCore, +tabPress: {| +data: void, +canPreventDefault: true |}, +tabLongPress: {| +data: void, +canPreventDefault: false |}, +swipeStart: {| +data: void, +canPreventDefault: false |}, +swipeEnd: {| +data: void, +canPreventDefault: false |}, |}; declare export type MaterialTopTabNavigationHelpers< ParamList: ParamListBase = ParamListBase, EventMap: EventMapBase = MaterialTopTabNavigationEventMap, > = { ...$Exact>, ...TabExtraNavigationHelpers, ... }; declare export type MaterialTopTabNavigationProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, Options: {...} = MaterialTopTabOptions, EventMap: EventMapBase = MaterialTopTabNavigationEventMap, > = {| ...$Exact>, ...TabExtraNavigationHelpers, |}; /** * Miscellaneous material top tab exports */ declare type MaterialTopTabPagerCommonProps = {| +keyboardDismissMode: 'none' | 'on-drag' | 'auto', +swipeEnabled: boolean, +swipeVelocityImpact?: number, +springVelocityScale?: number, +springConfig: $Partial<{| +damping: number, +mass: number, +stiffness: number, +restSpeedThreshold: number, +restDisplacementThreshold: number, |}>, +timingConfig: $Partial<{| +duration: number, |}>, |}; declare export type MaterialTopTabPagerProps = {| ...MaterialTopTabPagerCommonProps, +onSwipeStart?: () => void, +onSwipeEnd?: () => void, +onIndexChange: (index: number) => void, +navigationState: TabNavigationState, +layout: {| +width: number, +height: number |}, +removeClippedSubviews: boolean, +children: ({| +addListener: (type: 'enter', listener: number => void) => void, +removeListener: (type: 'enter', listener: number => void) => void, +position: any, // Reanimated.Node +render: React$Node => React$Node, +jumpTo: string => void, |}) => React$Node, +gestureHandlerProps: PanGestureHandlerProps, |}; declare export type MaterialTopTabBarIndicatorProps = {| +state: TabNavigationState, +width: string, +style?: ViewStyleProp, +getTabWidth: number => number, |}; declare export type MaterialTopTabDescriptor = Descriptor< MaterialBottomTabNavigationHelpers<>, MaterialBottomTabOptions, >; declare export type MaterialTopTabNavigationBuilderResult = {| +state: TabNavigationState, +navigation: MaterialTopTabNavigationHelpers<>, +descriptors: {| +[key: string]: MaterialTopTabDescriptor |}, |}; declare export type MaterialTopTabBarProps = {| ...MaterialTopTabNavigationBuilderResult, +layout: {| +width: number, +height: number |}, +position: any, // Reanimated.Node +jumpTo: string => void, |}; declare export type MaterialTopTabNavigationConfig = {| ...$Partial, +position?: any, // Reanimated.Value +tabBarPosition?: 'top' | 'bottom', +initialLayout?: $Partial<{| +width: number, +height: number |}>, +lazyPreloadDistance?: number, +removeClippedSubviews?: boolean, +sceneContainerStyle?: ViewStyleProp, +style?: ViewStyleProp, +gestureHandlerProps?: PanGestureHandlerProps, +pager?: MaterialTopTabPagerProps => React$Node, +tabBar?: MaterialTopTabBarProps => React$Node, |}; declare export type ExtraMaterialTopTabNavigatorProps = {| ...$Exact, ...TabRouterOptions, ...MaterialTopTabNavigationConfig, |}; declare export type MaterialTopTabNavigatorProps< NavHelpers: MaterialTopTabNavigationHelpers<> = MaterialTopTabNavigationHelpers<>, > = $Exact, RouteProp<>, NavHelpers, ExtraMaterialTopTabNavigatorProps, >>; /** * Drawer options */ declare export type DrawerOptions = $Partial<{| +title: string, +lazy: boolean, +drawerLabel: | string | ({| +color: string, +focused: boolean |}) => React$Node, +drawerIcon: ({| +color: string, +size: number, +focused: boolean, |}) => React$Node, +drawerActiveTintColor: string, +drawerActiveBackgroundColor: string, +drawerInactiveTintColor: string, +drawerInactiveBackgroundColor: string, +drawerItemStyle: ViewStyleProp, +drawerLabelStyle: TextStyleProp, +drawerContentContainerStyle: ViewStyleProp, +drawerContentStyle: ViewStyleProp, +drawerStyle: ViewStyleProp, +drawerPosition: 'left' | 'right', +drawerType: 'front' | 'back' | 'slide' | 'permanent', +drawerHideStatusBarOnOpen: boolean, +drawerStatusBarAnimation: 'slide' | 'none' | 'fade', +overlayColor: string, +sceneContainerStyle: ViewStyleProp, +gestureHandlerProps: PanGestureHandlerProps, +swipeEnabled: boolean, +swipeEdgeWidth: number, +swipeMinDistance: number, +keyboardDismissMode: 'on-drag' | 'none', +unmountOnBlur: boolean, ...HeaderCommonOptions< HeaderProps, DrawerOptions>, HeaderLeftButtonProps, HeaderButtonProps, >, |}>; /** * Drawer navigation prop */ declare export type DrawerNavigationEventMap = EventMapCore; declare type DrawerExtraNavigationHelpers< ParamList: ParamListBase = ParamListBase, > = {| +jumpTo: SimpleNavigate, +openDrawer: () => void, +closeDrawer: () => void, +toggleDrawer: () => void, |}; declare export type DrawerNavigationHelpers< ParamList: ParamListBase = ParamListBase, EventMap: EventMapBase = DrawerNavigationEventMap, > = { ...$Exact>, ...DrawerExtraNavigationHelpers, ... }; declare export type DrawerNavigationProp< ParamList: ParamListBase = ParamListBase, RouteName: $Keys = $Keys, Options: {...} = DrawerOptions, EventMap: EventMapBase = DrawerNavigationEventMap, > = {| ...$Exact>, ...DrawerExtraNavigationHelpers, |}; /** * Miscellaneous drawer exports */ declare export type DrawerDescriptor = Descriptor< DrawerNavigationHelpers<>, DrawerOptions, >; declare export type DrawerNavigationBuilderResult = {| +state: DrawerNavigationState, +navigation: DrawerNavigationHelpers<>, +descriptors: {| +[key: string]: DrawerDescriptor |}, |}; declare export type DrawerNavigationConfig = {| +drawerContent?: DrawerNavigationBuilderResult => React$Node, +detachInactiveScreens?: boolean, +useLegacyImplementation?: boolean, |}; declare export type ExtraDrawerNavigatorProps = {| ...$Exact, ...DrawerRouterOptions, ...DrawerNavigationConfig, |}; declare export type DrawerNavigatorProps< NavHelpers: DrawerNavigationHelpers<> = DrawerNavigationHelpers<>, > = $Exact, RouteProp<>, NavHelpers, ExtraDrawerNavigatorProps, >>; /** * BaseNavigationContainer */ declare export type BaseNavigationContainerProps = {| +children: React$Node, +initialState?: PossiblyStaleNavigationState, +onStateChange?: (state: ?PossiblyStaleNavigationState) => void, +independent?: boolean, |}; declare export type ContainerEventMap = {| ...GlobalEventMap, +options: {| +data: {| +options: { +[key: string]: mixed, ... } |}, +canPreventDefault: false, |}, +__unsafe_action__: {| +data: {| +action: GenericNavigationAction, +noop: boolean, |}, +canPreventDefault: false, |}, |}; declare export type BaseNavigationContainerInterface = {| ...$Exact>, +resetRoot: (state?: PossiblyStaleNavigationState) => void, +getRootState: () => NavigationState, +getCurrentRoute: () => RouteProp<> | void, +getCurrentOptions: () => Object | void, +isReady: () => boolean, |}; declare export type BaseNavigationContainerInterfaceRef = {| ...BaseNavigationContainerInterface, +current: BaseNavigationContainerInterface | null, |}; /** * State utils */ declare export type GetStateFromPath = ( path: string, options?: LinkingConfig, ) => PossiblyStaleNavigationState; declare export type GetPathFromState = ( state?: ?PossiblyStaleNavigationState, options?: LinkingConfig, ) => string; declare export type GetFocusedRouteNameFromRoute = PossiblyStaleRoute => ?string; /** * Linking */ declare export type ScreenLinkingConfig = {| +path?: string, +exact?: boolean, +parse?: {| +[param: string]: string => mixed |}, +stringify?: {| +[param: string]: mixed => string |}, +screens?: ScreenLinkingConfigMap, +initialRouteName?: string, |}; declare export type ScreenLinkingConfigMap = {| +[routeName: string]: string | ScreenLinkingConfig, |}; declare export type LinkingConfig = {| +initialRouteName?: string, +screens: ScreenLinkingConfigMap, |}; declare export type LinkingOptions = {| +enabled?: boolean, +prefixes: $ReadOnlyArray, +config?: LinkingConfig, +getStateFromPath?: GetStateFromPath, +getPathFromState?: GetPathFromState, |}; /** * NavigationContainer */ declare export type Theme = {| +dark: boolean, +colors: {| +primary: string, +background: string, +card: string, +text: string, +border: string, |}, |}; declare export type NavigationContainerType = React$AbstractComponent< {| ...BaseNavigationContainerProps, +theme?: Theme, +linking?: LinkingOptions, +fallback?: React$Node, +onReady?: () => mixed, |}, BaseNavigationContainerInterface, >; //--------------------------------------------------------------------------- // SECTION 2: EXPORTED MODULE //--------------------------------------------------------------------------- /** * Actions and routers */ declare export var CommonActions: CommonActionsType; declare export var StackActions: StackActionsType; declare export var TabActions: TabActionsType; declare export var DrawerActions: DrawerActionsType; declare export var BaseRouter: RouterFactory< NavigationState, CommonAction, DefaultRouterOptions, >; declare export var StackRouter: RouterFactory< StackNavigationState, StackAction, StackRouterOptions, >; declare export var TabRouter: RouterFactory< TabNavigationState, TabAction, TabRouterOptions, >; declare export var DrawerRouter: RouterFactory< DrawerNavigationState, DrawerAction, DrawerRouterOptions, >; /** * Navigator utils */ declare export var BaseNavigationContainer: React$AbstractComponent< BaseNavigationContainerProps, BaseNavigationContainerInterface, >; declare export var createNavigatorFactory: CreateNavigatorFactory; declare export var useNavigationBuilder: UseNavigationBuilder; declare export var NavigationHelpersContext: React$Context< ?NavigationHelpers, >; /** * Navigation prop / route accessors */ declare export var NavigationContext: React$Context< ?NavigationProp, >; declare export function useNavigation(): NavigationProp; declare export var NavigationRouteContext: React$Context>; declare export function useRoute(): LeafRoute<>; declare export function useNavigationState( selector: NavigationState => T, ): T; /** * Focus utils */ declare export function useFocusEffect( effect: () => ?(() => mixed), ): void; declare export function useIsFocused(): boolean; /** * State utils */ declare export var getStateFromPath: GetStateFromPath; declare export var getPathFromState: GetPathFromState; declare export function getActionFromState( state: PossiblyStaleNavigationState, ): ?NavigateAction; declare export var getFocusedRouteNameFromRoute: GetFocusedRouteNameFromRoute; }