diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js index cdde202b0..ade0ec1e4 100644 --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -1,861 +1,858 @@ // @flow import invariant from 'invariant'; import _throttle from 'lodash/throttle'; import * as React from 'react'; import { View, TextInput, TouchableOpacity, Platform, Text, ActivityIndicator, TouchableWithoutFeedback, NativeAppEventEmitter, } from 'react-native'; import { TextInputKeyboardMangerIOS } from 'react-native-keyboard-input'; import Animated, { Easing } from 'react-native-reanimated'; import FAIcon from 'react-native-vector-icons/FontAwesome'; import Icon from 'react-native-vector-icons/Ionicons'; import { useDispatch } from 'react-redux'; import { joinThreadActionTypes, joinThread } from 'lib/actions/thread-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { localIDPrefix, trimMessage } from 'lib/shared/message-utils'; import { threadHasPermission, viewerIsMember, threadFrozenDueToViewerBlock, threadActualMembers, } from 'lib/shared/thread-utils'; import type { CalendarQuery } from 'lib/types/entry-types'; import type { LoadingStatus } from 'lib/types/loading-types'; import type { PhotoPaste } from 'lib/types/media-types'; import { messageTypes } from 'lib/types/message-types'; import type { Dispatch } from 'lib/types/redux-types'; import { type ThreadInfo, threadPermissions, type ClientThreadJoinRequest, type ThreadJoinPayload, } from 'lib/types/thread-types'; import { type UserInfos } from 'lib/types/user-types'; import { type DispatchActionPromise, useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils'; import Button from '../components/button.react'; import ClearableTextInput from '../components/clearable-text-input.react'; import { type InputState, InputStateContext } from '../input/input-state'; import { getKeyboardHeight } from '../keyboard/keyboard'; import KeyboardInputHost from '../keyboard/keyboard-input-host.react'; import { type KeyboardState, KeyboardContext, } from '../keyboard/keyboard-state'; import { nonThreadCalendarQuery, activeThreadSelector, } from '../navigation/nav-selectors'; import { NavContext } from '../navigation/navigation-context'; import { type NavigationRoute, CameraModalRouteName, ImagePasteModalRouteName, } from '../navigation/route-names'; import { useSelector } from '../redux/redux-utils'; import { type Colors, useStyles, useColors } from '../themes/colors'; import type { ViewStyle } from '../types/styles'; import { runTiming } from '../utils/animation-utils'; import type { ChatNavigationProp } from './chat.react'; /* eslint-disable import/no-named-as-default-member */ const { Value, Clock, block, set, cond, neq, sub, interpolate, stopClock, } = Animated; /* eslint-enable import/no-named-as-default-member */ const expandoButtonsAnimationConfig = { duration: 500, easing: Easing.inOut(Easing.ease), }; const sendButtonAnimationConfig = { duration: 150, easing: Easing.inOut(Easing.ease), }; const draftKeyFromThreadID = (threadID: string) => `${threadID}/message_composer`; type BaseProps = {| +threadInfo: ThreadInfo, +navigation: ChatNavigationProp<'MessageList'>, +route: NavigationRoute<'MessageList'>, |}; type Props = {| ...BaseProps, // Redux state +viewerID: ?string, +draft: string, +joinThreadLoadingStatus: LoadingStatus, +calendarQuery: () => CalendarQuery, +nextLocalID: number, +userInfos: UserInfos, +colors: Colors, +styles: typeof unboundStyles, // connectNav +isActive: boolean, // withKeyboardState +keyboardState: ?KeyboardState, // Redux dispatch functions +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs +joinThread: (request: ClientThreadJoinRequest) => Promise, // withInputState +inputState: ?InputState, |}; type State = {| +text: string, +buttonsExpanded: boolean, |}; class ChatInputBar extends React.PureComponent { textInput: ?React.ElementRef; clearableTextInput: ?ClearableTextInput; expandoButtonsOpen: Value; targetExpandoButtonsOpen: Value; expandoButtonsStyle: ViewStyle; cameraRollIconStyle: ViewStyle; cameraIconStyle: ViewStyle; expandIconStyle: ViewStyle; sendButtonContainerOpen: Value; targetSendButtonContainerOpen: Value; sendButtonContainerStyle: ViewStyle; constructor(props: Props) { super(props); this.state = { text: props.draft, buttonsExpanded: true, }; this.expandoButtonsOpen = new Value(1); this.targetExpandoButtonsOpen = new Value(1); const prevTargetExpandoButtonsOpen = new Value(1); const expandoButtonClock = new Clock(); const expandoButtonsOpen = block([ cond(neq(this.targetExpandoButtonsOpen, prevTargetExpandoButtonsOpen), [ stopClock(expandoButtonClock), set(prevTargetExpandoButtonsOpen, this.targetExpandoButtonsOpen), ]), cond( neq(this.expandoButtonsOpen, this.targetExpandoButtonsOpen), set( this.expandoButtonsOpen, runTiming( expandoButtonClock, this.expandoButtonsOpen, this.targetExpandoButtonsOpen, true, expandoButtonsAnimationConfig, ), ), ), this.expandoButtonsOpen, ]); this.cameraRollIconStyle = { ...unboundStyles.cameraRollIcon, opacity: expandoButtonsOpen, }; this.cameraIconStyle = { ...unboundStyles.cameraIcon, opacity: expandoButtonsOpen, }; const expandoButtonsWidth = interpolate(expandoButtonsOpen, { inputRange: [0, 1], outputRange: [22, 60], }); this.expandoButtonsStyle = { ...unboundStyles.expandoButtons, width: expandoButtonsWidth, }; const expandOpacity = sub(1, expandoButtonsOpen); this.expandIconStyle = { ...unboundStyles.expandIcon, opacity: expandOpacity, }; const initialSendButtonContainerOpen = trimMessage(props.draft) ? 1 : 0; this.sendButtonContainerOpen = new Value(initialSendButtonContainerOpen); this.targetSendButtonContainerOpen = new Value( initialSendButtonContainerOpen, ); const prevTargetSendButtonContainerOpen = new Value( initialSendButtonContainerOpen, ); const sendButtonClock = new Clock(); const sendButtonContainerOpen = block([ cond( neq( this.targetSendButtonContainerOpen, prevTargetSendButtonContainerOpen, ), [ stopClock(sendButtonClock), set( prevTargetSendButtonContainerOpen, this.targetSendButtonContainerOpen, ), ], ), cond( neq(this.sendButtonContainerOpen, this.targetSendButtonContainerOpen), set( this.sendButtonContainerOpen, runTiming( sendButtonClock, this.sendButtonContainerOpen, this.targetSendButtonContainerOpen, true, sendButtonAnimationConfig, ), ), ), this.sendButtonContainerOpen, ]); const sendButtonContainerWidth = interpolate(sendButtonContainerOpen, { inputRange: [0, 1], outputRange: [4, 38], }); this.sendButtonContainerStyle = { width: sendButtonContainerWidth }; } static mediaGalleryOpen(props: Props) { const { keyboardState } = props; return !!(keyboardState && keyboardState.mediaGalleryOpen); } static systemKeyboardShowing(props: Props) { const { keyboardState } = props; return !!(keyboardState && keyboardState.systemKeyboardShowing); } get systemKeyboardShowing() { return ChatInputBar.systemKeyboardShowing(this.props); } immediatelyShowSendButton() { this.sendButtonContainerOpen.setValue(1); this.targetSendButtonContainerOpen.setValue(1); } updateSendButton(currentText: string) { this.targetSendButtonContainerOpen.setValue(currentText === '' ? 0 : 1); } componentDidMount() { if (this.props.isActive) { this.addReplyListener(); } } componentWillUnmount() { if (this.props.isActive) { this.removeReplyListener(); } } componentDidUpdate(prevProps: Props, prevState: State) { if (this.props.isActive && !prevProps.isActive) { this.addReplyListener(); } else if (!this.props.isActive && prevProps.isActive) { this.removeReplyListener(); } const currentText = trimMessage(this.state.text); const prevText = trimMessage(prevState.text); if ( (currentText === '' && prevText !== '') || (currentText !== '' && prevText === '') ) { this.updateSendButton(currentText); } const systemKeyboardIsShowing = ChatInputBar.systemKeyboardShowing( this.props, ); const systemKeyboardWasShowing = ChatInputBar.systemKeyboardShowing( prevProps, ); if (systemKeyboardIsShowing && !systemKeyboardWasShowing) { this.hideButtons(); } else if (!systemKeyboardIsShowing && systemKeyboardWasShowing) { this.expandButtons(); } const imageGalleryIsOpen = ChatInputBar.mediaGalleryOpen(this.props); const imageGalleryWasOpen = ChatInputBar.mediaGalleryOpen(prevProps); if (!imageGalleryIsOpen && imageGalleryWasOpen) { this.hideButtons(); } else if (imageGalleryIsOpen && !imageGalleryWasOpen) { this.expandButtons(); this.setIOSKeyboardHeight(); } } addReplyListener() { invariant( this.props.inputState, 'inputState should be set in addReplyListener', ); this.props.inputState.addReplyListener(this.focusAndUpdateText); } removeReplyListener() { invariant( this.props.inputState, 'inputState should be set in removeReplyListener', ); this.props.inputState.removeReplyListener(this.focusAndUpdateText); } setIOSKeyboardHeight() { if (Platform.OS !== 'ios') { return; } const { textInput } = this; if (!textInput) { return; } const keyboardHeight = getKeyboardHeight(); if (keyboardHeight === null || keyboardHeight === undefined) { return; } TextInputKeyboardMangerIOS.setKeyboardHeight(textInput, keyboardHeight); } render() { const isMember = viewerIsMember(this.props.threadInfo); const canJoin = threadHasPermission( this.props.threadInfo, threadPermissions.JOIN_THREAD, ); let joinButton = null; if (!isMember && canJoin) { let buttonContent; if (this.props.joinThreadLoadingStatus === 'loading') { buttonContent = ( ); } else { buttonContent = ( Join Thread ); } joinButton = ( ); } let content; if (threadHasPermission(this.props.threadInfo, threadPermissions.VOICED)) { content = this.renderInput(); } else if ( threadFrozenDueToViewerBlock( this.props.threadInfo, this.props.viewerID, this.props.userInfos, ) && threadActualMembers(this.props.threadInfo.members).length === 2 ) { content = ( You can't send messages to a user that you've blocked. ); } else if (isMember) { content = ( You don't have permission to send messages. ); } else { const defaultRoleID = Object.keys(this.props.threadInfo.roles).find( (roleID) => this.props.threadInfo.roles[roleID].isDefault, ); invariant( defaultRoleID !== undefined, 'all threads should have a default role', ); const defaultRole = this.props.threadInfo.roles[defaultRoleID]; const membersAreVoiced = !!defaultRole.permissions[ threadPermissions.VOICED ]; if (membersAreVoiced && canJoin) { content = ( Join this thread to send messages. ); } else { content = ( You don't have permission to send messages. ); } } const keyboardInputHost = Platform.OS === 'android' ? null : ( ); return ( {joinButton} {content} {keyboardInputHost} ); } renderInput() { const expandoButton = ( ); const threadColor = `#${this.props.threadInfo.color}`; return ( {this.state.buttonsExpanded ? expandoButton : null} {this.state.buttonsExpanded ? null : expandoButton} ); } textInputRef = (textInput: ?React.ElementRef) => { this.textInput = textInput; }; clearableTextInputRef = (clearableTextInput: ?ClearableTextInput) => { this.clearableTextInput = clearableTextInput; }; updateText = (text: string) => { this.setState({ text }); this.saveDraft(text); }; saveDraft = _throttle((text) => { global.CommCoreModule.updateDraft({ key: draftKeyFromThreadID(this.props.threadInfo.id), text, }); }, 400); focusAndUpdateText = (text: string) => { const currentText = this.state.text; if (!currentText.startsWith(text)) { const prependedText = text.concat(currentText); this.updateText(prependedText); this.immediatelyShowSendButton(); this.immediatelyHideButtons(); } invariant(this.textInput, 'textInput should be set in focusAndUpdateText'); this.textInput.focus(); }; onSend = async () => { if (!trimMessage(this.state.text)) { return; } this.updateSendButton(''); const { clearableTextInput } = this; invariant( clearableTextInput, 'clearableTextInput should be sent in onSend', ); let text = await clearableTextInput.getValueAndReset(); text = trimMessage(text); if (!text) { return; } const localID = `${localIDPrefix}${this.props.nextLocalID}`; const creatorID = this.props.viewerID; invariant(creatorID, 'should have viewer ID in order to send a message'); invariant( this.props.inputState, 'inputState should be set in ChatInputBar.onSend', ); this.props.inputState.sendTextMessage( { type: messageTypes.TEXT, localID, threadID: this.props.threadInfo.id, text, creatorID, time: Date.now(), }, this.props.threadInfo, ); }; onPressJoin = () => { this.props.dispatchActionPromise(joinThreadActionTypes, this.joinAction()); }; async joinAction() { const query = this.props.calendarQuery(); return await this.props.joinThread({ threadID: this.props.threadInfo.id, calendarQuery: { startDate: query.startDate, endDate: query.endDate, filters: [ ...query.filters, { type: 'threads', threadIDs: [this.props.threadInfo.id] }, ], }, }); } expandButtons = () => { if (this.state.buttonsExpanded) { return; } this.targetExpandoButtonsOpen.setValue(1); this.setState({ buttonsExpanded: true }); }; hideButtons() { if ( ChatInputBar.mediaGalleryOpen(this.props) || !this.systemKeyboardShowing || !this.state.buttonsExpanded ) { return; } this.targetExpandoButtonsOpen.setValue(0); this.setState({ buttonsExpanded: false }); } immediatelyHideButtons() { this.expandoButtonsOpen.setValue(0); this.targetExpandoButtonsOpen.setValue(0); this.setState({ buttonsExpanded: false }); } openCamera = async () => { this.dismissKeyboard(); this.props.navigation.navigate({ name: CameraModalRouteName, params: { presentedFrom: this.props.route.key, thread: { threadInfo: this.props.threadInfo, sourceMessageID: this.props.route.params.sourceMessageID, }, }, }); }; showMediaGallery = () => { const { keyboardState } = this.props; invariant(keyboardState, 'keyboardState should be initialized'); - keyboardState.showMediaGallery({ - threadInfo: this.props.threadInfo, - sourceMessageID: this.props.route.params.sourceMessageID, - }); + keyboardState.showMediaGallery(this.props.threadInfo); }; dismissKeyboard = () => { const { keyboardState } = this.props; keyboardState && keyboardState.dismissKeyboard(); }; } const unboundStyles = { cameraIcon: { paddingBottom: Platform.OS === 'android' ? 11 : 10, paddingRight: 3, }, cameraRollIcon: { paddingBottom: Platform.OS === 'android' ? 8 : 7, paddingRight: 8, }, container: { backgroundColor: 'listBackground', }, expandButton: { bottom: 0, position: 'absolute', right: 0, }, expandIcon: { paddingBottom: Platform.OS === 'android' ? 12 : 10, }, expandoButtons: { alignSelf: 'flex-end', }, explanation: { color: 'listBackgroundSecondaryLabel', paddingBottom: 4, paddingTop: 1, textAlign: 'center', }, innerExpandoButtons: { alignItems: 'flex-end', alignSelf: 'flex-end', flexDirection: 'row', }, inputContainer: { flexDirection: 'row', }, joinButton: { backgroundColor: 'mintButton', borderRadius: 5, flex: 1, justifyContent: 'center', marginHorizontal: 12, marginVertical: 3, paddingBottom: 5, paddingTop: 3, }, joinButtonContainer: { flexDirection: 'row', height: 36, }, joinButtonText: { color: 'listBackground', fontSize: 20, textAlign: 'center', }, joinThreadLoadingIndicator: { paddingVertical: 2, }, sendButton: { position: 'absolute', bottom: Platform.OS === 'android' ? 4 : 3, left: 0, }, sendIcon: { paddingLeft: 9, paddingRight: 8, paddingVertical: 5, }, textInput: { backgroundColor: 'listInputBackground', borderRadius: 10, color: 'listForegroundLabel', fontSize: 16, marginLeft: 4, marginVertical: 5, maxHeight: 250, paddingHorizontal: 10, paddingVertical: 5, }, }; const joinThreadLoadingStatusSelector = createLoadingStatusSelector( joinThreadActionTypes, ); export default React.memo(function ConnectedChatInputBar( props: BaseProps, ) { const inputState = React.useContext(InputStateContext); const keyboardState = React.useContext(KeyboardContext); const navContext = React.useContext(NavContext); const styles = useStyles(unboundStyles); const colors = useColors(); const isActive = React.useMemo( () => props.threadInfo.id === activeThreadSelector(navContext), [props.threadInfo.id, navContext], ); const draftKey = draftKeyFromThreadID(props.threadInfo.id); const draft = React.useMemo( () => global.CommCoreModule.getDraft(draftKey), // eslint-disable-next-line react-hooks/exhaustive-deps [], ); const viewerID = useSelector( (state) => state.currentUserInfo && state.currentUserInfo.id, ); const joinThreadLoadingStatus = useSelector(joinThreadLoadingStatusSelector); const calendarQuery = useSelector((state) => nonThreadCalendarQuery({ redux: state, navContext, }), ); const nextLocalID = useSelector((state) => state.nextLocalID); const userInfos = useSelector((state) => state.userStore.userInfos); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const callJoinThread = useServerCall(joinThread); const imagePastedCallback = React.useCallback( (imagePastedEvent) => { if (props.threadInfo.id !== imagePastedEvent['threadID']) { return; } const pastedImage: PhotoPaste = { step: 'photo_paste', dimensions: { height: imagePastedEvent.height, width: imagePastedEvent.width, }, filename: imagePastedEvent.fileName, uri: 'file://' + imagePastedEvent.filePath, selectTime: 0, sendTime: 0, retries: 0, }; props.navigation.navigate({ name: ImagePasteModalRouteName, params: { imagePasteStagingInfo: pastedImage, thread: { threadInfo: props.threadInfo, sourceMessageID: props.route.params.sourceMessageID, }, }, }); }, [props.navigation, props.route.params.sourceMessageID, props.threadInfo], ); React.useEffect(() => { const imagePasteListener = NativeAppEventEmitter.addListener( 'imagePasted', imagePastedCallback, ); return () => imagePasteListener.remove(); }, [imagePastedCallback]); return ( ); }); diff --git a/native/keyboard/keyboard-input-host.react.js b/native/keyboard/keyboard-input-host.react.js index 98b9b22db..b7e291159 100644 --- a/native/keyboard/keyboard-input-host.react.js +++ b/native/keyboard/keyboard-input-host.react.js @@ -1,112 +1,111 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { TextInput } from 'react-native'; import { KeyboardAccessoryView } from 'react-native-keyboard-input'; import type { MediaLibrarySelection } from 'lib/types/media-types'; import { type InputState, InputStateContext } from '../input/input-state'; import { mediaGalleryKeyboardName } from '../media/media-gallery-keyboard.react'; import { activeMessageListSelector } from '../navigation/nav-selectors'; import { NavContext } from '../navigation/navigation-context'; import { useStyles } from '../themes/colors'; import { type KeyboardState, KeyboardContext } from './keyboard-state'; type BaseProps = {| +textInputRef?: React.ElementRef, |}; type Props = {| ...BaseProps, // Redux state +styles: typeof unboundStyles, +activeMessageList: ?string, // withKeyboardState +keyboardState: KeyboardState, // withInputState +inputState: ?InputState, |}; class KeyboardInputHost extends React.PureComponent { componentDidUpdate(prevProps: Props) { if ( prevProps.activeMessageList && this.props.activeMessageList !== prevProps.activeMessageList ) { this.hideMediaGallery(); } } static mediaGalleryOpen(props: Props) { const { keyboardState } = props; return !!(keyboardState && keyboardState.mediaGalleryOpen); } render() { const kbComponent = KeyboardInputHost.mediaGalleryOpen(this.props) ? mediaGalleryKeyboardName : null; return ( ); } onMediaGalleryItemSelected = async ( keyboardName: string, selections: $ReadOnlyArray, ) => { const { keyboardState } = this.props; keyboardState.dismissKeyboard(); - const mediaGalleryThread = keyboardState.getMediaGalleryThread() - ?.threadInfo; + const mediaGalleryThread = keyboardState.getMediaGalleryThread(); if (!mediaGalleryThread) { return; } const { inputState } = this.props; invariant( inputState, 'inputState should be set in onMediaGalleryItemSelected', ); inputState.sendMultimediaMessage(selections, mediaGalleryThread); }; hideMediaGallery = () => { const { keyboardState } = this.props; keyboardState.hideMediaGallery(); }; } const unboundStyles = { kbInitialProps: { backgroundColor: 'listBackground', }, }; export default React.memo(function ConnectedKeyboardInputHost( props: BaseProps, ) { const inputState = React.useContext(InputStateContext); const keyboardState = React.useContext(KeyboardContext); invariant(keyboardState, 'keyboardState should be initialized'); const navContext = React.useContext(NavContext); const styles = useStyles(unboundStyles); const activeMessageList = activeMessageListSelector(navContext); return ( ); }); diff --git a/native/keyboard/keyboard-state-container.react.js b/native/keyboard/keyboard-state-container.react.js index 03dc5088a..c790de55c 100644 --- a/native/keyboard/keyboard-state-container.react.js +++ b/native/keyboard/keyboard-state-container.react.js @@ -1,155 +1,155 @@ // @flow import * as React from 'react'; import { Platform } from 'react-native'; import { KeyboardUtils } from 'react-native-keyboard-input'; import type { Shape } from 'lib/types/core'; -import type { OptimisticThreadInfo } from 'lib/types/thread-types'; +import type { ThreadInfo } from 'lib/types/thread-types'; import sleep from 'lib/utils/sleep'; import { tabBarAnimationDuration } from '../navigation/tab-bar.react'; import { waitForInteractions } from '../utils/timers'; import { addKeyboardShowListener, addKeyboardDismissListener, removeKeyboardListener, androidKeyboardResizesFrame, } from './keyboard'; import KeyboardInputHost from './keyboard-input-host.react'; import { KeyboardContext } from './keyboard-state'; type Props = {| +children: React.Node, |}; type State = {| +systemKeyboardShowing: boolean, +mediaGalleryOpen: boolean, - +mediaGalleryThread: ?OptimisticThreadInfo, + +mediaGalleryThread: ?ThreadInfo, +renderKeyboardInputHost: boolean, |}; class KeyboardStateContainer extends React.PureComponent { state: State = { systemKeyboardShowing: false, mediaGalleryOpen: false, mediaGalleryThread: null, renderKeyboardInputHost: false, }; keyboardShowListener: ?Object; keyboardDismissListener: ?Object; keyboardShow = () => { this.setState({ systemKeyboardShowing: true }); }; keyboardDismiss = () => { this.setState({ systemKeyboardShowing: false }); }; componentDidMount() { this.keyboardShowListener = addKeyboardShowListener(this.keyboardShow); this.keyboardDismissListener = addKeyboardDismissListener( this.keyboardDismiss, ); } componentWillUnmount() { if (this.keyboardShowListener) { removeKeyboardListener(this.keyboardShowListener); this.keyboardShowListener = null; } if (this.keyboardDismissListener) { removeKeyboardListener(this.keyboardDismissListener); this.keyboardDismissListener = null; } } componentDidUpdate(prevProps: Props, prevState: State) { if (Platform.OS !== 'android' || androidKeyboardResizesFrame) { return; } if (this.state.mediaGalleryOpen && !prevState.mediaGalleryOpen) { (async () => { await sleep(tabBarAnimationDuration); await waitForInteractions(); this.setState({ renderKeyboardInputHost: true }); })(); } } dismissKeyboard = () => { KeyboardUtils.dismiss(); this.hideMediaGallery(); }; dismissKeyboardIfShowing = () => { if (!this.keyboardShowing) { return false; } this.dismissKeyboard(); return true; }; get keyboardShowing() { const { systemKeyboardShowing, mediaGalleryOpen } = this.state; return systemKeyboardShowing || mediaGalleryOpen; } - showMediaGallery = (thread: OptimisticThreadInfo) => { + showMediaGallery = (thread: ThreadInfo) => { let updates: Shape = { mediaGalleryOpen: true, mediaGalleryThread: thread, }; if (androidKeyboardResizesFrame) { updates = { ...updates, renderKeyboardInputHost: true }; } this.setState(updates); }; hideMediaGallery = () => { this.setState({ mediaGalleryOpen: false, mediaGalleryThread: null, renderKeyboardInputHost: false, }); }; getMediaGalleryThread = () => this.state.mediaGalleryThread; render() { const { systemKeyboardShowing, mediaGalleryOpen, renderKeyboardInputHost, } = this.state; const { keyboardShowing, dismissKeyboard, dismissKeyboardIfShowing, showMediaGallery, hideMediaGallery, getMediaGalleryThread, } = this; const keyboardState = { keyboardShowing, dismissKeyboard, dismissKeyboardIfShowing, systemKeyboardShowing, mediaGalleryOpen, showMediaGallery, hideMediaGallery, getMediaGalleryThread, }; const keyboardInputHost = renderKeyboardInputHost ? ( ) : null; return ( {this.props.children} {keyboardInputHost} ); } } export default KeyboardStateContainer; diff --git a/native/keyboard/keyboard-state.js b/native/keyboard/keyboard-state.js index 4d812ad22..896a853af 100644 --- a/native/keyboard/keyboard-state.js +++ b/native/keyboard/keyboard-state.js @@ -1,20 +1,20 @@ // @flow import * as React from 'react'; -import type { OptimisticThreadInfo } from 'lib/types/thread-types'; +import type { ThreadInfo } from 'lib/types/thread-types'; export type KeyboardState = {| +keyboardShowing: boolean, +dismissKeyboard: () => void, +dismissKeyboardIfShowing: () => boolean, +systemKeyboardShowing: boolean, +mediaGalleryOpen: boolean, - +showMediaGallery: (thread: OptimisticThreadInfo) => void, + +showMediaGallery: (thread: ThreadInfo) => void, +hideMediaGallery: () => void, - +getMediaGalleryThread: () => ?OptimisticThreadInfo, + +getMediaGalleryThread: () => ?ThreadInfo, |}; const KeyboardContext = React.createContext(null); export { KeyboardContext };