diff --git a/native/chat/message-press-responder-context.js b/native/chat/message-press-responder-context.js
new file mode 100644
index 000000000..a40a90c30
--- /dev/null
+++ b/native/chat/message-press-responder-context.js
@@ -0,0 +1,13 @@
+// @flow
+
+import * as React from 'react';
+
+export type MessagePressResponderContextType = {
+ +onPressMessage: () => void,
+};
+
+const MessagePressResponderContext: React.Context = React.createContext(
+ null,
+);
+
+export { MessagePressResponderContext };
diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js
index b005baa7f..00dced8ba 100644
--- a/native/chat/text-message-tooltip-button.react.js
+++ b/native/chat/text-message-tooltip-button.react.js
@@ -1,107 +1,119 @@
// @flow
import * as React from 'react';
import Animated from 'react-native-reanimated';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import type { TooltipRoute } from '../navigation/tooltip.react';
import { useSelector } from '../redux/redux-utils';
import { TooltipInlineSidebar } from './inline-sidebar.react';
import { InnerTextMessage } from './inner-text-message.react';
import { MessageHeader } from './message-header.react';
import { MessageListContextProvider } from './message-list-types';
+import { MessagePressResponderContext } from './message-press-responder-context';
import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react';
import { useAnimatedMessageTooltipButton } from './utils';
/* eslint-disable import/no-named-as-default-member */
const { Node, interpolateNode, Extrapolate } = Animated;
/* eslint-enable import/no-named-as-default-member */
type Props = {
+navigation: AppNavigationProp<'TextMessageTooltipModal'>,
+route: TooltipRoute<'TextMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
};
function TextMessageTooltipButton(props: Props): React.Node {
const { progress } = props;
const windowWidth = useSelector(state => state.dimensions.width);
const [
sidebarInputBarHeight,
setSidebarInputBarHeight,
] = React.useState(null);
const onInputBarMeasured = React.useCallback((height: number) => {
setSidebarInputBarHeight(height);
}, []);
const { item, verticalBounds, initialCoordinates } = props.route.params;
const {
style: messageContainerStyle,
threadColorOverride,
isThreadColorDarkOverride,
} = useAnimatedMessageTooltipButton({
sourceMessage: item,
initialCoordinates,
messageListVerticalBounds: verticalBounds,
progress,
targetInputBarHeight: sidebarInputBarHeight,
});
const headerStyle = React.useMemo(() => {
const bottom = initialCoordinates.height;
const opacity = interpolateNode(progress, {
inputRange: [0, 0.05],
outputRange: [0, 1],
extrapolate: Extrapolate.CLAMP,
});
return {
opacity,
position: 'absolute',
left: -initialCoordinates.x,
width: windowWidth,
bottom,
};
}, [initialCoordinates.height, initialCoordinates.x, progress, windowWidth]);
const threadID = item.threadInfo.id;
const { navigation, isOpeningSidebar } = props;
+ const messagePressResponderContext = React.useMemo(
+ () => ({
+ onPressMessage: navigation.goBackOnce,
+ }),
+ [navigation.goBackOnce],
+ );
+
const inlineSidebar = React.useMemo(() => {
if (!item.threadCreatedFromMessage) {
return null;
}
return (
);
}, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]);
return (
-
+
+
+
{inlineSidebar}
);
}
export default TextMessageTooltipButton;
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
index de18ec3d5..064fca849 100644
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -1,241 +1,257 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { messageKey } from 'lib/shared/message-utils';
import {
threadHasPermission,
useCanCreateSidebarFromMessage,
} from 'lib/shared/thread-utils';
import { threadPermissions } from 'lib/types/thread-types';
import { ChatContext, type ChatContextType } from '../chat/chat-context';
import { MarkdownLinkContext } from '../markdown/markdown-link-context';
import {
OverlayContext,
type OverlayContextType,
} from '../navigation/overlay-context';
import type { NavigationRoute } from '../navigation/route-names';
import { TextMessageTooltipModalRouteName } from '../navigation/route-names';
import { fixedTooltipHeight } from '../navigation/tooltip.react';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types';
import type { VerticalBounds } from '../types/layout-types';
import type { ChatNavigationProp } from './chat.react';
import ComposedMessage from './composed-message.react';
import { InnerTextMessage } from './inner-text-message.react';
+import {
+ MessagePressResponderContext,
+ type MessagePressResponderContextType,
+} from './message-press-responder-context';
import textMessageSendFailed from './text-message-send-failed';
import { getMessageTooltipKey } from './utils';
type BaseProps = {
...React.ElementConfig,
+item: ChatTextMessageInfoItemWithHeight,
+navigation: ChatNavigationProp<'MessageList'>,
+route: NavigationRoute<'MessageList'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
};
type Props = {
...BaseProps,
// Redux state
+canCreateSidebarFromMessage: boolean,
// withOverlayContext
+overlayContext: ?OverlayContextType,
// MarkdownLinkContext
+chatContext: ?ChatContextType,
+linkModalActive: boolean,
+linkIsBlockingPresses: boolean,
};
class TextMessage extends React.PureComponent {
message: ?React.ElementRef;
+ messagePressResponderContext: MessagePressResponderContextType;
+
+ constructor(props: Props) {
+ super(props);
+ this.messagePressResponderContext = {
+ onPressMessage: this.onPress,
+ };
+ }
render() {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
overlayContext,
chatContext,
linkModalActive,
linkIsBlockingPresses,
canCreateSidebarFromMessage,
...viewProps
} = this.props;
let swipeOptions = 'none';
const canReply = this.canReply();
const canNavigateToSidebar = this.canNavigateToSidebar();
if (linkModalActive) {
swipeOptions = 'none';
} else if (canReply && canNavigateToSidebar) {
swipeOptions = 'both';
} else if (canReply) {
swipeOptions = 'reply';
} else if (canNavigateToSidebar) {
swipeOptions = 'sidebar';
}
return (
-
-
-
+ sendFailed={textMessageSendFailed(item)}
+ focused={focused}
+ swipeOptions={swipeOptions}
+ {...viewProps}
+ >
+
+
+
);
}
messageRef = (message: ?React.ElementRef) => {
this.message = message;
};
canReply() {
return threadHasPermission(
this.props.item.threadInfo,
threadPermissions.VOICED,
);
}
canNavigateToSidebar() {
return (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
);
}
visibleEntryIDs() {
const result = ['copy'];
if (this.canReply()) {
result.push('reply');
}
if (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
) {
result.push('sidebar');
}
if (!this.props.item.messageInfo.creator.isViewer) {
result.push('report');
}
return result;
}
onPress = () => {
const visibleEntryIDs = this.visibleEntryIDs();
if (visibleEntryIDs.length === 0) {
return;
}
const {
message,
props: { verticalBounds, linkIsBlockingPresses },
} = this;
if (!message || !verticalBounds || linkIsBlockingPresses) {
return;
}
const { focused, toggleFocus, item } = this.props;
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
const { overlayContext } = this.props;
invariant(overlayContext, 'TextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
message.measure((x, y, width, height, pageX, pageY) => {
const coordinates = { x: pageX, y: pageY, width, height };
const messageTop = pageY;
const messageBottom = pageY + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const belowMargin = 20;
const belowSpace = fixedTooltipHeight + belowMargin;
const { isViewer } = item.messageInfo.creator;
const aboveMargin = isViewer ? 30 : 50;
const aboveSpace = fixedTooltipHeight + aboveMargin;
let margin = belowMargin;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
this.props.chatContext?.chatInputBarHeights.get(item.threadInfo.id) ??
0;
this.props.navigation.navigate<'TextMessageTooltipModal'>({
name: TextMessageTooltipModalRouteName,
params: {
presentedFrom: this.props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
location: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
});
};
}
const ConnectedTextMessage: React.ComponentType = React.memo(
function ConnectedTextMessage(props: BaseProps) {
const overlayContext = React.useContext(OverlayContext);
const chatContext = React.useContext(ChatContext);
const [linkModalActive, setLinkModalActive] = React.useState(false);
const [linkPressActive, setLinkPressActive] = React.useState(false);
const markdownLinkContext = React.useMemo(
() => ({
setLinkModalActive,
setLinkPressActive,
}),
[setLinkModalActive, setLinkPressActive],
);
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
props.item.threadInfo,
props.item.messageInfo,
);
const linkIsBlockingPresses = linkModalActive || linkPressActive;
return (
);
},
);
export { ConnectedTextMessage as TextMessage };