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 @@ -46,18 +46,18 @@ const spec = { entries: [ - { id: 'copy', text: 'Copy', onPress: onPressCopy }, { id: 'reply', text: 'Reply', onPress: onPressReply }, - { - id: 'report', - text: 'Report', - onPress: onPressReport, - }, { id: 'sidebar', text: 'Thread', onPress: navigateToSidebar, }, + { id: 'copy', text: 'Copy', onPress: onPressCopy }, + { + id: 'report', + text: 'Report', + onPress: onPressReport, + }, ], }; diff --git a/native/navigation/tooltip.react.js b/native/navigation/tooltip.react.js --- a/native/navigation/tooltip.react.js +++ b/native/navigation/tooltip.react.js @@ -1,5 +1,9 @@ // @flow +import { + useActionSheet, + type ShowActionSheetWithOptions, +} from '@expo/react-native-action-sheet'; import type { RouteProp } from '@react-navigation/native'; import * as Haptics from 'expo-haptics'; import invariant from 'invariant'; @@ -10,6 +14,7 @@ TouchableWithoutFeedback, Platform, TouchableOpacity, + Keyboard, } from 'react-native'; import Animated from 'react-native-reanimated'; import { useDispatch } from 'react-redux'; @@ -111,7 +116,11 @@ // withInputState +inputState: ?InputState, +chatContext: ?ChatContextType, + +showActionSheetWithOptions: ShowActionSheetWithOptions, + +actionSheetShown: boolean, + +setActionSheetShown: (actionSheetShown: boolean) => void, }; + function createTooltip< RouteName: $Keys, BaseTooltipPropsType: BaseTooltipProps = BaseTooltipProps, @@ -138,6 +147,10 @@ size={16} /> ); + } else if (this.props.spec.id === 'more') { + icon = ( + + ); } return ( @@ -363,6 +376,9 @@ overlayContext, inputState, chatContext, + showActionSheetWithOptions, + actionSheetShown, + setActionSheetShown, ...navAndRouteForFlow } = this.props; @@ -391,6 +407,27 @@ ); }); + if (this.location === 'fixed' && entries.length > 3) { + items.splice(3); + + const moreSpec = { + id: 'more', + text: 'More', + onPress: this.onPressMore, + }; + + const moreTooltipItem = ( + + ); + + items.push(moreTooltipItem); + } + let triangleStyle; const { route } = this.props; const { initialCoordinates } = route.params; @@ -434,6 +471,11 @@ itemsStyle.push(styles.itemsFixed); } + let tooltip = {items}; + if (this.props.actionSheetShown) { + tooltip = null; + } + return ( @@ -448,7 +490,7 @@ onLayout={this.onTooltipContainerLayout} > {triangleUp} - {items} + {tooltip} {triangleDown} @@ -477,6 +519,87 @@ ); }; + onPressMore = () => { + Keyboard.dismiss(); + this.props.setActionSheetShown(true); + + const { entries } = this; + const options = entries.map(entry => entry.text); + + const { + destructiveButtonIndex, + cancelButtonIndex, + } = this.getPlatformSpecificButtonIndices(options); + + // We're reversing options to populate the action sheet from bottom to + // top instead of the default (top to bottom) ordering. + options.reverse(); + + const containerStyle = { + paddingBottom: 24, + }; + + const icons = [ + , + , + , + , + ]; + + const onPressAction = (selectedIndex?: number) => { + if (selectedIndex === cancelButtonIndex) { + this.props.navigation.goBackOnce(); + return; + } + const index = entries.length - (selectedIndex ?? 0); + const entry = entries[Platform.OS === 'ios' ? index : index - 1]; + this.onPressEntry(entry); + }; + + this.props.showActionSheetWithOptions( + { + options, + cancelButtonIndex, + destructiveButtonIndex, + containerStyle, + icons, + }, + onPressAction, + ); + }; + + getPlatformSpecificButtonIndices = (options: Array) => { + const destructiveButtonIndex = Platform.OS === 'ios' ? 1 : undefined; + const cancelButtonIndex = Platform.OS === 'ios' ? 0 : -1; + + // The "Cancel" action is iOS-specific + if (Platform.OS === 'ios') { + options.push('Cancel'); + } + + return { destructiveButtonIndex, cancelButtonIndex }; + }; + bindServerCall = (serverCall: ActionFunc): F => { const { cookie, @@ -515,6 +638,8 @@ return React.memo(function ConnectedTooltip( props: BaseTooltipPropsType, ) { + const { showActionSheetWithOptions } = useActionSheet(); + const dimensions = useSelector(state => state.dimensions); const serverCallState = useSelector(serverCallStateSelector); const viewerID = useSelector( @@ -525,6 +650,11 @@ const overlayContext = React.useContext(OverlayContext); const inputState = React.useContext(InputStateContext); const chatContext = React.useContext(ChatContext); + + const [actionSheetShown, setActionSheetShown] = React.useState( + false, + ); + return ( ); }); @@ -550,6 +683,9 @@ right: 0, top: 0, }, + bottomSheetIcon: { + color: '#000000', + }, container: { flex: 1, }, diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -1,5 +1,6 @@ // @flow +import { ActionSheetProvider } from '@expo/react-native-action-sheet'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useReduxDevToolsExtension } from '@react-navigation/devtools'; import { NavigationContainer } from '@react-navigation/native'; @@ -248,23 +249,25 @@ - - - - - {gated} - - - - - {navigation} - - - + + + + + + {gated} + + + + + {navigation} + + + +