diff --git a/native/chat/chat-constants.js b/native/chat/chat-constants.js
index 61e69a597..0a78171ab 100644
--- a/native/chat/chat-constants.js
+++ b/native/chat/chat-constants.js
@@ -1,33 +1,30 @@
// @flow
export const composedMessageStyle = {
marginLeft: 12,
marginRight: 7,
};
export const inlineEngagementStyle = {
height: 38,
marginTop: 5,
marginBottom: 3,
-};
-export const inlineEngagementLeftStyle = {
topOffset: -10,
};
export const inlineEngagementCenterStyle = {
topOffset: -5,
};
export const inlineEngagementRightStyle = {
- marginRight: 22,
- topOffset: -10,
+ marginLeft: 22,
};
export const inlineEngagementLabelStyle = {
height: 16,
topOffset: 8,
};
export const clusterEndHeight = 7;
export const avatarOffset = 32;
diff --git a/native/chat/inline-engagement.react.js b/native/chat/inline-engagement.react.js
index ea8d4ca78..84f733a93 100644
--- a/native/chat/inline-engagement.react.js
+++ b/native/chat/inline-engagement.react.js
@@ -1,333 +1,325 @@
// @flow
import { useNavigation } from '@react-navigation/native';
import invariant from 'invariant';
import * as React from 'react';
import { Text, View } from 'react-native';
import Animated, {
Extrapolate,
interpolateNode,
} from 'react-native-reanimated';
import useInlineEngagementText from 'lib/hooks/inline-engagement-text.react.js';
import type { ReactionInfo } from 'lib/selectors/chat-selectors.js';
-import { stringForReactionList } from 'lib/shared/reaction-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import {
inlineEngagementLabelStyle,
inlineEngagementStyle,
inlineEngagementCenterStyle,
inlineEngagementRightStyle,
- inlineEngagementLeftStyle,
composedMessageStyle,
avatarOffset,
} from './chat-constants.js';
import { useNavigateToThread } from './message-list-types.js';
import CommIcon from '../components/comm-icon.react.js';
import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react.js';
import { MessageReactionsModalRouteName } from '../navigation/route-names.js';
import { useStyles } from '../themes/colors.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
type Props = {
+sidebarThreadInfo: ?ThreadInfo,
+reactions?: ReactionInfo,
+disabled?: boolean,
- +positioning?: 'left' | 'right',
+ +positioning?: 'left' | 'right' | 'center',
+label?: ?string,
};
function InlineEngagement(props: Props): React.Node {
const {
disabled = false,
reactions,
sidebarThreadInfo,
positioning,
label,
} = props;
- const repliesText = useInlineEngagementText(sidebarThreadInfo);
+
+ const isLeft = positioning === 'left';
+ const isRight = positioning === 'right';
+ const isCenter = positioning === 'center';
const navigateToThread = useNavigateToThread();
const { navigate } = useNavigation();
const styles = useStyles(unboundStyles);
+ const editedLabel = React.useMemo(() => {
+ if (!label) {
+ return null;
+ }
+
+ const labelLeftRight = isLeft
+ ? styles.messageLabelLeft
+ : styles.messageLabelRight;
+
+ return (
+
+ {label}
+
+ );
+ }, [isLeft, label, styles]);
+
const unreadStyle = sidebarThreadInfo?.currentUser.unread
? styles.unread
: null;
const repliesStyles = React.useMemo(
() => [styles.repliesText, unreadStyle],
[styles.repliesText, unreadStyle],
);
const onPressSidebar = React.useCallback(() => {
if (sidebarThreadInfo && !disabled) {
navigateToThread({ threadInfo: sidebarThreadInfo });
}
}, [disabled, navigateToThread, sidebarThreadInfo]);
+ const repliesText = useInlineEngagementText(sidebarThreadInfo);
+
const sidebarItem = React.useMemo(() => {
if (!sidebarThreadInfo) {
return null;
}
return (
{repliesText}
);
}, [
sidebarThreadInfo,
onPressSidebar,
styles.sidebar,
styles.icon,
repliesStyles,
repliesText,
]);
const onPressReactions = React.useCallback(() => {
navigate<'MessageReactionsModal'>({
name: MessageReactionsModalRouteName,
params: { reactions },
});
}, [navigate, reactions]);
- const marginLeft = React.useMemo(
- () => (sidebarItem ? styles.reactionMarginLeft : null),
- [sidebarItem, styles.reactionMarginLeft],
- );
-
const reactionList = React.useMemo(() => {
if (!reactions || Object.keys(reactions).length === 0) {
return null;
}
- const reactionText = stringForReactionList(reactions);
- const reactionItems = {reactionText};
-
- return (
-
- {reactionItems}
-
- );
- }, [
- marginLeft,
- onPressReactions,
- reactions,
- styles.reaction,
- styles.reactionsContainer,
- ]);
-
- const isLeft = positioning === 'left';
-
- const editedLabel = React.useMemo(() => {
- if (!label) {
- return null;
- }
-
- const labelLeftRight = isLeft
- ? styles.messageLabelLeft
- : styles.messageLabelRight;
-
- return {label};
- }, [isLeft, label, styles]);
+ return Object.keys(reactions).map(reaction => {
+ const numOfReacts = reactions[reaction].users.length;
+ return (
+
+ {`${reaction} ${numOfReacts}`}
+
+ );
+ });
+ }, [onPressReactions, reactions, styles.reaction, styles.reactionsContainer]);
const inlineEngagementPositionStyle = React.useMemo(() => {
const styleResult = [styles.inlineEngagement];
- if (!isLeft) {
+ if (isRight) {
styleResult.push(styles.rightInlineEngagement);
+ } else if (isCenter) {
+ styleResult.push(styles.centerInlineEngagement);
}
return styleResult;
- }, [isLeft, styles.inlineEngagement, styles.rightInlineEngagement]);
-
- let body;
- if (isLeft) {
- body = (
- <>
- {editedLabel}
- {sidebarItem}
- {reactionList}
- >
- );
- } else {
- body = (
- <>
- {sidebarItem}
- {reactionList}
- {editedLabel}
- >
- );
- }
+ }, [
+ isCenter,
+ isRight,
+ styles.centerInlineEngagement,
+ styles.inlineEngagement,
+ styles.rightInlineEngagement,
+ ]);
- return {body};
+ return (
+
+ {editedLabel}
+ {sidebarItem}
+ {reactionList}
+
+ );
}
const unboundStyles = {
inlineEngagement: {
flexDirection: 'row',
marginBottom: inlineEngagementStyle.marginBottom,
- marginTop: inlineEngagementStyle.marginTop,
marginLeft: avatarOffset,
- top: inlineEngagementLeftStyle.topOffset,
+ flexWrap: 'wrap',
+ top: inlineEngagementStyle.topOffset,
+ },
+ centerInlineEngagement: {
+ marginLeft: 20,
+ marginRight: 20,
+ justifyContent: 'center',
},
rightInlineEngagement: {
- alignSelf: 'flex-end',
- right: inlineEngagementRightStyle.marginRight,
+ flexDirection: 'row-reverse',
+ marginLeft: inlineEngagementRightStyle.marginLeft,
},
sidebar: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'inlineEngagementBackground',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
+ marginTop: inlineEngagementStyle.marginTop,
},
icon: {
color: 'inlineEngagementLabel',
marginRight: 4,
},
repliesText: {
color: 'inlineEngagementLabel',
fontSize: 14,
lineHeight: 22,
},
unread: {
color: 'listForegroundLabel',
fontWeight: 'bold',
},
reactionsContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'inlineEngagementBackground',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
+ marginTop: inlineEngagementStyle.marginTop,
},
reaction: {
color: 'inlineEngagementLabel',
fontSize: 14,
lineHeight: 22,
},
- reactionMarginLeft: {
- marginLeft: 12,
- },
messageLabel: {
color: 'messageLabel',
paddingHorizontal: 3,
fontSize: 13,
top: inlineEngagementLabelStyle.topOffset,
height: inlineEngagementLabelStyle.height,
+ marginTop: inlineEngagementStyle.marginTop,
},
messageLabelLeft: {
marginLeft: 9,
marginRight: 4,
},
messageLabelRight: {
marginRight: 10,
marginLeft: 4,
},
avatarOffset: {
width: avatarOffset,
},
};
type TooltipInlineEngagementProps = {
+item: ChatMessageInfoItemWithHeight,
+isOpeningSidebar: boolean,
+progress: Animated.Node,
+windowWidth: number,
+positioning: 'left' | 'right' | 'center',
+initialCoordinates: {
+x: number,
+y: number,
+width: number,
+height: number,
},
};
function TooltipInlineEngagement(
props: TooltipInlineEngagementProps,
): React.Node {
const {
item,
isOpeningSidebar,
progress,
windowWidth,
initialCoordinates,
positioning,
} = props;
// ESLint doesn't recognize that invariant always throws
// eslint-disable-next-line consistent-return
const inlineEngagementStyles = React.useMemo(() => {
if (positioning === 'left') {
return {
position: 'absolute',
- top:
- inlineEngagementStyle.marginTop + inlineEngagementLeftStyle.topOffset,
+ top: inlineEngagementStyle.marginTop + inlineEngagementStyle.topOffset,
left: composedMessageStyle.marginLeft,
};
} else if (positioning === 'right') {
return {
position: 'absolute',
right:
- inlineEngagementRightStyle.marginRight +
+ inlineEngagementRightStyle.marginLeft +
composedMessageStyle.marginRight,
- top:
- inlineEngagementStyle.marginTop +
- inlineEngagementRightStyle.topOffset,
+ top: inlineEngagementStyle.marginTop + inlineEngagementStyle.topOffset,
};
} else if (positioning === 'center') {
return {
alignSelf: 'center',
top: inlineEngagementCenterStyle.topOffset,
};
}
invariant(
false,
`${positioning} is not a valid positioning value for InlineEngagement`,
);
}, [positioning]);
const inlineEngagementContainer = React.useMemo(() => {
const opacity = isOpeningSidebar
? 0
: interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [1, 0],
extrapolate: Extrapolate.CLAMP,
});
return {
position: 'absolute',
width: windowWidth,
top: initialCoordinates.height,
left: -initialCoordinates.x,
opacity,
};
}, [
initialCoordinates.height,
initialCoordinates.x,
isOpeningSidebar,
progress,
windowWidth,
]);
return (
);
}
export { InlineEngagement, TooltipInlineEngagement };
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
index 992d006bf..68be611b9 100644
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -1,223 +1,224 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { messageKey } from 'lib/shared/message-utils.js';
import { useCanCreateSidebarFromMessage } from 'lib/shared/thread-utils.js';
import { inlineEngagementCenterStyle } from './chat-constants.js';
import type { ChatNavigationProp } from './chat.react.js';
import { InlineEngagement } from './inline-engagement.react.js';
import { InnerRobotextMessage } from './inner-robotext-message.react.js';
import { Timestamp } from './timestamp.react.js';
import { getMessageTooltipKey, useContentAndHeaderOpacity } from './utils.js';
import { ChatContext } from '../chat/chat-context.js';
import { KeyboardContext } from '../keyboard/keyboard-state.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { OverlayContext } from '../navigation/overlay-context.js';
import { RobotextMessageTooltipModalRouteName } from '../navigation/route-names.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { useStyles } from '../themes/colors.js';
import { fixedTooltipHeight } from '../tooltip/tooltip.react.js';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import { AnimatedView } from '../types/styles.js';
type Props = {
...React.ElementConfig,
+item: ChatRobotextMessageInfoItemWithHeight,
+navigation:
| ChatNavigationProp<'MessageList'>
| AppNavigationProp<'TogglePinModal'>
| ChatNavigationProp<'MessageResultsScreen'>
| ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'MessageList'>
| NavigationRoute<'TogglePinModal'>
| NavigationRoute<'MessageResultsScreen'>
| NavigationRoute<'MessageSearch'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
};
function RobotextMessage(props: Props): React.Node {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
...viewProps
} = props;
let timestamp = null;
if (focused || item.startsConversation) {
timestamp = (
);
}
const styles = useStyles(unboundStyles);
let inlineEngagement = null;
if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) {
inlineEngagement = (
);
}
const chatContext = React.useContext(ChatContext);
const keyboardState = React.useContext(KeyboardContext);
const key = messageKey(item.messageInfo);
const onPress = React.useCallback(() => {
const didDismiss =
keyboardState && keyboardState.dismissKeyboardIfShowing();
if (!didDismiss) {
toggleFocus(key);
}
}, [keyboardState, toggleFocus, key]);
const overlayContext = React.useContext(OverlayContext);
const viewRef = React.useRef>();
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
item.threadInfo,
item.messageInfo,
);
const visibleEntryIDs = React.useMemo(() => {
const result = [];
if (item.threadCreatedFromMessage || canCreateSidebarFromMessage) {
result.push('sidebar');
}
return result;
}, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]);
const openRobotextTooltipModal = React.useCallback(
(x, y, width, height, pageX, pageY) => {
invariant(
verticalBounds,
'verticalBounds should be present in openRobotextTooltipModal',
);
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 = 0;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
chatContext?.chatInputBarHeights.get(item.threadInfo.id) ?? 0;
props.navigation.navigate<'RobotextMessageTooltipModal'>({
name: RobotextMessageTooltipModalRouteName,
params: {
presentedFrom: props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
tooltipLocation: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
},
[
item,
props.navigation,
props.route.key,
verticalBounds,
visibleEntryIDs,
chatContext,
],
);
const onLongPress = React.useCallback(() => {
if (keyboardState && keyboardState.dismissKeyboardIfShowing()) {
return;
}
if (visibleEntryIDs.length === 0) {
return;
}
if (!viewRef.current || !verticalBounds) {
return;
}
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
invariant(overlayContext, 'RobotextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
viewRef.current?.measure(openRobotextTooltipModal);
}, [
focused,
item,
keyboardState,
overlayContext,
toggleFocus,
verticalBounds,
viewRef,
visibleEntryIDs,
openRobotextTooltipModal,
]);
const onLayout = React.useCallback(() => {}, []);
const contentAndHeaderOpacity = useContentAndHeaderOpacity(item);
return (
{timestamp}
{inlineEngagement}
);
}
const unboundStyles = {
sidebar: {
marginTop: inlineEngagementCenterStyle.topOffset,
marginBottom: -inlineEngagementCenterStyle.topOffset,
alignSelf: 'center',
},
};
export { RobotextMessage };