diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js
index b303feba2..eafc0c280 100644
--- a/native/chat/multimedia-message-tooltip-button.react.js
+++ b/native/chat/multimedia-message-tooltip-button.react.js
@@ -1,186 +1,187 @@
// @flow
import * as React from 'react';
import Animated from 'react-native-reanimated';
import { localIDPrefix } from 'lib/shared/message-utils.js';
import {
useViewerAlreadySelectedMessageReactions,
useCanCreateReactionFromMessage,
} from 'lib/shared/reaction-utils.js';
import { TooltipInlineEngagement } from './inline-engagement.react.js';
import { InnerMultimediaMessage } from './inner-multimedia-message.react.js';
import { MessageHeader } from './message-header.react.js';
import MessageTooltipButtonAvatar from './message-tooltip-button-avatar.react.js';
import { useSendReaction } from './reaction-message-utils.js';
import ReactionSelectionPopover from './reaction-selection-popover.react.js';
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 { AppNavigationProp } from '../navigation/app-navigator.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { useTooltipActions } from '../tooltip/tooltip-hooks.js';
import type { TooltipRoute } from '../tooltip/tooltip.react.js';
/* eslint-disable import/no-named-as-default-member */
const { Node, Extrapolate, interpolateNode } = Animated;
/* eslint-enable import/no-named-as-default-member */
function noop() {}
type Props = {
+navigation: AppNavigationProp<'MultimediaMessageTooltipModal'>,
+route: TooltipRoute<'MultimediaMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
};
function MultimediaMessageTooltipButton(props: Props): React.Node {
const { navigation, route, progress, isOpeningSidebar } = 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 } = route.params;
const { style: messageContainerStyle } = 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 inlineEngagement = React.useMemo(() => {
if (!item.threadCreatedFromMessage) {
return null;
}
return (
);
}, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]);
const innerMultimediaMessage = React.useMemo(
() => (
),
[item, navigation.goBackOnce, verticalBounds],
);
const { messageInfo, threadInfo, reactions } = item;
const nextLocalID = useSelector(state => state.nextLocalID);
const localID = `${localIDPrefix}${nextLocalID}`;
const canCreateReactionFromMessage = useCanCreateReactionFromMessage(
threadInfo,
messageInfo,
);
const sendReaction = useSendReaction(
messageInfo.id,
localID,
threadInfo.id,
reactions,
);
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
const openEmojiPicker = React.useCallback(() => {
setEmojiPickerOpen(true);
}, []);
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
}
return (
);
}, [
navigation,
route,
openEmojiPicker,
canCreateReactionFromMessage,
sendReaction,
]);
const tooltipRouteKey = route.key;
const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
dismissTooltip();
},
[sendReaction, dismissTooltip],
);
const alreadySelectedEmojis =
useViewerAlreadySelectedMessageReactions(reactions);
return (
<>
{reactionSelectionPopover}
{innerMultimediaMessage}
{inlineEngagement}
>
);
}
export default MultimediaMessageTooltipButton;
diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js
index 3e63c1aac..541ec5bec 100644
--- a/native/chat/robotext-message-tooltip-button.react.js
+++ b/native/chat/robotext-message-tooltip-button.react.js
@@ -1,168 +1,169 @@
// @flow
import * as React from 'react';
import Animated from 'react-native-reanimated';
import { localIDPrefix } from 'lib/shared/message-utils.js';
import {
useViewerAlreadySelectedMessageReactions,
useCanCreateReactionFromMessage,
} from 'lib/shared/reaction-utils.js';
import { TooltipInlineEngagement } from './inline-engagement.react.js';
import { InnerRobotextMessage } from './inner-robotext-message.react.js';
import { useSendReaction } from './reaction-message-utils.js';
import ReactionSelectionPopover from './reaction-selection-popover.react.js';
import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js';
import { Timestamp } from './timestamp.react.js';
import { useAnimatedMessageTooltipButton } from './utils.js';
import EmojiKeyboard 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';
import type { TooltipRoute } from '../tooltip/tooltip.react.js';
/* 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<'RobotextMessageTooltipModal'>,
+route: TooltipRoute<'RobotextMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
};
function RobotextMessageTooltipButton(props: Props): React.Node {
const { navigation, route, progress, isOpeningSidebar } = 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 } = route.params;
const { style: messageContainerStyle } = 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 inlineEngagement = React.useMemo(() => {
if (!item.threadCreatedFromMessage) {
return null;
}
return (
);
}, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]);
const { messageInfo, threadInfo, reactions } = item;
const nextLocalID = useSelector(state => state.nextLocalID);
const localID = `${localIDPrefix}${nextLocalID}`;
const canCreateReactionFromMessage = useCanCreateReactionFromMessage(
threadInfo,
messageInfo,
);
const sendReaction = useSendReaction(
messageInfo.id,
localID,
threadInfo.id,
reactions,
);
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
const openEmojiPicker = React.useCallback(() => {
setEmojiPickerOpen(true);
}, []);
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
}
return (
);
}, [
navigation,
route,
openEmojiPicker,
canCreateReactionFromMessage,
sendReaction,
]);
const tooltipRouteKey = route.key;
const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
dismissTooltip();
},
[sendReaction, dismissTooltip],
);
const alreadySelectedEmojis =
useViewerAlreadySelectedMessageReactions(reactions);
return (
<>
{reactionSelectionPopover}
{inlineEngagement}
>
);
}
export default RobotextMessageTooltipButton;
diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js
index 37cbfd496..dd8e43555 100644
--- a/native/chat/text-message-tooltip-button.react.js
+++ b/native/chat/text-message-tooltip-button.react.js
@@ -1,192 +1,193 @@
// @flow
import * as React from 'react';
import Animated from 'react-native-reanimated';
import { localIDPrefix } from 'lib/shared/message-utils.js';
import {
useViewerAlreadySelectedMessageReactions,
useCanCreateReactionFromMessage,
} from 'lib/shared/reaction-utils.js';
import { TooltipInlineEngagement } from './inline-engagement.react.js';
import { InnerTextMessage } from './inner-text-message.react.js';
import { MessageHeader } from './message-header.react.js';
import { MessageListContextProvider } from './message-list-types.js';
import { MessagePressResponderContext } from './message-press-responder-context.js';
import MessageTooltipButtonAvatar from './message-tooltip-button-avatar.react.js';
import { useSendReaction } from './reaction-message-utils.js';
import ReactionSelectionPopover from './reaction-selection-popover.react.js';
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 { AppNavigationProp } from '../navigation/app-navigator.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { useTooltipActions } from '../tooltip/tooltip-hooks.js';
import type { TooltipRoute } from '../tooltip/tooltip.react.js';
/* 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 { navigation, route, progress, isOpeningSidebar } = 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 } = 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 messagePressResponderContext = React.useMemo(
() => ({
onPressMessage: navigation.goBackOnce,
}),
[navigation.goBackOnce],
);
const inlineEngagement = React.useMemo(() => {
if (!item.threadCreatedFromMessage) {
return null;
}
return (
);
}, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]);
const { messageInfo, threadInfo, reactions } = item;
const nextLocalID = useSelector(state => state.nextLocalID);
const localID = `${localIDPrefix}${nextLocalID}`;
const canCreateReactionFromMessage = useCanCreateReactionFromMessage(
threadInfo,
messageInfo,
);
const sendReaction = useSendReaction(
messageInfo.id,
localID,
threadInfo.id,
reactions,
);
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
const openEmojiPicker = React.useCallback(() => {
setEmojiPickerOpen(true);
}, []);
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
}
return (
);
}, [
navigation,
route,
openEmojiPicker,
canCreateReactionFromMessage,
sendReaction,
]);
const tooltipRouteKey = route.key;
const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
dismissTooltip();
},
[sendReaction, dismissTooltip],
);
const alreadySelectedEmojis =
useViewerAlreadySelectedMessageReactions(reactions);
return (
{reactionSelectionPopover}
{inlineEngagement}
);
}
export default TextMessageTooltipButton;
diff --git a/native/components/emoji-keyboard.react.js b/native/components/emoji-keyboard.react.js
index 71758960c..d1466260b 100644
--- a/native/components/emoji-keyboard.react.js
+++ b/native/components/emoji-keyboard.react.js
@@ -1,152 +1,152 @@
// @flow
import AsyncStorage from '@react-native-async-storage/async-storage';
import _flatMap from 'lodash/fp/flatMap.js';
import _flow from 'lodash/fp/flow.js';
import _keyBy from 'lodash/fp/keyBy.js';
import * as React from 'react';
import EmojiPicker, { useRecentPicksPersistence } from 'rn-emoji-keyboard';
import emojisData from 'rn-emoji-keyboard/src/assets/emojis.json';
import { useColors } from '../themes/colors.js';
const STORAGE_KEY = 'EMOJI_KEYBOARD_RECENT';
const categoryOrder = [
'recently_used',
'smileys_emotion',
'people_body',
'animals_nature',
'food_drink',
'travel_places',
'activities',
'objects',
'symbols',
'flags',
'search',
];
const initializationCallback = async () => {
const recentlyUsedEmojis = await AsyncStorage.getItem(STORAGE_KEY);
return JSON.parse(recentlyUsedEmojis ?? '[]');
};
const onStateChangeCallback = nextRecentlyUsedEmojis =>
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(nextRecentlyUsedEmojis));
const useRecentPicksPersistenceArgs = {
initialization: initializationCallback,
onStateChange: onStateChangeCallback,
};
const keyedEmojiData = _flow(_flatMap('data'), _keyBy('emoji'))(emojisData);
export type EmojiSelection = {
+emoji: string,
+name: string,
+slug: string,
+unicode_version: string,
+toneEnabled: string,
+alreadySelected?: boolean,
};
type Props = {
+onEmojiSelected: (emoji: EmojiSelection) => mixed,
+emojiKeyboardOpen: boolean,
+onEmojiKeyboardClose: () => mixed,
+selectMultipleEmojis?: boolean,
+alreadySelectedEmojis: $ReadOnlyArray,
};
function EmojiKeyboard(props: Props): React.Node {
const {
onEmojiSelected,
emojiKeyboardOpen,
onEmojiKeyboardClose,
- selectMultipleEmojis,
+ selectMultipleEmojis = false,
alreadySelectedEmojis,
} = props;
const [currentlySelected, setCurrentlySelected] = React.useState<
$ReadOnlyArray,
>(() => alreadySelectedEmojis.map(emoji => keyedEmojiData[emoji]?.name));
const handleOnEmojiSelected = React.useCallback(
(emoji: EmojiSelection) => {
if (!selectMultipleEmojis) {
setCurrentlySelected([emoji.name]);
} else if (emoji.alreadySelected) {
setCurrentlySelected(prev =>
prev.filter(emojiName => emojiName !== emoji.name),
);
} else {
setCurrentlySelected(prev => [...prev, emoji.name]);
}
onEmojiSelected(emoji);
},
[onEmojiSelected, setCurrentlySelected, selectMultipleEmojis],
);
useRecentPicksPersistence(useRecentPicksPersistenceArgs);
const colors = useColors();
const theme = React.useMemo(
() => ({
knob: colors.emojiKeyboardKnob,
container: colors.modalForeground,
header: colors.modalBackgroundLabel,
category: {
icon: colors.emojiKeyboardCategoryIcon,
iconActive: colors.panelForegroundLabel,
container: colors.panelInputBackground,
containerActive: colors.emojiKeyboardContainerActive,
},
search: {
text: colors.panelForegroundLabel,
placeholder: colors.panelInputSecondaryForeground,
icon: colors.panelInputSecondaryForeground,
background: colors.panelInputBackground,
},
emoji: {
selected: colors.panelSecondaryForegroundBorder,
},
}),
[
colors.emojiKeyboardCategoryIcon,
colors.emojiKeyboardKnob,
colors.emojiKeyboardContainerActive,
colors.modalBackgroundLabel,
colors.modalForeground,
colors.panelForegroundLabel,
colors.panelInputBackground,
colors.panelInputSecondaryForeground,
colors.panelSecondaryForegroundBorder,
],
);
return (
);
}
const emojiKeyboardStyles = {
searchBar: {
container: {
borderWidth: 0,
borderRadius: 8,
},
},
};
export default EmojiKeyboard;