diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js --- a/native/account/logged-out-modal.react.js +++ b/native/account/logged-out-modal.react.js @@ -270,7 +270,7 @@ activeAlert = false; contentHeight: Value; - keyboardHeightValue = new Value(0); + keyboardHeightValue: Value = new Value(0); modeValue: Value; buttonOpacity: Value; @@ -448,7 +448,7 @@ this.props.dispatch({ type: resetUserStateActionType }); } - hardwareBack = () => { + hardwareBack: () => boolean = () => { if (this.nextMode !== 'prompt') { this.goBackToPrompt(); return true; @@ -456,7 +456,7 @@ return false; }; - panelPaddingTop() { + panelPaddingTop(): Node { const headerHeight = Platform.OS === 'ios' ? 62.33 : 58.54; const promptButtonsSize = Platform.OS === 'ios' ? 40 : 61; const logInContainerSize = 140; @@ -525,7 +525,7 @@ ]); } - panelOpacity() { + panelOpacity(): Node { const targetPanelOpacity = isPastPrompt(this.modeValue); const panelOpacity = new Value(-1); @@ -593,7 +593,7 @@ Keyboard.dismiss(); }; - render() { + render(): React.Node { const { styles } = this.props; const siweButton = ( diff --git a/native/account/register-panel.react.js b/native/account/register-panel.react.js --- a/native/account/register-panel.react.js +++ b/native/account/register-panel.react.js @@ -84,7 +84,7 @@ confirmPasswordInput: ?TextInput; passwordBeingAutoFilled = false; - render() { + render(): React.Node { let confirmPasswordTextInputExtraProps; if ( Platform.OS !== 'ios' || @@ -346,7 +346,7 @@ ); }; - async registerAction(extraInfo: LogInExtraInfo) { + async registerAction(extraInfo: LogInExtraInfo): Promise { try { const result = await this.props.register({ ...extraInfo, diff --git a/native/account/registration/emoji-avatar-selection.react.js b/native/account/registration/emoji-avatar-selection.react.js --- a/native/account/registration/emoji-avatar-selection.react.js +++ b/native/account/registration/emoji-avatar-selection.react.js @@ -6,6 +6,7 @@ import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js'; import { getDefaultAvatar } from 'lib/shared/avatar-utils.js'; +import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; @@ -38,7 +39,7 @@ const { goBack } = props.navigation; const onSuccess = React.useCallback( - avatarRequest => { + (avatarRequest: UpdateUserAvatarRequest) => { goBack(); return nativeSetUserAvatar(avatarRequest); }, diff --git a/native/account/siwe-hooks.js b/native/account/siwe-hooks.js --- a/native/account/siwe-hooks.js +++ b/native/account/siwe-hooks.js @@ -4,7 +4,10 @@ import { siweAuth, siweAuthActionTypes } from 'lib/actions/siwe-actions.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; -import type { LogInStartingPayload } from 'lib/types/account-types.js'; +import type { + LogInStartingPayload, + LogInExtraInfo, +} from 'lib/types/account-types.js'; import { useServerCall, useDispatchActionPromise, @@ -32,7 +35,12 @@ const siweAuthCall = useServerCall(siweAuth); const callSIWE = React.useCallback( - async (message, signature, extraInfo, callServerEndpointOptions) => { + async ( + message: string, + signature: string, + extraInfo: LogInExtraInfo, + callServerEndpointOptions: ?CallServerEndpointOptions, + ) => { try { return await siweAuthCall( { diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js --- a/native/account/siwe-panel.react.js +++ b/native/account/siwe-panel.react.js @@ -32,6 +32,14 @@ const siweAuthLoadingStatusSelector = createLoadingStatusSelector(siweAuthActionTypes); +type WebViewMessageEvent = { + +nativeEvent: { + +data: string, + ... + }, + ... +}; + type Props = { +onClosed: () => mixed, +onClosing: () => mixed, @@ -119,7 +127,7 @@ const closeBottomSheet = bottomSheetRef.current?.close; const { closing, onSuccessfulWalletSignature } = props; const handleMessage = React.useCallback( - async event => { + async (event: WebViewMessageEvent) => { const data: SIWEWebViewMessage = JSON.parse(event.nativeEvent.data); if (data.type === 'siwe_success') { const { address, message, signature } = data; diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/avatars/emoji-avatar-creation.react.js --- a/native/avatars/emoji-avatar-creation.react.js +++ b/native/avatars/emoji-avatar-creation.react.js @@ -11,12 +11,13 @@ import type { UpdateUserAvatarRequest, ClientEmojiAvatar, -} from 'lib/types/avatar-types'; +} from 'lib/types/avatar-types.js'; import Avatar from './avatar.react.js'; import Button from '../components/button.react.js'; import ColorRows from '../components/color-rows.react.js'; import EmojiKeyboard from '../components/emoji-keyboard.react.js'; +import type { EmojiSelection } from '../components/emoji-keyboard.react.js'; import { useStyles } from '../themes/colors.js'; type Props = { @@ -60,7 +61,7 @@ setPendingColor(resetEmojiAvatar.color); }, [savedEmojiAvatarFunc]); - const onEmojiSelected = React.useCallback(emoji => { + const onEmojiSelected = React.useCallback((emoji: EmojiSelection) => { setPendingEmoji(emoji.emoji); }, []); diff --git a/native/calendar/calendar.react.js b/native/calendar/calendar.react.js --- a/native/calendar/calendar.react.js +++ b/native/calendar/calendar.react.js @@ -215,7 +215,7 @@ topLoaderWaitingToLeaveView = true; bottomLoaderWaitingToLeaveView = true; // We keep refs to the entries so CalendarInputBar can save them - entryRefs = new Map(); + entryRefs: Map = new Map(); constructor(props: Props) { super(props); @@ -389,7 +389,12 @@ static datesFromListData( lastLDWH: $ReadOnlyArray, newLDWH: $ReadOnlyArray, - ) { + ): { + +lastStartDate: Date, + +newStartDate: Date, + +lastEndDate: Date, + +newEndDate: Date, + } { const lastSecondItem = lastLDWH[1]; const newSecondItem = newLDWH[1]; invariant( @@ -472,7 +477,7 @@ // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return - renderItem = (row: { item: CalendarItemWithHeight, ... }) => { + renderItem = (row: { +item: CalendarItemWithHeight, ... }): React.Node => { const item = row.item; if (item.itemType === 'loader') { return ; @@ -500,7 +505,7 @@ invariant(false, 'renderItem conditions should be exhaustive'); }; - renderSectionHeader = (item: SectionHeaderItem) => { + renderSectionHeader = (item: SectionHeaderItem): React.Node => { let date = prettyDate(item.dateString); if (dateString(new Date()) === item.dateString) { date += ' (today)'; @@ -519,7 +524,7 @@ ); }; - renderSectionFooter = (item: SectionFooterItem) => { + renderSectionFooter = (item: SectionFooterItem): React.Node => { return ( { + static keyExtractor = ( + item: CalendarItemWithHeight | CalendarItem, + // ESLint doesn't recognize that invariant always throws + // eslint-disable-next-line consistent-return + ): string => { if (item.itemType === 'loader') { return item.key; } else if (item.itemType === 'header') { @@ -554,7 +561,7 @@ static getItemLayout = ( data: ?$ReadOnlyArray, index: number, - ) => { + ): { length: number, offset: number, index: number } => { if (!data) { return { length: 0, offset: 0, index }; } @@ -566,7 +573,7 @@ // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return - static itemHeight = (item: CalendarItemWithHeight) => { + static itemHeight = (item: CalendarItemWithHeight): number => { if (item.itemType === 'loader') { return 56; } else if (item.itemType === 'header') { @@ -580,11 +587,13 @@ invariant(false, 'itemHeight conditions should be exhaustive'); }; - static heightOfItems = (data: $ReadOnlyArray) => { + static heightOfItems = ( + data: $ReadOnlyArray, + ): number => { return _sum(data.map(Calendar.itemHeight)); }; - render() { + render(): React.Node { const { listDataWithHeights } = this.state; let flatList = null; if (listDataWithHeights) { @@ -648,12 +657,12 @@ ); } - flatListHeight() { + flatListHeight(): number { const { safeAreaHeight, tabBarHeight } = this.props.dimensions; return safeAreaHeight - tabBarHeight; } - initialScrollIndex(data: $ReadOnlyArray) { + initialScrollIndex(data: $ReadOnlyArray): number { const todayIndex = _findIndex(['dateString', dateString(new Date())])(data); const heightOfTodayHeader = Calendar.itemHeight(data[todayIndex]); @@ -811,14 +820,14 @@ this.flatList.scrollToOffset({ offset, animated: true }); } - heightMeasurerKey = (item: CalendarItem) => { + heightMeasurerKey = (item: CalendarItem): ?string => { if (item.itemType !== 'entryInfo') { return null; } return item.entryInfo.text; }; - heightMeasurerDummy = (item: CalendarItem) => { + heightMeasurerDummy = (item: CalendarItem): React.MixedElement => { invariant( item.itemType === 'entryInfo', 'NodeHeightMeasurer asked for dummy for non-entryInfo item', @@ -826,7 +835,10 @@ return dummyNodeForEntryHeightMeasurement(item.entryInfo.text); }; - heightMeasurerMergeItem = (item: CalendarItem, height: ?number) => { + heightMeasurerMergeItem = ( + item: CalendarItem, + height: ?number, + ): CalendarItemWithHeight => { if (item.itemType !== 'entryInfo') { return item; } @@ -994,7 +1006,7 @@ ); } - loadMoreAbove = _throttle(() => { + loadMoreAbove: () => void = _throttle(() => { if ( this.topLoadingFromScroll && this.topLoaderWaitingToLeaveView && @@ -1004,7 +1016,7 @@ } }, 1000); - loadMoreBelow = _throttle(() => { + loadMoreBelow: () => void = _throttle(() => { if ( this.bottomLoadingFromScroll && this.bottomLoaderWaitingToLeaveView && diff --git a/native/calendar/section-footer.react.js b/native/calendar/section-footer.react.js --- a/native/calendar/section-footer.react.js +++ b/native/calendar/section-footer.react.js @@ -48,7 +48,7 @@ +styles: typeof unboundStyles, }; class SectionFooter extends React.PureComponent { - render() { + render(): React.Node { return ( diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -1,6 +1,7 @@ // @flow import Icon from '@expo/vector-icons/Ionicons.js'; +import type { GenericNavigationAction } from '@react-navigation/core'; import invariant from 'invariant'; import _throttle from 'lodash/throttle.js'; import * as React from 'react'; @@ -455,17 +456,17 @@ this.sendButtonContainerStyle = { width: sendButtonContainerWidth }; } - static mediaGalleryOpen(props: Props) { + static mediaGalleryOpen(props: Props): boolean { const { keyboardState } = props; return !!(keyboardState && keyboardState.mediaGalleryOpen); } - static systemKeyboardShowing(props: Props) { + static systemKeyboardShowing(props: Props): boolean { const { keyboardState } = props; return !!(keyboardState && keyboardState.systemKeyboardShowing); } - get systemKeyboardShowing() { + get systemKeyboardShowing(): boolean { return ChatInputBar.systemKeyboardShowing(this.props); } @@ -629,7 +630,7 @@ return checkIfDefaultMembersAreVoiced(this.props.threadInfo); } - render() { + render(): React.Node { const isMember = viewerIsMember(this.props.threadInfo); const canJoin = threadHasPermission( this.props.threadInfo, @@ -805,7 +806,7 @@ ); } - renderInput() { + renderInput(): React.Node { const expandoButton = ( { + saveDraft: (text: string) => void = _throttle(text => { this.props.dispatch({ type: updateDraftActionType, payload: { @@ -1021,14 +1022,14 @@ ); }; - isEditMode = () => { + isEditMode = (): boolean => { const editState = this.props.messageEditingContext?.editState; const isThisThread = editState?.editedMessage?.threadID === this.props.threadInfo.id; - return editState && editState.editedMessage !== null && isThisThread; + return editState?.editedMessage !== null && isThisThread; }; - isMessageEdited = newText => { + isMessageEdited = (newText?: string): boolean => { let text = newText ?? this.state.text; text = trimMessage(text); const originalText = this.props.editedMessageInfo?.text; @@ -1097,7 +1098,7 @@ } }; - getEditedMessage = () => { + getEditedMessage = (): ?MessageInfo => { const editState = this.props.messageEditingContext?.editState; return editState?.editedMessage; }; @@ -1144,7 +1145,11 @@ ); }; - onNavigationBeforeRemove = e => { + onNavigationBeforeRemove = (e: { + +data: { +action: GenericNavigationAction }, + +preventDefault: () => void, + ... + }) => { if (!this.isEditMode()) { return; } @@ -1173,7 +1178,7 @@ this.props.dispatchActionPromise(joinThreadActionTypes, this.joinAction()); }; - async joinAction() { + async joinAction(): Promise { const query = this.props.calendarQuery(); return await this.props.joinThread({ threadID: this.props.threadInfo.id, diff --git a/native/chat/chat-list.react.js b/native/chat/chat-list.react.js --- a/native/chat/chat-list.react.js +++ b/native/chat/chat-list.react.js @@ -70,7 +70,7 @@ flatList: ?FlatListElementRef; scrollPos = 0; - newMessagesPillProgress = new Animated.Value(0); + newMessagesPillProgress: Animated.Value = new Animated.Value(0); newMessagesPillStyle: ViewStyle; constructor(props: Props) { @@ -125,7 +125,7 @@ } }; - get scrolledToBottom() { + get scrolledToBottom(): boolean { return this.scrollPos <= 0; } @@ -218,7 +218,7 @@ } } - render() { + render(): React.Node { const { navigation, viewerID, ...rest } = this.props; const { newMessageCount } = this.state; return ( @@ -250,7 +250,7 @@ static getItemLayout = ( data: ?$ReadOnlyArray, index: number, - ) => { + ): { length: number, offset: number, index: number } => { if (!data) { return { length: 0, offset: 0, index }; } diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js --- a/native/chat/chat.react.js +++ b/native/chat/chat.react.js @@ -13,6 +13,8 @@ ParamListBase, StackRouterOptions, MaterialTopTabNavigationHelpers, + HeaderTitleInputProps, + StackHeaderLeftButtonProps, } from '@react-navigation/core'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import { @@ -72,6 +74,7 @@ type ChatTopTabsParamList, MessageSearchRouteName, ChangeRolesScreenRouteName, + type NavigationRoute, } 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'; @@ -107,14 +110,14 @@ const homeChatThreadListOptions = { title: 'Focused', // eslint-disable-next-line react/display-name - tabBarIcon: ({ color }) => ( + tabBarIcon: ({ color }: { +color: string, ... }) => ( ), }; const backgroundChatThreadListOptions = { title: 'Background', // eslint-disable-next-line react/display-name - tabBarIcon: ({ color }) => ( + tabBarIcon: ({ color }: { +color: string, ... }) => ( ), }; @@ -234,7 +237,13 @@ const headerRightStyle = { flexDirection: 'row' }; -const messageListOptions = ({ navigation, route }) => { +const messageListOptions = ({ + navigation, + route, +}: { + +navigation: ChatNavigationProp<'MessageList'>, + +route: NavigationRoute<'MessageList'>, +}) => { const isSearchEmpty = !!route.params.searching && route.params.threadInfo.members.length === 1; @@ -244,7 +253,7 @@ return { // This is a render prop, not a component // eslint-disable-next-line react/display-name - headerTitle: props => ( + headerTitle: (props: HeaderTitleInputProps) => ( ({ +const threadSettingsOptions = ({ + route, +}: { + +route: NavigationRoute<'ThreadSettings'>, + ... +}) => ({ // eslint-disable-next-line react/display-name - headerTitle: props => ( + headerTitle: (props: HeaderTitleInputProps) => ( ({ +const changeRolesScreenOptions = ({ + route, +}: { + +route: NavigationRoute<'ChangeRolesScreen'>, + ... +}) => ({ // eslint-disable-next-line react/display-name - headerLeft: headerLeftProps => ( + headerLeft: (headerLeftProps: StackHeaderLeftButtonProps) => ( ), headerTitle: 'Change Role', @@ -349,7 +368,7 @@ } const headerLeftButton = React.useCallback( - headerProps => { + (headerProps: StackHeaderLeftButtonProps) => { if (headerProps.canGoBack) { return ; } @@ -379,7 +398,12 @@ ); const chatThreadListOptions = React.useCallback( - ({ navigation }) => ({ + ({ + navigation, + }: { + +navigation: ChatNavigationProp<'ChatThreadList'>, + ... + }) => ({ headerTitle: 'Inbox', headerRight: Platform.OS === 'ios' diff --git a/native/chat/failed-send.react.js b/native/chat/failed-send.react.js --- a/native/chat/failed-send.react.js +++ b/native/chat/failed-send.react.js @@ -86,7 +86,7 @@ } } - render() { + render(): React.Node { if (!this.props.rawMessageInfo) { return null; } diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js --- a/native/chat/message-list-container.react.js +++ b/native/chat/message-list-container.react.js @@ -107,7 +107,7 @@ }; pendingListDataWithHeights: ?$ReadOnlyArray; - get frozen() { + get frozen(): boolean { const { overlayContext } = this.props; invariant( overlayContext, @@ -155,7 +155,7 @@ } } - render() { + render(): React.Node { const { threadInfo, styles } = this.props; const { listDataWithHeights } = this.state; const { searching } = this.props.route.params; diff --git a/native/chat/message-list-header-title.react.js b/native/chat/message-list-header-title.react.js --- a/native/chat/message-list-header-title.react.js +++ b/native/chat/message-list-header-title.react.js @@ -43,7 +43,7 @@ +title: string, }; class MessageListHeaderTitle extends React.PureComponent { - render() { + render(): React.Node { const { threadInfo, navigate, diff --git a/native/chat/message-list.react.js b/native/chat/message-list.react.js --- a/native/chat/message-list.react.js +++ b/native/chat/message-list.react.js @@ -113,40 +113,41 @@ }; flatListContainer: ?React.ElementRef; - flatListExtraDataSelector = createSelector( - (propsAndState: PropsAndState) => propsAndState.messageListVerticalBounds, - (propsAndState: PropsAndState) => propsAndState.focusedMessageKey, - (propsAndState: PropsAndState) => propsAndState.navigation, - (propsAndState: PropsAndState) => propsAndState.route, - ( - messageListVerticalBounds: ?VerticalBounds, - focusedMessageKey: ?string, - navigation: ChatNavigationProp<'MessageList'>, - route: NavigationRoute<'MessageList'>, - ) => ({ - messageListVerticalBounds, - focusedMessageKey, - navigation, - route, - }), - ); + flatListExtraDataSelector: PropsAndState => FlatListExtraData = + createSelector( + (propsAndState: PropsAndState) => propsAndState.messageListVerticalBounds, + (propsAndState: PropsAndState) => propsAndState.focusedMessageKey, + (propsAndState: PropsAndState) => propsAndState.navigation, + (propsAndState: PropsAndState) => propsAndState.route, + ( + messageListVerticalBounds: ?VerticalBounds, + focusedMessageKey: ?string, + navigation: ChatNavigationProp<'MessageList'>, + route: NavigationRoute<'MessageList'>, + ) => ({ + messageListVerticalBounds, + focusedMessageKey, + navigation, + route, + }), + ); get flatListExtraData(): FlatListExtraData { return this.flatListExtraDataSelector({ ...this.props, ...this.state }); } - static getOverlayContext(props: Props) { + static getOverlayContext(props: Props): OverlayContextType { const { overlayContext } = props; invariant(overlayContext, 'MessageList should have OverlayContext'); return overlayContext; } - static scrollDisabled(props: Props) { + static scrollDisabled(props: Props): boolean { const overlayContext = MessageList.getOverlayContext(props); return overlayContext.scrollBlockingModalStatus !== 'closed'; } - static modalOpen(props: Props) { + static modalOpen(props: Props): boolean { const overlayContext = MessageList.getOverlayContext(props); return overlayContext.scrollBlockingModalStatus === 'open'; } @@ -174,7 +175,7 @@ keyboardState && keyboardState.dismissKeyboard(); }; - renderItem = (row: { item: ChatMessageItemWithHeight, ... }) => { + renderItem = (row: { item: ChatMessageItemWithHeight, ... }): React.Node => { if (row.item.itemType === 'loader') { return ( @@ -211,9 +212,11 @@ }; // Actually header, it's just that our FlatList is inverted - ListFooterComponent = () => ; + ListFooterComponent = (): React.Node => ( + + ); - render() { + render(): React.Node { const { messageListData, startReached } = this.props; const footer = startReached ? this.ListFooterComponent : undefined; let relationshipPrompt = null; diff --git a/native/chat/message-reactions-modal.react.js b/native/chat/message-reactions-modal.react.js --- a/native/chat/message-reactions-modal.react.js +++ b/native/chat/message-reactions-modal.react.js @@ -12,7 +12,10 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; -import { useMessageReactionsList } from 'lib/shared/reaction-utils.js'; +import { + useMessageReactionsList, + type MessageReactionListInfo, +} from 'lib/shared/reaction-utils.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import Modal from '../components/modal.react.js'; @@ -70,7 +73,7 @@ ); const renderItem = React.useCallback( - ({ item }) => ( + ({ item }: { +item: MessageReactionListInfo, ... }) => ( onPressUser(item.id)} key={item.id} diff --git a/native/chat/multimedia-message-multimedia.react.js b/native/chat/multimedia-message-multimedia.react.js --- a/native/chat/multimedia-message-multimedia.react.js +++ b/native/chat/multimedia-message-multimedia.react.js @@ -74,7 +74,7 @@ }; } - static getOverlayContext(props: Props) { + static getOverlayContext(props: Props): OverlayContextType { const { overlayContext } = props; invariant( overlayContext, @@ -83,7 +83,7 @@ return overlayContext; } - static getModalOverlayPosition(props: Props) { + static getModalOverlayPosition(props: Props): ?Animated.Value { const overlayContext = MultimediaMessageMultimedia.getOverlayContext(props); const { visibleOverlays } = overlayContext; for (const overlay of visibleOverlays) { @@ -98,7 +98,7 @@ return undefined; } - getOpacity() { + getOpacity(): number | Animated.Node { const overlayPosition = MultimediaMessageMultimedia.getModalOverlayPosition( this.props, ); @@ -136,7 +136,7 @@ } } - render() { + render(): React.Node { const { opacity } = this.state; const animatedWrapperStyle: AnimatedStyleObj = { opacity }; const wrapperStyles = [ @@ -202,7 +202,7 @@ }); }; - dismissKeyboardIfShowing = () => { + dismissKeyboardIfShowing = (): boolean => { const { keyboardState } = this.props; return !!(keyboardState && keyboardState.dismissKeyboardIfShowing()); }; diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js --- a/native/chat/multimedia-message-tooltip-button.react.js +++ b/native/chat/multimedia-message-tooltip-button.react.js @@ -18,6 +18,7 @@ import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; import { useAnimatedMessageTooltipButton } from './utils.js'; import EmojiKeyboard from '../components/emoji-keyboard.react.js'; +import type { EmojiSelection } from '../components/emoji-keyboard.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; @@ -147,7 +148,7 @@ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey); const onEmojiSelected = React.useCallback( - emoji => { + (emoji: EmojiSelection) => { sendReaction(emoji.emoji); dismissTooltip(); }, diff --git a/native/chat/multimedia-message-tooltip-modal.react.js b/native/chat/multimedia-message-tooltip-modal.react.js --- a/native/chat/multimedia-message-tooltip-modal.react.js +++ b/native/chat/multimedia-message-tooltip-modal.react.js @@ -16,6 +16,7 @@ } from '../tooltip/tooltip.react.js'; import type { ChatMultimediaMessageInfoItem } from '../types/chat-types.js'; import type { VerticalBounds } from '../types/layout-types.js'; +import type { TextStyle } from '../types/styles.js'; import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js'; export type MultimediaMessageTooltipModalParams = TooltipParams<{ @@ -33,17 +34,21 @@ const onPressTogglePin = useNavigateToPinModal(overlayContext, route); const renderPinIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); const renderUnpinIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item); const renderSidebarIcon = React.useCallback( - style => ( + (style: TextStyle) => ( ), [], @@ -51,7 +56,9 @@ const onPressReport = useOnPressReport(route); const renderReportIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); diff --git a/native/chat/multimedia-message.react.js b/native/chat/multimedia-message.react.js --- a/native/chat/multimedia-message.react.js +++ b/native/chat/multimedia-message.react.js @@ -86,7 +86,7 @@ }); }; - visibleEntryIDs() { + visibleEntryIDs(): $ReadOnlyArray { const result = []; if (this.props.canTogglePins) { @@ -182,14 +182,14 @@ }); }; - canNavigateToSidebar() { + canNavigateToSidebar(): boolean { return ( - this.props.item.threadCreatedFromMessage || + !!this.props.item.threadCreatedFromMessage || this.props.canCreateSidebarFromMessage ); } - render() { + render(): React.Node { const { item, focused, diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js --- a/native/chat/robotext-message-tooltip-button.react.js +++ b/native/chat/robotext-message-tooltip-button.react.js @@ -17,6 +17,7 @@ import { Timestamp } from './timestamp.react.js'; import { useAnimatedMessageTooltipButton } from './utils.js'; import EmojiKeyboard from '../components/emoji-keyboard.react.js'; +import type { EmojiSelection } from '../components/emoji-keyboard.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; @@ -130,7 +131,7 @@ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey); const onEmojiSelected = React.useCallback( - emoji => { + (emoji: EmojiSelection) => { sendReaction(emoji.emoji); dismissTooltip(); }, diff --git a/native/chat/robotext-message-tooltip-modal.react.js b/native/chat/robotext-message-tooltip-modal.react.js --- a/native/chat/robotext-message-tooltip-modal.react.js +++ b/native/chat/robotext-message-tooltip-modal.react.js @@ -12,6 +12,7 @@ type TooltipMenuProps, } from '../tooltip/tooltip.react.js'; import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js'; +import type { TextStyle } from '../types/styles.js'; export type RobotextMessageTooltipModalParams = TooltipParams<{ +item: ChatRobotextMessageInfoItemWithHeight, @@ -24,7 +25,7 @@ const onPress = useAnimatedNavigateToSidebar(route.params.item); const renderIcon = React.useCallback( - style => ( + (style: TextStyle) => ( ), [], diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js --- a/native/chat/robotext-message.react.js +++ b/native/chat/robotext-message.react.js @@ -106,7 +106,14 @@ }, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]); const openRobotextTooltipModal = React.useCallback( - (x, y, width, height, pageX, pageY) => { + ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number, + ) => { invariant( verticalBounds, 'verticalBounds should be present in openRobotextTooltipModal', diff --git a/native/chat/settings/compose-subchannel-modal.react.js b/native/chat/settings/compose-subchannel-modal.react.js --- a/native/chat/settings/compose-subchannel-modal.react.js +++ b/native/chat/settings/compose-subchannel-modal.react.js @@ -69,7 +69,7 @@ +styles: typeof unboundStyles, }; class ComposeSubchannelModal extends React.PureComponent { - render() { + render(): React.Node { return ( Chat type diff --git a/native/chat/settings/delete-thread.react.js b/native/chat/settings/delete-thread.react.js --- a/native/chat/settings/delete-thread.react.js +++ b/native/chat/settings/delete-thread.react.js @@ -142,7 +142,7 @@ this.mounted = false; } - render() { + render(): React.Node { const buttonContent = this.props.loadingStatus === 'loading' ? ( @@ -207,7 +207,7 @@ ); }; - async deleteThread() { + async deleteThread(): Promise { const { threadInfo, navDispatch } = this.props; navDispatch({ type: clearThreadsActionType, diff --git a/native/chat/settings/emoji-thread-avatar-creation.react.js b/native/chat/settings/emoji-thread-avatar-creation.react.js --- a/native/chat/settings/emoji-thread-avatar-creation.react.js +++ b/native/chat/settings/emoji-thread-avatar-creation.react.js @@ -5,6 +5,7 @@ import { EditThreadAvatarContext } from 'lib/components/base-edit-thread-avatar-provider.react.js'; import { savedEmojiAvatarSelectorForThread } from 'lib/selectors/thread-selectors.js'; +import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js'; import type { MinimallyEncodedRawThreadInfo, MinimallyEncodedThreadInfo, @@ -47,7 +48,7 @@ const nativeSetThreadAvatar = useNativeSetThreadAvatar(); const setAvatar = React.useCallback( - async avatarRequest => { + async (avatarRequest: UpdateUserAvatarRequest) => { const result = await nativeSetThreadAvatar(threadID, avatarRequest); displayActionResultModal('Avatar updated!'); return result; diff --git a/native/chat/settings/thread-settings-color.react.js b/native/chat/settings/thread-settings-color.react.js --- a/native/chat/settings/thread-settings-color.react.js +++ b/native/chat/settings/thread-settings-color.react.js @@ -54,7 +54,7 @@ +styles: typeof unboundStyles, }; class ThreadSettingsColor extends React.PureComponent { - render() { + render(): React.Node { let colorButton; if (this.props.loadingStatus !== 'loading') { colorButton = ( diff --git a/native/chat/settings/thread-settings-description.react.js b/native/chat/settings/thread-settings-description.react.js --- a/native/chat/settings/thread-settings-description.react.js +++ b/native/chat/settings/thread-settings-description.react.js @@ -111,7 +111,7 @@ class ThreadSettingsDescription extends React.PureComponent { textInput: ?React.ElementRef; - render() { + render(): React.Node { if ( this.props.descriptionEditValue !== null && this.props.descriptionEditValue !== undefined @@ -197,7 +197,7 @@ return null; } - renderButton() { + renderButton(): React.Node { if (this.props.loadingStatus === 'loading') { return ( { try { return await this.props.changeThreadSettings({ threadID: this.props.threadInfo.id, diff --git a/native/chat/settings/thread-settings-home-notifs.react.js b/native/chat/settings/thread-settings-home-notifs.react.js --- a/native/chat/settings/thread-settings-home-notifs.react.js +++ b/native/chat/settings/thread-settings-home-notifs.react.js @@ -66,7 +66,7 @@ }; } - render() { + render(): React.Node { const componentLabel = 'Background'; return ( diff --git a/native/chat/settings/thread-settings-leave-thread.react.js b/native/chat/settings/thread-settings-leave-thread.react.js --- a/native/chat/settings/thread-settings-leave-thread.react.js +++ b/native/chat/settings/thread-settings-leave-thread.react.js @@ -67,7 +67,7 @@ +navContext: ?NavContextType, }; class ThreadSettingsLeaveThread extends React.PureComponent { - render() { + render(): React.Node { const { panelIosHighlightUnderlay, panelForegroundSecondaryLabel } = this.props.colors; const loadingIndicator = @@ -122,7 +122,7 @@ ); }; - async leaveThread() { + async leaveThread(): Promise { const threadID = this.props.threadInfo.id; const { navContext } = this.props; invariant(navContext, 'navContext should exist in leaveThread'); diff --git a/native/chat/settings/thread-settings-media-gallery.react.js b/native/chat/settings/thread-settings-media-gallery.react.js --- a/native/chat/settings/thread-settings-media-gallery.react.js +++ b/native/chat/settings/thread-settings-media-gallery.react.js @@ -101,7 +101,7 @@ }, [activeTab, mediaInfos]); const renderItem = React.useCallback( - ({ item, index }) => ( + ({ item, index }: { +item: Media, +index: number, ... }) => ( { editButton: ?React.ElementRef; - render() { + render(): React.Node { const userText = stringForUser(this.props.memberInfo); let usernameInfo = null; @@ -248,7 +248,7 @@ }); }; - dismissKeyboardIfShowing = () => { + dismissKeyboardIfShowing = (): boolean => { const { keyboardState } = this.props; return !!(keyboardState && keyboardState.dismissKeyboardIfShowing()); }; diff --git a/native/chat/settings/thread-settings-name.react.js b/native/chat/settings/thread-settings-name.react.js --- a/native/chat/settings/thread-settings-name.react.js +++ b/native/chat/settings/thread-settings-name.react.js @@ -83,7 +83,7 @@ class ThreadSettingsName extends React.PureComponent { textInput: ?React.ElementRef; - render() { + render(): React.Node { return ( Name @@ -92,7 +92,7 @@ ); } - renderButton() { + renderButton(): React.Node { if (this.props.loadingStatus === 'loading') { return ( ; } - renderContent() { + renderContent(): React.Node { if ( this.props.nameEditValue === null || this.props.nameEditValue === undefined @@ -152,7 +152,7 @@ this.textInput = textInput; }; - threadEditName() { + threadEditName(): string { return firstLine( this.props.threadInfo.name ? this.props.threadInfo.name : '', ); @@ -191,7 +191,7 @@ }); }; - async editName(newName: string) { + async editName(newName: string): Promise { try { return await this.props.changeThreadSettings({ threadID: this.props.threadInfo.id, diff --git a/native/chat/settings/thread-settings-promote-sidebar.react.js b/native/chat/settings/thread-settings-promote-sidebar.react.js --- a/native/chat/settings/thread-settings-promote-sidebar.react.js +++ b/native/chat/settings/thread-settings-promote-sidebar.react.js @@ -59,7 +59,7 @@ ); }; - render() { + render(): React.Node { const { panelIosHighlightUnderlay, panelForegroundSecondaryLabel } = this.props.colors; const loadingIndicator = diff --git a/native/chat/settings/thread-settings-push-notifs.react.js b/native/chat/settings/thread-settings-push-notifs.react.js --- a/native/chat/settings/thread-settings-push-notifs.react.js +++ b/native/chat/settings/thread-settings-push-notifs.react.js @@ -79,7 +79,7 @@ }; } - render() { + render(): React.Node { const componentLabel = 'Push notifs'; let notificationsSettingsLinkingIcon: React.Node = undefined; if (!this.props.hasPushPermissions) { diff --git a/native/chat/settings/thread-settings.react.js b/native/chat/settings/thread-settings.react.js --- a/native/chat/settings/thread-settings.react.js +++ b/native/chat/settings/thread-settings.react.js @@ -312,7 +312,7 @@ }; } - static scrollDisabled(props: Props) { + static scrollDisabled(props: Props): boolean { const { overlayContext } = props; invariant(overlayContext, 'ThreadSettings should have OverlayContext'); return overlayContext.scrollBlockingModalStatus !== 'closed'; @@ -340,548 +340,557 @@ } } - 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, - ); + threadBasicsListDataSelector: PropsAndState => $ReadOnlyArray = + 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', - }); + const canChangeAvatar = canEditThreadAvatar && canStartEditing; + const canChangeName = canEditThreadName && canStartEditing; + const canChangeDescription = + canEditThreadDescription && canStartEditing; + const canChangeColor = canEditThreadColor && canStartEditing; - if ( - (descriptionEditValue !== null && descriptionEditValue !== undefined) || - threadInfo.description || - canEditThreadDescription - ) { + const listData: ChatSettingsItem[] = []; + listData.push({ + itemType: 'header', + key: 'avatarHeader', + title: 'Channel Avatar', + categoryType: 'unpadded', + }); listData.push({ - itemType: 'description', - key: 'description', + itemType: 'avatar', + key: 'avatar', threadInfo, - descriptionEditValue, - descriptionTextHeight, - canChangeSettings: canChangeDescription, + canChangeSettings: canChangeAvatar, + }); + listData.push({ + itemType: 'footer', + key: 'avatarFooter', + categoryType: 'outline', }); - } - const isMember = viewerIsMember(threadInfo); - if (isMember) { listData.push({ itemType: 'header', - key: 'subscriptionHeader', - title: 'Subscription', + key: 'basicsHeader', + title: 'Basics', categoryType: 'full', }); listData.push({ - itemType: 'pushNotifs', - key: 'pushNotifs', + 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 (threadInfo.type !== threadTypes.SIDEBAR) { + + 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: 'homeNotifs', - key: 'homeNotifs', + 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: 'subscriptionFooter', + key: 'privacyFooter', 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', - }); + subchannelsListDataSelector: PropsAndState => $ReadOnlyArray = + 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; + } - if (canCreateSubchannels) { listData.push({ - itemType: 'addSubchannel', - key: 'addSubchannel', + itemType: 'header', + key: 'subchannelHeader', + title: 'Subchannels', + categoryType: 'unpadded', }); - } - const numItems = Math.min(numSubchannelsShowing, subchannels.length); - for (let i = 0; i < numItems; i++) { - const subchannelInfo = subchannels[i]; + 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: 'childThread', - key: `childThread${subchannelInfo.id}`, - threadInfo: subchannelInfo, - firstListItem: i === 0 && !canCreateSubchannels, - lastListItem: i === numItems - 1 && numItems === subchannels.length, + itemType: 'footer', + key: 'subchannelFooter', + categoryType: 'unpadded', }); - } - if (numItems < subchannels.length) { + return listData; + }, + ); + + sidebarsListDataSelector: PropsAndState => $ReadOnlyArray = + 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: 'seeMore', - key: 'seeMoreSubchannels', - onPress: this.onPressSeeMoreSubchannels, + 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', }); - } - 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', - }); + threadMembersListDataSelector: PropsAndState => $ReadOnlyArray = + 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; + } - 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, + itemType: 'header', + key: 'memberHeader', + title: 'Members', + categoryType: 'unpadded', }); - } - if (numItems < sidebars.length) { + 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: 'seeMore', - key: 'seeMoreSidebars', - onPress: this.onPressSeeMoreSidebars, + itemType: 'footer', + key: 'memberFooter', + categoryType: 'unpadded', }); - } - 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', - }); + mediaGalleryListDataSelector: PropsAndState => $ReadOnlyArray = + createSelector( + (propsAndState: PropsAndState) => propsAndState.threadInfo, + (propsAndState: PropsAndState) => propsAndState.verticalBounds, + ( + threadInfo: ThreadInfo | MinimallyEncodedThreadInfo, + verticalBounds: ?VerticalBounds, + ) => { + const listData: ChatSettingsItem[] = []; + const limit = 6; - if (canAddMembers) { listData.push({ - itemType: 'addMember', - key: 'addMember', + itemType: 'actionHeader', + key: 'mediaGalleryHeader', + title: 'Media Gallery', + actionText: 'See more', + onPress: this.onPressSeeMoreMediaGallery, }); - } - 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, + itemType: 'mediaGallery', + key: 'mediaGallery', threadInfo, - canEdit: canStartEditing, - navigate, - firstListItem: i === 0 && !canAddMembers, - lastListItem: - i === numItems - 1 && numItems === threadInfo.members.length, + limit, verticalBounds, - threadSettingsRouteKey: routeKey, }); - } - if (numItems < threadInfo.members.length) { listData.push({ - itemType: 'seeMore', - key: 'seeMoreMembers', - onPress: this.onPressSeeMoreMembers, + itemType: 'footer', + key: 'mediaGalleryFooter', + categoryType: 'outline', }); - } - 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, - }); - } + return listData; + }, + ); - const canLeaveThread = threadHasPermission( - threadInfo, - threadPermissions.LEAVE_THREAD, - ); + actionsListDataSelector: PropsAndState => $ReadOnlyArray = + 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, + }); + } - if (viewerIsMember(threadInfo) && canLeaveThread) { - buttons.push({ - itemType: 'leaveThread', - key: 'leaveThread', + const canLeaveThread = threadHasPermission( threadInfo, - navigate, - }); - } + threadPermissions.LEAVE_THREAD, + ); - const canDeleteThread = threadHasPermission( - threadInfo, - threadPermissions.DELETE_THREAD, - ); - if (canDeleteThread) { - buttons.push({ - itemType: 'deleteThread', - key: 'deleteThread', + if (viewerIsMember(threadInfo) && canLeaveThread) { + buttons.push({ + itemType: 'leaveThread', + key: 'leaveThread', + threadInfo, + navigate, + }); + } + + const canDeleteThread = threadHasPermission( threadInfo, - navigate, - }); - } + 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 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', + }); - 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() { + listDataSelector: PropsAndState => $ReadOnlyArray = + createSelector( + this.threadBasicsListDataSelector, + this.subchannelsListDataSelector, + this.sidebarsListDataSelector, + this.threadMembersListDataSelector, + this.mediaGalleryListDataSelector, + this.actionsListDataSelector, + ( + threadBasicsListData: $ReadOnlyArray, + subchannelsListData: $ReadOnlyArray, + sidebarsListData: $ReadOnlyArray, + threadMembersListData: $ReadOnlyArray, + mediaGalleryListData: $ReadOnlyArray, + actionsListData: $ReadOnlyArray, + ) => [ + ...threadBasicsListData, + ...subchannelsListData, + ...sidebarsListData, + ...threadMembersListData, + ...mediaGalleryListData, + ...actionsListData, + ], + ); + + get listData(): $ReadOnlyArray { return this.listDataSelector({ ...this.props, ...this.state }); } - render() { + render(): React.Node { return ( { + renderItem = (row: { +item: ChatSettingsItem, ... }): React.Node => { const item = row.item; if (item.itemType === 'header') { return ( diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js --- a/native/chat/text-message-tooltip-button.react.js +++ b/native/chat/text-message-tooltip-button.react.js @@ -20,6 +20,7 @@ import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; import { useAnimatedMessageTooltipButton } from './utils.js'; import EmojiKeyboard from '../components/emoji-keyboard.react.js'; +import type { EmojiSelection } from '../components/emoji-keyboard.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; @@ -144,7 +145,7 @@ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey); const onEmojiSelected = React.useCallback( - emoji => { + (emoji: EmojiSelection) => { sendReaction(emoji.emoji); dismissTooltip(); }, diff --git a/native/chat/text-message-tooltip-modal.react.js b/native/chat/text-message-tooltip-modal.react.js --- a/native/chat/text-message-tooltip-modal.react.js +++ b/native/chat/text-message-tooltip-modal.react.js @@ -23,6 +23,7 @@ type TooltipMenuProps, } from '../tooltip/tooltip.react.js'; import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js'; +import type { TextStyle } from '../types/styles.js'; import { exitEditAlert } from '../utils/edit-messages-utils.js'; import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js'; @@ -55,13 +56,13 @@ }); }, [inputState, navigateToThread, threadInfo, text]); const renderReplyIcon = React.useCallback( - style => , + (style: TextStyle) => , [], ); const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item); const renderSidebarIcon = React.useCallback( - style => ( + (style: TextStyle) => ( ), [], @@ -96,18 +97,24 @@ } }, [inputState, messageEditingContext, messageInfo, text]); const renderEditIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); const onPressTogglePin = useNavigateToPinModal(overlayContext, route); const renderPinIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); const renderUnpinIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); @@ -116,13 +123,15 @@ setTimeout(confirmCopy); }, [text]); const renderCopyIcon = React.useCallback( - style => , + (style: TextStyle) => , [], ); const onPressReport = useOnPressReport(route); const renderReportIcon = React.useCallback( - style => , + (style: TextStyle) => ( + + ), [], ); diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js --- a/native/chat/text-message.react.js +++ b/native/chat/text-message.react.js @@ -78,7 +78,7 @@ }; } - render() { + render(): React.Node { const { item, navigation, @@ -136,21 +136,21 @@ this.message = message; }; - canReply() { + canReply(): boolean { return threadHasPermission( this.props.item.threadInfo, threadPermissions.VOICED, ); } - canNavigateToSidebar() { + canNavigateToSidebar(): boolean { return ( - this.props.item.threadCreatedFromMessage || + !!this.props.item.threadCreatedFromMessage || this.props.canCreateSidebarFromMessage ); } - visibleEntryIDs() { + visibleEntryIDs(): $ReadOnlyArray { const result = ['copy']; if (this.canReply()) { diff --git a/native/chat/thread-settings-button.react.js b/native/chat/thread-settings-button.react.js --- a/native/chat/thread-settings-button.react.js +++ b/native/chat/thread-settings-button.react.js @@ -28,7 +28,7 @@ }; class ThreadSettingsButton extends React.PureComponent { - render() { + render(): React.Node { return (