diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js index a9b51dffa..7380c341e 100644 --- a/native/chat/chat.react.js +++ b/native/chat/chat.react.js @@ -1,438 +1,438 @@ // @flow import type { MaterialTopTabNavigationProp, StackNavigationState, StackOptions, StackNavigationEventMap, StackNavigatorProps, ExtraStackNavigatorProps, StackHeaderProps, StackNavigationProp, StackNavigationHelpers, ParamListBase, } from '@react-navigation/core'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import { createNavigatorFactory, useNavigationBuilder, } from '@react-navigation/native'; import { StackView } from '@react-navigation/stack'; import invariant from 'invariant'; import * as React from 'react'; import { Platform, View, useWindowDimensions } from 'react-native'; import { useSelector } from 'react-redux'; import ThreadDraftUpdater from 'lib/components/thread-draft-updater.react.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { threadIsPending } from 'lib/shared/thread-utils.js'; import BackgroundChatThreadList from './background-chat-thread-list.react.js'; import ChatHeader from './chat-header.react.js'; import ChatRouter, { type ChatRouterNavigationHelpers } from './chat-router.js'; import ComposeSubchannel from './compose-subchannel.react.js'; import ComposeThreadButton from './compose-thread-button.react.js'; import FullScreenThreadMediaGallery from './fullscreen-thread-media-gallery.react.js'; import HomeChatThreadList from './home-chat-thread-list.react.js'; import { MessageEditingContext } from './message-editing-context.react.js'; import MessageListContainer from './message-list-container.react.js'; import MessageListHeaderTitle from './message-list-header-title.react.js'; import MessageResultsScreen from './message-results-screen.react.js'; import MessageStorePruner from './message-store-pruner.react.js'; import DeleteThread from './settings/delete-thread.react.js'; import EmojiThreadAvatarCreation from './settings/emoji-thread-avatar-creation.react.js'; import ThreadSettings from './settings/thread-settings.react.js'; import ThreadScreenPruner from './thread-screen-pruner.react.js'; import ThreadSettingsButton from './thread-settings-button.react.js'; import ThreadSettingsHeaderTitle from './thread-settings-header-title.react.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { InputStateContext } from '../input/input-state.js'; import CommunityDrawerButton from '../navigation/community-drawer-button.react.js'; -import type { CommunityDrawerNavigationProp } from '../navigation/community-drawer-navigator.react.js'; import HeaderBackButton from '../navigation/header-back-button.react.js'; import { defaultStackScreenOptions } from '../navigation/options.js'; import { ComposeSubchannelRouteName, DeleteThreadRouteName, ThreadSettingsRouteName, EmojiThreadAvatarCreationRouteName, FullScreenThreadMediaGalleryRouteName, MessageResultsScreenRouteName, MessageListRouteName, ChatThreadListRouteName, HomeChatThreadListRouteName, BackgroundChatThreadListRouteName, type ScreenParamList, type ChatParamList, type ChatTopTabsParamList, MessageSearchRouteName, ChangeRolesScreenRouteName, } from '../navigation/route-names.js'; +import type { TabNavigationProp } from '../navigation/tab-navigator.react.js'; import ChangeRolesHeaderLeftButton from '../roles/change-roles-header-left-button.react.js'; import ChangeRolesScreen from '../roles/change-roles-screen.react.js'; import MessageSearch from '../search/message-search.react.js'; import SearchHeader from '../search/search-header.react.js'; import SearchMessagesButton from '../search/search-messages-button.react.js'; import { useColors, useStyles } from '../themes/colors.js'; const unboundStyles = { keyboardAvoidingView: { flex: 1, }, view: { flex: 1, backgroundColor: 'listBackground', }, threadListHeaderStyle: { elevation: 0, shadowOffset: { width: 0, height: 0 }, borderBottomWidth: 0, backgroundColor: 'tabBarBackground', }, }; export type ChatTopTabsNavigationProp< RouteName: $Keys = $Keys, > = MaterialTopTabNavigationProp; const homeChatThreadListOptions = { title: 'Focused', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const backgroundChatThreadListOptions = { title: 'Background', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const ChatThreadsTopTab = createMaterialTopTabNavigator(); function ChatThreadsComponent(): React.Node { const colors = useColors(); const { tabBarBackground, tabBarAccent } = colors; const screenOptions = React.useMemo( () => ({ tabBarShowIcon: true, tabBarStyle: { backgroundColor: tabBarBackground, }, tabBarItemStyle: { flexDirection: 'row', }, tabBarIndicatorStyle: { borderColor: tabBarAccent, borderBottomWidth: 2, }, }), [tabBarAccent, tabBarBackground], ); return ( ); } export type ChatNavigationHelpers = { ...$Exact>, ...ChatRouterNavigationHelpers, }; type ChatNavigatorProps = StackNavigatorProps>; function ChatNavigator({ initialRouteName, children, screenOptions, defaultScreenOptions, screenListeners, id, ...rest }: ChatNavigatorProps) { const { state, descriptors, navigation } = useNavigationBuilder(ChatRouter, { id, initialRouteName, children, screenOptions, defaultScreenOptions, screenListeners, }); // Clear ComposeSubchannel screens after each message is sent. If a user goes // to ComposeSubchannel to create a new thread, but finds an existing one and // uses it instead, we can assume the intent behind opening ComposeSubchannel // is resolved const inputState = React.useContext(InputStateContext); invariant(inputState, 'InputState should be set in ChatNavigator'); const clearComposeScreensAfterMessageSend = React.useCallback(() => { navigation.clearScreens([ComposeSubchannelRouteName]); }, [navigation]); React.useEffect(() => { inputState.registerSendCallback(clearComposeScreensAfterMessageSend); return () => { inputState.unregisterSendCallback(clearComposeScreensAfterMessageSend); }; }, [inputState, clearComposeScreensAfterMessageSend]); return ( ); } const createChatNavigator = createNavigatorFactory< StackNavigationState, StackOptions, StackNavigationEventMap, ChatNavigationHelpers<>, ExtraStackNavigatorProps, >(ChatNavigator); const header = (props: StackHeaderProps) => { // Flow has trouble reconciling identical types between different libdefs, // and flow-typed has no way for one libdef to depend on another const castProps: StackHeaderProps = (props: any); return ; }; const headerRightStyle = { flexDirection: 'row' }; const messageListOptions = ({ navigation, route }) => { const isSearchEmpty = !!route.params.searching && route.params.threadInfo.members.length === 1; const areSettingsEnabled = !threadIsPending(route.params.threadInfo.id) && !isSearchEmpty; return { // This is a render prop, not a component // eslint-disable-next-line react/display-name headerTitle: props => ( ), headerRight: areSettingsEnabled ? // This is a render prop, not a component // eslint-disable-next-line react/display-name () => ( ) : undefined, headerBackTitleVisible: false, headerTitleAlign: isSearchEmpty ? 'center' : 'left', headerLeftContainerStyle: { width: Platform.OS === 'ios' ? 32 : 40 }, headerTitleStyle: areSettingsEnabled ? { marginRight: 20 } : undefined, }; }; const composeThreadOptions = { headerTitle: 'Compose chat', headerBackTitleVisible: false, }; const threadSettingsOptions = ({ route }) => ({ // eslint-disable-next-line react/display-name headerTitle: props => ( ), headerBackTitleVisible: false, }); const emojiAvatarCreationOptions = { headerTitle: 'Emoji avatar selection', headerBackTitleVisible: false, }; const fullScreenThreadMediaGalleryOptions = { headerTitle: 'All Media', headerBackTitleVisible: false, }; const deleteThreadOptions = { headerTitle: 'Delete chat', headerBackTitleVisible: false, }; const messageSearchOptions = { // eslint-disable-next-line react/display-name headerTitle: () => , headerBackTitleVisible: false, headerTitleContainerStyle: { width: '100%', }, }; const messageResultsScreenOptions = { headerTitle: 'Pinned Messages', headerBackTitleVisible: false, }; const changeRolesScreenOptions = ({ route }) => ({ // eslint-disable-next-line react/display-name headerLeft: headerLeftProps => ( ), headerTitle: 'Change Role', presentation: 'modal', }); export type ChatNavigationProp< RouteName: $Keys = $Keys, > = { ...StackNavigationProp, ...ChatRouterNavigationHelpers, }; const Chat = createChatNavigator< ScreenParamList, ChatParamList, ChatNavigationHelpers, >(); type Props = { - +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, + +navigation: TabNavigationProp<'Chat'>, ... }; export default function ChatComponent(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const loggedIn = useSelector(isLoggedIn); let draftUpdater = null; if (loggedIn) { draftUpdater = ; } const headerLeftButton = React.useCallback( headerProps => { if (headerProps.canGoBack) { return ; } return ; }, [props.navigation], ); const messageEditingContext = React.useContext(MessageEditingContext); const editState = messageEditingContext?.editState; const editMode = !!editState?.editedMessage; const { width: screenWidth } = useWindowDimensions(); const screenOptions = React.useMemo( () => ({ ...defaultStackScreenOptions, header, headerLeft: headerLeftButton, headerStyle: { backgroundColor: colors.tabBarBackground, borderBottomWidth: 1, }, gestureEnabled: true, gestureResponseDistance: editMode ? 0 : screenWidth, }), [colors.tabBarBackground, headerLeftButton, screenWidth, editMode], ); const chatThreadListOptions = React.useCallback( ({ navigation }) => ({ headerTitle: 'Inbox', headerRight: Platform.OS === 'ios' ? () => : undefined, headerBackTitleVisible: false, headerStyle: styles.threadListHeaderStyle, }), [styles.threadListHeaderStyle], ); return ( {draftUpdater} ); } diff --git a/native/navigation/community-drawer-button.react.js b/native/navigation/community-drawer-button.react.js index deb273ff8..322e557d1 100644 --- a/native/navigation/community-drawer-button.react.js +++ b/native/navigation/community-drawer-button.react.js @@ -1,31 +1,35 @@ // @flow import Icon from '@expo/vector-icons/Feather.js'; import * as React from 'react'; import { TouchableOpacity } from 'react-native'; import type { CommunityDrawerNavigationProp } from './community-drawer-navigator.react.js'; +import type { TabNavigationProp } from './tab-navigator.react.js'; import { useStyles } from '../themes/colors.js'; type Props = { - +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, + +navigation: + | TabNavigationProp<'Chat'> + | TabNavigationProp<'Profile'> + | CommunityDrawerNavigationProp<'TabNavigator'>, }; function CommunityDrawerButton(props: Props): React.Node { const styles = useStyles(unboundStyles); const { navigation } = props; return ( ); } const unboundStyles = { drawerButton: { color: 'listForegroundSecondaryLabel', marginLeft: 16, }, }; export default CommunityDrawerButton; diff --git a/native/navigation/tab-router.js b/native/navigation/tab-router.js index 5b10b61a7..767ad650e 100644 --- a/native/navigation/tab-router.js +++ b/native/navigation/tab-router.js @@ -1,42 +1,41 @@ // @flow import type { Router, RouterConfigOptions, TabRouterOptions, TabNavigationState, + TabAction, } from '@react-navigation/core'; import { TabRouter } from '@react-navigation/native'; import { getChatNavStateFromTabNavState, getRemoveEditMode, } from './nav-selectors.js'; -type TabRouterNavigationAction = empty; - export type TabRouterExtraNavigationHelpers = {}; function CustomTabRouter( routerOptions: TabRouterOptions, -): Router { +): Router { const { getStateForAction: baseGetStateForAction, ...rest } = TabRouter(routerOptions); return { ...rest, getStateForAction: ( lastState: TabNavigationState, - action: TabRouterNavigationAction, + action: TabAction, options: RouterConfigOptions, ) => { const chatNavState = getChatNavStateFromTabNavState(lastState); const removeEditMode = getRemoveEditMode(chatNavState); if (removeEditMode && removeEditMode(action) === 'ignore_action') { return lastState; } return baseGetStateForAction(lastState, action, options); }, }; } export default CustomTabRouter; diff --git a/native/profile/profile.react.js b/native/profile/profile.react.js index 2310e0a40..28731fe6a 100644 --- a/native/profile/profile.react.js +++ b/native/profile/profile.react.js @@ -1,245 +1,245 @@ // @flow import type { StackNavigationProp, StackNavigationHelpers, StackHeaderProps, } from '@react-navigation/core'; import { createStackNavigator } from '@react-navigation/stack'; import * as React from 'react'; import { View, useWindowDimensions } from 'react-native'; import AddKeyserver from './add-keyserver.react.js'; import AppearancePreferences from './appearance-preferences.react.js'; import BackupMenu from './backup-menu.react.js'; import BuildInfo from './build-info.react.js'; import DefaultNotificationsPreferences from './default-notifications-preferences.react.js'; import DeleteAccount from './delete-account.react.js'; import DevTools from './dev-tools.react.js'; import EditPassword from './edit-password.react.js'; import EmojiUserAvatarCreation from './emoji-user-avatar-creation.react.js'; import KeyserverSelectionListHeaderRightButton from './keyserver-selection-list-header-right-button.react.js'; import KeyserverSelectionList from './keyserver-selection-list.react.js'; import LinkedDevicesHeaderRightButton from './linked-devices-header-right-button.react.js'; import LinkedDevices from './linked-devices.react.js'; import PrivacyPreferences from './privacy-preferences.react.js'; import ProfileHeader from './profile-header.react.js'; import ProfileScreen from './profile-screen.react.js'; import RelationshipList from './relationship-list.react.js'; import SecondaryDeviceQRCodeScanner from './secondary-device-qr-code-scanner.react.js'; import TunnelbrokerMenu from './tunnelbroker-menu.react.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import CommunityDrawerButton from '../navigation/community-drawer-button.react.js'; -import type { CommunityDrawerNavigationProp } from '../navigation/community-drawer-navigator.react.js'; import HeaderBackButton from '../navigation/header-back-button.react.js'; import { ProfileScreenRouteName, EditPasswordRouteName, DeleteAccountRouteName, EmojiUserAvatarCreationRouteName, BuildInfoRouteName, DevToolsRouteName, AppearancePreferencesRouteName, PrivacyPreferencesRouteName, FriendListRouteName, DefaultNotificationsPreferencesRouteName, BlockListRouteName, LinkedDevicesRouteName, SecondaryDeviceQRCodeScannerRouteName, BackupMenuRouteName, KeyserverSelectionListRouteName, AddKeyserverRouteName, type ScreenParamList, type ProfileParamList, TunnelbrokerMenuRouteName, } from '../navigation/route-names.js'; +import type { TabNavigationProp } from '../navigation/tab-navigator.react.js'; import { useStyles, useColors } from '../themes/colors.js'; const header = (props: StackHeaderProps) => ; const profileScreenOptions = { headerTitle: 'Profile' }; const emojiAvatarCreationOptions = { headerTitle: 'Emoji avatar selection', headerBackTitleVisible: false, }; const editPasswordOptions = { headerTitle: 'Change password' }; const deleteAccountOptions = { headerTitle: 'Delete account' }; const linkedDevicesOptions = { headerTitle: 'Linked devices', // eslint-disable-next-line react/display-name headerRight: () => , }; const keyserverSelectionListOptions = { headerTitle: 'Keyservers', // eslint-disable-next-line react/display-name headerRight: () => , }; const addKeyserverOptions = { headerTitle: 'Add keyserver' }; const backupMenuOptions = { headerTitle: 'Backup menu' }; const tunnelbrokerMenuOptions = { headerTitle: 'Tunnelbroker menu' }; const secondaryDeviceQRCodeScannerOptions = { headerTitle: '', headerBackTitleVisible: false, }; const buildInfoOptions = { headerTitle: 'Build info' }; const devToolsOptions = { headerTitle: 'Developer tools' }; const appearanceOptions = { headerTitle: 'Appearance' }; const privacyOptions = { headerTitle: 'Privacy' }; const friendListOptions = { headerTitle: 'Friend list' }; const blockListOptions = { headerTitle: 'Block list' }; const defaultNotificationsOptions = { headerTitle: 'Default Notifications' }; export type ProfileNavigationProp< RouteName: $Keys = $Keys, > = StackNavigationProp; const Profile = createStackNavigator< ScreenParamList, ProfileParamList, StackNavigationHelpers, >(); type Props = { - +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, + +navigation: TabNavigationProp<'Profile'>, ... }; function ProfileComponent(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const headerLeftButton = React.useCallback( headerProps => headerProps.canGoBack ? ( ) : ( ), [props.navigation], ); const { width: screenWidth } = useWindowDimensions(); const screenOptions = React.useMemo( () => ({ header, headerLeft: headerLeftButton, headerStyle: { backgroundColor: colors.tabBarBackground, shadowOpacity: 0, }, gestureEnabled: true, gestureResponseDistance: screenWidth, }), [colors.tabBarBackground, headerLeftButton, screenWidth], ); return ( ); } const unboundStyles = { keyboardAvoidingView: { flex: 1, }, view: { flex: 1, backgroundColor: 'panelBackground', }, }; export default ProfileComponent; diff --git a/native/profile/user-relationship-tooltip-modal.react.js b/native/profile/user-relationship-tooltip-modal.react.js index af6b3ca9d..3a2870c2f 100644 --- a/native/profile/user-relationship-tooltip-modal.react.js +++ b/native/profile/user-relationship-tooltip-modal.react.js @@ -1,167 +1,167 @@ // @flow import * as React from 'react'; import { TouchableOpacity } from 'react-native'; import { updateRelationshipsActionTypes, updateRelationships, } from 'lib/actions/relationship-actions.js'; import { stringForUser } from 'lib/shared/user-utils.js'; import type { RelativeUserInfo } from 'lib/types/user-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import PencilIcon from '../components/pencil-icon.react.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; -import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useColors } from '../themes/colors.js'; import { createTooltip, type TooltipParams, type BaseTooltipProps, type TooltipMenuProps, type TooltipRoute, } from '../tooltip/tooltip.react.js'; +import type { UserProfileBottomSheetNavigationProp } from '../user-profile/user-profile-bottom-sheet-navigator.react.js'; import Alert from '../utils/alert.js'; type Action = 'unfriend' | 'block' | 'unblock'; type TooltipButtonIcon = 'pencil' | 'menu'; export type UserRelationshipTooltipModalParams = TooltipParams<{ +tooltipButtonIcon: TooltipButtonIcon, +relativeUserInfo: RelativeUserInfo, }>; type OnRemoveUserProps = { ...UserRelationshipTooltipModalParams, +action: Action, }; function useRelationshipAction(input: OnRemoveUserProps) { const boundRemoveRelationships = useServerCall(updateRelationships); const dispatchActionPromise = useDispatchActionPromise(); const userText = stringForUser(input.relativeUserInfo); return React.useCallback(() => { const callRemoveRelationships = async () => { try { return await boundRemoveRelationships({ action: input.action, userIDs: [input.relativeUserInfo.id], }); } catch (e) { Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], { cancelable: true, }); throw e; } }; const onConfirmRemoveUser = () => { const customKeyName = `${updateRelationshipsActionTypes.started}:${input.relativeUserInfo.id}`; dispatchActionPromise( updateRelationshipsActionTypes, callRemoveRelationships(), { customKeyName }, ); }; const action = { unfriend: 'removal', block: 'block', unblock: 'unblock', }[input.action]; const message = { unfriend: `remove ${userText} from friends?`, block: `block ${userText}`, unblock: `unblock ${userText}?`, }[input.action]; Alert.alert( `Confirm ${action}`, `Are you sure you want to ${message}`, [ { text: 'Cancel', style: 'cancel' }, { text: 'OK', onPress: onConfirmRemoveUser }, ], { cancelable: true }, ); }, [boundRemoveRelationships, dispatchActionPromise, userText, input]); } function TooltipMenu( props: TooltipMenuProps<'UserRelationshipTooltipModal'>, ): React.Node { const { route, tooltipItem: TooltipItem } = props; const onRemoveUser = useRelationshipAction({ ...route.params, action: 'unfriend', }); const onBlockUser = useRelationshipAction({ ...route.params, action: 'block', }); const onUnblockUser = useRelationshipAction({ ...route.params, action: 'unblock', }); return ( <> ); } type Props = { - +navigation: AppNavigationProp<'UserRelationshipTooltipModal'>, + +navigation: UserProfileBottomSheetNavigationProp<'UserRelationshipTooltipModal'>, +route: TooltipRoute<'UserRelationshipTooltipModal'>, ... }; function UserRelationshipTooltipButton(props: Props): React.Node { const { navigation, route } = props; const { goBackOnce } = navigation; const { tooltipButtonIcon } = route.params; const colors = useColors(); const icon = React.useMemo(() => { if (tooltipButtonIcon === 'pencil') { return ; } return ( ); }, [colors.modalBackgroundLabel, tooltipButtonIcon]); return {icon}; } const UserRelationshipTooltipModal: React.ComponentType< BaseTooltipProps<'UserRelationshipTooltipModal'>, > = createTooltip<'UserRelationshipTooltipModal'>( UserRelationshipTooltipButton, TooltipMenu, ); export default UserRelationshipTooltipModal; diff --git a/native/user-profile/user-profile-bottom-sheet-navigator.react.js b/native/user-profile/user-profile-bottom-sheet-navigator.react.js index b69391d70..e8bbb8988 100644 --- a/native/user-profile/user-profile-bottom-sheet-navigator.react.js +++ b/native/user-profile/user-profile-bottom-sheet-navigator.react.js @@ -1,57 +1,57 @@ // @flow import * as React from 'react'; import UserProfileAvatarModal from './user-profile-avatar-modal.react.js'; import UserProfileBottomSheet from './user-profile-bottom-sheet.react.js'; import { createOverlayNavigator } from '../navigation/overlay-navigator.react.js'; import type { OverlayNavigationProp, OverlayNavigationHelpers, } from '../navigation/overlay-navigator.react.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import { UserProfileBottomSheetRouteName, UserProfileAvatarModalRouteName, UserRelationshipTooltipModalRouteName, type ScreenParamList, type UserProfileBottomSheetParamList, } from '../navigation/route-names.js'; import UserRelationshipTooltipModal from '../profile/user-relationship-tooltip-modal.react.js'; export type UserProfileBottomSheetNavigationProp< RouteName: $Keys, > = OverlayNavigationProp; const UserProfileBottomSheetOverlayNavigator = createOverlayNavigator< ScreenParamList, UserProfileBottomSheetParamList, OverlayNavigationHelpers, >(); type Props = { - +navigation: RootNavigationProp<'UserProfileBottomSheet'>, + +navigation: RootNavigationProp<'UserProfileBottomSheetNavigator'>, ... }; // eslint-disable-next-line no-unused-vars function UserProfileBottomSheetNavigator(props: Props): React.Node { return ( ); } export default UserProfileBottomSheetNavigator; diff --git a/native/user-profile/user-profile-bottom-sheet.react.js b/native/user-profile/user-profile-bottom-sheet.react.js index bf21c0760..5e1711961 100644 --- a/native/user-profile/user-profile-bottom-sheet.react.js +++ b/native/user-profile/user-profile-bottom-sheet.react.js @@ -1,43 +1,43 @@ // @flow import * as React from 'react'; import type { UserInfo } from 'lib/types/user-types'; +import type { UserProfileBottomSheetNavigationProp } from './user-profile-bottom-sheet-navigator.react.js'; import UserProfile from './user-profile.react.js'; import BottomSheet from '../bottom-sheet/bottom-sheet.react.js'; -import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; export type UserProfileBottomSheetParams = { +userID: string, }; type Props = { - +navigation: RootNavigationProp<'UserProfileBottomSheet'>, + +navigation: UserProfileBottomSheetNavigationProp<'UserProfileBottomSheet'>, +route: NavigationRoute<'UserProfileBottomSheet'>, }; function UserProfileBottomSheet(props: Props): React.Node { const { navigation, route: { params: { userID }, }, } = props; const { goBack } = navigation; const userInfo: ?UserInfo = useSelector( state => state.userStore.userInfos[userID], ); return ( ); } export default UserProfileBottomSheet;