From cd14d8ab5cda36a0d42b4a0381b8668109c16a53 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Wed, 25 Sep 2024 12:18:06 +0530
Subject: [PATCH 01/19] feat: add new message action list and reaction selector
UI
---
package/src/components/Channel/Channel.tsx | 28 +-
.../Channel/hooks/useCreateMessagesContext.ts | 14 +-
package/src/components/Message/Message.tsx | 160 +++--
.../MessageSimple/MessageTextContainer.tsx | 7 +-
.../Message/MessageSimple/ReactionList.tsx | 12 +-
.../Message/hooks/useMessageActions.tsx | 95 +--
.../Message/hooks/useProcessReactions.ts | 6 +-
.../Message/utils/messageActions.ts | 7 +-
.../components/MessageInput/MessageInput.tsx | 1 -
.../AudioRecordingLockIndicator.tsx | 1 -
.../MessageOverlay/MessageActionList.tsx | 147 +---
.../MessageOverlay/MessageActionListItem.tsx | 112 +--
.../MessageOverlay/MessageOverlay.tsx | 680 +++---------------
.../MessageOverlay/OverlayBackdrop.tsx | 18 -
.../MessageOverlay/OverlayReactionList.tsx | 438 ++---------
.../MessageOverlay/OverlayReactions.tsx | 311 +++-----
.../MessageOverlay/OverlayReactionsAvatar.tsx | 3 +-
.../MessageOverlay/OverlayReactionsItem.tsx | 105 +--
.../MessageOverlay/ReactionButton.tsx | 71 ++
.../MessageOverlay/hooks/useFetchReactions.ts | 2 +
package/src/components/index.ts | 1 -
package/src/contexts/index.ts | 1 -
.../MessageOverlayContext.tsx | 147 ----
.../hooks/useResettableState.test.tsx | 48 --
.../hooks/useResettableState.ts | 22 -
.../messagesContext/MessagesContext.tsx | 34 +-
.../overlayContext/OverlayContext.tsx | 24 +-
.../overlayContext/OverlayProvider.tsx | 87 +--
.../src/contexts/themeContext/utils/theme.ts | 41 +-
.../__tests__/useTranslatedMessage.test.tsx | 12 +-
package/src/hooks/useTranslatedMessage.ts | 5 +-
.../store/apis/getReactionsforFilterSort.ts | 4 +-
package/src/types/types.ts | 7 +
33 files changed, 673 insertions(+), 1978 deletions(-)
delete mode 100644 package/src/components/MessageOverlay/OverlayBackdrop.tsx
create mode 100644 package/src/components/MessageOverlay/ReactionButton.tsx
delete mode 100644 package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx
delete mode 100644 package/src/contexts/messageOverlayContext/hooks/useResettableState.test.tsx
delete mode 100644 package/src/contexts/messageOverlayContext/hooks/useResettableState.ts
diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx
index 63a9ae0501..440796043b 100644
--- a/package/src/components/Channel/Channel.tsx
+++ b/package/src/components/Channel/Channel.tsx
@@ -7,6 +7,7 @@ import {
View,
} from 'react-native';
+import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import debounce from 'lodash/debounce';
import omit from 'lodash/omit';
import throttle from 'lodash/throttle';
@@ -171,7 +172,12 @@ import { ScrollToBottomButton as ScrollToBottomButtonDefault } from '../MessageL
import { StickyHeader as StickyHeaderDefault } from '../MessageList/StickyHeader';
import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/TypingIndicator';
import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer';
+import { MessageActionList as MessageActionListDefault } from '../MessageOverlay/MessageActionList';
+import { MessageActionListItem as MessageActionListItemDefault } from '../MessageOverlay/MessageActionListItem';
import { OverlayReactionList as OverlayReactionListDefault } from '../MessageOverlay/OverlayReactionList';
+import { OverlayReactions as OverlayReactionDefault } from '../MessageOverlay/OverlayReactions';
+import { OverlayReactionsAvatar as OverlayReactionsAvatarDefault } from '../MessageOverlay/OverlayReactionsAvatar';
+import { OverlayReactionsItem as OverlayReactionsItemDefault } from '../MessageOverlay/OverlayReactionsItem';
import { Reply as ReplyDefault } from '../Reply/Reply';
const styles = StyleSheet.create({
@@ -301,6 +307,8 @@ export type ChannelPropsWithContext<
| 'ImageLoadingIndicator'
| 'markdownRules'
| 'Message'
+ | 'MessageActionList'
+ | 'MessageActionListItem'
| 'messageActions'
| 'MessageAvatar'
| 'MessageBounce'
@@ -319,12 +327,16 @@ export type ChannelPropsWithContext<
| 'MessageStatus'
| 'MessageSystem'
| 'MessageText'
+ | 'messageTextNumberOfLines'
| 'MessageTimestamp'
| 'myMessageTheme'
| 'onLongPressMessage'
| 'onPressInMessage'
| 'onPressMessage'
+ | 'OverlayReactions'
| 'OverlayReactionList'
+ | 'OverlayReactionsAvatar'
+ | 'OverlayReactionsItem'
| 'ReactionList'
| 'Reply'
| 'ScrollToBottomButton'
@@ -540,6 +552,8 @@ const ChannelWithContext = <
mentionAllAppUsersEnabled = false,
mentionAllAppUsersQuery,
Message = MessageDefault,
+ MessageActionList = MessageActionListDefault,
+ MessageActionListItem = MessageActionListItemDefault,
messageActions,
MessageAvatar = MessageAvatarDefault,
MessageBounce = MessageBounceDefault,
@@ -560,6 +574,7 @@ const ChannelWithContext = <
MessageStatus = MessageStatusDefault,
MessageSystem = MessageSystemDefault,
MessageText,
+ messageTextNumberOfLines,
MessageTimestamp = MessageTimestampDefault,
MoreOptionsButton = MoreOptionsButtonDefault,
myMessageTheme,
@@ -571,6 +586,9 @@ const ChannelWithContext = <
onPressInMessage,
onPressMessage,
OverlayReactionList = OverlayReactionListDefault,
+ OverlayReactions = OverlayReactionDefault,
+ OverlayReactionsAvatar = OverlayReactionsAvatarDefault,
+ OverlayReactionsItem = OverlayReactionsItemDefault,
overrideOwnCapabilities,
ReactionList = ReactionListDefault,
read,
@@ -2360,6 +2378,8 @@ const ChannelWithContext = <
legacyImageViewerSwipeBehaviour,
markdownRules,
Message,
+ MessageActionList,
+ MessageActionListItem,
messageActions,
MessageAvatar,
MessageBounce,
@@ -2378,12 +2398,16 @@ const ChannelWithContext = <
MessageStatus,
MessageSystem,
MessageText,
+ messageTextNumberOfLines,
MessageTimestamp,
myMessageTheme,
onLongPressMessage,
onPressInMessage,
onPressMessage,
OverlayReactionList,
+ OverlayReactions,
+ OverlayReactionsAvatar,
+ OverlayReactionsItem,
ReactionList,
removeMessage,
Reply,
@@ -2455,7 +2479,9 @@ const ChannelWithContext = <
value={threadContext}>
value={suggestionsContext}>
value={inputMessageInputContext}>
- {children}
+
+ {children}
+
diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts
index a0789400be..7f74cf8dca 100644
--- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts
+++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts
@@ -52,6 +52,8 @@ export const useCreateMessagesContext = <
legacyImageViewerSwipeBehaviour,
markdownRules,
Message,
+ MessageActionList,
+ MessageActionListItem,
messageActions,
MessageAvatar,
MessageBounce,
@@ -70,12 +72,16 @@ export const useCreateMessagesContext = <
MessageStatus,
MessageSystem,
MessageText,
+ messageTextNumberOfLines,
MessageTimestamp,
myMessageTheme,
onLongPressMessage,
onPressInMessage,
onPressMessage,
OverlayReactionList,
+ OverlayReactions,
+ OverlayReactionsAvatar,
+ OverlayReactionsItem,
ReactionList,
removeMessage,
Reply,
@@ -101,7 +107,7 @@ export const useCreateMessagesContext = <
const additionalTouchablePropsLength = Object.keys(additionalTouchableProps || {}).length;
const markdownRulesLength = Object.keys(markdownRules || {}).length;
const messageContentOrderValue = messageContentOrder.join();
- const supportedReactionsLength = supportedReactions.length;
+ const supportedReactionsLength = supportedReactions?.length;
const messagesContext: MessagesContextValue = useMemo(
() => ({
@@ -150,6 +156,8 @@ export const useCreateMessagesContext = <
legacyImageViewerSwipeBehaviour,
markdownRules,
Message,
+ MessageActionList,
+ MessageActionListItem,
messageActions,
MessageAvatar,
MessageBounce,
@@ -168,12 +176,16 @@ export const useCreateMessagesContext = <
MessageStatus,
MessageSystem,
MessageText,
+ messageTextNumberOfLines,
MessageTimestamp,
myMessageTheme,
onLongPressMessage,
onPressInMessage,
onPressMessage,
OverlayReactionList,
+ OverlayReactions,
+ OverlayReactionsAvatar,
+ OverlayReactionsItem,
ReactionList,
removeMessage,
Reply,
diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx
index 1e962a96d3..0bd13696e3 100644
--- a/package/src/components/Message/Message.tsx
+++ b/package/src/components/Message/Message.tsx
@@ -1,6 +1,8 @@
-import React, { useMemo, useState } from 'react';
+import React, { useMemo, useRef, useState } from 'react';
import { GestureResponderEvent, Keyboard, StyleProp, View, ViewStyle } from 'react-native';
+import type { BottomSheetModal } from '@gorhom/bottom-sheet';
+
import type { Attachment, UserResponse } from 'stream-chat';
import { useCreateMessageContext } from './hooks/useCreateMessageContext';
@@ -19,18 +21,10 @@ import {
useKeyboardContext,
} from '../../contexts/keyboardContext/KeyboardContext';
import { MessageContextValue, MessageProvider } from '../../contexts/messageContext/MessageContext';
-import {
- MessageOverlayContextValue,
- useMessageOverlayContext,
-} from '../../contexts/messageOverlayContext/MessageOverlayContext';
import {
MessagesContextValue,
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
-import {
- OverlayContextValue,
- useOverlayContext,
-} from '../../contexts/overlayContext/OverlayContext';
import { useOwnCapabilitiesContext } from '../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext';
@@ -53,7 +47,7 @@ import {
isMessageWithStylesReadByAndDateSeparator,
MessageType,
} from '../MessageList/hooks/useMessageList';
-import type { MessageActionListItemProps } from '../MessageOverlay/MessageActionListItem';
+import { MessageOverlay } from '../MessageOverlay/MessageOverlay';
export type TouchableEmitter =
| 'fileAttachment'
@@ -159,6 +153,8 @@ export type MessagePropsWithContext<
| 'handleRetry'
| 'handleThreadReply'
| 'isAttachmentEqual'
+ | 'MessageActionList'
+ | 'MessageActionListItem'
| 'messageActions'
| 'messageContentOrder'
| 'MessageBounce'
@@ -167,6 +163,9 @@ export type MessagePropsWithContext<
| 'onPressInMessage'
| 'onPressMessage'
| 'OverlayReactionList'
+ | 'OverlayReactions'
+ | 'OverlayReactionsAvatar'
+ | 'OverlayReactionsItem'
| 'removeMessage'
| 'deleteReaction'
| 'retrySendMessage'
@@ -176,8 +175,6 @@ export type MessagePropsWithContext<
| 'supportedReactions'
| 'updateMessage'
> &
- Pick, 'setData'> &
- Pick &
Pick, 'openThread'> &
Pick & {
chatContext: ChatContextValue;
@@ -238,8 +235,10 @@ const MessageWithContext = <
>(
props: MessagePropsWithContext,
) => {
+ const [isErrorInMessage, setIsErrorInMessage] = useState(false);
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
const [isEditedMessageOpen, setIsEditedMessageOpen] = useState(false);
+ const [isMessageActionsVisible, setIsMessageActionsVisible] = useState(true);
const isMessageTypeDeleted = props.message.type === 'deleted';
const {
@@ -270,6 +269,8 @@ const MessageWithContext = <
lastReceivedId,
members,
message,
+ MessageActionList,
+ MessageActionListItem,
messageActions: messageActionsProp = defaultMessageActions,
MessageBounce,
messageContentOrder: messageContentOrderProp,
@@ -284,14 +285,15 @@ const MessageWithContext = <
onThreadSelect,
openThread,
OverlayReactionList,
+ OverlayReactions,
+ OverlayReactionsAvatar,
+ OverlayReactionsItem,
preventPress,
removeMessage,
retrySendMessage,
selectReaction,
sendReaction,
- setData,
setEditingState,
- setOverlay,
setQuotedMessageState,
showAvatar,
showMessageStatus,
@@ -309,6 +311,21 @@ const MessageWithContext = <
messageSimple: { targetedMessageContainer, targetedMessageUnderlay },
},
} = useTheme();
+ const messageActionsBottomSheetRef = useRef(null);
+
+ const openMessageActionsBottomSheet = () => {
+ if (messageActionsBottomSheetRef.current?.present) {
+ messageActionsBottomSheetRef.current.present();
+ } else {
+ console.warn('bottom and top insets must be set for the image picker to work correctly');
+ }
+ };
+
+ const closeMessageActionsBottomSheet = () => {
+ if (messageActionsBottomSheetRef.current?.dismiss) {
+ messageActionsBottomSheetRef.current.dismiss();
+ }
+ };
const actionsEnabled =
message.type === 'regular' && message.status === MessageStatusTypes.RECEIVED;
@@ -346,6 +363,7 @@ const MessageWithContext = <
}
const quotedMessage = message.quoted_message as MessageType;
if (error) {
+ setIsErrorInMessage(true);
/**
* If its a Blocked message, we don't do anything as per specs.
*/
@@ -359,7 +377,7 @@ const MessageWithContext = <
setIsBounceDialogOpen(true);
return;
}
- showMessageOverlay(true, true);
+ showMessageOverlay();
} else if (quotedMessage) {
onPressQuotedMessage(quotedMessage);
}
@@ -531,6 +549,7 @@ const MessageWithContext = <
client,
deleteMessage: deleteMessageFromContext,
deleteReaction,
+ dismissOverlay: closeMessageActionsBottomSheet,
enforceUniqueReaction,
handleBan,
handleBlock,
@@ -552,72 +571,44 @@ const MessageWithContext = <
selectReaction,
sendReaction,
setEditingState,
- setOverlay,
setQuotedMessageState,
supportedReactions,
t,
updateMessage,
});
- const { userLanguage } = useTranslationContext();
+ // const { userLanguage } = useTranslationContext();
+ const isThreadMessage = threadList || !!message.parent_id;
+
+ const messageActions =
+ typeof messageActionsProp !== 'function'
+ ? messageActionsProp
+ : messageActionsProp({
+ banUser,
+ blockUser,
+ copyMessage,
+ deleteMessage,
+ dismissOverlay: closeMessageActionsBottomSheet,
+ editMessage,
+ error: isErrorInMessage,
+ flagMessage,
+ isMessageActionsVisible,
+ isMyMessage,
+ isThreadMessage,
+ message,
+ muteUser,
+ ownCapabilities,
+ pinMessage,
+ quotedReply,
+ retry,
+ threadReply,
+ unpinMessage,
+ });
- const showMessageOverlay = async (isMessageActionsVisible = true, error = errorOrFailed) => {
+ const showMessageOverlay = async (isVisible = true) => {
+ setIsMessageActionsVisible(isVisible);
await dismissKeyboard();
-
- const isThreadMessage = threadList || !!message.parent_id;
-
- const dismissOverlay = () => setOverlay('none');
-
- const messageActions =
- typeof messageActionsProp !== 'function'
- ? messageActionsProp
- : messageActionsProp({
- banUser,
- blockUser,
- copyMessage,
- deleteMessage,
- dismissOverlay,
- editMessage,
- error,
- flagMessage,
- isMessageActionsVisible,
- isMyMessage,
- isThreadMessage,
- message,
- messageReactions: isMessageActionsVisible === false,
- muteUser,
- ownCapabilities,
- pinMessage,
- quotedReply,
- retry,
- threadReply,
- unpinMessage,
- });
-
- setData({
- alignment,
- chatContext,
- clientId: client.userID,
- files: attachments.files,
- groupStyles,
- handleReaction: ownCapabilities.sendReaction ? handleReaction : undefined,
- images: attachments.images,
- message,
- messageActions: messageActions?.filter(Boolean) as MessageActionListItemProps[] | undefined,
- messageContext: { ...messageContext, preventPress: true },
- messageReactionTitle: !error && !isMessageActionsVisible ? t('Message Reactions') : undefined,
- messagesContext: { ...messagesContext, messageContentOrder },
- onlyEmojis,
- otherAttachments: attachments.other,
- OverlayReactionList,
- ownCapabilities,
- supportedReactions,
- threadList,
- userLanguage,
- videos: attachments.videos,
- });
-
- setOverlay('message');
+ openMessageActionsBottomSheet();
};
const actionHandlers: MessageActionHandlers = {
@@ -664,7 +655,7 @@ const MessageWithContext = <
return;
}
triggerHaptic('impactMedium');
- showMessageOverlay(true);
+ showMessageOverlay();
}
: () => null;
@@ -776,6 +767,23 @@ const MessageWithContext = <
{isBounceDialogOpen && }
+
@@ -942,9 +950,7 @@ export const Message = <
const { channel, enforceUniqueReaction, members } = useChannelContext();
const chatContext = useChatContext();
const { dismissKeyboard } = useKeyboardContext();
- const { setData } = useMessageOverlayContext();
const messagesContext = useMessagesContext();
- const { setOverlay } = useOverlayContext();
const { openThread } = useThreadContext();
const { t } = useTranslationContext();
@@ -959,8 +965,6 @@ export const Message = <
members,
messagesContext,
openThread,
- setData,
- setOverlay,
t,
}}
{...props}
diff --git a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx
index 2eadb234af..187902f9f9 100644
--- a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx
+++ b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx
@@ -37,11 +37,10 @@ export type MessageTextContainerPropsWithContext<
> &
Pick<
MessagesContextValue,
- 'markdownRules' | 'MessageText' | 'myMessageTheme'
+ 'markdownRules' | 'MessageText' | 'myMessageTheme' | 'messageTextNumberOfLines'
> & {
markdownStyles?: MarkdownStyle;
messageOverlay?: boolean;
- messageTextNumberOfLines?: number;
styles?: Partial<{
textContainer: StyleProp;
}>;
@@ -183,8 +182,8 @@ export const MessageTextContainer = <
) => {
const { message, onLongPress, onlyEmojis, onPress, preventPress } =
useMessageContext();
- const { markdownRules, MessageText, myMessageTheme } = useMessagesContext();
- const { messageTextNumberOfLines } = props;
+ const { markdownRules, MessageText, messageTextNumberOfLines, myMessageTheme } =
+ useMessagesContext();
return (
& {
size: number;
- supportedReactions: ReactionData[];
type: string;
+ supportedReactions?: ReactionData[];
};
const Icon = ({ pathFill, size, style, supportedReactions, type }: Props) => {
const ReactionIcon =
- supportedReactions.find((reaction) => reaction.type === type)?.Icon || Unknown;
+ supportedReactions?.find((reaction) => reaction.type === type)?.Icon || Unknown;
return (
@@ -57,9 +57,8 @@ export type ReactionListPropsWithContext<
| 'reactions'
| 'showMessageOverlay'
> &
- Pick, 'targetedMessage'> & {
+ Pick, 'targetedMessage' | 'supportedReactions'> & {
messageContentWidth: number;
- supportedReactions: ReactionData[];
fill?: string;
/** An array of the reaction objects to display in the list */
latest_reactions?: ReactionResponse[];
@@ -129,11 +128,12 @@ const ReactionListWithContext = <
const width = useWindowDimensions().width;
- const supportedReactionTypes = supportedReactions.map(
+ const supportedReactionTypes = supportedReactions?.map(
(supportedReaction) => supportedReaction.type,
);
+
const hasSupportedReactions = reactions.some((reaction) =>
- supportedReactionTypes.includes(reaction.type),
+ supportedReactionTypes?.includes(reaction.type),
);
if (!hasSupportedReactions || messageContentWidth === 0) {
diff --git a/package/src/components/Message/hooks/useMessageActions.tsx b/package/src/components/Message/hooks/useMessageActions.tsx
index 3ee3ad8bf1..4525c15254 100644
--- a/package/src/components/Message/hooks/useMessageActions.tsx
+++ b/package/src/components/Message/hooks/useMessageActions.tsx
@@ -6,7 +6,6 @@ import type { ChannelContextValue } from '../../../contexts/channelContext/Chann
import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext';
import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext';
import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';
-import type { OverlayContextValue } from '../../../contexts/overlayContext/OverlayContext';
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext';
import type { TranslationContextValue } from '../../../contexts/translationContext/TranslationContext';
@@ -30,38 +29,9 @@ import { MessageStatusTypes } from '../../../utils/utils';
import type { MessageType } from '../../MessageList/hooks/useMessageList';
import type { MessageActionType } from '../../MessageOverlay/MessageActionListItem';
-export const useMessageActions = <
+export type MessageActionsHookProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->({
- channel,
- client,
- deleteMessage: deleteMessageFromContext,
- deleteReaction,
- enforceUniqueReaction,
- handleBan,
- handleBlock,
- handleCopy,
- handleDelete,
- handleEdit,
- handleFlag,
- handleMute,
- handlePinMessage,
- handleQuotedReply,
- handleReaction: handleReactionProp,
- handleRetry,
- handleThreadReply,
- message,
- onThreadSelect,
- openThread,
- retrySendMessage,
- selectReaction,
- sendReaction,
- setEditingState,
- setOverlay,
- setQuotedMessageState,
- supportedReactions,
- t,
-}: Pick<
+> = Pick<
MessagesContextValue,
| 'deleteMessage'
| 'sendReaction'
@@ -88,12 +58,45 @@ export const useMessageActions = <
> &
Pick, 'channel' | 'enforceUniqueReaction'> &
Pick, 'client'> &
- Pick &
Pick, 'openThread'> &
Pick, 'message'> &
Pick & {
+ dismissOverlay: () => void;
onThreadSelect?: (message: MessageType) => void;
- }) => {
+ };
+
+export const useMessageActions = <
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
+>({
+ channel,
+ client,
+ deleteMessage: deleteMessageFromContext,
+ deleteReaction,
+ dismissOverlay,
+ enforceUniqueReaction,
+ handleBan,
+ handleBlock,
+ handleCopy,
+ handleDelete,
+ handleEdit,
+ handleFlag,
+ handleMute,
+ handlePinMessage,
+ handleQuotedReply,
+ handleReaction: handleReactionProp,
+ handleRetry,
+ handleThreadReply,
+ message,
+ onThreadSelect,
+ openThread,
+ retrySendMessage,
+ selectReaction,
+ sendReaction,
+ setEditingState,
+ setQuotedMessageState,
+ supportedReactions,
+ t,
+}: MessageActionsHookProps) => {
const {
theme: {
colors: { accent_red, grey },
@@ -141,7 +144,7 @@ export const useMessageActions = <
const banUser: MessageActionType = {
action: async () => {
- setOverlay('none');
+ dismissOverlay();
if (message.user?.id) {
if (handleBan) {
handleBan(message);
@@ -160,7 +163,7 @@ export const useMessageActions = <
*/
const blockUser: MessageActionType = {
action: async () => {
- setOverlay('none');
+ dismissOverlay();
if (message.user?.id) {
if (handleBlock) {
handleBlock(message);
@@ -176,7 +179,7 @@ export const useMessageActions = <
const copyMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleCopy) {
handleCopy(message);
}
@@ -189,7 +192,7 @@ export const useMessageActions = <
const deleteMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleDelete) {
handleDelete(message);
}
@@ -203,7 +206,7 @@ export const useMessageActions = <
const editMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleEdit) {
handleEdit(message);
}
@@ -216,7 +219,7 @@ export const useMessageActions = <
const pinMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handlePinMessage) {
handlePinMessage(message);
}
@@ -229,7 +232,7 @@ export const useMessageActions = <
const unpinMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handlePinMessage) {
handlePinMessage(message);
}
@@ -242,7 +245,7 @@ export const useMessageActions = <
const flagMessage: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleFlag) {
handleFlag(message);
}
@@ -268,7 +271,7 @@ export const useMessageActions = <
const muteUser: MessageActionType = {
action: async () => {
- setOverlay('none');
+ dismissOverlay();
if (message.user?.id) {
if (handleMute) {
handleMute(message);
@@ -284,7 +287,7 @@ export const useMessageActions = <
const quotedReply: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleQuotedReply) {
handleQuotedReply(message);
}
@@ -297,7 +300,7 @@ export const useMessageActions = <
const retry: MessageActionType = {
action: async () => {
- setOverlay('none');
+ dismissOverlay();
const messageWithoutReservedFields = removeReservedFields(message);
if (handleRetry) {
handleRetry(messageWithoutReservedFields as MessageType);
@@ -312,7 +315,7 @@ export const useMessageActions = <
const threadReply: MessageActionType = {
action: () => {
- setOverlay('none');
+ dismissOverlay();
if (handleThreadReply) {
handleThreadReply(message);
}
diff --git a/package/src/components/Message/hooks/useProcessReactions.ts b/package/src/components/Message/hooks/useProcessReactions.ts
index 96a9cea310..69bd55d237 100644
--- a/package/src/components/Message/hooks/useProcessReactions.ts
+++ b/package/src/components/Message/hooks/useProcessReactions.ts
@@ -48,13 +48,13 @@ const isOwnReaction = <
ownReactions?: ReactionResponse[] | null,
) => (ownReactions ? ownReactions.some((reaction) => reaction.type === reactionType) : false);
-const isSupportedReaction = (reactionType: string, supportedReactions: ReactionData[]) =>
+const isSupportedReaction = (reactionType: string, supportedReactions?: ReactionData[]) =>
supportedReactions
? supportedReactions.some((reactionOption) => reactionOption.type === reactionType)
: false;
-const getEmojiByReactionType = (reactionType: string, supportedReactions: ReactionData[]) =>
- supportedReactions.find(({ type }) => type === reactionType)?.Icon ?? null;
+const getEmojiByReactionType = (reactionType: string, supportedReactions?: ReactionData[]) =>
+ supportedReactions ? supportedReactions.find(({ type }) => type === reactionType)?.Icon : null;
const getLatestReactedUserNames = (reactionType: string, latestReactions?: ReactionResponse[]) =>
latestReactions
diff --git a/package/src/components/Message/utils/messageActions.ts b/package/src/components/Message/utils/messageActions.ts
index fea05791b2..6d2f6cbccb 100644
--- a/package/src/components/Message/utils/messageActions.ts
+++ b/package/src/components/Message/utils/messageActions.ts
@@ -19,10 +19,6 @@ export type MessageActionsParams<
*/
isMessageActionsVisible: boolean;
isThreadMessage: boolean;
- /**
- * @deprecated use `isMessageActionsVisible` instead.
- */
- messageReactions: boolean;
muteUser: MessageActionType;
ownCapabilities: OwnCapabilitiesContextValue;
pinMessage: MessageActionType;
@@ -54,7 +50,6 @@ export const messageActions = <
isMyMessage,
isThreadMessage,
message,
- messageReactions,
ownCapabilities,
pinMessage,
quotedReply,
@@ -62,7 +57,7 @@ export const messageActions = <
threadReply,
unpinMessage,
}: MessageActionsParams) => {
- if (messageReactions || !isMessageActionsVisible) {
+ if (!isMessageActionsVisible) {
return [];
}
diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx
index 20a9cf52fb..6fb7652679 100644
--- a/package/src/components/MessageInput/MessageInput.tsx
+++ b/package/src/components/MessageInput/MessageInput.tsx
@@ -693,7 +693,6 @@ const MessageInputWithContext = <
micButton: useAnimatedStyle(() => ({
opacity: interpolate(micPositionX.value, [0, X_AXIS_POSITION], [1, 0], Extrapolation.CLAMP),
transform: [{ translateX: micPositionX.value }, { translateY: micPositionY.value }],
- zIndex: 2,
})),
slideToCancel: useAnimatedStyle(() => ({
opacity: interpolate(micPositionX.value, [0, X_AXIS_POSITION], [1, 0], Extrapolation.CLAMP),
diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx
index 936fcd3381..81f934d08e 100644
--- a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx
+++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx
@@ -80,6 +80,5 @@ const styles = StyleSheet.create({
padding: 8,
position: 'absolute',
right: 0,
- zIndex: 1,
},
});
diff --git a/package/src/components/MessageOverlay/MessageActionList.tsx b/package/src/components/MessageOverlay/MessageActionList.tsx
index 72e44c2b69..0f344c6f32 100644
--- a/package/src/components/MessageOverlay/MessageActionList.tsx
+++ b/package/src/components/MessageOverlay/MessageActionList.tsx
@@ -1,103 +1,45 @@
import React from 'react';
-import { StyleSheet, ViewStyle } from 'react-native';
-import Animated, {
- interpolate,
- SharedValue,
- useAnimatedStyle,
- useSharedValue,
-} from 'react-native-reanimated';
+import { StyleSheet, View } from 'react-native';
-import { MessageActionListItem as DefaultMessageActionListItem } from './MessageActionListItem';
+import { MessageActionType } from './MessageActionListItem';
-import {
- MessageOverlayData,
- useMessageOverlayContext,
-} from '../../contexts/messageOverlayContext/MessageOverlayContext';
+import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext';
import type { OverlayProviderProps } from '../../contexts/overlayContext/OverlayContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
-import { useViewport } from '../../hooks/useViewport';
import type { DefaultStreamChatGenerics } from '../../types/types';
-export type MessageActionListPropsWithContext<
+export type MessageActionListProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick<
OverlayProviderProps,
- | 'MessageActionListItem'
- | 'error'
- | 'isMyMessage'
- | 'isThreadMessage'
- | 'message'
- | 'messageReactions'
+ 'error' | 'isMyMessage' | 'isThreadMessage' | 'message'
> &
- Pick, 'alignment' | 'messageActions'> & {
- showScreen: SharedValue;
+ Pick & {
+ messageActions?: MessageActionType[];
};
-const MessageActionListWithContext = <
+export const MessageActionList = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
>(
- props: MessageActionListPropsWithContext,
+ props: MessageActionListProps,
) => {
+ const { error, isMyMessage, isThreadMessage, message, MessageActionListItem, messageActions } =
+ props;
const {
- alignment,
- error,
- isMyMessage,
- isThreadMessage,
- message,
- MessageActionListItem = DefaultMessageActionListItem,
- messageActions,
- messageReactions,
- showScreen,
- } = props;
+ theme: {
+ messageActionList: { container },
+ },
+ } = useTheme();
const messageActionProps = {
error,
isMyMessage,
isThreadMessage,
message,
- messageReactions,
};
- const { vw } = useViewport();
-
- const {
- theme: {
- colors: { white_snow },
- },
- } = useTheme();
-
- const height = useSharedValue(0);
- const width = useSharedValue(0);
-
- const showScreenStyle = useAnimatedStyle(
- () => ({
- transform: [
- {
- translateY: interpolate(showScreen.value, [0, 1], [-height.value / 2, 0]),
- },
- {
- translateX: interpolate(
- showScreen.value,
- [0, 1],
- [alignment === 'left' ? -width.value / 2 : width.value / 2, 0],
- ),
- },
- {
- scale: showScreen.value,
- },
- ],
- }),
- [alignment],
- );
return (
- {
- width.value = layout.width;
- height.value = layout.height;
- }}
- style={[styles.container, { backgroundColor: white_snow, minWidth: vw(65) }, showScreenStyle]}
- testID='message-action-list'
- >
+
{messageActions?.map((messageAction, index) => (
))}
-
+
);
};
-const areEqual = (
- prevProps: MessageActionListPropsWithContext,
- nextProps: MessageActionListPropsWithContext,
-) => {
- const { alignment: prevAlignment, messageActions: prevMessageActions } = prevProps;
- const { alignment: nextAlignment, messageActions: nextMessageActions } = nextProps;
-
- const messageActionsEqual = prevMessageActions?.length === nextMessageActions?.length;
- if (!messageActionsEqual) return false;
-
- const alignmentEqual = prevAlignment === nextAlignment;
- if (!alignmentEqual) return false;
-
- return true;
-};
-
-const MemoizedMessageActionList = React.memo(
- MessageActionListWithContext,
- areEqual,
-) as typeof MessageActionListWithContext;
-
-export type MessageActionListProps<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Partial, 'showScreen'>> &
- Pick<
- MessageActionListPropsWithContext,
- 'showScreen' | 'message' | 'isMyMessage' | 'error' | 'isThreadMessage' | 'messageReactions'
- >;
-
-/**
- * MessageActionList - A high level component which implements all the logic required for MessageActions
- */
-export const MessageActionList = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: MessageActionListProps,
-) => {
- const { data } = useMessageOverlayContext();
-
- const { alignment, messageActions } = data || {};
-
- return ;
-};
-
const styles = StyleSheet.create({
- bottomBorder: {
- borderBottomWidth: 1,
- },
container: {
- borderRadius: 16,
- marginTop: 8,
- overflow: 'hidden',
- },
- titleStyle: {
- paddingLeft: 20,
+ flex: 1,
+ paddingHorizontal: 16,
},
});
diff --git a/package/src/components/MessageOverlay/MessageActionListItem.tsx b/package/src/components/MessageOverlay/MessageActionListItem.tsx
index 4ffe14ad74..b781e7c386 100644
--- a/package/src/components/MessageOverlay/MessageActionListItem.tsx
+++ b/package/src/components/MessageOverlay/MessageActionListItem.tsx
@@ -1,12 +1,7 @@
import React from 'react';
-import { StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native';
-import { Gesture, GestureDetector } from 'react-native-gesture-handler';
-import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
+import { Pressable, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
-import { useViewport } from '../../hooks/useViewport';
-import type { DefaultStreamChatGenerics } from '../../types/types';
-import type { MessageOverlayPropsWithContext } from '../MessageOverlay/MessageOverlay';
export type ActionType =
| 'banUser'
@@ -48,110 +43,41 @@ export type MessageActionType = {
titleStyle?: StyleProp;
};
-export type MessageActionListItemProps<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = MessageActionType &
- Pick<
- MessageOverlayPropsWithContext,
- 'error' | 'isMyMessage' | 'isThreadMessage' | 'message' | 'messageReactions'
- > & {
- index: number;
- length: number;
- };
+/**
+ * MessageActionListItem - A high-level component that implements all the logic required for a `MessageAction` in a `MessageActionList`
+ */
+export type MessageActionListItemProps = MessageActionType;
-const MessageActionListItemWithContext = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: MessageActionListItemProps,
-) => {
- const { action, actionType, icon, index, length, title, titleStyle } = props;
- const { vw } = useViewport();
- const opacity = useSharedValue(1);
- const activeOpacity = 0.2;
+export const MessageActionListItem = (props: MessageActionListItemProps) => {
+ const { action, actionType, icon, title, titleStyle } = props;
const {
theme: {
- colors: { black, border },
- overlay: { messageActions },
+ colors: { black },
+ overlay: {
+ messageActions: { actionContainer, icon: iconTheme, title: titleTheme },
+ },
},
} = useTheme();
- const animatedStyle = useAnimatedStyle(() => ({
- opacity: opacity.value,
- }));
-
- const tap = Gesture.Tap()
- .onStart(() => {
- opacity.value = activeOpacity;
- })
- .onFinalize(() => {
- opacity.value = 1;
- })
- .onEnd(() => {
- runOnJS(action)();
- });
-
return (
-
-
- {icon}
-
- {title}
-
-
-
+
+
+ {icon}
+ {title}
+
+
);
};
-const messageActionIsEqual = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- prevProps: MessageActionListItemProps,
- nextProps: MessageActionListItemProps,
-) => prevProps.length === nextProps.length;
-
-export const MemoizedMessageActionListItem = React.memo(
- MessageActionListItemWithContext,
- messageActionIsEqual,
-) as typeof MessageActionListItemWithContext;
-
-/**
- * MessageActionListItem - A high-level component that implements all the logic required for a `MessageAction` in a `MessageActionList`
- */
-export const MessageActionListItem = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: MessageActionListItemProps,
-) => ;
-
const styles = StyleSheet.create({
- bottomBorder: {
- borderBottomWidth: 1,
- },
container: {
- borderRadius: 16,
- marginTop: 8,
- maxWidth: 275,
- },
- row: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'flex-start',
- paddingHorizontal: 20,
- paddingVertical: 10,
+ paddingVertical: 8,
},
titleStyle: {
- paddingLeft: 20,
+ paddingLeft: 16,
},
});
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index 74e5f62e72..65bfab5d5e 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -1,615 +1,133 @@
-import React, { useEffect, useMemo, useState } from 'react';
-import { Keyboard, Platform, SafeAreaView, StyleSheet, View, ViewStyle } from 'react-native';
-import { Gesture, GestureDetector, ScrollView } from 'react-native-gesture-handler';
-import Animated, {
- cancelAnimation,
- Easing,
- Extrapolation,
- interpolate,
- runOnJS,
- SharedValue,
- useAnimatedStyle,
- useSharedValue,
- withDecay,
- withSpring,
- withTiming,
-} from 'react-native-reanimated';
+import React, { useCallback, useMemo } from 'react';
+import { StyleSheet } from 'react-native';
-import { MessageActionList as DefaultMessageActionList } from './MessageActionList';
-import { OverlayReactionList as OverlayReactionListDefault } from './OverlayReactionList';
-import { OverlayReactionsAvatar as OverlayReactionsAvatarDefault } from './OverlayReactionsAvatar';
+import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
+import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
-import { ChatProvider } from '../../contexts/chatContext/ChatContext';
-import { MessageProvider } from '../../contexts/messageContext/MessageContext';
-import {
- MessageOverlayContextValue,
- MessageOverlayData,
- useMessageOverlayContext,
-} from '../../contexts/messageOverlayContext/MessageOverlayContext';
+import { MessageActionType } from './MessageActionListItem';
-import { MessagesProvider } from '../../contexts/messagesContext/MessagesContext';
+import { useMessageContext } from '../../contexts/messageContext/MessageContext';
import {
- OverlayContextValue,
- OverlayProviderProps,
- useOverlayContext,
-} from '../../contexts/overlayContext/OverlayContext';
-import { mergeThemes, ThemeProvider, useTheme } from '../../contexts/themeContext/ThemeContext';
-
-import { useViewport } from '../../hooks/useViewport';
-import type { DefaultStreamChatGenerics } from '../../types/types';
-import { MessageTextContainer } from '../Message/MessageSimple/MessageTextContainer';
-import { OverlayReactions as DefaultOverlayReactions } from '../MessageOverlay/OverlayReactions';
-import type { ReplyProps } from '../Reply/Reply';
-
-const styles = StyleSheet.create({
- alignEnd: { alignItems: 'flex-end' },
- alignStart: { alignItems: 'flex-start' },
- center: {
- flexGrow: 1,
- justifyContent: 'center',
- },
- containerInner: {
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- borderWidth: 1,
- overflow: 'hidden',
- },
- flex: {
- flex: 1,
- },
- overlayPadding: {
- padding: 8,
- },
- replyContainer: {
- flexDirection: 'row',
- paddingHorizontal: 8,
- paddingTop: 8,
- },
- row: { flexDirection: 'row' },
- scrollView: { overflow: Platform.OS === 'ios' ? 'visible' : 'scroll' },
-});
+ MessagesContextValue,
+ useMessagesContext,
+} from '../../contexts/messagesContext/MessagesContext';
+import { OverlayProviderProps } from '../../contexts/overlayContext/OverlayContext';
+import { DefaultStreamChatGenerics } from '../../types/types';
-const DefaultMessageTextNumberOfLines = 5;
-
-export type MessageOverlayPropsWithContext<
+export type MessageOverlayProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Pick<
- MessageOverlayContextValue,
- | 'MessageActionList'
- | 'MessageActionListItem'
- | 'OverlayReactionList'
- | 'OverlayReactions'
- | 'OverlayReactionsAvatar'
-> &
- Omit, 'supportedReactions'> &
- Pick &
+> = Partial<
Pick<
- OverlayProviderProps,
- | 'error'
- | 'isMyMessage'
- | 'isThreadMessage'
- | 'message'
- | 'messageReactions'
- | 'messageTextNumberOfLines'
+ MessagesContextValue,
+ | 'MessageActionList'
+ | 'MessageActionListItem'
+ | 'OverlayReactionList'
+ | 'OverlayReactions'
+ | 'OverlayReactionsAvatar'
+ | 'OverlayReactionsItem'
+ >
+> &
+ Partial<
+ Pick, 'isMyMessage' | 'isThreadMessage' | 'message'>
> & {
- overlayOpacity: SharedValue;
- showScreen?: SharedValue;
+ closeMessageActionsBottomSheet: () => void;
+ isErrorInMessage: boolean;
+ isMessageActionsVisible: boolean;
+ messageActions: MessageActionType[];
+ messageActionsBottomSheetRef: React.RefObject;
+ handleReaction?: (reactionType: string) => Promise;
};
-const MessageOverlayWithContext = <
+export const MessageOverlay = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
>(
- props: MessageOverlayPropsWithContext,
+ props: MessageOverlayProps,
) => {
const {
- alignment,
- chatContext,
- clientId,
- error,
- files,
- groupStyles,
+ closeMessageActionsBottomSheet,
handleReaction,
- images,
- isMyMessage,
+ isErrorInMessage,
+ isMessageActionsVisible,
+ isMyMessage: propIsMyMessage,
isThreadMessage,
message,
- MessageActionList = DefaultMessageActionList,
- MessageActionListItem,
+ MessageActionList: propMessageActionList,
+ MessageActionListItem: propMessageActionListItem,
messageActions,
- messageContext,
- messageReactions,
- messageReactionTitle,
- messagesContext,
- messageTextNumberOfLines = DefaultMessageTextNumberOfLines,
- onlyEmojis,
- otherAttachments,
- overlay,
- overlayOpacity,
- OverlayReactionList = OverlayReactionListDefault,
- OverlayReactions = DefaultOverlayReactions,
- OverlayReactionsAvatar = OverlayReactionsAvatarDefault,
- ownCapabilities,
- setOverlay,
- threadList,
- videos,
+ messageActionsBottomSheetRef,
+ OverlayReactionList: propOverlayReactionList,
+ OverlayReactions: propOverlayReactions,
+ OverlayReactionsAvatar: propOverlayReactionsAvatar,
+ OverlayReactionsItem: propOverlayReactionsItem,
} = props;
+ const {
+ MessageActionList: contextMessageActionList,
+ MessageActionListItem: contextMessageActionListItem,
+ OverlayReactionList: contextOverlayReactionList,
+ OverlayReactions: contextOverlayReactions,
+ OverlayReactionsAvatar: contextOverlayReactionsAvatar,
+ OverlayReactionsItem: contextOverlayReactionsItem,
+ } = useMessagesContext();
+ const { isMyMessage: contextIsMyMessage } = useMessageContext();
+ const snapPoints = useMemo(() => ['50%', '50%'], []);
+ const isMyMessage = propIsMyMessage ?? contextIsMyMessage;
+ const MessageActionList = propMessageActionList ?? contextMessageActionList;
+ const MessageActionListItem = propMessageActionListItem ?? contextMessageActionListItem;
+ const OverlayReactionList = propOverlayReactionList ?? contextOverlayReactionList;
+ const OverlayReactions = propOverlayReactions ?? contextOverlayReactions;
+ const OverlayReactionsAvatar = propOverlayReactionsAvatar ?? contextOverlayReactionsAvatar;
+ const OverlayReactionsItem = propOverlayReactionsItem ?? contextOverlayReactionsItem;
+
+ const handleSheetChanges = useCallback((index: number) => {
+ console.log('handleSheetChanges', index);
+ }, []);
const messageActionProps = {
- error,
+ error: isErrorInMessage,
isMyMessage,
isThreadMessage,
message,
- messageReactions,
- };
-
- const { theme } = useTheme();
- const { vh, vw } = useViewport();
-
- const screenHeight = vh(100);
- const halfScreenHeight = vh(50);
-
- const myMessageTheme = messagesContext?.myMessageTheme;
- const wrapMessageInTheme = clientId === message?.user?.id && !!myMessageTheme;
-
- const [reactionListHeight, setReactionListHeight] = useState(0);
-
- const myMessageThemeString = useMemo(() => JSON.stringify(myMessageTheme), [myMessageTheme]);
-
- const modifiedTheme = useMemo(
- () => mergeThemes({ style: myMessageTheme, theme }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [myMessageThemeString, theme],
- );
-
- const {
- colors: { blue_alice, grey_gainsboro, grey_whisper, transparent, white_smoke },
- messageSimple: {
- content: {
- container: { borderRadiusL, borderRadiusS },
- containerInner,
- replyContainer,
- },
- },
- overlay: { container: containerStyle, padding: overlayPadding },
- } = wrapMessageInTheme ? modifiedTheme : theme;
-
- const messageHeight = useSharedValue(0);
- const messageLayout = useSharedValue({ x: 0, y: 0 });
- const messageWidth = useSharedValue(0);
-
- const offsetY = useSharedValue(0);
- const translateY = useSharedValue(0);
- const scale = useSharedValue(1);
-
- const showScreen = useSharedValue(0);
- const fadeScreen = () => {
- 'worklet';
-
- offsetY.value = 0;
- translateY.value = 0;
- scale.value = 1;
- showScreen.value = withSpring(1, {
- damping: 600,
- mass: 0.5,
- restDisplacementThreshold: 0.01,
- restSpeedThreshold: 0.01,
- stiffness: 200,
- velocity: 32,
- });
+ messageActions,
};
- useEffect(() => {
- Keyboard.dismiss();
- fadeScreen();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- const pan = Gesture.Pan()
- .enabled(overlay === 'message')
- .maxPointers(1)
- .minDistance(10)
- .onBegin(() => {
- cancelAnimation(translateY);
- offsetY.value = translateY.value;
- })
- .onChange((event) => {
- translateY.value = offsetY.value + event.translationY;
- overlayOpacity.value = interpolate(
- translateY.value,
- [0, halfScreenHeight],
- [1, 0.75],
- Extrapolation.CLAMP,
- );
- scale.value = interpolate(
- translateY.value,
- [0, halfScreenHeight],
- [1, 0.85],
- Extrapolation.CLAMP,
- );
- })
- .onEnd((event) => {
- const finalYPosition = event.translationY + event.velocityY * 0.1;
-
- if (finalYPosition > halfScreenHeight && translateY.value > 0) {
- cancelAnimation(translateY);
- overlayOpacity.value = withTiming(
- 0,
- {
- duration: 200,
- easing: Easing.out(Easing.ease),
- },
- () => {
- runOnJS(setOverlay)('none');
- },
- );
- translateY.value =
- event.velocityY > 1000
- ? withDecay({
- velocity: event.velocityY,
- })
- : withTiming(screenHeight, {
- duration: 200,
- easing: Easing.out(Easing.ease),
- });
- } else {
- translateY.value = withTiming(0);
- scale.value = withTiming(1);
- overlayOpacity.value = withTiming(1);
- }
- });
-
- const tap = Gesture.Tap()
- .maxDistance(32)
- .onEnd(() => {
- runOnJS(setOverlay)('none');
- });
-
- const panStyle = useAnimatedStyle(() => ({
- transform: [
- {
- translateY: translateY.value,
- },
- {
- scale: scale.value,
- },
- ],
- }));
-
- const showScreenStyle = useAnimatedStyle(
- () => ({
- transform: [
- {
- translateY: interpolate(showScreen.value, [0, 1], [messageHeight.value / 2, 0]),
- },
- {
- translateX: interpolate(
- showScreen.value,
- [0, 1],
- [alignment === 'left' ? -messageWidth.value / 2 : messageWidth.value / 2, 0],
- ),
- },
- {
- scale: showScreen.value,
- },
- ],
- }),
- [alignment],
- );
-
- const groupStyle = `${alignment}_${(groupStyles?.[0] || 'bottom').toLowerCase()}`;
-
- const hasThreadReplies = !!message?.reply_count;
-
- const { Attachment, FileAttachmentGroup, Gallery, MessageAvatar, Reply } = messagesContext || {};
-
- const renderContent = (messageTextNumberOfLines?: number) => (
-
-
- {message && (
-
- {handleReaction && ownCapabilities?.sendReaction ? (
- reaction.type) || []}
- setReactionListHeight={setReactionListHeight}
- showScreen={showScreen}
- />
- ) : null}
- {
- messageLayout.value = {
- x: alignment === 'left' ? x + layoutWidth : x,
- y,
- };
- messageWidth.value = layoutWidth;
- messageHeight.value = layoutHeight;
- }}
- style={[styles.alignEnd, styles.row, showScreenStyle]}
- >
- {alignment === 'left' && MessageAvatar && (
-
- )}
-
- {messagesContext?.messageContentOrder?.map(
- (messageContentType, messageContentOrderIndex) => {
- switch (messageContentType) {
- case 'quoted_reply':
- return (
- message.quoted_message &&
- Reply && (
-
- ['quotedMessage']
- }
- styles={{
- messageContainer: {
- maxWidth: vw(60),
- },
- }}
- />
-
- )
- );
- case 'attachments':
- return otherAttachments?.map(
- (attachment, attachmentIndex) =>
- Attachment && (
-
- ),
- );
- case 'files':
- return (
- FileAttachmentGroup && (
-
- )
- );
- case 'gallery':
- return (
- Gallery && (
-
- )
- );
- case 'text':
- default:
- return otherAttachments?.length && otherAttachments[0].actions ? null : (
-
- key={`message_text_container_${messageContentOrderIndex}`}
- message={message}
- messageOverlay
- messageTextNumberOfLines={messageTextNumberOfLines}
- onlyEmojis={onlyEmojis}
- />
- );
- }
- },
- )}
-
-
- {messageActions && (
+ return (
+
+
+ {isMessageActionsVisible ? (
+ <>
+ reaction.type) || []}
+ />
+ {messageActions?.length ? (
- )}
- {!!messageReactionTitle && (
-
- )}
-
+ ) : null}
+ >
+ ) : (
+
)}
-
-
- );
-
- // Scroll will only be enabled for message overlay when we show actions.
- // When we show the reactions, we don't want to enable scroll since OverlayReactions component
- // in itself is scrollable (FlatList). FlatList inside a ScrollView is not a good idea and results in error from RN.
- const isScrollEnabled = !!messageActions && overlay === 'message';
-
- return (
-
-
-
-
-
-
-
-
- {isScrollEnabled ? (
-
- {renderContent()}
-
- ) : (
- renderContent(messageTextNumberOfLines)
- )}
-
-
-
-
-
-
-
-
+
+
);
};
-const areEqual = (
- prevProps: MessageOverlayPropsWithContext,
- nextProps: MessageOverlayPropsWithContext,
-) => {
- const {
- alignment: prevAlignment,
- message: prevMessage,
- messageReactionTitle: prevMessageReactionTitle,
- messagesContext: prevMessagesContext,
- } = prevProps;
- const {
- alignment: nextAlignment,
- message: nextMessage,
- messageReactionTitle: nextMessageReactionTitle,
- messagesContext: nextMessagesContext,
- } = nextProps;
-
- const alignmentEqual = prevAlignment === nextAlignment;
- if (!alignmentEqual) return false;
-
- const messageReactionTitleEqual = prevMessageReactionTitle === nextMessageReactionTitle;
- if (!messageReactionTitleEqual) return false;
-
- const prevMyMessageTheme = JSON.stringify(prevMessagesContext?.myMessageTheme);
- const nextMyMessageTheme = JSON.stringify(nextMessagesContext?.myMessageTheme);
-
- const myMessageThemeEqual = prevMyMessageTheme === nextMyMessageTheme;
- if (!myMessageThemeEqual) return false;
-
- const latestReactionsEqual =
- Array.isArray(prevMessage?.latest_reactions) && Array.isArray(nextMessage?.latest_reactions)
- ? prevMessage?.latest_reactions.length === nextMessage?.latest_reactions.length &&
- prevMessage?.latest_reactions.every(
- ({ type }, index) => type === nextMessage?.latest_reactions?.[index].type,
- )
- : prevMessage?.latest_reactions === nextMessage?.latest_reactions;
- if (!latestReactionsEqual) return false;
-
- return true;
-};
-
-const MemoizedMessageOverlay = React.memo(
- MessageOverlayWithContext,
- areEqual,
-) as typeof MessageOverlayWithContext;
-
-export type MessageOverlayProps<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Partial, 'overlayOpacity'>> &
- Pick, 'overlayOpacity'> &
- Pick<
- MessageOverlayPropsWithContext,
- 'isMyMessage' | 'error' | 'isThreadMessage' | 'message' | 'messageReactions'
- >;
-
-/**
- * MessageOverlay - A high level component which implements all the logic required for a message overlay
- */
-export const MessageOverlay = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: MessageOverlayProps,
-) => {
- const {
- data,
- MessageActionList,
- MessageActionListItem,
- OverlayReactionList,
- OverlayReactions,
- OverlayReactionsAvatar,
- } = useMessageOverlayContext();
- const { overlay, setOverlay } = useOverlayContext();
-
- const componentProps = {
- MessageActionList: props.MessageActionList || MessageActionList,
- MessageActionListItem: props.MessageActionListItem || MessageActionListItem,
- OverlayReactionList:
- props.OverlayReactionList || OverlayReactionList || data?.OverlayReactionList,
- OverlayReactions: props.OverlayReactions || OverlayReactions,
- OverlayReactionsAvatar: props.OverlayReactionsAvatar || OverlayReactionsAvatar,
- };
-
- return (
-
- );
-};
+const styles = StyleSheet.create({
+ contentContainer: {
+ flex: 1,
+ },
+});
diff --git a/package/src/components/MessageOverlay/OverlayBackdrop.tsx b/package/src/components/MessageOverlay/OverlayBackdrop.tsx
deleted file mode 100644
index b574c2adb8..0000000000
--- a/package/src/components/MessageOverlay/OverlayBackdrop.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { StyleProp, View, ViewStyle } from 'react-native';
-
-import { useTheme } from '../../contexts/themeContext/ThemeContext';
-
-type OverlayBackdropProps = {
- style?: StyleProp;
-};
-
-export const OverlayBackdrop = (props: OverlayBackdropProps): JSX.Element => {
- const { style = {} } = props;
- const {
- theme: {
- colors: { overlay },
- },
- } = useTheme();
- return ;
-};
diff --git a/package/src/components/MessageOverlay/OverlayReactionList.tsx b/package/src/components/MessageOverlay/OverlayReactionList.tsx
index c9aedc7400..3ac8e54038 100644
--- a/package/src/components/MessageOverlay/OverlayReactionList.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionList.tsx
@@ -1,435 +1,81 @@
import React from 'react';
-import { StyleSheet, useWindowDimensions, View, ViewStyle } from 'react-native';
-import { Gesture, GestureDetector } from 'react-native-gesture-handler';
-import Animated, {
- cancelAnimation,
- interpolate,
- runOnJS,
- SharedValue,
- useAnimatedReaction,
- useAnimatedStyle,
- useSharedValue,
- withDelay,
- withSequence,
- withTiming,
-} from 'react-native-reanimated';
+import { StyleSheet, View } from 'react-native';
import { FillProps } from 'react-native-svg';
-import {
- MessageOverlayData,
- useMessageOverlayContext,
-} from '../../contexts/messageOverlayContext/MessageOverlayContext';
+import { ReactionButton } from './ReactionButton';
+
import {
MessagesContextValue,
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
-import {
- OverlayContextValue,
- useOverlayContext,
-} from '../../contexts/overlayContext/OverlayContext';
-import { useTheme } from '../../contexts/themeContext/ThemeContext';
-import {
- IconProps,
- LOLReaction,
- LoveReaction,
- ThumbsDownReaction,
- ThumbsUpReaction,
- WutReaction,
-} from '../../icons';
+import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { triggerHaptic } from '../../native';
-
import type { DefaultStreamChatGenerics } from '../../types/types';
-import type { ReactionData } from '../../utils/utils';
-const styles = StyleSheet.create({
- notLastReaction: {
- marginRight: 16,
- },
- reactionList: {
- alignItems: 'center',
- borderRadius: 24,
- flexDirection: 'row',
- justifyContent: 'center',
- paddingHorizontal: 16,
- paddingVertical: 12,
- position: 'absolute',
- },
- selectedIcon: {
- position: 'absolute',
- },
-});
-
-const reactionData: ReactionData[] = [
- {
- Icon: LoveReaction,
- type: 'love',
- },
- {
- Icon: ThumbsUpReaction,
- type: 'like',
- },
- {
- Icon: ThumbsDownReaction,
- type: 'sad',
- },
- {
- Icon: LOLReaction,
- type: 'haha',
- },
- {
- Icon: WutReaction,
- type: 'wow',
- },
-];
-
-type ReactionButtonProps<
+export type OverlayReactionListProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Pick<
- OverlayReactionListPropsWithContext,
- 'ownReactionTypes' | 'handleReaction' | 'setOverlay'
-> & {
- Icon: React.ComponentType;
- index: number;
- numberOfReactions: number;
- showScreen: SharedValue;
- type: string;
+> = Pick, 'supportedReactions'> & {
+ dismissOverlay: () => void;
+ ownReactionTypes: string[];
+ fill?: FillProps['fill'];
+ handleReaction?: (reactionType: string) => Promise;
};
-export const ReactionButton = <
+/**
+ * OverlayReactionList - A high level component which implements all the logic required for a message overlay reaction list
+ */
+export const OverlayReactionList = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
>(
- props: ReactionButtonProps,
+ props: OverlayReactionListProps,
) => {
const {
+ dismissOverlay,
handleReaction,
- Icon,
- index,
- numberOfReactions,
ownReactionTypes,
- setOverlay,
- showScreen,
- type,
+ supportedReactions: propSupportedReactions,
} = props;
+ const { supportedReactions: contextSupportedReactions } = useMessagesContext();
const {
theme: {
- colors: { accent_blue, grey },
overlay: {
- reactionsList: { reaction, reactionSize },
+ reactionsList: { container },
},
},
} = useTheme();
- const selected = ownReactionTypes.includes(type);
- const animationScale = useSharedValue(0);
- const hasShown = useSharedValue(0);
- const scale = useSharedValue(1);
- const selectedOpacity = useSharedValue(selected ? 1 : 0);
- const tap = Gesture.Tap()
- .hitSlop({
- bottom:
- Number(reaction.paddingVertical || 0) ||
- Number(reaction.paddingBottom || 0) ||
- styles.reactionList.paddingVertical,
- left:
- (Number(reaction.paddingHorizontal || 0) ||
- Number(reaction.paddingLeft || 0) ||
- styles.notLastReaction.marginRight) / 2,
- right:
- (Number(reaction.paddingHorizontal || 0) ||
- Number(reaction.paddingRight || 0) ||
- styles.notLastReaction.marginRight) / 2,
- top:
- Number(reaction.paddingVertical || 0) ||
- Number(reaction.paddingTop || 0) ||
- styles.reactionList.paddingVertical,
- })
- .maxDuration(3000)
- .onStart(() => {
- cancelAnimation(scale);
- scale.value = withTiming(1.5, { duration: 100 });
- })
- .onEnd(() => {
- runOnJS(triggerHaptic)('impactLight');
- selectedOpacity.value = withTiming(selected ? 0 : 1, { duration: 250 }, () => {
- if (handleReaction) {
- runOnJS(handleReaction)(type);
- }
- runOnJS(setOverlay)('none');
- });
- })
- .onFinalize(() => {
- cancelAnimation(scale);
- scale.value = withTiming(1, { duration: 100 });
- });
-
- useAnimatedReaction(
- () => {
- if (showScreen.value > 0.8 && hasShown.value === 0) {
- return 1;
- }
- return 0;
- },
- (result) => {
- if (hasShown.value === 0 && result !== 0) {
- hasShown.value = 1;
- animationScale.value = withSequence(
- withDelay(60 * (numberOfReactions - (index + 1)), withTiming(0.1, { duration: 50 })),
- withTiming(1.5, { duration: 250 }),
- withTiming(1, { duration: 250 }),
- );
- }
- },
- [index, numberOfReactions],
- );
-
- const iconStyle = useAnimatedStyle(
- () => ({
- transform: [
- {
- scale: animationScale.value,
- },
- {
- scale: scale.value,
- },
- ],
- }),
- [],
- );
-
- const selectedStyle = useAnimatedStyle(() => ({
- opacity: selectedOpacity.value,
- }));
- return (
-
-
-
-
-
-
-
-
- );
-};
+ const supportedReactions = propSupportedReactions || contextSupportedReactions;
-export type OverlayReactionListPropsWithContext<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Pick<
- MessageOverlayData,
- 'alignment' | 'handleReaction' | 'messagesContext'
-> &
- Pick, 'supportedReactions'> &
- Pick & {
- messageLayout: SharedValue<{
- x: number;
- y: number;
- }>;
- ownReactionTypes: string[];
- setReactionListHeight: React.Dispatch>;
- showScreen: SharedValue;
- fill?: FillProps['fill'];
+ const onSelectReaction = (type: string) => {
+ triggerHaptic('impactLight');
+ if (handleReaction) {
+ handleReaction(type);
+ }
+ dismissOverlay();
};
-const OverlayReactionListWithContext = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: OverlayReactionListPropsWithContext,
-) => {
- const {
- alignment,
- fill,
- handleReaction,
- messageLayout,
- ownReactionTypes,
- setOverlay,
- setReactionListHeight,
- showScreen,
- supportedReactions = reactionData,
- } = props;
-
- const {
- theme: {
- colors: { white_snow },
- overlay: {
- padding: screenPadding,
- reactionsList: { radius, reactionList, reactionListBorderRadius },
- },
- },
- } = useTheme();
-
- const reactionListHeight = useSharedValue(0);
- const reactionBubbleWidth = useSharedValue(0);
- const reactionListLayout = useSharedValue({
- height: 0,
- width: 0,
- });
-
- const { width } = useWindowDimensions();
-
- const animatedStyle = useAnimatedStyle(() => {
- const borderRadius = reactionListBorderRadius || styles.reactionList.borderRadius;
- const insideLeftBound =
- messageLayout.value.x - reactionListLayout.value.width + borderRadius > screenPadding;
- const insideRightBound = messageLayout.value.x + borderRadius < width - screenPadding;
- const left = !insideLeftBound
- ? screenPadding
- : !insideRightBound
- ? width - screenPadding - reactionListLayout.value.width
- : messageLayout.value.x - reactionListLayout.value.width + borderRadius;
- const top = messageLayout.value.y - reactionListLayout.value.height - radius * 2;
-
- return {
- left,
- top,
- };
- });
-
- const animatedBigCircleStyle = useAnimatedStyle(() => ({
- borderRadius: radius,
- height: radius * 2,
- left: messageLayout.value.x - radius * 3,
- top: messageLayout.value.y - radius * 3,
- width: radius * 2,
- }));
-
- const animatedSmallCircleStyle = useAnimatedStyle(() => ({
- borderRadius: radius / 2,
- height: radius,
- left: messageLayout.value.x - radius,
- top: messageLayout.value.y,
- width: radius,
- }));
-
- const showScreenStyle = useAnimatedStyle(
- () => ({
- transform: [
- {
- translateY: interpolate(showScreen.value, [0, 1], [-reactionListHeight.value / 2, 0]),
- },
- {
- translateX: interpolate(
- showScreen.value,
- [0, 1],
- [
- alignment === 'left' ? -reactionBubbleWidth.value / 2 : reactionBubbleWidth.value / 2,
- 0,
- ],
- ),
- },
- {
- scale: interpolate(showScreen.value, [0, 0.8, 1], [0, 0, 1]),
- },
- ],
- }),
- [alignment],
- );
-
- const numberOfReactions = supportedReactions.length;
-
return (
-
- {
- reactionBubbleWidth.value = layout.width;
- }}
- style={showScreenStyle}
- >
-
-
+ {supportedReactions?.map(({ Icon, type }, index) => (
+
-
- {
- reactionListLayout.value = { height, width: layoutWidth };
- reactionListHeight.value = height;
- setReactionListHeight(height);
- }}
- style={[
- styles.reactionList,
- { backgroundColor: white_snow },
- animatedStyle,
- reactionList,
- ]}
- >
- {supportedReactions?.map(({ Icon, type }, index) => (
-
- handleReaction={handleReaction}
- Icon={Icon}
- index={index}
- key={`${type}_${index}`}
- numberOfReactions={numberOfReactions}
- ownReactionTypes={ownReactionTypes}
- setOverlay={setOverlay}
- showScreen={showScreen}
- type={type}
- />
- ))}
-
-
+ ))}
);
};
-const areEqual = (
- prevProps: OverlayReactionListPropsWithContext,
- nextProps: OverlayReactionListPropsWithContext,
-) => {
- const { alignment: prevAlignment, ownReactionTypes: prevOwnReactionTypes } = prevProps;
- const { alignment: nextAlignment, ownReactionTypes: nextOwnReactionTypes } = nextProps;
-
- const alignmentEqual = prevAlignment === nextAlignment;
- if (!alignmentEqual) return false;
-
- const ownReactionTypesEqual = prevOwnReactionTypes.length === nextOwnReactionTypes.length;
- if (!ownReactionTypesEqual) return false;
-
- return true;
-};
-
-const MemoizedOverlayReactionList = React.memo(
- OverlayReactionListWithContext,
- areEqual,
-) as typeof OverlayReactionListWithContext;
-
-export type OverlayReactionListProps<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Omit<
- OverlayReactionListPropsWithContext,
- 'setOverlay' | 'supportedReactions'
-> &
- Partial<
- Pick<
- OverlayReactionListPropsWithContext,
- 'setOverlay' | 'supportedReactions'
- >
- >;
-
-/**
- * OverlayReactionList - A high level component which implements all the logic required for a message overlay reaction list
- */
-export const OverlayReactionList = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: OverlayReactionListProps,
-) => {
- const { data } = useMessageOverlayContext();
- const { supportedReactions } = useMessagesContext();
- const { setOverlay } = useOverlayContext();
-
- return (
-
- );
-};
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingHorizontal: 16,
+ },
+});
OverlayReactionList.displayName = 'OverlayReactionList{overlay{reactionList}}';
diff --git a/package/src/components/MessageOverlay/OverlayReactions.tsx b/package/src/components/MessageOverlay/OverlayReactions.tsx
index 982420154e..6a6901a77e 100644
--- a/package/src/components/MessageOverlay/OverlayReactions.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactions.tsx
@@ -1,138 +1,94 @@
import React, { useMemo } from 'react';
-import { StyleSheet, Text, useWindowDimensions, View, ViewStyle } from 'react-native';
-import { FlatList } from 'react-native-gesture-handler';
-import Animated, {
- interpolate,
- SharedValue,
- useAnimatedStyle,
- useSharedValue,
-} from 'react-native-reanimated';
+import { FlatList, StyleSheet, Text, View } from 'react-native';
import { ReactionSortBase } from 'stream-chat';
import { useFetchReactions } from './hooks/useFetchReactions';
+import { ReactionButton } from './ReactionButton';
-import { OverlayReactionsItem } from './OverlayReactionsItem';
-
-import type { Alignment } from '../../contexts/messageContext/MessageContext';
-import type { MessageOverlayContextValue } from '../../contexts/messageOverlayContext/MessageOverlayContext';
-import { useTheme } from '../../contexts/themeContext/ThemeContext';
import {
- LOLReaction,
- LoveReaction,
- ThumbsDownReaction,
- ThumbsUpReaction,
- WutReaction,
-} from '../../icons';
-
-import type { DefaultStreamChatGenerics } from '../../types/types';
-import type { ReactionData } from '../../utils/utils';
-
-const styles = StyleSheet.create({
- avatarContainer: {
- padding: 8,
- },
- container: {
- alignItems: 'center',
- borderRadius: 16,
- marginTop: 8,
- width: '100%',
- },
- flatListContainer: {
- paddingHorizontal: 12,
- paddingVertical: 8,
- },
- flatListContentContainer: {
- alignItems: 'center',
- paddingBottom: 12,
- },
- title: {
- fontSize: 16,
- fontWeight: '700',
- paddingTop: 16,
- },
- unseenItemContainer: {
- opacity: 0,
- position: 'absolute',
- },
-});
-
-const reactionData: ReactionData[] = [
- {
- Icon: LoveReaction,
- type: 'love',
- },
- {
- Icon: ThumbsUpReaction,
- type: 'like',
- },
- {
- Icon: ThumbsDownReaction,
- type: 'sad',
- },
- {
- Icon: LOLReaction,
- type: 'haha',
- },
- {
- Icon: WutReaction,
- type: 'wow',
- },
-];
-
-export type Reaction = {
- alignment: Alignment;
- id: string;
- name: string;
- type: string;
- image?: string;
-};
+ MessagesContextValue,
+ useMessagesContext,
+} from '../../contexts/messagesContext/MessagesContext';
+import { useTheme } from '../../contexts/themeContext/ThemeContext';
+import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
+import { DefaultStreamChatGenerics, Reaction } from '../../types/types';
+import { ReactionData } from '../../utils/utils';
+import { MessageType } from '../MessageList/hooks/useMessageList';
export type OverlayReactionsProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Pick, 'OverlayReactionsAvatar'> & {
- showScreen: SharedValue;
- title: string;
- alignment?: Alignment;
- messageId?: string;
+> = Pick<
+ MessagesContextValue,
+ 'OverlayReactionsAvatar' | 'OverlayReactionsItem' | 'supportedReactions'
+> & {
+ message?: MessageType;
reactions?: Reaction[];
- supportedReactions?: ReactionData[];
};
const sort: ReactionSortBase = {
created_at: -1,
};
-/**
- * OverlayReactions - A high level component which implements all the logic required for message overlay reactions
- */
export const OverlayReactions = (props: OverlayReactionsProps) => {
- const [itemHeight, setItemHeight] = React.useState(0);
const {
- alignment: overlayAlignment,
- messageId,
- OverlayReactionsAvatar,
+ message,
+ OverlayReactionsAvatar: propOverlayReactionsAvatar,
+ OverlayReactionsItem: propOverlayReactionsItem,
reactions: propReactions,
- showScreen,
- supportedReactions = reactionData,
- title,
+ supportedReactions: propSupportedReactions,
} = props;
- const layoutHeight = useSharedValue(0);
- const layoutWidth = useSharedValue(0);
+ const reactionTypes = Object.keys(message?.reaction_groups ?? {});
+ const [selectedReaction, setSelectedReaction] = React.useState(
+ reactionTypes[0],
+ );
+ const {
+ OverlayReactionsAvatar: contextOverlayReactionsAvatar,
+ OverlayReactionsItem: contextOverlayReactionsItem,
+ supportedReactions: contextSupportedReactions,
+ } = useMessagesContext();
+ const supportedReactions = propSupportedReactions ?? contextSupportedReactions;
+ const OverlayReactionsAvatar = propOverlayReactionsAvatar ?? contextOverlayReactionsAvatar;
+ const OverlayReactionsItem = propOverlayReactionsItem ?? contextOverlayReactionsItem;
+ const messageReactions = reactionTypes.reduce((acc, reaction) => {
+ const reactionData = supportedReactions?.find(
+ (supportedReaction) => supportedReaction.type === reaction,
+ );
+ if (reactionData) {
+ acc.push(reactionData);
+ }
+ return acc;
+ }, []);
+
const {
loading,
loadNextPage,
reactions: fetchedReactions,
} = useFetchReactions({
- messageId,
+ messageId: message?.id,
+ reactionType: selectedReaction,
sort,
});
+ const {
+ theme: {
+ overlay: {
+ reactions: {
+ container,
+ flatlistColumnContainer,
+ flatlistContainer,
+ reactionSelectorContainer,
+ reactionsText,
+ },
+ },
+ },
+ } = useTheme();
+ const { t } = useTranslationContext();
+
const reactions = useMemo(
() =>
propReactions ||
(fetchedReactions.map((reaction) => ({
- alignment: 'left',
id: reaction.user?.id,
image: reaction.user?.image,
name: reaction.user?.name,
@@ -141,115 +97,70 @@ export const OverlayReactions = (props: OverlayReactionsProps) => {
[propReactions, fetchedReactions],
);
- const {
- theme: {
- colors: { black, white },
- overlay: {
- padding: overlayPadding,
- reactions: { avatarContainer, avatarSize, container, flatListContainer, title: titleStyle },
- },
- },
- } = useTheme();
-
- const width = useWindowDimensions().width;
-
- const supportedReactionTypes = supportedReactions.map(
- (supportedReaction) => supportedReaction.type,
- );
-
- const filteredReactions = reactions.filter((reaction) =>
- supportedReactionTypes.includes(reaction.type),
- );
-
- const numColumns = Math.floor(
- (width -
- overlayPadding * 2 -
- ((Number(flatListContainer.paddingHorizontal || 0) ||
- styles.flatListContainer.paddingHorizontal) +
- (Number(avatarContainer.padding || 0) || styles.avatarContainer.padding)) *
- 2) /
- (avatarSize + (Number(avatarContainer.padding || 0) || styles.avatarContainer.padding) * 2),
- );
-
const renderItem = ({ item }: { item: Reaction }) => (
);
- const showScreenStyle = useAnimatedStyle(
- () => ({
- transform: [
- {
- translateY: interpolate(showScreen.value, [0, 1], [-layoutHeight.value / 2, 0]),
- },
- {
- translateX: interpolate(
- showScreen.value,
- [0, 1],
- [overlayAlignment === 'left' ? -layoutWidth.value / 2 : layoutWidth.value / 2, 0],
- ),
- },
- {
- scale: showScreen.value,
- },
- ],
- }),
- [overlayAlignment],
+ const renderHeader = () => (
+ {t('Message Reactions')}
);
+ const onSelectReaction = (reactionType: string) => {
+ setSelectedReaction(reactionType);
+ };
+
return (
- <>
- {
- layoutWidth.value = layout.width;
- layoutHeight.value = layout.height;
- }}
- style={[
- styles.container,
- { backgroundColor: white, opacity: itemHeight ? 1 : 0 },
- container,
- showScreenStyle,
- ]}
- >
- {title}
- {!loading && (
- `${name}${id}_${index}`}
- numColumns={numColumns}
- onEndReached={loadNextPage}
- renderItem={renderItem}
- scrollEnabled={filteredReactions.length / numColumns > 1}
- style={[
- styles.flatListContainer,
- flatListContainer,
- {
- // we show the item height plus a little extra to tease for scrolling if there are more than one row
- maxHeight:
- itemHeight + (filteredReactions.length / numColumns > 1 ? itemHeight / 4 : 8),
- },
- ]}
+
+
+ {messageReactions?.map(({ Icon, type }, index) => (
+
- )}
- {/* The below view is unseen by the user, we use it to compute the height that the item must be */}
- {!loading && (
- {
- setItemHeight(layout.height);
- }}
- style={[styles.unseenItemContainer, styles.flatListContentContainer]}
- >
- {renderItem({ item: filteredReactions[0] })}
-
- )}
-
- >
+ ))}
+
+
+ {!loading ? (
+ item.id}
+ ListHeaderComponent={renderHeader}
+ numColumns={4}
+ onEndReached={loadNextPage}
+ renderItem={renderItem}
+ />
+ ) : null}
+
);
};
-OverlayReactions.displayName = 'OverlayReactions{overlay{reactions}}';
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ flatListColumnContainer: {
+ justifyContent: 'space-evenly',
+ },
+ flatListContainer: {
+ justifyContent: 'center',
+ },
+ reactionSelectorContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-evenly',
+ },
+ reactionsText: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ marginVertical: 16,
+ textAlign: 'center',
+ },
+});
diff --git a/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx b/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
index 26e0a4ccc7..c1b92f6e91 100644
--- a/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
@@ -1,8 +1,7 @@
import React from 'react';
-import type { Reaction } from './OverlayReactions';
-
import { useTheme } from '../../contexts/themeContext/ThemeContext';
+import { Reaction } from '../../types/types';
import { Avatar, AvatarProps } from '../Avatar/Avatar';
export type OverlayReactionsAvatarProps = {
diff --git a/package/src/components/MessageOverlay/OverlayReactionsItem.tsx b/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
index cad971046f..21dc20f748 100644
--- a/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
@@ -1,23 +1,20 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
-import Svg, { Circle } from 'react-native-svg';
import { ReactionResponse } from 'stream-chat';
-import { Reaction } from './OverlayReactions';
-
import { useChatContext } from '../../contexts/chatContext/ChatContext';
-import type { MessageOverlayContextValue } from '../../contexts/messageOverlayContext/MessageOverlayContext';
+import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { Unknown } from '../../icons';
-import type { DefaultStreamChatGenerics } from '../../types/types';
+import type { DefaultStreamChatGenerics, Reaction } from '../../types/types';
import { ReactionData } from '../../utils/utils';
export type OverlayReactionsItemProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = Pick, 'OverlayReactionsAvatar'> & {
+> = Pick, 'OverlayReactionsAvatar'> & {
reaction: Reaction;
supportedReactions: ReactionData[];
};
@@ -45,14 +42,13 @@ export const OverlayReactionsItem = <
const { id, name, type } = reaction;
const {
theme: {
- colors: { accent_blue, black, grey_gainsboro, white },
+ colors: { accent_blue, black, grey, grey_gainsboro, white },
overlay: {
reactions: {
avatarContainer,
avatarName,
avatarSize,
radius,
- reactionBubble,
reactionBubbleBackground,
reactionBubbleBorderRadius,
},
@@ -60,7 +56,7 @@ export const OverlayReactionsItem = <
},
} = useTheme();
const { client } = useChatContext();
- const alignment = client.userID && client.userID === id ? 'right' : 'left';
+ const alignment = client.userID && client.userID === id ? 'left' : 'right';
const x = avatarSize / 2 - (avatarSize / (radius * 4)) * (alignment === 'left' ? 1 : -1);
const y = avatarSize - radius;
@@ -79,70 +75,25 @@ export const OverlayReactionsItem = <
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
@@ -156,7 +107,7 @@ export const OverlayReactionsItem = <
const styles = StyleSheet.create({
avatarContainer: {
- padding: 8,
+ marginBottom: 8,
},
avatarInnerContainer: {
alignSelf: 'center',
@@ -173,15 +124,11 @@ const styles = StyleSheet.create({
flexDirection: 'row',
flexGrow: 1,
},
- reactionBubble: {
- alignItems: 'center',
- borderRadius: 24,
- justifyContent: 'center',
- position: 'absolute',
- },
reactionBubbleBackground: {
+ alignItems: 'center',
borderRadius: 24,
height: 24,
+ justifyContent: 'center',
position: 'absolute',
width: 24,
},
diff --git a/package/src/components/MessageOverlay/ReactionButton.tsx b/package/src/components/MessageOverlay/ReactionButton.tsx
new file mode 100644
index 0000000000..8e545d0f11
--- /dev/null
+++ b/package/src/components/MessageOverlay/ReactionButton.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { Pressable, StyleSheet } from 'react-native';
+
+import { useTheme } from '../../contexts/themeContext/ThemeContext';
+import { IconProps } from '../../icons';
+
+type ReactionButtonProps = {
+ /**
+ * Icon to display for the reaction button
+ */
+ Icon: React.ComponentType;
+ /**
+ * Whether the reaction button is selected
+ */
+ selected: boolean;
+ /**
+ * The type of reaction
+ */
+ type: string;
+ /**
+ * Function to call when the reaction button is pressed
+ * @param reactionType
+ * @returns
+ */
+ onPress?: (reactionType: string) => void;
+};
+
+export const ReactionButton = (props: ReactionButtonProps) => {
+ const { Icon, onPress, selected, type } = props;
+ const {
+ theme: {
+ colors: { light_blue, white },
+ overlay: {
+ reactionButton: { filledColor, unfilledColor },
+ reactionsList: { buttonContainer, reactionIconSize },
+ },
+ },
+ } = useTheme();
+
+ const onPressHandler = () => {
+ if (onPress) {
+ onPress(type);
+ }
+ };
+
+ return (
+ [
+ styles.reactionButton,
+ { backgroundColor: pressed ? light_blue : white },
+ buttonContainer,
+ ]}
+ >
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ reactionButton: {
+ alignItems: 'center',
+ borderRadius: 8,
+ justifyContent: 'center',
+ padding: 8,
+ },
+});
diff --git a/package/src/components/MessageOverlay/hooks/useFetchReactions.ts b/package/src/components/MessageOverlay/hooks/useFetchReactions.ts
index 76eb848e73..898bf90954 100644
--- a/package/src/components/MessageOverlay/hooks/useFetchReactions.ts
+++ b/package/src/components/MessageOverlay/hooks/useFetchReactions.ts
@@ -80,6 +80,8 @@ export const useFetchReactions = <
}, [fetchReactions]);
useEffect(() => {
+ setReactions([]);
+ setNext(undefined);
fetchReactions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messageId, reactionType, sortString]);
diff --git a/package/src/components/index.ts b/package/src/components/index.ts
index abeb80568e..0492d71211 100644
--- a/package/src/components/index.ts
+++ b/package/src/components/index.ts
@@ -156,7 +156,6 @@ export * from './MessageOverlay/hooks/useMessageActionAnimation';
export * from './MessageOverlay/MessageActionList';
export * from './MessageOverlay/MessageActionListItem';
export * from './MessageOverlay/MessageOverlay';
-export * from './MessageOverlay/OverlayBackdrop';
export * from './MessageOverlay/OverlayReactions';
export * from './MessageOverlay/OverlayReactionsAvatar';
export * from './MessageOverlay/OverlayReactionList';
diff --git a/package/src/contexts/index.ts b/package/src/contexts/index.ts
index e5275fbc7d..24381db70b 100644
--- a/package/src/contexts/index.ts
+++ b/package/src/contexts/index.ts
@@ -9,7 +9,6 @@ export * from './messageContext/MessageContext';
export * from './messageInputContext/hooks/useCreateMessageInputContext';
export * from './messageInputContext/hooks/useMessageDetailsForState';
export * from './messageInputContext/MessageInputContext';
-export * from './messageOverlayContext/MessageOverlayContext';
export * from './messagesContext/MessagesContext';
export * from './paginatedMessageListContext/PaginatedMessageListContext';
export * from './overlayContext/OverlayContext';
diff --git a/package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx b/package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx
deleted file mode 100644
index b5d3d3aa07..0000000000
--- a/package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React, { PropsWithChildren, useContext } from 'react';
-
-import type { ImageProps } from 'react-native';
-
-import type { Attachment, TranslationLanguages } from 'stream-chat';
-
-import { useResettableState } from './hooks/useResettableState';
-
-import type { GroupType, MessageType } from '../../components/MessageList/hooks/useMessageList';
-import type { MessageActionListProps } from '../../components/MessageOverlay/MessageActionList';
-import type {
- MessageActionListItemProps,
- MessageActionType,
-} from '../../components/MessageOverlay/MessageActionListItem';
-import type { OverlayReactionListProps } from '../../components/MessageOverlay/OverlayReactionList';
-import type { OverlayReactionsProps } from '../../components/MessageOverlay/OverlayReactions';
-import type { OverlayReactionsAvatarProps } from '../../components/MessageOverlay/OverlayReactionsAvatar';
-import type { DefaultStreamChatGenerics, UnknownType } from '../../types/types';
-import type { ReactionData } from '../../utils/utils';
-import type { ChatContextValue } from '../chatContext/ChatContext';
-import type { Alignment, MessageContextValue } from '../messageContext/MessageContext';
-import type { MessagesContextValue } from '../messagesContext/MessagesContext';
-import type { OwnCapabilitiesContextValue } from '../ownCapabilitiesContext/OwnCapabilitiesContext';
-import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
-
-import { getDisplayName } from '../utils/getDisplayName';
-import { isTestEnvironment } from '../utils/isTestEnvironment';
-
-export type MessageOverlayData<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = {
- alignment?: Alignment;
- chatContext?: ChatContextValue;
- clientId?: string;
- files?: Attachment[];
- groupStyles?: GroupType[];
- handleReaction?: (reactionType: string) => Promise;
- ImageComponent?: React.ComponentType;
- images?: Attachment[];
- message?: MessageType;
- messageActions?: MessageActionType[];
- messageContext?: MessageContextValue;
- messageReactionTitle?: string;
- messagesContext?: MessagesContextValue;
- onlyEmojis?: boolean;
- otherAttachments?: Attachment[];
- OverlayReactionList?: React.ComponentType>;
- ownCapabilities?: OwnCapabilitiesContextValue;
- supportedReactions?: ReactionData[];
- threadList?: boolean;
- userLanguage?: TranslationLanguages;
- videos?: Attachment[];
-};
-
-export type MessageOverlayContextValue<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
-> = {
- /**
- * Custom UI component for rendering [message actions](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/2.png) in overlay.
- *
- * **Default** [MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActions.tsx)
- */
- MessageActionList: React.ComponentType>;
- MessageActionListItem: React.ComponentType>;
- /**
- * Custom UI component for rendering [reaction selector](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/2.png) in overlay (which shows up on long press on message).
- *
- * **Default** [OverlayReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/OverlayReactionList.tsx)
- */
- OverlayReactionList: React.ComponentType>;
- /**
- * Custom UI component for rendering [reactions list](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/2.png), in overlay (which shows up on long press on message).
- *
- * **Default** [OverlayReactions](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/OverlayReactions.tsx)
- */
- OverlayReactions: React.ComponentType>;
- OverlayReactionsAvatar: React.ComponentType;
- reset: () => void;
- setData: React.Dispatch>>;
- data?: MessageOverlayData;
-};
-
-export const MessageOverlayContext = React.createContext(
- DEFAULT_BASE_CONTEXT_VALUE as MessageOverlayContextValue,
-);
-
-export const MessageOverlayProvider = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->({
- children,
- value,
-}: PropsWithChildren<{
- value?: MessageOverlayContextValue;
-}>) => {
- const messageOverlayContext = useResettableState(value);
- return (
-
- {children}
-
- );
-};
-
-export const useMessageOverlayContext = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->() => {
- const contextValue = useContext(
- MessageOverlayContext,
- ) as unknown as MessageOverlayContextValue;
-
- if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) {
- throw new Error(
- `The useMessageOverlayContext hook was called outside the MessageOverlayContext Provider. Make sure you have configured OverlayProvider component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#overlay-provider`,
- );
- }
-
- return contextValue;
-};
-
-/**
- * @deprecated
- *
- * This will be removed in the next major version.
- *
- * Typescript currently does not support partial inference so if ChatContext
- * typing is desired while using the HOC withMessageOverlayContext the Props for the
- * wrapped component must be provided as the first generic.
- */
-export const withMessageOverlayContext = <
- P extends UnknownType,
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- Component: React.ComponentType,
-): React.ComponentType>> => {
- const WithMessageOverlayContextComponent = (
- props: Omit>,
- ) => {
- const messageContext = useMessageOverlayContext();
-
- return ;
- };
- WithMessageOverlayContextComponent.displayName = `WithMessageOverlayContext${getDisplayName(
- Component,
- )}`;
- return WithMessageOverlayContextComponent;
-};
diff --git a/package/src/contexts/messageOverlayContext/hooks/useResettableState.test.tsx b/package/src/contexts/messageOverlayContext/hooks/useResettableState.test.tsx
deleted file mode 100644
index 4f7fb3a566..0000000000
--- a/package/src/contexts/messageOverlayContext/hooks/useResettableState.test.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-
-import { Button, Text } from 'react-native';
-
-import { render, screen, userEvent, waitFor } from '@testing-library/react-native';
-
-import { useResettableState } from './useResettableState';
-
-const TestComponent = () => {
- const { data, reset, setData } = useResettableState(0);
-
- return (
- <>
- {`${data}`}
- {
- setData(data + 1);
- }}
- testID='increment'
- title='Super useful incrementer'
- />
- {
- reset();
- }}
- testID='reset'
- title='Oh no, go back!'
- />
- >
- );
-};
-
-const waitForOptions = {
- timeout: 1000,
-};
-
-test('useResettableState can be reset to its initial state', async () => {
- const user = userEvent.setup();
- render( );
-
- await waitFor(() => expect(screen.getByTestId('value').children[0]).toBe('0'), waitForOptions);
-
- user.press(screen.getByTestId('increment'));
- await waitFor(() => expect(screen.getByTestId('value').children[0]).toBe('1'), waitForOptions);
-
- user.press(screen.getByTestId('reset'));
- await waitFor(() => expect(screen.getByTestId('value').children[0]).toBe('0'), waitForOptions);
-});
diff --git a/package/src/contexts/messageOverlayContext/hooks/useResettableState.ts b/package/src/contexts/messageOverlayContext/hooks/useResettableState.ts
deleted file mode 100644
index 5354292652..0000000000
--- a/package/src/contexts/messageOverlayContext/hooks/useResettableState.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useState } from 'react';
-
-import { useIsMountedRef } from '../../../hooks/useIsMountedRef';
-
-/**
- * Wrapper around useState that provides a `reset`
- * function to reset the state to its initial value.
- *
- * Will not set state after being unmounted.
- * */
-export const useResettableState = (values: T) => {
- const [data, setData] = useState(values);
- const isMounted = useIsMountedRef();
-
- const reset = () => {
- if (isMounted.current) {
- setData(values);
- }
- };
-
- return { data, reset, setData };
-};
diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx
index 0dacf0cd93..999920f08c 100644
--- a/package/src/contexts/messagesContext/MessagesContext.tsx
+++ b/package/src/contexts/messagesContext/MessagesContext.tsx
@@ -45,8 +45,15 @@ import type { MessageSystemProps } from '../../components/MessageList/MessageSys
import type { ScrollToBottomButtonProps } from '../../components/MessageList/ScrollToBottomButton';
import { TypingIndicatorContainerProps } from '../../components/MessageList/TypingIndicatorContainer';
import type { getGroupStyles } from '../../components/MessageList/utils/getGroupStyles';
-import type { MessageActionType } from '../../components/MessageOverlay/MessageActionListItem';
+import { MessageActionListProps } from '../../components/MessageOverlay/MessageActionList';
+import type {
+ MessageActionListItemProps,
+ MessageActionType,
+} from '../../components/MessageOverlay/MessageActionListItem';
import type { OverlayReactionListProps } from '../../components/MessageOverlay/OverlayReactionList';
+import { OverlayReactionsProps } from '../../components/MessageOverlay/OverlayReactions';
+import { OverlayReactionsAvatarProps } from '../../components/MessageOverlay/OverlayReactionsAvatar';
+import { OverlayReactionsItemProps } from '../../components/MessageOverlay/OverlayReactionsItem';
import type { ReplyProps } from '../../components/Reply/Reply';
import type { DefaultStreamChatGenerics, UnknownType } from '../../types/types';
import type { ReactionData } from '../../utils/utils';
@@ -152,6 +159,14 @@ export type MessagesContextValue<
InlineUnreadIndicator: React.ComponentType;
Message: React.ComponentType>;
+ /**
+ * Custom UI component for rendering [message actions](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/2.png) in overlay.
+ *
+ * **Default** [MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActions.tsx)
+ */
+ MessageActionList: React.ComponentType>;
+ MessageActionListItem: React.ComponentType;
+
/**
* UI component for MessageAvatar
* Defaults to: [MessageAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageAvatar.tsx)
@@ -168,6 +183,7 @@ export type MessagesContextValue<
MessageContent: React.ComponentType>;
/** Order to render the message content */
messageContentOrder: MessageContentType[];
+
/**
* UI component for MessageDeleted
* Defaults to: [MessageDeleted](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageDeleted.tsx)
@@ -226,6 +242,14 @@ export type MessagesContextValue<
* UI component for OverlayReactionList
*/
OverlayReactionList: React.ComponentType>;
+ /**
+ * Custom UI component for rendering [reactions list](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/2.png), in overlay (which shows up on long press on message).
+ *
+ * **Default** [OverlayReactions](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/OverlayReactions.tsx)
+ */
+ OverlayReactions: React.ComponentType>;
+ OverlayReactionsAvatar: React.ComponentType;
+ OverlayReactionsItem: React.ComponentType;
/**
* UI component for ReactionList
* Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx)
@@ -249,7 +273,6 @@ export type MessagesContextValue<
sendReaction: (type: string, messageId: string) => Promise;
setEditingState: (message?: MessageType) => void;
setQuotedMessageState: (message?: MessageType) => void;
- supportedReactions: ReactionData[];
/**
* UI component for TypingIndicator
* Defaults to: [TypingIndicator](https://getstream.io/chat/docs/sdk/reactnative/ui-components/typing-indicator/)
@@ -415,7 +438,10 @@ export type MessagesContextValue<
MessageHeader?: React.ComponentType>;
/** Custom UI component for message text */
MessageText?: React.ComponentType>;
-
+ /**
+ * The number of lines of the message text to be displayed
+ */
+ messageTextNumberOfLines?: number;
/**
* Theme provided only to messages that are the current users
*/
@@ -511,6 +537,8 @@ export type MessagesContextValue<
message: MessageType,
) => (reactionType: string) => Promise;
+ supportedReactions?: ReactionData[];
+
targetedMessage?: string;
};
diff --git a/package/src/contexts/overlayContext/OverlayContext.tsx b/package/src/contexts/overlayContext/OverlayContext.tsx
index 525e9f5061..a158bf802a 100644
--- a/package/src/contexts/overlayContext/OverlayContext.tsx
+++ b/package/src/contexts/overlayContext/OverlayContext.tsx
@@ -10,7 +10,6 @@ import type { MessageType } from '../../components/MessageList/hooks/useMessageL
import type { DefaultStreamChatGenerics } from '../../types/types';
import type { Streami18n } from '../../utils/i18n/Streami18n';
import type { AttachmentPickerContextValue } from '../attachmentPickerContext/AttachmentPickerContext';
-import type { MessageOverlayContextValue } from '../messageOverlayContext/MessageOverlayContext';
import type { DeepPartial } from '../themeContext/ThemeContext';
import type { Theme } from '../themeContext/utils/theme';
import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
@@ -18,7 +17,7 @@ import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
import { getDisplayName } from '../utils/getDisplayName';
import { isTestEnvironment } from '../utils/isTestEnvironment';
-export type Overlay = 'alert' | 'gallery' | 'message' | 'none';
+export type Overlay = 'alert' | 'gallery' | 'none';
export type OverlayContextValue = {
overlay: Overlay;
@@ -48,17 +47,7 @@ export type OverlayProviderProps<
| 'topInset'
>
> &
- ImageGalleryCustomComponents &
- Partial<
- Pick<
- MessageOverlayContextValue,
- | 'MessageActionList'
- | 'MessageActionListItem'
- | 'OverlayReactionList'
- | 'OverlayReactions'
- | 'OverlayReactionsAvatar'
- >
- > & {
+ ImageGalleryCustomComponents & {
autoPlayVideo?: boolean;
/**
* The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default
@@ -73,15 +62,6 @@ export type OverlayProviderProps<
isMyMessage?: boolean;
isThreadMessage?: boolean;
message?: MessageType;
- /**
- * @deprecated use the following instead:
- * messageActions={(params) => {
- * const actions = messageActions({ ...params, isMessageActionsVisible: false });
- * return actions;
- * }}
- */
- messageReactions?: boolean;
- messageTextNumberOfLines?: number;
numberOfImageGalleryGridColumns?: number;
openPicker?: (ref: React.RefObject) => void;
value?: Partial;
diff --git a/package/src/contexts/overlayContext/OverlayProvider.tsx b/package/src/contexts/overlayContext/OverlayProvider.tsx
index adfb264b5e..25e3cda7a7 100644
--- a/package/src/contexts/overlayContext/OverlayProvider.tsx
+++ b/package/src/contexts/overlayContext/OverlayProvider.tsx
@@ -1,13 +1,8 @@
import React, { PropsWithChildren, useEffect, useRef, useState } from 'react';
-import { BackHandler, Dimensions, StyleSheet, ViewStyle } from 'react-native';
+import { BackHandler } from 'react-native';
-import Animated, {
- cancelAnimation,
- useAnimatedStyle,
- useSharedValue,
- withTiming,
-} from 'react-native-reanimated';
+import { cancelAnimation, useSharedValue, withTiming } from 'react-native-reanimated';
import type BottomSheet from '@gorhom/bottom-sheet';
@@ -25,8 +20,6 @@ import { FileSelectorIcon as DefaultFileSelectorIcon } from '../../components/At
import { ImageOverlaySelectedComponent as DefaultImageOverlaySelectedComponent } from '../../components/AttachmentPicker/components/ImageOverlaySelectedComponent';
import { ImageSelectorIcon as DefaultImageSelectorIcon } from '../../components/AttachmentPicker/components/ImageSelectorIcon';
import { ImageGallery } from '../../components/ImageGallery/ImageGallery';
-import { MessageOverlay } from '../../components/MessageOverlay/MessageOverlay';
-import { OverlayBackdrop } from '../../components/MessageOverlay/OverlayBackdrop';
import { useStreami18n } from '../../hooks/useStreami18n';
import { useViewport } from '../../hooks/useViewport';
@@ -34,7 +27,6 @@ import { isImageMediaLibraryAvailable } from '../../native';
import type { DefaultStreamChatGenerics } from '../../types/types';
import { AttachmentPickerProvider } from '../attachmentPickerContext/AttachmentPickerContext';
import { ImageGalleryProvider } from '../imageGalleryContext/ImageGalleryContext';
-import { MessageOverlayProvider } from '../messageOverlayContext/MessageOverlayContext';
import { ThemeProvider } from '../themeContext/ThemeContext';
import {
DEFAULT_USER_LANGUAGE,
@@ -107,9 +99,6 @@ export const OverlayProvider = <
imageGalleryGridSnapPoints,
ImageOverlaySelectedComponent = DefaultImageOverlaySelectedComponent,
ImageSelectorIcon = DefaultImageSelectorIcon,
- MessageActionList,
- MessageActionListItem,
- messageTextNumberOfLines,
numberOfAttachmentImagesToLoadPerCall,
numberOfAttachmentPickerImageColumns,
numberOfImageGalleryGridColumns,
@@ -123,9 +112,6 @@ export const OverlayProvider = <
console.warn('bottom and top insets must be set for the image picker to work correctly');
}
},
- OverlayReactionList,
- OverlayReactions,
- OverlayReactionsAvatar,
topInset,
value,
} = props;
@@ -150,7 +136,6 @@ export const OverlayProvider = <
const [overlay, setOverlay] = useState(value?.overlay || 'none');
const overlayOpacity = useSharedValue(0);
- const { height, width } = Dimensions.get('screen');
// Setup translators
const translators = useStreami18n(i18nInstance);
@@ -206,13 +191,6 @@ export const OverlayProvider = <
topInset,
};
- const overlayStyle = useAnimatedStyle(
- () => ({
- opacity: overlayOpacity.value,
- }),
- [],
- );
-
const overlayContext = {
overlay,
setOverlay,
@@ -222,46 +200,27 @@ export const OverlayProvider = <
return (
- >
-
-
- {children}
-
-
-
-
- {overlay === 'message' && (
-
- MessageActionList={MessageActionList}
- MessageActionListItem={MessageActionListItem}
- messageTextNumberOfLines={messageTextNumberOfLines}
- overlayOpacity={overlayOpacity}
- OverlayReactionList={OverlayReactionList}
- OverlayReactions={OverlayReactions}
- OverlayReactionsAvatar={OverlayReactionsAvatar}
- />
- )}
- {overlay === 'gallery' && (
-
- autoPlayVideo={autoPlayVideo}
- giphyVersion={giphyVersion}
- imageGalleryCustomComponents={imageGalleryCustomComponents}
- imageGalleryGridHandleHeight={imageGalleryGridHandleHeight}
- imageGalleryGridSnapPoints={imageGalleryGridSnapPoints}
- numberOfImageGalleryGridColumns={numberOfImageGalleryGridColumns}
- overlayOpacity={overlayOpacity}
- />
- )}
- {isImageMediaLibraryAvailable() ? (
-
- ) : null}
-
-
-
-
+
+
+ {children}
+
+ {overlay === 'gallery' && (
+
+ autoPlayVideo={autoPlayVideo}
+ giphyVersion={giphyVersion}
+ imageGalleryCustomComponents={imageGalleryCustomComponents}
+ imageGalleryGridHandleHeight={imageGalleryGridHandleHeight}
+ imageGalleryGridSnapPoints={imageGalleryGridSnapPoints}
+ numberOfImageGalleryGridColumns={numberOfImageGalleryGridColumns}
+ overlayOpacity={overlayOpacity}
+ />
+ )}
+ {isImageMediaLibraryAvailable() ? (
+
+ ) : null}
+
+
+
);
diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts
index 9efb82d710..45bc3f3753 100644
--- a/package/src/contexts/themeContext/utils/theme.ts
+++ b/package/src/contexts/themeContext/utils/theme.ts
@@ -20,6 +20,7 @@ export const Colors = {
grey_whisper: '#ECEBEB',
icon_background: '#FFFFFF',
label_bg_transparent: '#00000033', // 33 = 20% opacity
+ light_blue: '#E0F0FF',
light_gray: '#DBDDE1',
modal_shadow: '#00000099', // 99 = 60% opacity; x=0, y= 1, radius=4
overlay: '#000000CC', // CC = 80% opacity
@@ -237,6 +238,9 @@ export type Theme = {
container: ViewStyle;
loadingText: TextStyle;
};
+ messageActionList: {
+ container: ViewStyle;
+ };
messageInput: {
attachButton: ViewStyle;
attachButtonContainer: ViewStyle;
@@ -584,24 +588,29 @@ export type Theme = {
title: TextStyle;
};
padding: number;
+ reactionButton: {
+ filledColor: ColorValue;
+ unfilledColor: ColorValue;
+ };
reactions: {
avatarContainer: ViewStyle;
avatarName: TextStyle;
avatarSize: number;
container: ViewStyle;
- flatListContainer: ViewStyle;
+ flatlistColumnContainer: ViewStyle;
+ flatlistContainer: ViewStyle;
radius: number;
reactionBubble: ViewStyle;
reactionBubbleBackground: ViewStyle;
reactionBubbleBorderRadius: number;
+ reactionSelectorContainer: ViewStyle;
+ reactionsText: TextStyle;
title: TextStyle;
};
reactionsList: {
- radius: number;
- reaction: ViewStyle;
- reactionList: ViewStyle;
- reactionListBorderRadius: number;
- reactionSize: number;
+ buttonContainer: ViewStyle;
+ container: ViewStyle;
+ reactionIconSize: number;
};
};
progressControl: {
@@ -823,6 +832,9 @@ export const defaultTheme: Theme = {
container: {},
loadingText: {},
},
+ messageActionList: {
+ container: {},
+ },
messageInput: {
attachButton: {},
attachButtonContainer: {},
@@ -1187,24 +1199,29 @@ export const defaultTheme: Theme = {
title: {},
},
padding: 8,
+ reactionButton: {
+ filledColor: Colors.accent_blue,
+ unfilledColor: Colors.grey,
+ },
reactions: {
avatarContainer: {},
avatarName: {},
avatarSize: 64,
container: {},
- flatListContainer: {},
+ flatlistColumnContainer: {},
+ flatlistContainer: {},
radius: 2,
reactionBubble: {},
reactionBubbleBackground: {},
reactionBubbleBorderRadius: 24,
+ reactionSelectorContainer: {},
+ reactionsText: {},
title: {},
},
reactionsList: {
- radius: 2.5,
- reaction: {},
- reactionList: {},
- reactionListBorderRadius: 24,
- reactionSize: 24,
+ buttonContainer: {},
+ container: {},
+ reactionIconSize: 24,
},
},
progressControl: {
diff --git a/package/src/hooks/__tests__/useTranslatedMessage.test.tsx b/package/src/hooks/__tests__/useTranslatedMessage.test.tsx
index 55f0b26731..dd2004d3ca 100644
--- a/package/src/hooks/__tests__/useTranslatedMessage.test.tsx
+++ b/package/src/hooks/__tests__/useTranslatedMessage.test.tsx
@@ -5,10 +5,6 @@ import { render, screen, waitFor } from '@testing-library/react-native';
import type { MessageResponse } from 'stream-chat';
-import {
- MessageOverlayContextValue,
- MessageOverlayProvider,
-} from '../../contexts/messageOverlayContext/MessageOverlayContext';
import {
TranslationContextValue,
TranslationProvider,
@@ -21,7 +17,7 @@ type TestComponentProps = {
const TestComponent = ({ message }: TestComponentProps) => {
const translatedMessage = useTranslatedMessage(message);
- return {translatedMessage.text} ;
+ return {translatedMessage?.text} ;
};
describe('useTranslatedMessage', () => {
@@ -92,11 +88,9 @@ describe('useTranslatedMessage', () => {
* in the provider.
* */
render(
-
+
- ,
+ ,
);
await waitFor(() => {
diff --git a/package/src/hooks/useTranslatedMessage.ts b/package/src/hooks/useTranslatedMessage.ts
index a60bde1ae7..bc0bc938d7 100644
--- a/package/src/hooks/useTranslatedMessage.ts
+++ b/package/src/hooks/useTranslatedMessage.ts
@@ -1,6 +1,5 @@
import type { FormatMessageResponse, MessageResponse, TranslationLanguages } from 'stream-chat';
-import { useMessageOverlayContext } from '../contexts/messageOverlayContext/MessageOverlayContext';
import { useTranslationContext } from '../contexts/translationContext/TranslationContext';
import type { DefaultStreamChatGenerics } from '../types/types';
@@ -12,10 +11,8 @@ export const useTranslatedMessage = <
message?: MessageResponse | FormatMessageResponse,
) => {
const { userLanguage: translationContextUserLanguage } = useTranslationContext();
- const messageOverlayContextValue = useMessageOverlayContext();
- const userLanguage =
- messageOverlayContextValue.data?.userLanguage || translationContextUserLanguage;
+ const userLanguage = translationContextUserLanguage;
const translationKey: TranslationKey = `${userLanguage}_text`;
diff --git a/package/src/store/apis/getReactionsforFilterSort.ts b/package/src/store/apis/getReactionsforFilterSort.ts
index ed6f6d216c..394bce9666 100644
--- a/package/src/store/apis/getReactionsforFilterSort.ts
+++ b/package/src/store/apis/getReactionsforFilterSort.ts
@@ -39,5 +39,7 @@ export const getReactionsForFilterSort = <
return [];
}
- return getReactions({ reactions });
+ const filteredReactions = reactions.filter((reaction) => reaction.type === filters?.type);
+
+ return getReactions({ reactions: filteredReactions });
};
diff --git a/package/src/types/types.ts b/package/src/types/types.ts
index 3ff97c89f6..2aba21d59a 100644
--- a/package/src/types/types.ts
+++ b/package/src/types/types.ts
@@ -67,6 +67,13 @@ export type DefaultAttachmentType = UnknownType & {
waveform_data?: number[];
};
+export type Reaction = {
+ id: string;
+ name: string;
+ type: string;
+ image?: string;
+};
+
interface DefaultUserType extends UnknownType {
image?: string;
}
From fbf633f647b1d5402622f4a2d0975c32a0facc28 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Wed, 25 Sep 2024 18:30:30 +0530
Subject: [PATCH 02/19] fix: remove unnecessary props from OverlayContext
---
package/src/components/Message/Message.tsx | 3 --
.../MessageOverlay/MessageActionList.tsx | 31 +++----------------
.../MessageOverlay/MessageOverlay.tsx | 23 ++------------
.../messagesContext/MessagesContext.tsx | 2 +-
.../overlayContext/OverlayContext.tsx | 5 ---
5 files changed, 9 insertions(+), 55 deletions(-)
diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx
index 0bd13696e3..2ef555d2fc 100644
--- a/package/src/components/Message/Message.tsx
+++ b/package/src/components/Message/Message.tsx
@@ -770,10 +770,7 @@ const MessageWithContext = <
= Pick<
- OverlayProviderProps,
- 'error' | 'isMyMessage' | 'isThreadMessage' | 'message'
-> &
- Pick & {
- messageActions?: MessageActionType[];
- };
+export type MessageActionListProps = Pick & {
+ messageActions?: MessageActionType[];
+};
-export const MessageActionList = <
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
->(
- props: MessageActionListProps,
-) => {
- const { error, isMyMessage, isThreadMessage, message, MessageActionListItem, messageActions } =
- props;
+export const MessageActionList = (props: MessageActionListProps) => {
+ const { MessageActionListItem, messageActions } = props;
const {
theme: {
messageActionList: { container },
},
} = useTheme();
- const messageActionProps = {
- error,
- isMyMessage,
- isThreadMessage,
- message,
- };
-
return (
{messageActions?.map((messageAction, index) => (
))}
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index 65bfab5d5e..003482a6ee 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -6,12 +6,11 @@ import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/typ
import { MessageActionType } from './MessageActionListItem';
-import { useMessageContext } from '../../contexts/messageContext/MessageContext';
+import { MessageContextValue } from '../../contexts/messageContext/MessageContext';
import {
MessagesContextValue,
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
-import { OverlayProviderProps } from '../../contexts/overlayContext/OverlayContext';
import { DefaultStreamChatGenerics } from '../../types/types';
export type MessageOverlayProps<
@@ -27,11 +26,8 @@ export type MessageOverlayProps<
| 'OverlayReactionsItem'
>
> &
- Partial<
- Pick, 'isMyMessage' | 'isThreadMessage' | 'message'>
- > & {
+ Partial, 'message'>> & {
closeMessageActionsBottomSheet: () => void;
- isErrorInMessage: boolean;
isMessageActionsVisible: boolean;
messageActions: MessageActionType[];
messageActionsBottomSheetRef: React.RefObject;
@@ -46,10 +42,7 @@ export const MessageOverlay = <
const {
closeMessageActionsBottomSheet,
handleReaction,
- isErrorInMessage,
isMessageActionsVisible,
- isMyMessage: propIsMyMessage,
- isThreadMessage,
message,
MessageActionList: propMessageActionList,
MessageActionListItem: propMessageActionListItem,
@@ -68,9 +61,7 @@ export const MessageOverlay = <
OverlayReactionsAvatar: contextOverlayReactionsAvatar,
OverlayReactionsItem: contextOverlayReactionsItem,
} = useMessagesContext();
- const { isMyMessage: contextIsMyMessage } = useMessageContext();
const snapPoints = useMemo(() => ['50%', '50%'], []);
- const isMyMessage = propIsMyMessage ?? contextIsMyMessage;
const MessageActionList = propMessageActionList ?? contextMessageActionList;
const MessageActionListItem = propMessageActionListItem ?? contextMessageActionListItem;
const OverlayReactionList = propOverlayReactionList ?? contextOverlayReactionList;
@@ -82,14 +73,6 @@ export const MessageOverlay = <
console.log('handleSheetChanges', index);
}, []);
- const messageActionProps = {
- error: isErrorInMessage,
- isMyMessage,
- isThreadMessage,
- message,
- messageActions,
- };
-
return (
) : null}
>
diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx
index 999920f08c..d29e449c3a 100644
--- a/package/src/contexts/messagesContext/MessagesContext.tsx
+++ b/package/src/contexts/messagesContext/MessagesContext.tsx
@@ -164,7 +164,7 @@ export type MessagesContextValue<
*
* **Default** [MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActions.tsx)
*/
- MessageActionList: React.ComponentType>;
+ MessageActionList: React.ComponentType;
MessageActionListItem: React.ComponentType;
/**
diff --git a/package/src/contexts/overlayContext/OverlayContext.tsx b/package/src/contexts/overlayContext/OverlayContext.tsx
index a158bf802a..bafde6a886 100644
--- a/package/src/contexts/overlayContext/OverlayContext.tsx
+++ b/package/src/contexts/overlayContext/OverlayContext.tsx
@@ -6,7 +6,6 @@ import type { Attachment } from 'stream-chat';
import type { AttachmentPickerProps } from '../../components/AttachmentPicker/AttachmentPicker';
import type { ImageGalleryCustomComponents } from '../../components/ImageGallery/ImageGallery';
-import type { MessageType } from '../../components/MessageList/hooks/useMessageList';
import type { DefaultStreamChatGenerics } from '../../types/types';
import type { Streami18n } from '../../utils/i18n/Streami18n';
import type { AttachmentPickerContextValue } from '../attachmentPickerContext/AttachmentPickerContext';
@@ -53,15 +52,11 @@ export type OverlayProviderProps<
* The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default
* */
closePicker?: (ref: React.RefObject) => void;
- error?: boolean | Error;
giphyVersion?: keyof NonNullable;
/** https://github.com/GetStream/stream-chat-react-native/wiki/Internationalization-(i18n) */
i18nInstance?: Streami18n;
imageGalleryGridHandleHeight?: number;
imageGalleryGridSnapPoints?: [string | number, string | number];
- isMyMessage?: boolean;
- isThreadMessage?: boolean;
- message?: MessageType;
numberOfImageGalleryGridColumns?: number;
openPicker?: (ref: React.RefObject) => void;
value?: Partial;
From 5ad509f5992b9a049641f1b15e0923c259ece5b5 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Thu, 26 Sep 2024 10:39:49 +0530
Subject: [PATCH 03/19] docs: add comments for the props
---
.../MessageOverlay/MessageActionList.tsx | 3 +++
.../MessageOverlay/MessageOverlay.tsx | 18 ++++++++++++++++++
.../MessageOverlay/OverlayReactionList.tsx | 14 ++++++++++++--
.../MessageOverlay/OverlayReactions.tsx | 6 ++++++
.../MessageOverlay/OverlayReactionsAvatar.tsx | 3 +++
.../MessageOverlay/OverlayReactionsItem.tsx | 15 +++++++++++++++
6 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/package/src/components/MessageOverlay/MessageActionList.tsx b/package/src/components/MessageOverlay/MessageActionList.tsx
index d176018690..4e7117bc67 100644
--- a/package/src/components/MessageOverlay/MessageActionList.tsx
+++ b/package/src/components/MessageOverlay/MessageActionList.tsx
@@ -7,6 +7,9 @@ import { MessagesContextValue } from '../../contexts/messagesContext/MessagesCon
import { useTheme } from '../../contexts/themeContext/ThemeContext';
export type MessageActionListProps = Pick & {
+ /**
+ * An array of message actions to render
+ */
messageActions?: MessageActionType[];
};
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index 003482a6ee..77670e2fd7 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -27,10 +27,28 @@ export type MessageOverlayProps<
>
> &
Partial, 'message'>> & {
+ /**
+ * Function to close the message actions bottom sheet
+ * @returns void
+ */
closeMessageActionsBottomSheet: () => void;
+ /**
+ * Boolean to determine if there are message actions
+ */
isMessageActionsVisible: boolean;
+ /**
+ * An array of message actions to render
+ */
messageActions: MessageActionType[];
+ /**
+ * Reference to the bottom sheet modal
+ */
messageActionsBottomSheetRef: React.RefObject;
+ /**
+ * Function to handle reaction on press
+ * @param reactionType
+ * @returns
+ */
handleReaction?: (reactionType: string) => Promise;
};
diff --git a/package/src/components/MessageOverlay/OverlayReactionList.tsx b/package/src/components/MessageOverlay/OverlayReactionList.tsx
index 3ac8e54038..9a0fbdc63e 100644
--- a/package/src/components/MessageOverlay/OverlayReactionList.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionList.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
-import { FillProps } from 'react-native-svg';
import { ReactionButton } from './ReactionButton';
@@ -16,9 +15,20 @@ import type { DefaultStreamChatGenerics } from '../../types/types';
export type OverlayReactionListProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick, 'supportedReactions'> & {
+ /**
+ * Function to dismiss the action bottom sheet.
+ * @returns void
+ */
dismissOverlay: () => void;
+ /**
+ * An array of reaction types that the current user has reacted with
+ */
ownReactionTypes: string[];
- fill?: FillProps['fill'];
+ /**
+ * Function to handle reaction on press
+ * @param reactionType
+ * @returns
+ */
handleReaction?: (reactionType: string) => Promise;
};
diff --git a/package/src/components/MessageOverlay/OverlayReactions.tsx b/package/src/components/MessageOverlay/OverlayReactions.tsx
index 6a6901a77e..a8d40fd31d 100644
--- a/package/src/components/MessageOverlay/OverlayReactions.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactions.tsx
@@ -22,7 +22,13 @@ export type OverlayReactionsProps<
MessagesContextValue,
'OverlayReactionsAvatar' | 'OverlayReactionsItem' | 'supportedReactions'
> & {
+ /**
+ * The message object
+ */
message?: MessageType;
+ /**
+ * An array of reactions
+ */
reactions?: Reaction[];
};
diff --git a/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx b/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
index c1b92f6e91..b1cd58d98f 100644
--- a/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx
@@ -5,6 +5,9 @@ import { Reaction } from '../../types/types';
import { Avatar, AvatarProps } from '../Avatar/Avatar';
export type OverlayReactionsAvatarProps = {
+ /**
+ * The reaction object
+ */
reaction: Reaction;
} & Partial>;
diff --git a/package/src/components/MessageOverlay/OverlayReactionsItem.tsx b/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
index 21dc20f748..ece3fc4fcd 100644
--- a/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionsItem.tsx
@@ -15,15 +15,30 @@ import { ReactionData } from '../../utils/utils';
export type OverlayReactionsItemProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick, 'OverlayReactionsAvatar'> & {
+ /**
+ * The reaction object
+ */
reaction: Reaction;
+ /**
+ * An array of supported reactions
+ */
supportedReactions: ReactionData[];
};
type ReactionIconProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick, 'type'> & {
+ /**
+ * The fill color for the reaction icon
+ */
pathFill: string;
+ /**
+ * The size of the reaction icon
+ */
size: number;
+ /**
+ * An array of supported reactions
+ */
supportedReactions: ReactionData[];
};
From 4a10b4dfe8c1f5d06711772b27545fc519443614 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Thu, 26 Sep 2024 16:07:20 +0530
Subject: [PATCH 04/19] tests: add tests for the components
---
.../__tests__/ImageGalleryGrid.test.tsx | 2 +-
.../__tests__/ImageGalleryGridHandle.test.tsx | 2 +-
.../__tests__/MessageTextContainer.test.tsx | 6 +-
.../MessageOverlay/MessageOverlay.tsx | 5 +-
.../MessageOverlay/ReactionButton.tsx | 1 +
.../__tests__/MessageActionList.test.tsx | 48 ++++++++++++
.../__tests__/MessageActionListItem.test.tsx | 48 ++++++++++++
.../__tests__/OverlayReactionsAvatar.test.tsx | 36 +++++++++
.../__tests__/ReactionButton.test.tsx | 75 +++++++++++++++++++
package/src/contexts/__tests__/index.test.tsx | 5 --
.../__tests__/isValidMessage.test.tsx | 5 +-
.../__tests__/uploadFile.test.tsx | 3 +-
.../__tests__/uploadImage.test.tsx | 3 +-
13 files changed, 220 insertions(+), 19 deletions(-)
create mode 100644 package/src/components/MessageOverlay/__tests__/MessageActionList.test.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/OverlayReactionsAvatar.test.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx
index 55372d38de..494f256ffc 100644
--- a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx
+++ b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx
@@ -22,7 +22,7 @@ const getComponent = (props: Partial = {}) => {
return (
-
+
diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryGridHandle.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryGridHandle.test.tsx
index 7d67cba54c..4ab178ed4e 100644
--- a/package/src/components/ImageGallery/__tests__/ImageGalleryGridHandle.test.tsx
+++ b/package/src/components/ImageGallery/__tests__/ImageGalleryGridHandle.test.tsx
@@ -24,7 +24,7 @@ const getComponent = (props: Partial = {}) => {
return (
-
+
diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx
index af70ee73a4..c4abae945b 100644
--- a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx
+++ b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx
@@ -30,7 +30,7 @@ describe('MessageTextContainer', () => {
user: { ...staticUser, image: undefined },
});
const { getByTestId, getByText, rerender, toJSON } = render(
-
+
,
);
@@ -41,7 +41,7 @@ describe('MessageTextContainer', () => {
});
rerender(
-
+
{message?.text} }
@@ -60,7 +60,7 @@ describe('MessageTextContainer', () => {
});
rerender(
-
+
,
);
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index 77670e2fd7..d2acd5d422 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import { StyleSheet } from 'react-native';
-import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
+import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
import { MessageActionType } from './MessageActionListItem';
@@ -93,7 +93,8 @@ export const MessageOverlay = <
return (
{
{ backgroundColor: pressed ? light_blue : white },
buttonContainer,
]}
+ testID={`reaction-button-${type}`}
>
{props.title} ;
+
+const defaultProps = {
+ MessageActionListItem: MockMessageActionListItem,
+ messageActions: [
+ { action: jest.fn(), actionType: 'copyMessage', title: 'Copy Message' },
+ { action: jest.fn(), actionType: 'deleteMessage', title: 'Delete Message' },
+ ],
+};
+
+describe('MessageActionList', () => {
+ it('should render correctly with provided message actions', () => {
+ const { getByTestId, getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByTestId('message-action-list')).toBeTruthy();
+
+ expect(getByText('Copy Message')).toBeTruthy();
+ expect(getByText('Delete Message')).toBeTruthy();
+ });
+
+ it('should pass the correct props to MessageActionListItem', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('Copy Message')).toBeTruthy();
+ expect(getByText('Delete Message')).toBeTruthy();
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx b/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
new file mode 100644
index 0000000000..b36f1c2828
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
@@ -0,0 +1,48 @@
+// MessageActionListItem.test.tsx
+
+import React from 'react';
+
+import { Text } from 'react-native';
+
+import { fireEvent, render } from '@testing-library/react-native';
+
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+import { defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import { MessageActionListItem } from '../MessageActionListItem';
+
+describe('MessageActionListItem', () => {
+ const mockAction = jest.fn();
+
+ const defaultProps = {
+ action: mockAction,
+ actionType: 'copyMessage',
+ icon: Icon ,
+ title: 'Copy Message',
+ };
+
+ it('should render correctly with given props', () => {
+ const { getByTestId, getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText('Copy Message')).toBeTruthy();
+
+ expect(getByText('Icon')).toBeTruthy();
+
+ expect(getByTestId('copyMessage-list-item')).toBeTruthy();
+ });
+
+ it('should call the action callback when pressed', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ fireEvent.press(getByTestId('copyMessage-list-item'));
+
+ expect(mockAction).toHaveBeenCalled();
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/OverlayReactionsAvatar.test.tsx b/package/src/components/MessageOverlay/__tests__/OverlayReactionsAvatar.test.tsx
new file mode 100644
index 0000000000..8644920c46
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/OverlayReactionsAvatar.test.tsx
@@ -0,0 +1,36 @@
+// OverlayReactionsAvatar.test.tsx
+import React from 'react';
+
+import { render } from '@testing-library/react-native';
+
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+import { defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import { OverlayReactionsAvatar } from '../OverlayReactionsAvatar';
+
+describe('OverlayReactionsAvatar', () => {
+ const reaction = { id: 'test-user', image: 'image-url', name: 'Test User', type: 'like' }; // Mock reaction data
+
+ it('should render Avatar with correct image, name, and default size', () => {
+ const { queryByTestId } = render(
+
+
+ ,
+ );
+
+ // Check if the mocked Avatar component is rendered with correct props
+ expect(queryByTestId(`avatar-image`)).toBeTruthy();
+ });
+
+ it('should render Avatar with correct image, name, and custom size', () => {
+ const customSize = 40;
+
+ const { queryByTestId } = render(
+
+
+ ,
+ );
+
+ // Check if the mocked Avatar component is rendered with correct custom size
+ expect(queryByTestId(`avatar-image`)).toBeTruthy();
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx b/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
new file mode 100644
index 0000000000..4a49f224de
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+
+import { Text } from 'react-native';
+
+import { cleanup, fireEvent, render } from '@testing-library/react-native';
+
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+import { defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import { IconProps } from '../../../icons';
+import { ReactionButton } from '../ReactionButton';
+
+const MockIcon = (props: IconProps) => {props?.pathFill?.toString() || ''} ;
+
+describe('ReactionButton', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ cleanup();
+ });
+
+ const mockOnPress = jest.fn();
+
+ const defaultProps = {
+ Icon: MockIcon,
+ onPress: mockOnPress,
+ selected: false,
+ type: 'like',
+ };
+
+ it('should render correctly with given props', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ // Check if the unselected pathFill color is rendered by the mock Icon
+ expect(getByText(defaultTheme.overlay.reactionButton.unfilledColor.toString())).toBeTruthy();
+ });
+
+ it('should call onPress function with the correct reaction type when pressed', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ // Simulate a press event
+ fireEvent.press(getByTestId('reaction-button-like'));
+
+ // Verify if the mock function has been called with the correct reaction type
+ expect(mockOnPress).toHaveBeenCalledWith('like');
+ });
+
+ it('should not call onPress when the onPress prop is not provided', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ fireEvent.press(getByTestId('reaction-button-like'));
+
+ expect(mockOnPress).not.toHaveBeenCalled();
+ });
+
+ it('should apply selected styles correctly when selected is true', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(getByText(defaultTheme.overlay.reactionButton.filledColor.toString())).toBeTruthy();
+ });
+});
diff --git a/package/src/contexts/__tests__/index.test.tsx b/package/src/contexts/__tests__/index.test.tsx
index 55768597da..e87199034f 100644
--- a/package/src/contexts/__tests__/index.test.tsx
+++ b/package/src/contexts/__tests__/index.test.tsx
@@ -9,7 +9,6 @@ import {
useChannelsContext,
useChatContext,
useImageGalleryContext,
- useMessageOverlayContext,
useMessagesContext,
useOverlayContext,
useOwnCapabilitiesContext,
@@ -74,10 +73,6 @@ describe('contexts hooks in a component throws an error with message when not wr
useImageGalleryContext,
`The useImageGalleryContext hook was called outside the ImageGalleryContext Provider. Make sure you have configured OverlayProvider component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#overlay-provider`,
],
- [
- useMessageOverlayContext,
- `The useMessageOverlayContext hook was called outside the MessageOverlayContext Provider. Make sure you have configured OverlayProvider component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#overlay-provider`,
- ],
[
useMessagesContext,
`The useMessagesContext hook was called outside of the MessagesContext provider. Make sure you have configured MessageList component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#message-list`,
diff --git a/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx
index 73f38d52ec..2e9a04c1cc 100644
--- a/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx
+++ b/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx
@@ -13,13 +13,12 @@ import type { DefaultStreamChatGenerics } from '../../../types/types';
import { FileState } from '../../../utils/utils';
import {
InputMessageInputContextValue,
- MessageInputContextValue,
MessageInputProvider,
useMessageInputContext,
} from '../MessageInputContext';
const user1 = generateUser();
-const message = generateMessage({ user: user1 }, 'isValidMessage');
+const message = generateMessage({ user: user1 });
type WrapperType =
Partial>;
@@ -32,7 +31,7 @@ const Wrapper =
+ } as InputMessageInputContextValue
}
>
{children}
diff --git a/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx
index a0e71bb313..7d7b3c5049 100644
--- a/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx
+++ b/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx
@@ -9,7 +9,6 @@ import { generateUser } from '../../../mock-builders/generator/user';
import type { DefaultStreamChatGenerics } from '../../../types/types';
import {
InputMessageInputContextValue,
- MessageInputContextValue,
MessageInputProvider,
useMessageInputContext,
} from '../MessageInputContext';
@@ -28,7 +27,7 @@ const Wrapper =
+ } as InputMessageInputContextValue
}
>
{children}
diff --git a/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx b/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx
index c2d106a9e6..7bd2a571fc 100644
--- a/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx
+++ b/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx
@@ -8,7 +8,6 @@ import { generateUser } from '../../../mock-builders/generator/user';
import type { DefaultStreamChatGenerics } from '../../../types/types';
import {
InputMessageInputContextValue,
- MessageInputContextValue,
MessageInputProvider,
useMessageInputContext,
} from '../MessageInputContext';
@@ -24,7 +23,7 @@ const Wrapper =
+ } as InputMessageInputContextValue
}
>
{children}
From ab79a321f7db2e3ee244fbe549470de8c9614ff0 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Thu, 26 Sep 2024 17:33:54 +0530
Subject: [PATCH 05/19] tests: add tests for the components
---
package/jest-setup.js | 1 +
.../__snapshots__/Thread.test.js.snap | 8498 +++++++++++++----
2 files changed, 6888 insertions(+), 1611 deletions(-)
diff --git a/package/jest-setup.js b/package/jest-setup.js
index 7cbd9d1910..e20580254b 100644
--- a/package/jest-setup.js
+++ b/package/jest-setup.js
@@ -47,6 +47,7 @@ jest.mock('@gorhom/bottom-sheet', () => {
BottomSheetModal: react.View,
BottomSheetModalProvider: react.View,
BottomSheetScrollView: react.ScrollView,
+ BottomSheetView: react.View,
default: react.View,
TouchableOpacity: react.View,
};
diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
index 914a9e2165..0564079b6a 100644
--- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
+++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
@@ -10,1648 +10,6898 @@ exports[`Thread should match thread snapshot 1`] = `
}
}
>
-
+
- regular
",
- "attachments": [],
- "created_at": 2020-05-05T14:50:00.000Z,
- "dateSeparator": undefined,
- "groupStyles": [
- "single",
- ],
- "html": "regular
",
- "id": "38ef6f7c-3090-5759-a37f-ab0053aadb96",
- "message_text_updated_at": "2020-05-05T14:50:00.000Z",
- "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
- "pinned_at": null,
- "readBy": false,
- "status": "received",
- "text": "Message6",
- "type": "regular",
- "updated_at": 2020-05-05T14:50:00.000Z,
- "user": {
- "banned": false,
- "created_at": "2020-04-27T13:39:49.331742Z",
- "id": "arthur",
- "image": "https://i.imgur.com/LuuGvh0.png",
- "name": "Arthur",
- "online": false,
- "role": "user",
- "updated_at": "2020-04-27T13:39:49.332087Z",
- },
- },
- {
- "__html": "regular
",
- "attachments": [],
- "created_at": 2020-05-05T14:50:00.000Z,
- "dateSeparator": undefined,
- "groupStyles": [
- "single",
- ],
- "html": "regular
",
- "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd",
- "message_text_updated_at": "2020-05-05T14:50:00.000Z",
- "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
- "pinned_at": null,
- "readBy": false,
- "status": "received",
- "text": "Message5",
- "type": "regular",
- "updated_at": 2020-05-05T14:50:00.000Z,
- "user": {
- "banned": false,
- "created_at": "2020-04-27T13:39:49.331742Z",
- "id": "finn",
- "image": "https://i.imgur.com/spueyAP.png",
- "name": "Finn",
- "online": false,
- "role": "user",
- "updated_at": "2020-04-27T13:39:49.332087Z",
- },
+ "alignItems": "center",
+ "flex": 1,
+ "width": "100%",
},
{
- "__html": "regular
",
- "attachments": [],
- "created_at": 2020-05-05T14:50:00.000Z,
- "dateSeparator": 2020-05-05T14:50:00.000Z,
- "groupStyles": [
- "single",
- ],
- "html": "regular
",
- "id": "82a83b16-b611-527c-b3ac-765ef6220490",
- "message_text_updated_at": "2020-05-05T14:50:00.000Z",
- "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
- "pinned_at": null,
- "readBy": false,
- "status": "received",
- "text": "Message4",
- "type": "regular",
- "updated_at": 2020-05-05T14:50:00.000Z,
- "user": {
- "banned": false,
- "created_at": "2020-04-27T13:39:49.331742Z",
- "id": "arthur",
- "image": "https://i.imgur.com/LuuGvh0.png",
- "name": "Arthur",
- "online": false,
- "role": "user",
- "updated_at": "2020-04-27T13:39:49.332087Z",
- },
+ "backgroundColor": "#FCFCFC",
},
+ {},
]
}
- extraData={false}
- getItem={[Function]}
- getItemCount={[Function]}
- invertStickyHeaders={true}
- inverted={true}
- isInvertedVirtualizedList={true}
- keyExtractor={[Function]}
- keyboardShouldPersistTaps="handled"
- maintainVisibleContentPosition={
- {
- "autoscrollToTopThreshold": 10,
- "minIndexForVisible": 2,
- }
- }
- maxToRenderPerBatch={30}
- onContentSizeChange={[Function]}
- onLayout={[Function]}
- onMomentumScrollBegin={[Function]}
- onMomentumScrollEnd={[Function]}
- onScroll={[Function]}
- onScrollBeginDrag={[Function]}
- onScrollEndDrag={[Function]}
- onScrollToIndexFailed={[Function]}
- onTouchEnd={[Function]}
- onViewableItemsChanged={[Function]}
- removeClippedSubviews={false}
- renderItem={[Function]}
- scrollEnabled={false}
- scrollEventThrottle={0.0001}
- showsVerticalScrollIndicator={true}
- stickyHeaderIndices={[]}
- style={
- [
- {
- "transform": [
- {
- "scaleY": -1,
- },
- ],
- },
+ testID="message-flat-list-wrapper"
+ >
+ regular
",
+ "attachments": [],
+ "created_at": 2020-05-05T14:50:00.000Z,
+ "dateSeparator": undefined,
+ "groupStyles": [
+ "single",
+ ],
+ "html": "regular
",
+ "id": "38ef6f7c-3090-5759-a37f-ab0053aadb96",
+ "message_text_updated_at": "2020-05-05T14:50:00.000Z",
+ "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
+ "pinned_at": null,
+ "readBy": false,
+ "status": "received",
+ "text": "Message6",
+ "type": "regular",
+ "updated_at": 2020-05-05T14:50:00.000Z,
+ "user": {
+ "banned": false,
+ "created_at": "2020-04-27T13:39:49.331742Z",
+ "id": "arthur",
+ "image": "https://i.imgur.com/LuuGvh0.png",
+ "name": "Arthur",
+ "online": false,
+ "role": "user",
+ "updated_at": "2020-04-27T13:39:49.332087Z",
+ },
},
- },
- ]
- }
- >
-
- regular
",
+ "attachments": [],
+ "created_at": 2020-05-05T14:50:00.000Z,
+ "dateSeparator": undefined,
+ "groupStyles": [
+ "single",
+ ],
+ "html": "regular
",
+ "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd",
+ "message_text_updated_at": "2020-05-05T14:50:00.000Z",
+ "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
+ "pinned_at": null,
+ "readBy": false,
+ "status": "received",
+ "text": "Message5",
+ "type": "regular",
+ "updated_at": 2020-05-05T14:50:00.000Z,
+ "user": {
+ "banned": false,
+ "created_at": "2020-04-27T13:39:49.331742Z",
+ "id": "finn",
+ "image": "https://i.imgur.com/spueyAP.png",
+ "name": "Finn",
+ "online": false,
+ "role": "user",
+ "updated_at": "2020-04-27T13:39:49.332087Z",
+ },
+ },
+ {
+ "__html": "regular
",
+ "attachments": [],
+ "created_at": 2020-05-05T14:50:00.000Z,
+ "dateSeparator": 2020-05-05T14:50:00.000Z,
+ "groupStyles": [
+ "single",
+ ],
+ "html": "regular
",
+ "id": "82a83b16-b611-527c-b3ac-765ef6220490",
+ "message_text_updated_at": "2020-05-05T14:50:00.000Z",
+ "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04",
+ "pinned_at": null,
+ "readBy": false,
+ "status": "received",
+ "text": "Message4",
+ "type": "regular",
+ "updated_at": 2020-05-05T14:50:00.000Z,
+ "user": {
+ "banned": false,
+ "created_at": "2020-04-27T13:39:49.331742Z",
+ "id": "arthur",
+ "image": "https://i.imgur.com/LuuGvh0.png",
+ "name": "Arthur",
+ "online": false,
+ "role": "user",
+ "updated_at": "2020-04-27T13:39:49.332087Z",
+ },
+ },
+ ]
+ }
+ extraData={false}
+ getItem={[Function]}
+ getItemCount={[Function]}
+ invertStickyHeaders={true}
+ inverted={true}
+ isInvertedVirtualizedList={true}
+ keyExtractor={[Function]}
+ keyboardShouldPersistTaps="handled"
+ maintainVisibleContentPosition={
+ {
+ "autoscrollToTopThreshold": 10,
+ "minIndexForVisible": 2,
+ }
+ }
+ maxToRenderPerBatch={30}
+ onContentSizeChange={[Function]}
+ onLayout={[Function]}
+ onMomentumScrollBegin={[Function]}
+ onMomentumScrollEnd={[Function]}
+ onScroll={[Function]}
+ onScrollBeginDrag={[Function]}
+ onScrollEndDrag={[Function]}
+ onScrollToIndexFailed={[Function]}
+ onTouchEnd={[Function]}
+ onViewableItemsChanged={[Function]}
+ removeClippedSubviews={false}
+ renderItem={[Function]}
+ scrollEnabled={false}
+ scrollEventThrottle={0.0001}
+ showsVerticalScrollIndicator={true}
+ stickyHeaderIndices={[]}
+ style={
+ [
{
"transform": [
{
"scaleY": -1,
},
],
- }
- }
- >
-
-
-
+
+
+ }
+ }
+ >
+
+
-
-
-
+
+
+ >
+
+
-
-
-
-
- Message6
+
+ Message6
+
-
+
-
-
-
- 2:50 PM
-
-
- ⦁
-
-
- Edited
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ 2:50 PM
+
+
+ ⦁
+
+
+ >
+ Edited
+
-
-
-
- Message5
-
-
-
+ fillRule={0}
+ propList={
+ [
+ "fill",
+ "fillRule",
+ ]
+ }
+ />
+
+
-
-
-
-
- 2:50 PM
-
-
+
+
+
+
+
+
+
- ⦁
-
-
- Edited
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
- Message4
-
-
-
+ fillRule={0}
+ propList={
+ [
+ "fill",
+ "fillRule",
+ ]
+ }
+ />
+
+
-
-
-
- 2:50 PM
-
-
- ⦁
-
-
- Edited
-
-
+ "max": undefined,
+ "min": undefined,
+ "now": undefined,
+ "text": undefined,
+ }
+ }
+ accessible={true}
+ collapsable={false}
+ focusable={true}
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onResponderGrant={[Function]}
+ onResponderMove={[Function]}
+ onResponderRelease={[Function]}
+ onResponderTerminate={[Function]}
+ onResponderTerminationRequest={[Function]}
+ onStartShouldSetResponder={[Function]}
+ >
+
+
+
+
+
+
+
+
+
+ Edit Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copy Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flag Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pin to Conversation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ban User
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Message5
+
+
+
+
+
+
+
+
+ 2:50 PM
+
+
+ ⦁
+
+
+ Edited
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copy Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flag Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pin to Conversation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ban User
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Message4
+
+
+
+
+
+
+
+
+ 2:50 PM
+
+
+ ⦁
+
+
+ Edited
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copy Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flag Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pin to Conversation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ban User
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete Message
+
+
+
+
+
-
-
-
- 05/05/2020
-
-
-
-
-
-
+ 05/05/2020
+
+
+
+
+ }
+ >
+
+
+ "alignItems": "flex-end",
+ "flexDirection": "row",
+ },
+ [
+ {
+ "marginBottom": 12,
+ },
+ {},
+ ],
+ {
+ "justifyContent": "flex-start",
+ "marginTop": 0,
+ },
+ {},
+ ]
+ }
+ testID="message-simple-wrapper"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Message3
+
+
+
+
+
+
+
+
+ 2:50 PM
+
+
+ ⦁
+
+
+ Edited
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ testID="reaction-button-wow"
+ >
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+ Edit Message
+
+
+
+
+
+
-
+
+
+
+
+
+
+ Copy Message
+
+
+
+
+
+
+
- Message3
-
+
+
+
+
+
+
+ Flag Message
-
-
-
-
- 2:50 PM
-
-
+
+
+
+
+
+
+
+
+
+ Pin to Conversation
+
+
+
+
- ⦁
-
-
+
+
+
+
+
+
+
+
+
+
+ Ban User
+
+
+
+
- Edited
-
+ "max": undefined,
+ "min": undefined,
+ "now": undefined,
+ "text": undefined,
+ }
+ }
+ accessible={true}
+ collapsable={false}
+ focusable={true}
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onResponderGrant={[Function]}
+ onResponderMove={[Function]}
+ onResponderRelease={[Function]}
+ onResponderTerminate={[Function]}
+ onResponderTerminationRequest={[Function]}
+ onStartShouldSetResponder={[Function]}
+ >
+
+
+
+
+
+
+
+
+
+
+ Delete Message
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
- Replies
-
+
+
+
+
+
+
+ Replies
+
+
-
-
-
+
-
-
+ />
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+ name="24 / mic_iOS"
+ >
+
+
@@ -1920,82 +7384,21 @@ exports[`Thread should match thread snapshot 1`] = `
style={
[
{
- "borderRadius": 20,
- "borderWidth": 1,
- "flex": 1,
- "marginHorizontal": 10,
- },
- {
- "borderColor": "#ECEBEB",
- "paddingVertical": 12,
+ "flexDirection": "row",
+ "marginHorizontal": 2,
+ "marginTop": 8,
},
{},
- null,
- ]
- }
- >
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
- Also send to channel
-
+ "color": "#7A7A7A",
+ },
+ {},
+ ]
+ }
+ >
+ Also send to channel
+
+
From fa62bc0aa7df8e8b1089dc29e7e2c0b4ef3ca437 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Fri, 27 Sep 2024 14:34:21 +0530
Subject: [PATCH 06/19] fix: chat.test.ts
---
package/src/components/Chat/__tests__/Chat.test.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/package/src/components/Chat/__tests__/Chat.test.js b/package/src/components/Chat/__tests__/Chat.test.js
index d3c6271810..98a9e5fe0d 100644
--- a/package/src/components/Chat/__tests__/Chat.test.js
+++ b/package/src/components/Chat/__tests__/Chat.test.js
@@ -132,7 +132,13 @@ describe('ChatContext', () => {
});
describe('TranslationContext', () => {
- afterEach(cleanup);
+ beforeEach(() => {
+ jest.spyOn(DBSyncManager, 'init');
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ cleanup();
+ });
const chatClient = getTestClient();
it('exposes the translation context', async () => {
let context;
@@ -226,7 +232,6 @@ describe('TranslationContext', () => {
it('makes sure DBSyncManager listeners are cleaned up after Chat remount', async () => {
const chatClientWithUser = await getTestClientWithUser({ id: 'testID' });
- jest.spyOn(DBSyncManager, 'init');
// initial mount and render
const { rerender } = render( );
@@ -249,7 +254,6 @@ describe('TranslationContext', () => {
it('makes sure DBSyncManager listeners are cleaned up if the user changes', async () => {
const chatClientWithUser = await getTestClientWithUser({ id: 'testID1' });
- jest.spyOn(DBSyncManager, 'init');
// initial render
const { rerender } = render( );
@@ -275,7 +279,6 @@ describe('TranslationContext', () => {
it('makes sure DBSyncManager state stays intact during normal rerenders', async () => {
const chatClientWithUser = await getTestClientWithUser({ id: 'testID' });
- jest.spyOn(DBSyncManager, 'init');
// initial render
const { rerender } = render( );
From 705db4cceba123cb5b1fa8f4e617d5619de2c21c Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Fri, 27 Sep 2024 14:34:32 +0530
Subject: [PATCH 07/19] fix: chat.test.ts
---
package/jest.config.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/package/jest.config.js b/package/jest.config.js
index a016e3d429..77a3f2f0f8 100644
--- a/package/jest.config.js
+++ b/package/jest.config.js
@@ -28,5 +28,4 @@ module.exports = {
'^.+\\.[t|j]sx?$': 'babel-jest',
},
transformIgnorePatterns: ['node_modules/!(react-native-reanimated)'],
- verbose: true,
};
From 11202b18d6cdf4e77367bbf7325434944459383f Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Tue, 1 Oct 2024 15:10:01 +0530
Subject: [PATCH 08/19] fix: add tests for the code
---
package/src/components/Channel/Channel.tsx | 4 +
.../Channel/__tests__/ownCapabilities.test.js | 114 +-
.../Channel/hooks/useCreateMessagesContext.ts | 2 +
package/src/components/Message/Message.tsx | 77 +-
.../Message/MessageSimple/ReactionList.tsx | 4 +-
.../hooks/useMessageActionsOverlay.tsx | 49 +
.../Message/utils/messageActions.ts | 12 +-
.../MessageOverlay/MessageActionList.tsx | 4 +-
.../MessageOverlay/MessageActionListItem.tsx | 5 +-
.../MessageOverlay/MessageOverlay.tsx | 55 +-
.../MessageOverlay/OverlayReactionList.tsx | 11 +-
.../MessageOverlay/OverlayReactions.tsx | 17 +-
.../MessageOverlay/OverlayReactionsItem.tsx | 38 +-
.../MessageOverlay/ReactionButton.tsx | 2 +-
.../__tests__/MessageActionList.test.tsx | 4 +-
.../__tests__/MessageActionListItem.test.tsx | 8 +-
.../__tests__/OverlayReactionList.test.tsx | 101 +
.../__tests__/OverlayReactions.test.tsx | 186 +
.../__tests__/OverlayReactionsItem.test.tsx | 81 +
.../__tests__/ReactionButton.test.tsx | 8 +-
.../__snapshots__/Thread.test.js.snap | 6794 ++---------------
.../messageContext/MessageContext.tsx | 2 +-
.../messagesContext/MessagesContext.tsx | 5 +
23 files changed, 1365 insertions(+), 6218 deletions(-)
create mode 100644 package/src/components/Message/hooks/useMessageActionsOverlay.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/OverlayReactionList.test.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/OverlayReactions.test.tsx
create mode 100644 package/src/components/MessageOverlay/__tests__/OverlayReactionsItem.test.tsx
diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx
index 440796043b..25f07f1063 100644
--- a/package/src/components/Channel/Channel.tsx
+++ b/package/src/components/Channel/Channel.tsx
@@ -174,6 +174,7 @@ import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/Typing
import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer';
import { MessageActionList as MessageActionListDefault } from '../MessageOverlay/MessageActionList';
import { MessageActionListItem as MessageActionListItemDefault } from '../MessageOverlay/MessageActionListItem';
+import { MessageOverlay as MessageOverlayDefault } from '../MessageOverlay/MessageOverlay';
import { OverlayReactionList as OverlayReactionListDefault } from '../MessageOverlay/OverlayReactionList';
import { OverlayReactions as OverlayReactionDefault } from '../MessageOverlay/OverlayReactions';
import { OverlayReactionsAvatar as OverlayReactionsAvatarDefault } from '../MessageOverlay/OverlayReactionsAvatar';
@@ -320,6 +321,7 @@ export type ChannelPropsWithContext<
| 'MessageFooter'
| 'MessageHeader'
| 'MessageList'
+ | 'MessageOverlay'
| 'MessagePinnedHeader'
| 'MessageReplies'
| 'MessageRepliesAvatars'
@@ -566,6 +568,7 @@ const ChannelWithContext = <
MessageHeader,
messageId,
MessageList = MessageListDefault,
+ MessageOverlay = MessageOverlayDefault,
MessagePinnedHeader = MessagePinnedHeaderDefault,
MessageReplies = MessageRepliesDefault,
MessageRepliesAvatars = MessageRepliesAvatarsDefault,
@@ -2391,6 +2394,7 @@ const ChannelWithContext = <
MessageFooter,
MessageHeader,
MessageList,
+ MessageOverlay,
MessagePinnedHeader,
MessageReplies,
MessageRepliesAvatars,
diff --git a/package/src/components/Channel/__tests__/ownCapabilities.test.js b/package/src/components/Channel/__tests__/ownCapabilities.test.js
index 7158b30204..a879746ec7 100644
--- a/package/src/components/Channel/__tests__/ownCapabilities.test.js
+++ b/package/src/components/Channel/__tests__/ownCapabilities.test.js
@@ -68,249 +68,249 @@ describe('Own capabilities', () => {
};
const renderChannelAndOpenMessageActionsList = async (targetMessage, props = {}) => {
- const { findByTestId, queryByTestId, queryByText, unmount } = render(getComponent(props));
+ const { findByTestId, queryByLabelText, queryByText, unmount } = render(getComponent(props));
await waitFor(() => queryByText(targetMessage.text));
act(() => {
fireEvent(queryByText(targetMessage.text), 'onLongPress');
});
- await waitFor(() => expect(!!queryByTestId('message-action-list')).toBeTruthy());
+ await waitFor(() => expect(!!queryByLabelText('Message action list')).toBeTruthy());
- return { findByTestId, queryByTestId, queryByText, unmount };
+ return { findByTestId, queryByLabelText, queryByText, unmount };
};
describe(`${allOwnCapabilities.sendReply} capability`, () => {
it(`should render "Thread Reply" message action when ${allOwnCapabilities.sendReply} capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.sendReply]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage);
- expect(!!queryByTestId('threadReply-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage);
+ expect(!!queryByLabelText('threadReply action list item')).toBeTruthy();
});
it('should not render "Thread Reply" message action when "send-reply" capability is disabled', async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage);
- expect(!!queryByTestId('threadReply-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage);
+ expect(!!queryByLabelText('threadReply action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.sendReply" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.sendReply]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage, {
overrideOwnCapabilities: {
sendReply: false,
},
});
- expect(!!queryByTestId('threadReply-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('threadReply action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.banChannelMembers} capability`, () => {
it(`should render "Ban User" message action when ${allOwnCapabilities.banChannelMembers} capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.banChannelMembers]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('banUser-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('banUser action list item')).toBeTruthy();
});
it(`should not render "Ban User" message action when ${allOwnCapabilities.banChannelMembers} capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('banUser-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('banUser action list item')).toBeFalsy();
});
it(`should override capability from "overrideOwnCapability.banChannelMembers" prop`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.banChannelMembers]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
banChannelMembers: false,
},
});
- expect(!!queryByTestId('banUser-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('banUser action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.deleteAnyMessage} capability`, () => {
it(`should render "Delete Message" action for received message when "${allOwnCapabilities.deleteAnyMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities(['delete-any-message']);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('deleteMessage-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeTruthy();
});
it(`should not render "Delete Message" action for received message when "${allOwnCapabilities.deleteAnyMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('deleteMessage-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.deleteAnyMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.deleteAnyMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
deleteAnyMessage: false,
},
});
- expect(!!queryByTestId('deleteMessage-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.deleteOwnMessage} capability`, () => {
it(`should render "Delete Message" action for sent message when "${allOwnCapabilities.deleteOwnMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.deleteOwnMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage);
- expect(!!queryByTestId('deleteMessage-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage);
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeTruthy();
});
it(`should not render "Delete Message" action for sent message when "${allOwnCapabilities.deleteOwnMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage);
- expect(!!queryByTestId('deleteMessage-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage);
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.deleteOwnMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.deleteOwnMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(sentMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(sentMessage, {
overrideOwnCapabilities: {
deleteOwnMessage: false,
},
});
- expect(!!queryByTestId('deleteMessage-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('deleteMessage action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.updateAnyMessage} capability`, () => {
it(`should render "Edit Message" action for received message when "${allOwnCapabilities.updateAnyMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.updateAnyMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('editMessage-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('editMessage action list item')).toBeTruthy();
});
it(`should not render "Edit Message" action for received message when "${allOwnCapabilities.updateAnyMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('editMessage-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('editMessage action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.updateAnyMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.updateAnyMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
updateAnyMessage: false,
},
});
- expect(!!queryByTestId('editMessage-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('editMessage action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.flagMessage} capability`, () => {
it(`should render "Flag Message" action for sent message when "${allOwnCapabilities.flagMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.flagMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('flagMessage-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('flagMessage action list item')).toBeTruthy();
});
it(`should not render "Flag Message" action for sent message when "${allOwnCapabilities.flagMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('flagMessage-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('flagMessage action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.deleteOwnMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.flagMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
flagMessage: false,
},
});
- expect(!!queryByTestId('flagMessage-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('flagMessage action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.pinMessage} capability`, () => {
it(`should render "Pin Message" action for sent message when "${allOwnCapabilities.pinMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.pinMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('pinMessage-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('pinMessage action list item')).toBeTruthy();
});
it(`should not render "Pin Message" action for sent message when "${allOwnCapabilities.pinMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('pinMessage-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('pinMessage action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.pinMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.pinMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
pinMessage: false,
},
});
- expect(!!queryByTestId('pinMessage-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('pinMessage action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.quoteMessage} capability`, () => {
it(`should render "Reply" action for sent message when "${allOwnCapabilities.quoteMessage}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.quoteMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('quotedReply-list-item')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('quotedReply action list item')).toBeTruthy();
});
it(`should not render "Reply" action for sent message when "${allOwnCapabilities.quoteMessage}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('quotedReply-list-item')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('quotedReply action list item')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.quoteMessage" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.quoteMessage]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
quoteMessage: false,
},
});
- expect(!!queryByTestId('quotedReply-list-item')).toBeFalsy();
+ expect(!!queryByLabelText('quotedReply action list item')).toBeFalsy();
});
});
describe(`${allOwnCapabilities.sendReaction} capability`, () => {
it(`should render reaction selector when "${allOwnCapabilities.sendReaction}" capability is enabled`, async () => {
await generateChannelWithCapabilities([allOwnCapabilities.sendReaction]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('overlay-reaction-list')).toBeTruthy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('Reaction Selector on long pressing message')).toBeTruthy();
});
it(`should not render reaction selector when "${allOwnCapabilities.sendReaction}" capability is disabled`, async () => {
await generateChannelWithCapabilities();
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage);
- expect(!!queryByTestId('overlay-reaction-list')).toBeFalsy();
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage);
+ expect(!!queryByLabelText('Reaction Selector on long pressing message')).toBeFalsy();
});
it('should override capability from "overrideOwnCapability.sendReaction" prop', async () => {
await generateChannelWithCapabilities([allOwnCapabilities.sendReaction]);
- const { queryByTestId } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
+ const { queryByLabelText } = await renderChannelAndOpenMessageActionsList(receivedMessage, {
overrideOwnCapabilities: {
sendReaction: false,
},
});
- expect(!!queryByTestId('overlay-reaction-list')).toBeFalsy();
+ expect(!!queryByLabelText('Reaction Selector on long pressing message')).toBeFalsy();
});
});
diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts
index 7f74cf8dca..fbe3bde284 100644
--- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts
+++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts
@@ -65,6 +65,7 @@ export const useCreateMessagesContext = <
MessageFooter,
MessageHeader,
MessageList,
+ MessageOverlay,
MessagePinnedHeader,
MessageReplies,
MessageRepliesAvatars,
@@ -169,6 +170,7 @@ export const useCreateMessagesContext = <
MessageFooter,
MessageHeader,
MessageList,
+ MessageOverlay,
MessagePinnedHeader,
MessageReplies,
MessageRepliesAvatars,
diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx
index 2ef555d2fc..74b1e7a997 100644
--- a/package/src/components/Message/Message.tsx
+++ b/package/src/components/Message/Message.tsx
@@ -1,13 +1,12 @@
-import React, { useMemo, useRef, useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { GestureResponderEvent, Keyboard, StyleProp, View, ViewStyle } from 'react-native';
-import type { BottomSheetModal } from '@gorhom/bottom-sheet';
-
import type { Attachment, UserResponse } from 'stream-chat';
import { useCreateMessageContext } from './hooks/useCreateMessageContext';
import { useMessageActionHandlers } from './hooks/useMessageActionHandlers';
import { useMessageActions } from './hooks/useMessageActions';
+import { useMessageActionsOverlay } from './hooks/useMessageActionsOverlay';
import { useProcessReactions } from './hooks/useProcessReactions';
import { messageActions as defaultMessageActions } from './utils/messageActions';
@@ -47,7 +46,6 @@ import {
isMessageWithStylesReadByAndDateSeparator,
MessageType,
} from '../MessageList/hooks/useMessageList';
-import { MessageOverlay } from '../MessageOverlay/MessageOverlay';
export type TouchableEmitter =
| 'fileAttachment'
@@ -153,8 +151,7 @@ export type MessagePropsWithContext<
| 'handleRetry'
| 'handleThreadReply'
| 'isAttachmentEqual'
- | 'MessageActionList'
- | 'MessageActionListItem'
+ | 'MessageOverlay'
| 'messageActions'
| 'messageContentOrder'
| 'MessageBounce'
@@ -162,10 +159,6 @@ export type MessagePropsWithContext<
| 'onLongPressMessage'
| 'onPressInMessage'
| 'onPressMessage'
- | 'OverlayReactionList'
- | 'OverlayReactions'
- | 'OverlayReactionsAvatar'
- | 'OverlayReactionsItem'
| 'removeMessage'
| 'deleteReaction'
| 'retrySendMessage'
@@ -236,11 +229,14 @@ const MessageWithContext = <
props: MessagePropsWithContext,
) => {
const [isErrorInMessage, setIsErrorInMessage] = useState(false);
+ const [showMessageReactions, setShowMessageReactions] = useState(true);
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
const [isEditedMessageOpen, setIsEditedMessageOpen] = useState(false);
- const [isMessageActionsVisible, setIsMessageActionsVisible] = useState(true);
const isMessageTypeDeleted = props.message.type === 'deleted';
+ const { messageActionsBottomSheetRef, messageOverlayVisible, setMessageOverlayVisible } =
+ useMessageActionsOverlay();
+
const {
channel,
chatContext,
@@ -269,11 +265,10 @@ const MessageWithContext = <
lastReceivedId,
members,
message,
- MessageActionList,
- MessageActionListItem,
messageActions: messageActionsProp = defaultMessageActions,
MessageBounce,
messageContentOrder: messageContentOrderProp,
+ MessageOverlay,
messagesContext,
MessageSimple,
onLongPress: onLongPressProp,
@@ -284,10 +279,6 @@ const MessageWithContext = <
onPressMessage: onPressMessageProp,
onThreadSelect,
openThread,
- OverlayReactionList,
- OverlayReactions,
- OverlayReactionsAvatar,
- OverlayReactionsItem,
preventPress,
removeMessage,
retrySendMessage,
@@ -311,20 +302,15 @@ const MessageWithContext = <
messageSimple: { targetedMessageContainer, targetedMessageUnderlay },
},
} = useTheme();
- const messageActionsBottomSheetRef = useRef(null);
- const openMessageActionsBottomSheet = () => {
- if (messageActionsBottomSheetRef.current?.present) {
- messageActionsBottomSheetRef.current.present();
- } else {
- console.warn('bottom and top insets must be set for the image picker to work correctly');
- }
+ const showMessageOverlay = async (showMessageReactions = false) => {
+ setMessageOverlayVisible(true);
+ setShowMessageReactions(showMessageReactions);
+ await dismissKeyboard();
};
- const closeMessageActionsBottomSheet = () => {
- if (messageActionsBottomSheetRef.current?.dismiss) {
- messageActionsBottomSheetRef.current.dismiss();
- }
+ const closeMessageOverlay = () => {
+ setMessageOverlayVisible(false);
};
const actionsEnabled =
@@ -549,7 +535,7 @@ const MessageWithContext = <
client,
deleteMessage: deleteMessageFromContext,
deleteReaction,
- dismissOverlay: closeMessageActionsBottomSheet,
+ dismissOverlay: closeMessageOverlay,
enforceUniqueReaction,
handleBan,
handleBlock,
@@ -588,11 +574,10 @@ const MessageWithContext = <
blockUser,
copyMessage,
deleteMessage,
- dismissOverlay: closeMessageActionsBottomSheet,
+ dismissOverlay: closeMessageOverlay,
editMessage,
error: isErrorInMessage,
flagMessage,
- isMessageActionsVisible,
isMyMessage,
isThreadMessage,
message,
@@ -601,16 +586,11 @@ const MessageWithContext = <
pinMessage,
quotedReply,
retry,
+ showMessageReactions,
threadReply,
unpinMessage,
});
- const showMessageOverlay = async (isVisible = true) => {
- setIsMessageActionsVisible(isVisible);
- await dismissKeyboard();
- openMessageActionsBottomSheet();
- };
-
const actionHandlers: MessageActionHandlers = {
copyMessage: handleCopyMessage,
deleteMessage: handleDeleteMessage,
@@ -767,20 +747,15 @@ const MessageWithContext = <
{isBounceDialogOpen && }
-
+ {messageOverlayVisible ? (
+
+ ) : null}
diff --git a/package/src/components/Message/MessageSimple/ReactionList.tsx b/package/src/components/Message/MessageSimple/ReactionList.tsx
index d220d244b3..2e35aff0ba 100644
--- a/package/src/components/Message/MessageSimple/ReactionList.tsx
+++ b/package/src/components/Message/MessageSimple/ReactionList.tsx
@@ -211,7 +211,7 @@ const ReactionListWithContext = <
onPress={(event) => {
if (onPress) {
onPress({
- defaultHandler: () => showMessageOverlay(false),
+ defaultHandler: () => showMessageOverlay(true),
emitter: 'reactionList',
event,
});
@@ -220,7 +220,7 @@ const ReactionListWithContext = <
onPressIn={(event) => {
if (onPressIn) {
onPressIn({
- defaultHandler: () => showMessageOverlay(false),
+ defaultHandler: () => showMessageOverlay(true),
emitter: 'reactionList',
event,
});
diff --git a/package/src/components/Message/hooks/useMessageActionsOverlay.tsx b/package/src/components/Message/hooks/useMessageActionsOverlay.tsx
new file mode 100644
index 0000000000..4b6fe3129c
--- /dev/null
+++ b/package/src/components/Message/hooks/useMessageActionsOverlay.tsx
@@ -0,0 +1,49 @@
+import { useEffect, useRef, useState } from 'react';
+
+import { BottomSheetModal } from '@gorhom/bottom-sheet';
+
+/**
+ * Hook to manage the message actions overlay
+ */
+export const useMessageActionsOverlay = () => {
+ const [messageOverlayVisible, setMessageOverlayVisible] = useState(false);
+
+ const messageActionsBottomSheetRef = useRef(null);
+
+ /**
+ * Function to open the message actions bottom sheet
+ */
+ const openMessageActionsBottomSheet = () => {
+ if (messageActionsBottomSheetRef.current?.present) {
+ messageActionsBottomSheetRef.current.present();
+ } else {
+ console.warn('bottom and top insets must be set for the image picker to work correctly');
+ }
+ };
+
+ /**
+ * Function to close the message actions bottom sheet
+ */
+ const closeMessageActionsBottomSheet = () => {
+ if (messageActionsBottomSheetRef.current?.dismiss) {
+ messageActionsBottomSheetRef.current.dismiss();
+ }
+ };
+
+ /**
+ * Effect to open or close the message actions bottom sheet
+ */
+ useEffect(() => {
+ if (messageOverlayVisible) {
+ openMessageActionsBottomSheet();
+ } else {
+ closeMessageActionsBottomSheet();
+ }
+ }, [messageOverlayVisible]);
+
+ return {
+ messageActionsBottomSheetRef,
+ messageOverlayVisible,
+ setMessageOverlayVisible,
+ };
+};
diff --git a/package/src/components/Message/utils/messageActions.ts b/package/src/components/Message/utils/messageActions.ts
index 6d2f6cbccb..61190cf256 100644
--- a/package/src/components/Message/utils/messageActions.ts
+++ b/package/src/components/Message/utils/messageActions.ts
@@ -14,16 +14,16 @@ export type MessageActionsParams<
editMessage: MessageActionType;
error: boolean | Error;
flagMessage: MessageActionType;
- /**
- * Determines if the message actions are visible.
- */
- isMessageActionsVisible: boolean;
isThreadMessage: boolean;
muteUser: MessageActionType;
ownCapabilities: OwnCapabilitiesContextValue;
pinMessage: MessageActionType;
quotedReply: MessageActionType;
retry: MessageActionType;
+ /**
+ * Determines if the message actions are visible.
+ */
+ showMessageReactions: boolean;
threadReply: MessageActionType;
unpinMessage: MessageActionType;
/**
@@ -46,7 +46,6 @@ export const messageActions = <
editMessage,
error,
flagMessage,
- isMessageActionsVisible,
isMyMessage,
isThreadMessage,
message,
@@ -54,10 +53,11 @@ export const messageActions = <
pinMessage,
quotedReply,
retry,
+ showMessageReactions,
threadReply,
unpinMessage,
}: MessageActionsParams) => {
- if (!isMessageActionsVisible) {
+ if (showMessageReactions) {
return [];
}
diff --git a/package/src/components/MessageOverlay/MessageActionList.tsx b/package/src/components/MessageOverlay/MessageActionList.tsx
index 4e7117bc67..9d93e87cdb 100644
--- a/package/src/components/MessageOverlay/MessageActionList.tsx
+++ b/package/src/components/MessageOverlay/MessageActionList.tsx
@@ -21,8 +21,10 @@ export const MessageActionList = (props: MessageActionListProps) => {
},
} = useTheme();
+ if (messageActions?.length === 0) return null;
+
return (
-
+
{messageActions?.map((messageAction, index) => (
{
return (
-
+
{icon}
{title}
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index d2acd5d422..17882298fa 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -6,7 +6,10 @@ import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/typ
import { MessageActionType } from './MessageActionListItem';
-import { MessageContextValue } from '../../contexts/messageContext/MessageContext';
+import {
+ MessageContextValue,
+ useMessageContext,
+} from '../../contexts/messageContext/MessageContext';
import {
MessagesContextValue,
useMessagesContext,
@@ -31,11 +34,7 @@ export type MessageOverlayProps<
* Function to close the message actions bottom sheet
* @returns void
*/
- closeMessageActionsBottomSheet: () => void;
- /**
- * Boolean to determine if there are message actions
- */
- isMessageActionsVisible: boolean;
+ closeMessageOverlay: () => void;
/**
* An array of message actions to render
*/
@@ -44,6 +43,10 @@ export type MessageOverlayProps<
* Reference to the bottom sheet modal
*/
messageActionsBottomSheetRef: React.RefObject;
+ /**
+ * Boolean to determine if there are message actions
+ */
+ showMessageReactions: boolean;
/**
* Function to handle reaction on press
* @param reactionType
@@ -58,10 +61,9 @@ export const MessageOverlay = <
props: MessageOverlayProps,
) => {
const {
- closeMessageActionsBottomSheet,
+ closeMessageOverlay,
handleReaction,
- isMessageActionsVisible,
- message,
+ message: propMessage,
MessageActionList: propMessageActionList,
MessageActionListItem: propMessageActionListItem,
messageActions,
@@ -70,6 +72,7 @@ export const MessageOverlay = <
OverlayReactions: propOverlayReactions,
OverlayReactionsAvatar: propOverlayReactionsAvatar,
OverlayReactionsItem: propOverlayReactionsItem,
+ showMessageReactions,
} = props;
const {
MessageActionList: contextMessageActionList,
@@ -78,7 +81,8 @@ export const MessageOverlay = <
OverlayReactions: contextOverlayReactions,
OverlayReactionsAvatar: contextOverlayReactionsAvatar,
OverlayReactionsItem: contextOverlayReactionsItem,
- } = useMessagesContext();
+ } = useMessagesContext();
+ const { message: contextMessage } = useMessageContext();
const snapPoints = useMemo(() => ['50%', '50%'], []);
const MessageActionList = propMessageActionList ?? contextMessageActionList;
const MessageActionListItem = propMessageActionListItem ?? contextMessageActionListItem;
@@ -86,6 +90,7 @@ export const MessageOverlay = <
const OverlayReactions = propOverlayReactions ?? contextOverlayReactions;
const OverlayReactionsAvatar = propOverlayReactionsAvatar ?? contextOverlayReactionsAvatar;
const OverlayReactionsItem = propOverlayReactionsItem ?? contextOverlayReactionsItem;
+ const message = propMessage ?? contextMessage;
const handleSheetChanges = useCallback((index: number) => {
console.log('handleSheetChanges', index);
@@ -95,33 +100,31 @@ export const MessageOverlay = <
- {isMessageActionsVisible ? (
+ {showMessageReactions ? (
+
+ ) : (
<>
reaction.type) || []}
/>
- {messageActions?.length ? (
-
- ) : null}
+
>
- ) : (
-
)}
diff --git a/package/src/components/MessageOverlay/OverlayReactionList.tsx b/package/src/components/MessageOverlay/OverlayReactionList.tsx
index 9a0fbdc63e..7e2e2c0dfa 100644
--- a/package/src/components/MessageOverlay/OverlayReactionList.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionList.tsx
@@ -8,6 +8,7 @@ import {
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
+import { useOwnCapabilitiesContext } from '../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { triggerHaptic } from '../../native';
import type { DefaultStreamChatGenerics } from '../../types/types';
@@ -54,6 +55,7 @@ export const OverlayReactionList = <
},
},
} = useTheme();
+ const own_capabilities = useOwnCapabilitiesContext();
const supportedReactions = propSupportedReactions || contextSupportedReactions;
@@ -65,8 +67,15 @@ export const OverlayReactionList = <
dismissOverlay();
};
+ if (!own_capabilities.sendReaction) {
+ return null;
+ }
+
return (
-
+
{supportedReactions?.map(({ Icon, type }, index) => (
= Pick<
- MessagesContextValue,
- 'OverlayReactionsAvatar' | 'OverlayReactionsItem' | 'supportedReactions'
+> = Partial<
+ Pick<
+ MessagesContextValue,
+ 'OverlayReactionsAvatar' | 'OverlayReactionsItem' | 'supportedReactions'
+ >
> & {
/**
* The message object
@@ -56,6 +58,7 @@ export const OverlayReactions = (props: OverlayReactionsProps) => {
const supportedReactions = propSupportedReactions ?? contextSupportedReactions;
const OverlayReactionsAvatar = propOverlayReactionsAvatar ?? contextOverlayReactionsAvatar;
const OverlayReactionsItem = propOverlayReactionsItem ?? contextOverlayReactionsItem;
+
const messageReactions = reactionTypes.reduce((acc, reaction) => {
const reactionData = supportedReactions?.find(
(supportedReaction) => supportedReaction.type === reaction,
@@ -107,7 +110,7 @@ export const OverlayReactions = (props: OverlayReactionsProps) => {
);
@@ -120,7 +123,10 @@ export const OverlayReactions = (props: OverlayReactionsProps) => {
};
return (
-
+
{messageReactions?.map(({ Icon, type }, index) => (
{
{!loading ? (
= Pick, 'type'> & {
- /**
- * The fill color for the reaction icon
- */
- pathFill: string;
- /**
- * The size of the reaction icon
- */
- size: number;
- /**
- * An array of supported reactions
- */
- supportedReactions: ReactionData[];
-};
-
-const ReactionIcon = ({ pathFill, size, supportedReactions, type }: ReactionIconProps) => {
- const Icon = supportedReactions.find((reaction) => reaction.type === type)?.Icon || Unknown;
- return ;
-};
-
export const OverlayReactionsItem = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
>({
@@ -86,8 +62,13 @@ export const OverlayReactionsItem = <
radius -
(Number(reactionBubbleBackground.height || 0) || styles.reactionBubbleBackground.height);
+ const Icon = supportedReactions.find((reaction) => reaction.type === type)?.Icon ?? Unknown;
+
return (
-
+
-
diff --git a/package/src/components/MessageOverlay/ReactionButton.tsx b/package/src/components/MessageOverlay/ReactionButton.tsx
index 85e8caf55f..d8c6b0c986 100644
--- a/package/src/components/MessageOverlay/ReactionButton.tsx
+++ b/package/src/components/MessageOverlay/ReactionButton.tsx
@@ -45,13 +45,13 @@ export const ReactionButton = (props: ReactionButtonProps) => {
return (
[
styles.reactionButton,
{ backgroundColor: pressed ? light_blue : white },
buttonContainer,
]}
- testID={`reaction-button-${type}`}
>
{
it('should render correctly with provided message actions', () => {
- const { getByTestId, getByText } = render(
+ const { getByLabelText, getByText } = render(
,
);
- expect(getByTestId('message-action-list')).toBeTruthy();
+ expect(getByLabelText('Message action list')).toBeTruthy();
expect(getByText('Copy Message')).toBeTruthy();
expect(getByText('Delete Message')).toBeTruthy();
diff --git a/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx b/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
index b36f1c2828..ec5ac0f4b3 100644
--- a/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
+++ b/package/src/components/MessageOverlay/__tests__/MessageActionListItem.test.tsx
@@ -21,7 +21,7 @@ describe('MessageActionListItem', () => {
};
it('should render correctly with given props', () => {
- const { getByTestId, getByText } = render(
+ const { getByLabelText, getByText } = render(
,
@@ -31,17 +31,17 @@ describe('MessageActionListItem', () => {
expect(getByText('Icon')).toBeTruthy();
- expect(getByTestId('copyMessage-list-item')).toBeTruthy();
+ expect(getByLabelText('copyMessage action list item')).toBeTruthy();
});
it('should call the action callback when pressed', () => {
- const { getByTestId } = render(
+ const { getByLabelText } = render(
,
);
- fireEvent.press(getByTestId('copyMessage-list-item'));
+ fireEvent.press(getByLabelText('copyMessage action list item'));
expect(mockAction).toHaveBeenCalled();
});
diff --git a/package/src/components/MessageOverlay/__tests__/OverlayReactionList.test.tsx b/package/src/components/MessageOverlay/__tests__/OverlayReactionList.test.tsx
new file mode 100644
index 0000000000..4e3171e165
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/OverlayReactionList.test.tsx
@@ -0,0 +1,101 @@
+import React from 'react';
+
+import { fireEvent, render } from '@testing-library/react-native';
+
+import {
+ MessagesContextValue,
+ MessagesProvider,
+} from '../../../contexts/messagesContext/MessagesContext';
+import {
+ OwnCapabilitiesContextValue,
+ OwnCapabilitiesProvider,
+} from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext';
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+import { defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import { triggerHaptic } from '../../../native';
+import { OverlayReactionList } from '../OverlayReactionList';
+
+jest.mock('../../../native', () => ({
+ triggerHaptic: jest.fn(),
+}));
+
+const mockSupportedReactions = [
+ { Icon: () => null, type: 'like' },
+ { Icon: () => null, type: 'love' },
+];
+
+const defaultProps = {
+ dismissOverlay: jest.fn(),
+ handleReaction: jest.fn(),
+ ownReactionTypes: ['like'],
+};
+
+const renderComponent = (props = {}, ownCapabilities = { sendReaction: true }) =>
+ render(
+
+
+
+
+
+
+ ,
+ );
+
+describe('OverlayReactionList', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders correctly with supported reactions', () => {
+ const { getAllByLabelText, getByLabelText } = renderComponent();
+ expect(getByLabelText('Reaction Selector on long pressing message')).toBeTruthy();
+ expect(getAllByLabelText(/\breaction-button[^\s]+/)).toHaveLength(
+ mockSupportedReactions.length,
+ );
+ });
+
+ it('does not render when sendReaction capability is false', () => {
+ const { queryByLabelText } = renderComponent({}, { sendReaction: false });
+ expect(queryByLabelText('Reaction Selector on long pressing message')).toBeNull();
+ });
+
+ it('marks own reactions as selected', () => {
+ const { getAllByLabelText } = renderComponent();
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+
+ expect(reactionButtons[0].props.accessibilityLabel).toBe('reaction-button-like-selected');
+ expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-unselected');
+ });
+
+ it('calls handleReaction and dismissOverlay when a reaction is pressed', () => {
+ const { getAllByLabelText } = renderComponent();
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+
+ fireEvent.press(reactionButtons[1]);
+
+ expect(defaultProps.handleReaction).toHaveBeenCalledWith('love');
+ expect(defaultProps.dismissOverlay).toHaveBeenCalled();
+ expect(triggerHaptic).toHaveBeenCalledWith('impactLight');
+ });
+
+ it("doesn't call handleReaction when it's not provided", () => {
+ const { getAllByLabelText } = renderComponent({ handleReaction: undefined });
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+
+ fireEvent.press(reactionButtons[1]);
+
+ expect(defaultProps.handleReaction).not.toHaveBeenCalled();
+ });
+
+ it('uses provided supportedReactions prop over context value', () => {
+ const customSupportedReactions = [
+ { Icon: () => null, type: 'wow' },
+ { Icon: () => null, type: 'haha' },
+ ];
+ const { getAllByLabelText } = renderComponent({ supportedReactions: customSupportedReactions });
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+ expect(reactionButtons).toHaveLength(customSupportedReactions.length);
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/OverlayReactions.test.tsx b/package/src/components/MessageOverlay/__tests__/OverlayReactions.test.tsx
new file mode 100644
index 0000000000..59029779ad
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/OverlayReactions.test.tsx
@@ -0,0 +1,186 @@
+import React from 'react';
+
+import { Text } from 'react-native';
+
+import { fireEvent, render } from '@testing-library/react-native';
+
+import { ReactionResponse } from 'stream-chat';
+
+import { MessageType } from '../../../components/MessageList/hooks/useMessageList';
+import {
+ MessagesContextValue,
+ MessagesProvider,
+} from '../../../contexts/messagesContext/MessagesContext';
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+import { defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import {
+ TranslationContextValue,
+ TranslationProvider,
+} from '../../../contexts/translationContext/TranslationContext';
+import { generateMessage } from '../../../mock-builders/generator/message';
+import * as useFetchReactionsModule from '../hooks/useFetchReactions';
+import { OverlayReactions } from '../OverlayReactions';
+import { OverlayReactionsItemProps } from '../OverlayReactionsItem';
+
+const mockTranslations = {
+ t: jest.fn((key) => key),
+};
+
+const mockSupportedReactions = [
+ { Icon: () => null, type: 'like' },
+ { Icon: () => null, type: 'love' },
+];
+
+const defaultProps = {
+ message: {
+ ...generateMessage(),
+ reaction_groups: { like: { count: 1, sum_scores: 1 }, love: { count: 1, sum_scores: 1 } },
+ } as unknown as MessageType,
+ supportedReactions: mockSupportedReactions,
+};
+
+describe('OverlayReactions when the supportedReactions are defined', () => {
+ beforeAll(() => {
+ const mockLoadNextPage = jest.fn();
+
+ const mockUseFetchReactions = jest.spyOn(useFetchReactionsModule, 'useFetchReactions');
+ mockUseFetchReactions.mockReturnValue({
+ loading: false,
+ loadNextPage: mockLoadNextPage,
+ reactions: [
+ {
+ type: 'like',
+ user: { id: '1', image: 'user1.jpg', name: 'User 1' },
+ } as unknown as ReactionResponse,
+ {
+ type: 'love',
+ user: { id: '2', image: 'user2.jpg', name: 'User 2' },
+ } as unknown as ReactionResponse,
+ ],
+ });
+ });
+ const renderComponent = (props = {}) =>
+ render(
+
+
+ null,
+ OverlayReactionsItem: (props: OverlayReactionsItemProps) => (
+ {props.reaction.id + ' ' + props.reaction.type}
+ ),
+ } as unknown as MessagesContextValue
+ }
+ >
+
+
+
+ ,
+ );
+
+ it('renders correctly', () => {
+ const { getByLabelText, getByText } = renderComponent();
+ expect(getByLabelText('User Reactions on long press message')).toBeTruthy();
+ expect(getByText('Message Reactions')).toBeTruthy();
+ });
+
+ it('renders reaction buttons', () => {
+ const { getByLabelText } = renderComponent();
+ const likeReactionButton = getByLabelText('reaction-button-like-selected');
+ expect(likeReactionButton).toBeDefined();
+ const loveReactionButton = getByLabelText('reaction-button-love-unselected');
+ expect(loveReactionButton).toBeDefined();
+ });
+
+ it('selects the first reaction by default', () => {
+ const { getAllByLabelText } = renderComponent();
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+ expect(reactionButtons[0].props.accessibilityLabel).toBe('reaction-button-like-selected');
+ expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-unselected');
+ });
+
+ it('changes selected reaction when a reaction button is pressed', () => {
+ const { getAllByLabelText } = renderComponent();
+ const reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
+
+ fireEvent.press(reactionButtons[1]);
+
+ expect(reactionButtons[0].props.accessibilityLabel).toBe('reaction-button-like-unselected');
+ expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-selected');
+ });
+
+ it('renders reactions list', async () => {
+ const { getByText } = renderComponent();
+ const reactionItems = await getByText('1 like');
+ expect(reactionItems).toBeDefined();
+ });
+
+ it('uses provided reactions when passed as a prop', () => {
+ const propReactions = [{ id: '3', image: 'user3.jpg', name: 'User 3', type: 'wow' }];
+ const { getByText } = renderComponent({ reactions: propReactions });
+ const reactionItem = getByText('3 wow');
+ expect(reactionItem).toBeDefined();
+ });
+
+ it("don't render reaction buttons that is of unsupported type", () => {
+ const { queryAllByLabelText } = renderComponent({
+ message: { ...generateMessage(), reaction_groups: { money: 1 } },
+ });
+ const reactionButtons = queryAllByLabelText(/\breaction-button[^\s]+/);
+
+ expect(reactionButtons.length).toBe(0);
+ });
+});
+
+const renderComponent = (props = {}) =>
+ render(
+
+
+ null,
+ OverlayReactionsItem: (props: OverlayReactionsItemProps) => (
+ {props.reaction.id + ' ' + props.reaction.type}
+ ),
+ } as unknown as MessagesContextValue
+ }
+ >
+
+
+
+ ,
+ );
+
+describe("OverlayReactions when the supportedReactions aren't defined", () => {
+ it("don't render reaction buttons that is of unsupported type", () => {
+ const { queryAllByLabelText } = renderComponent({
+ message: { ...generateMessage(), reaction_groups: undefined },
+ supportedReactions: undefined,
+ });
+ const reactionButtons = queryAllByLabelText(/\breaction-button[^\s]+/);
+
+ expect(reactionButtons.length).toBe(0);
+ });
+});
+
+describe('OverlayReactions when the reactions are in loading phase', () => {
+ beforeAll(() => {
+ const mockLoadNextPage = jest.fn();
+
+ const mockUseFetchReactions = jest.spyOn(useFetchReactionsModule, 'useFetchReactions');
+ mockUseFetchReactions.mockReturnValue({
+ loading: true,
+ loadNextPage: mockLoadNextPage,
+ reactions: [],
+ });
+ });
+
+ it("don't render reactions flatlist when loading is false", () => {
+ const { queryByLabelText } = renderComponent();
+ const reactionsFlatList = queryByLabelText('reaction-flat-list');
+
+ expect(reactionsFlatList).toBeNull();
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/OverlayReactionsItem.test.tsx b/package/src/components/MessageOverlay/__tests__/OverlayReactionsItem.test.tsx
new file mode 100644
index 0000000000..0cd91983b9
--- /dev/null
+++ b/package/src/components/MessageOverlay/__tests__/OverlayReactionsItem.test.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+
+import { render } from '@testing-library/react-native';
+
+import { ChatContextValue, ChatProvider } from '../../../contexts/chatContext/ChatContext';
+import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext';
+
+import { Colors, defaultTheme } from '../../../contexts/themeContext/utils/theme';
+import { getTestClientWithUser } from '../../../mock-builders/mock';
+import { OverlayReactionsItem } from '../OverlayReactionsItem';
+
+jest.mock('../../../icons', () => ({
+ Unknown: () => null,
+}));
+
+const mockReaction = {
+ id: 'user1',
+ name: 'John Doe',
+ type: 'like',
+};
+
+const mockSupportedReactions = [
+ { Icon: () => null, type: 'like' },
+ { Icon: () => null, type: 'love' },
+];
+
+const MockOverlayReactionsAvatar = () => null;
+
+const renderComponent = async (props = {}, clientUserID = 'user2') =>
+ render(
+
+
+
+
+ ,
+ );
+
+describe('OverlayReactionsItem', () => {
+ it('renders correctly', async () => {
+ const { getByLabelText, getByText } = await renderComponent();
+ expect(getByLabelText('Individual User Reaction on long press message')).toBeTruthy();
+ expect(getByText('John Doe')).toBeTruthy();
+ });
+
+ it('aligns reaction bubble to the right for other users', async () => {
+ const { getByLabelText } = await renderComponent();
+ const container = getByLabelText('Individual User Reaction on long press message');
+ const reactionBubble = container.props.children[0].props.children[1];
+ expect(reactionBubble.props.style[1].borderColor).toBe(Colors.grey_gainsboro);
+ });
+
+ it('aligns reaction bubble to the left for the current user', async () => {
+ const { getByLabelText } = await renderComponent({}, 'user1');
+ const container = getByLabelText('Individual User Reaction on long press message');
+ const reactionBubble = container.props.children[0].props.children[1];
+ expect(reactionBubble.props.style[1].borderColor).toBe(Colors.white);
+ });
+
+ it('uses Unknown icon for unsupported reaction types', async () => {
+ const { getByLabelText } = await renderComponent({
+ reaction: { ...mockReaction, type: 'unsupported' },
+ });
+ const container = getByLabelText('Individual User Reaction on long press message');
+ const reactionIcon = container.props.children[0].props.children[1].props.children;
+ expect(reactionIcon.type.name).toBe('Unknown');
+ });
+
+ it('uses correct icon for supported reaction types', async () => {
+ const { getByLabelText } = await renderComponent();
+ const container = getByLabelText('Individual User Reaction on long press message');
+ const reactionIcon = container.props.children[0].props.children[1].props.children;
+ expect(reactionIcon.type).not.toBe('Unknown');
+ });
+});
diff --git a/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx b/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
index 4a49f224de..55451447d7 100644
--- a/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
+++ b/package/src/components/MessageOverlay/__tests__/ReactionButton.test.tsx
@@ -38,27 +38,27 @@ describe('ReactionButton', () => {
});
it('should call onPress function with the correct reaction type when pressed', () => {
- const { getByTestId } = render(
+ const { getByLabelText } = render(
,
);
// Simulate a press event
- fireEvent.press(getByTestId('reaction-button-like'));
+ fireEvent.press(getByLabelText('reaction-button-like-unselected'));
// Verify if the mock function has been called with the correct reaction type
expect(mockOnPress).toHaveBeenCalledWith('like');
});
it('should not call onPress when the onPress prop is not provided', () => {
- const { getByTestId } = render(
+ const { getByLabelText } = render(
,
);
- fireEvent.press(getByTestId('reaction-button-like'));
+ fireEvent.press(getByLabelText('reaction-button-like-unselected'));
expect(mockOnPress).not.toHaveBeenCalled();
});
diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
index 0564079b6a..c43de453c3 100644
--- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
+++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap
@@ -532,4576 +532,317 @@ exports[`Thread should match thread snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+ testID="avatar-image"
+ />
+
+
+
+
-
-
-
-
-
+ >
+
+ Message5
+
+
+
+
-
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Copy Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Flag Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pin to Conversation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Ban User
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Delete Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message5
-
-
-
-
-
-
-
-
- 2:50 PM
-
-
- ⦁
-
-
- Edited
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Copy Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Flag Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pin to Conversation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Ban User
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Delete Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message4
-
-
-
-
-
-
-
-
- 2:50 PM
-
-
- ⦁
-
-
- Edited
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Copy Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Flag Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pin to Conversation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Ban User
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Delete Message
-
-
-
+ Edited
+
@@ -5109,1669 +850,668 @@ exports[`Thread should match thread snapshot 1`] = `
-
-
- 05/05/2020
-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
+
-
-
-
-
- Message3
-
-
-
-
-
-
-
-
- 2:50 PM
-
-
- ⦁
-
-
- Edited
-
-
+ testID="avatar-image"
+ />
-
+
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+ "maxWidth": 250,
+ "paddingHorizontal": 16,
+ },
+ {},
+ undefined,
+ ]
+ }
+ testID="message-text-container"
+ >
-
-
-
-
-
+ Message4
+
+
+
+
+
+
+
+ 2:50 PM
+
+
+ ⦁
+
+
+ Edited
+
+
+
+
+
+
+
+
+
+
+ 05/05/2020
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Copy Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Flag Message
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pin to Conversation
-
-
+ testID="avatar-image"
+ />
+
+
+
+
-
-
-
-
-
-
-
-
- Ban User
-
-
-
-
-
-
-
-
-
-
-
-
+ Message3
+
+
-
- Delete Message
-
+
+
+ 2:50 PM
+
+
+ ⦁
+
+
+ Edited
+
+
diff --git a/package/src/contexts/messageContext/MessageContext.tsx b/package/src/contexts/messageContext/MessageContext.tsx
index e482e19f5d..081f17a408 100644
--- a/package/src/contexts/messageContext/MessageContext.tsx
+++ b/package/src/contexts/messageContext/MessageContext.tsx
@@ -125,7 +125,7 @@ export type MessageContextValue<
reactions: ReactionSummary[];
/** React set state function to set the state of `isEditedMessageOpen` */
setIsEditedMessageOpen: React.Dispatch>;
- showMessageOverlay: (isMessageActionsVisible?: boolean, error?: boolean) => void;
+ showMessageOverlay: (showMessageReactions?: boolean, error?: boolean) => void;
showMessageStatus: boolean;
/** Whether or not the Message is part of a Thread */
threadList: boolean;
diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx
index d29e449c3a..c38a160093 100644
--- a/package/src/contexts/messagesContext/MessagesContext.tsx
+++ b/package/src/contexts/messagesContext/MessagesContext.tsx
@@ -50,6 +50,7 @@ import type {
MessageActionListItemProps,
MessageActionType,
} from '../../components/MessageOverlay/MessageActionListItem';
+import { MessageOverlayProps } from '../../components/MessageOverlay/MessageOverlay';
import type { OverlayReactionListProps } from '../../components/MessageOverlay/OverlayReactionList';
import { OverlayReactionsProps } from '../../components/MessageOverlay/OverlayReactions';
import { OverlayReactionsAvatarProps } from '../../components/MessageOverlay/OverlayReactionsAvatar';
@@ -203,6 +204,10 @@ export type MessagesContextValue<
*/
MessageFooter: React.ComponentType>;
MessageList: React.ComponentType>;
+ /**
+ * UI component for MessageOverlay
+ */
+ MessageOverlay: React.ComponentType>;
/**
* Custom message pinned component
*/
From b6dc01edcd81fa12477250c488969bf8bbe3cdf2 Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Wed, 2 Oct 2024 11:29:06 +0530
Subject: [PATCH 09/19] fix: use rn animated to create new modal and add docs
---
.../docs/reactnative/basics/navigation.mdx | 2 +-
.../reactnative/basics/troubleshooting.mdx | 2 +-
.../message-context/show_message_overlay.mdx | 6 +-
.../contexts/message-overlay-context/data.mdx | 83 ---------
.../props/message-action-list-item.mdx} | 6 +-
.../props/message-action-list.mdx} | 6 +-
.../channel/props/message-overlay.mdx | 5 +
.../props/message-text-number-of-lines.mdx} | 0
.../ui-components/channel/props/message.mdx | 5 +
...ion_list.mdx => overlay-reaction-list.mdx} | 8 +-
.../props/overlay-reactions-avatar.mdx | 5 +
.../channel/props/overlay-reactions-item.mdx | 5 +
.../channel/props/overlay-reactions.mdx | 5 +
.../props/overlay_reaction_list.mdx | 5 -
.../props/overlay_reactions.mdx | 5 -
.../props/overlay_reactions_avatar.mdx | 5 -
.../contexts/message-overlay-context.mdx | 42 -----
.../reactnative/contexts/messages-context.mdx | 37 +++-
.../reactnative/contexts/overlay-context.mdx | 9 +-
.../reactnative/core-components/channel.mdx | 44 ++++-
.../core-components/overlay-provider.mdx | 51 +-----
.../guides/custom-message-actions.mdx | 41 +++--
.../ui-components/overlay-reaction-list.mdx | 5 -
docusaurus/sidebars-react-native.json | 1 -
package/src/components/Attachment/Card.tsx | 2 +-
package/src/components/Channel/Channel.tsx | 7 +-
.../ChannelListFooterLoadingIndicator.tsx | 2 +-
.../components/AnimatedGalleryVideo.tsx | 2 +-
.../Indicators/LoadingIndicator.tsx | 2 +-
package/src/components/Message/Message.tsx | 113 ++++--------
.../Message/hooks/useCreateMessageContext.ts | 2 +
.../hooks/useMessageActionsOverlay.tsx | 49 ------
.../MessageOverlay/MessageActionList.tsx | 5 +
.../MessageOverlay/MessageOverlay.tsx | 44 ++---
.../MessageOverlay/OverlayReactionList.tsx | 1 +
.../UIComponents/BottomSheetModal.tsx | 161 ++++++++++++++++++
.../{ => UIComponents}/ImageBackground.tsx | 0
.../{Spinner => UIComponents}/Spinner.tsx | 0
package/src/components/UIComponents/index.ts | 3 +
package/src/components/index.ts | 4 +-
.../messageContext/MessageContext.tsx | 11 +-
41 files changed, 383 insertions(+), 408 deletions(-)
delete mode 100644 docusaurus/docs/reactnative/common-content/contexts/message-overlay-context/data.mdx
rename docusaurus/docs/reactnative/common-content/ui-components/{overlay-provider/props/message_action_list_item.mdx => channel/props/message-action-list-item.mdx} (51%)
rename docusaurus/docs/reactnative/common-content/ui-components/{overlay-provider/props/message_action_list.mdx => channel/props/message-action-list.mdx} (53%)
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx
rename docusaurus/docs/reactnative/common-content/ui-components/{overlay-provider/props/message_text_number_of_lines.mdx => channel/props/message-text-number-of-lines.mdx} (100%)
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/message.mdx
rename docusaurus/docs/reactnative/common-content/ui-components/channel/props/{overlay_reaction_list.mdx => overlay-reaction-list.mdx} (57%)
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
delete mode 100644 docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx
delete mode 100644 docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions.mdx
delete mode 100644 docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions_avatar.mdx
delete mode 100644 docusaurus/docs/reactnative/contexts/message-overlay-context.mdx
delete mode 100644 package/src/components/Message/hooks/useMessageActionsOverlay.tsx
create mode 100644 package/src/components/UIComponents/BottomSheetModal.tsx
rename package/src/components/{ => UIComponents}/ImageBackground.tsx (100%)
rename package/src/components/{Spinner => UIComponents}/Spinner.tsx (100%)
create mode 100644 package/src/components/UIComponents/index.ts
diff --git a/docusaurus/docs/reactnative/basics/navigation.mdx b/docusaurus/docs/reactnative/basics/navigation.mdx
index 2afba8ce58..a0f1ed0c2a 100644
--- a/docusaurus/docs/reactnative/basics/navigation.mdx
+++ b/docusaurus/docs/reactnative/basics/navigation.mdx
@@ -8,7 +8,7 @@ import TabItem from '@theme/TabItem';
Stream Chat for React Native provides many features out of the box that require positioning the components on the screen in a certain manner to get the desired UI.
-The `AttachmentPicker`, `ImageGallery`, and `MessageOverlay`, all need to be rendered in front of other components to have the desired effect. All of these elements are controlled by the `OverlayProvider`. When used together with navigation, certain steps are needed to be taken to make these components appear fluently.
+The `AttachmentPicker` and `ImageGallery`, all need to be rendered in front of other components to have the desired effect. All of these elements are controlled by the `OverlayProvider`. When used together with navigation, certain steps are needed to be taken to make these components appear fluently.
The guidance provided makes the assumption you are using [React Navigation](https://reactnavigation.org/) in your app in conjunction with [`createStackNavigator`](https://reactnavigation.org/docs/stack-navigator/).
diff --git a/docusaurus/docs/reactnative/basics/troubleshooting.mdx b/docusaurus/docs/reactnative/basics/troubleshooting.mdx
index 8250a3c6cb..fb8ca98868 100644
--- a/docusaurus/docs/reactnative/basics/troubleshooting.mdx
+++ b/docusaurus/docs/reactnative/basics/troubleshooting.mdx
@@ -295,7 +295,7 @@ This includes ensuring you import `react-native-gesture-handler` at the top of y
import 'react-native-gesture-handler';
```
-Also do not forget to wrap your component tree(probably above `OverlayProvider`) with `` or `gestureHandlerRootHOC` as mentioned in [RNGH Installation docs](https://docs.swmansion.com/react-native-gesture-handler/docs/installation#js). Not doing so, can cause unusual behaviour with the `MessageOverlay` and `Imagegallery` gestures.
+Also do not forget to wrap your component tree(probably above `OverlayProvider`) with `` or `gestureHandlerRootHOC` as mentioned in [RNGH Installation docs](https://docs.swmansion.com/react-native-gesture-handler/docs/installation#js). Not doing so, can cause unusual behaviour with the `Imagegallery` gestures.
```tsx
diff --git a/docusaurus/docs/reactnative/common-content/contexts/message-context/show_message_overlay.mdx b/docusaurus/docs/reactnative/common-content/contexts/message-context/show_message_overlay.mdx
index cd85f03110..98bde0d530 100644
--- a/docusaurus/docs/reactnative/common-content/contexts/message-context/show_message_overlay.mdx
+++ b/docusaurus/docs/reactnative/common-content/contexts/message-context/show_message_overlay.mdx
@@ -1,5 +1,5 @@
Function to open the message action overlay. This function gets called when user long presses a message.
-| Type |
-| -------- |
-| function |
+| Type |
+| ------------------------------------------ |
+| `(showMessageReactions?: boolean) => void` |
diff --git a/docusaurus/docs/reactnative/common-content/contexts/message-overlay-context/data.mdx b/docusaurus/docs/reactnative/common-content/contexts/message-overlay-context/data.mdx
deleted file mode 100644
index 9087fba502..0000000000
--- a/docusaurus/docs/reactnative/common-content/contexts/message-overlay-context/data.mdx
+++ /dev/null
@@ -1,83 +0,0 @@
-import MessageActions from '../../ui-components/channel/props/message_actions.mdx';
-import SupportedReactions from '../../ui-components/channel/props/supported_reactions.mdx';
-import OverlayReactionList from '../../ui-components/overlay-provider/props/overlay_reaction_list.mdx';
-import HandleReaction from '../../ui-components/channel/props/handle_reaction.mdx';
-
-import Alignment from '../message-context/alignment.mdx';
-import Files from '../message-context/files.mdx';
-import GroupStyles from '../message-context/group_styles.mdx';
-import Images from '../message-context/images.mdx';
-import MessageProp from '../message-context/message.mdx';
-import OnlyEmojis from '../message-context/only_emojis.mdx';
-import OtherAttachments from '../message-context/other_attachments.mdx';
-import ThreadList from '../message-context/thread_list.mdx';
-
-This is an object with following keys:
-
-- `alignment`
-
-
-
-- `clientId`
-
-Id of the current user connected to the chat.
-
-| Type |
-| ------ |
-| String |
-
-- `files`
-
-
-
-- `groupStyles`
-
-
-
-- `handleReaction`
-
-
-
-- `images`
-
-
-
-- `message`
-
-
-
-- `messageActions`
-
-
-
-- `messageReactionTitle`
-
-Title for `MessageReactions` component.
-
-| Type | Default |
-| ------ | ------------------- |
-| String | "Message Reactions" |
-
-- `messagesContext`
-
-Entire value object of [MessagesContext](../../../contexts/messages-context.mdx#value)
-
-- `onlyEmojis`
-
-
-
-- `otherAttachments`
-
-
-
-- `OverlayReactionList`
-
-
-
-- `supportedReactions`
-
-
-
-- `threadList`
-
-
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list_item.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
similarity index 51%
rename from docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list_item.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
index 22b1fe5171..e19fa3a21f 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list_item.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
@@ -1,5 +1,5 @@
Component for rendering message action list items within a message action list.
-| Type | Default |
-| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [MessageActionListItem](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionListItem.tsx) |
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageActionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionListItem.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
similarity index 53%
rename from docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
index 65610973a3..775dad267d 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_action_list.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
@@ -1,5 +1,5 @@
Component for rendering a message action list within the message overlay.
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [[MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionList.tsx) |
+| Type | Default |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageActionList`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionList.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx
new file mode 100644
index 0000000000..03fda384fd
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx
@@ -0,0 +1,5 @@
+Component to customize the message contextual menu which allows users to perform actions on a message and react to messages.
+
+| Type | Default |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageOverlay`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageOverlay.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_text_number_of_lines.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-text-number-of-lines.mdx
similarity index 100%
rename from docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/message_text_number_of_lines.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-text-number-of-lines.mdx
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message.mdx
new file mode 100644
index 0000000000..f46b0dd7c5
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message.mdx
@@ -0,0 +1,5 @@
+Component to render a specific message bubble, within [`MessageList`](../../../../ui-components/message-list.mdx).
+
+| Type | Default |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`Message`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/Message/Message.tsx) |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay_reaction_list.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
similarity index 57%
rename from docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay_reaction_list.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
index 72efaac957..c274f55aea 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay_reaction_list.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
@@ -1,5 +1,5 @@
-Component to render reaction list within message overlay, which shows up when user long presses a message.
+Reaction selector component displayed within the message overlay when user long presses a message.
-| Type | Default |
-| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [`OverlayReactionList`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionList.tsx) |
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| ComponentType | [`OverlayReactionList`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionList.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx
new file mode 100644
index 0000000000..8e21fa9cc0
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx
@@ -0,0 +1,5 @@
+Component for rendering an avatar in the message reaction overlay.
+
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| ComponentType | [`OverlayReactionsAvatar`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx
new file mode 100644
index 0000000000..94fed903f8
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx
@@ -0,0 +1,5 @@
+Component prop to customize the individual user reaction item in the overlay reactions. This includes the avatar, reaction type, and the name of the person who has reacted, etc.
+
+| Type | Default |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`OverlayReactionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionListItem.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
new file mode 100644
index 0000000000..c4ca8da77b
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
@@ -0,0 +1,5 @@
+List of reactions component within the message overlay.
+
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| ComponentType | [`OverlayReactions`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactions.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx b/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx
deleted file mode 100644
index 83bbb2cdfc..0000000000
--- a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
-Reaction selector component displayed within the message overlay when user long presses a message.
-
-| Type | Default |
-| ------------- | -------------------------------------------------------------------------- |
-| ComponentType | [OverlayReactionList](../../../../ui-components/overlay-reaction-list.mdx) |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions.mdx b/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions.mdx
deleted file mode 100644
index 3c88e62df5..0000000000
--- a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
-List of reactions component within the message overlay.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------- |
-| ComponentType | [OverlayReactions](../../../../ui-components/overlay-reactions.mdx) |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions_avatar.mdx b/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions_avatar.mdx
deleted file mode 100644
index ed6109518b..0000000000
--- a/docusaurus/docs/reactnative/common-content/ui-components/overlay-provider/props/overlay_reactions_avatar.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
-Component for rendering a avatar in the message reaction overlay.
-
-| Type | Default |
-| ------------- | -------------------------------------------------------------------------------- |
-| ComponentType | [OverlayReactionsAvatar](../../../../ui-components/overlay-reactions-avatar.mdx) |
diff --git a/docusaurus/docs/reactnative/contexts/message-overlay-context.mdx b/docusaurus/docs/reactnative/contexts/message-overlay-context.mdx
deleted file mode 100644
index ff9f4ad9c3..0000000000
--- a/docusaurus/docs/reactnative/contexts/message-overlay-context.mdx
+++ /dev/null
@@ -1,42 +0,0 @@
----
-id: message-overlay-context
-title: MessageOverlayContext
----
-
-import OverlayReactionList from '../common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx';
-import OverlayReactions from '../common-content/ui-components/overlay-provider/props/overlay_reactions.mdx';
-import OverlayReactionsAvatar from '../common-content/ui-components/overlay-provider/props/overlay_reactions_avatar.mdx';
-
-import MessageOverlayContextData from '../common-content/contexts/message-overlay-context/data.mdx';
-
-`MessageOverlayContext` is a context provided by `OverlayProvider`, to provide values for message overlay. Message overlay shows up when user long presses a message.
-
-## Value
-
-
-
-
-
-### `setData`
-
-Setter function for [data](#data).
-
-| Type |
-| -------- |
-| function |
-
-### data
-
-
-
-### _forwarded from [OverlayProvider](../core-components/overlay-provider.mdx#overlayreactionlist)_ props
OverlayReactionList {#overlayreactionlist}
-
-
-
-### _forwarded from [OverlayProvider](../core-components/overlay-provider.mdx#overlayreactions)_ props
OverlayReactions {#overlayreactions}
-
-
-
-### _forwarded from [OverlayProvider](../core-components/overlay-provider.mdx#overlayreactionsavatar)_ props
OverlayReactionsAvatar {#overlayreactionsavatar}
-
-
diff --git a/docusaurus/docs/reactnative/contexts/messages-context.mdx b/docusaurus/docs/reactnative/contexts/messages-context.mdx
index 455c1cc30b..0d25640ba6 100644
--- a/docusaurus/docs/reactnative/contexts/messages-context.mdx
+++ b/docusaurus/docs/reactnative/contexts/messages-context.mdx
@@ -42,6 +42,8 @@ import InlineUnreadIndicator from '../common-content/ui-components/channel/props
import IsAttachmentEqual from '../common-content/ui-components/channel/props/is_attachment_equal.mdx';
import LegacyImageViewerSwipeBehaviour from '../common-content/ui-components/channel/props/legacy_image_viewer_swipe_behaviour.mdx';
import MarkdownRules from '../common-content/ui-components/channel/props/markdown_rules.mdx';
+import MessageActionList from '../common-content/ui-components/channel/props/message-action-list.mdx';
+import MessageActionListItem from '../common-content/ui-components/channel/props/message-action-list-item.mdx';
import MessageAvatar from '../common-content/ui-components/channel/props/message-avatar.mdx';
import MessageBounce from '../common-content/ui-components/channel/props/message-bounce.mdx';
import MessageContent from '../common-content/ui-components/channel/props/message-content.mdx';
@@ -52,17 +54,22 @@ import MessageEditedTimestamp from '../common-content/ui-components/channel/prop
import MessageError from '../common-content/ui-components/channel/props/message-error.mdx';
import MessageFooter from '../common-content/ui-components/channel/props/message-footer.mdx';
import MessageHeader from '../common-content/ui-components/channel/props/message-header.mdx';
+import MessageOverlay from '../common-content/ui-components/channel/props/message-overlay.mdx';
import MessageReplies from '../common-content/ui-components/channel/props/message-replies.mdx';
import MessageRepliesAvatars from '../common-content/ui-components/channel/props/message-replies-avatars.mdx';
import MessageSimple from '../common-content/ui-components/channel/props/message-simple.mdx';
import MessageStatus from '../common-content/ui-components/channel/props/message-status.mdx';
import MessageSystem from '../common-content/ui-components/channel/props/message-system.mdx';
import MessageText from '../common-content/ui-components/channel/props/message-text.mdx';
+import MessageTextNumberOfLines from '../common-content/ui-components/channel/props/message-text-number-of-lines.mdx';
import MyMessageTheme from '../common-content/ui-components/channel/props/my_message_theme.mdx';
import OnLongPressMessage from '../common-content/ui-components/channel/props/on_long_press_message.mdx';
import OnPressInMessage from '../common-content/ui-components/channel/props/on_press_in_message.mdx';
import OnPressMessage from '../common-content/ui-components/channel/props/on_press_message.mdx';
-import OverlayReactionList from '../common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx';
+import OverlayReactionList from '../common-content/ui-components/channel/props/overlay-reaction-list.mdx';
+import OverlayReactionsAvatar from '../common-content/ui-components/channel/props/overlay-reactions-avatar.mdx';
+import OverlayReactionsItem from '../common-content/ui-components/channel/props/overlay-reactions-item.mdx';
+import OverlayReactions from '../common-content/ui-components/channel/props/overlay-reactions.mdx';
import ReactionList from '../common-content/ui-components/channel/props/reaction-list.mdx';
import Reply from '../common-content/ui-components/channel/props/reply.mdx';
import ScrollToBottomButton from '../common-content/ui-components/channel/props/scroll-to-bottom-button.mdx';
@@ -183,6 +190,10 @@ Id of current channel.
+### _forwarded from [Channel](../../core-components/channel#messagetextnumberoflines)_ props
messageTextNumberOfLines {#messagetextnumberoflines}
+
+
+
### _forwarded from [Channel](../../core-components/channel#onlongpressmessage)_ props
onLongPressMessage {#onlongpressmessage}
@@ -327,6 +338,14 @@ Upserts a given message in local channel state. Please note that this function d
-->
+### _forwarded from [Channel](../../core-components/channel#messageactionlist)_ props
MessageActionList {#messageactionlist}
+
+
+
+### _forwarded from [Channel](../../core-components/channel#messageactionlistitem)_ props
MessageActionListItem {#messageactionlistitem}
+
+
+
### _forwarded from [Channel](../../core-components/channel#messageavatar)_ props
MessageAvatar {#messageavatar}
@@ -359,6 +378,10 @@ Upserts a given message in local channel state. Please note that this function d
+### _forwarded from [Channel](../../core-components/channel#messageoverlay)_ props
MessageOverlay {#messageoverlay}
+
+
+
@@ -391,6 +414,18 @@ Upserts a given message in local channel state. Please note that this function d
+### _forwarded from [Channel](../../core-components/channel#overlayreactionsavatar)_ props
OverlayReactionsAvatar {#overlayreactionsavatar}
+
+
+
+### _forwarded from [Channel](../../core-components/channel#overlayreactionsitem)_ props
OverlayReactionsItem {#overlayreactionsitem}
+
+
+
+### _forwarded from [Channel](../../core-components/channel#overlayreactions)_ props
OverlayReactions {#overlayreactions}
+
+
+
### _forwarded from [Channel](../../core-components/channel#reactionlist)_ props
ReactionList {#reactionlist}
diff --git a/docusaurus/docs/reactnative/contexts/overlay-context.mdx b/docusaurus/docs/reactnative/contexts/overlay-context.mdx
index 53f4deae1c..048a68382d 100644
--- a/docusaurus/docs/reactnative/contexts/overlay-context.mdx
+++ b/docusaurus/docs/reactnative/contexts/overlay-context.mdx
@@ -34,15 +34,12 @@ Current active overlay. Overlay gets rendered in following cases
- 'alert' - For delete message confirmation alert box
- 'gallery' - When image viewer/gallery is opened
-- 'message' - When message overlay is opened by long pressing a message
- 'none' - Default value
-| Type |
-| ------------------------------------------- |
-| enum('alert', 'gallery', 'message', 'none') |
+| Type |
+| -------------------------------- |
+| enum('alert', 'gallery', 'none') |
### `setOverlay`
-
-
diff --git a/docusaurus/docs/reactnative/core-components/channel.mdx b/docusaurus/docs/reactnative/core-components/channel.mdx
index 529e1a459f..32a7145787 100644
--- a/docusaurus/docs/reactnative/core-components/channel.mdx
+++ b/docusaurus/docs/reactnative/core-components/channel.mdx
@@ -101,16 +101,20 @@ import MaxMessageLength from '../common-content/ui-components/channel/props/max_
import MaxNumberOfFiles from '../common-content/ui-components/channel/props/max_number_of_files.mdx';
import MentionAllAppUsersEnabled from '../common-content/ui-components/channel/props/mention_all_app_users_enabled.mdx';
import MentionAllAppUsersQuery from '../common-content/ui-components/channel/props/mention_all_app_users_query.mdx';
+import Message from '../common-content/ui-components/channel/props/message.mdx';
+import MessageActionListItem from '../common-content/ui-components/channel/props/message-action-list-item.mdx';
+import MessageActionList from '../common-content/ui-components/channel/props/message-action-list.mdx';
+import MessageActions from '../common-content/ui-components/channel/props/message_actions.mdx';
import MessageAvatar from '../common-content/ui-components/channel/props/message-avatar.mdx';
import MessageBounce from '../common-content/ui-components/channel/props/message-bounce.mdx';
import MessageContent from '../common-content/ui-components/channel/props/message-content.mdx';
-import MessageActions from '../common-content/ui-components/channel/props/message_actions.mdx';
import MessageContentOrder from '../common-content/ui-components/channel/props/message_content_order.mdx';
import MessageDeleted from '../common-content/ui-components/channel/props/message-deleted.mdx';
import MessageEditedTimestamp from '../common-content/ui-components/channel/props/message-edited-timestamp.mdx';
import MessageError from '../common-content/ui-components/channel/props/message-error.mdx';
import MessageFooter from '../common-content/ui-components/channel/props/message-footer.mdx';
import MessageHeader from '../common-content/ui-components/channel/props/message-header.mdx';
+import MessageOverlay from '../common-content/ui-components/channel/props/message-overlay.mdx';
import MessagePinnedHeader from '../common-content/ui-components/channel/props/message-pinned-header.mdx';
import MessageReplies from '../common-content/ui-components/channel/props/message-replies.mdx';
import MessageRepliesAvatars from '../common-content/ui-components/channel/props/message-replies-avatars.mdx';
@@ -118,6 +122,7 @@ import MessageSimple from '../common-content/ui-components/channel/props/message
import MessageStatus from '../common-content/ui-components/channel/props/message-status.mdx';
import MessageSystem from '../common-content/ui-components/channel/props/message-system.mdx';
import MessageText from '../common-content/ui-components/channel/props/message-text.mdx';
+import MessageTextNumberOfLines from '../common-content/ui-components/channel/props/message-text-number-of-lines.mdx';
import MoreOptionsButton from '../common-content/ui-components/channel/props/more-options-button.mdx';
import MyMessageTheme from '../common-content/ui-components/channel/props/my_message_theme.mdx';
import NewMessageStateUpdateThrottleInterval from '../common-content/ui-components/channel/props/new_message_state_update_throttle_interval.mdx';
@@ -126,7 +131,10 @@ import OnChangeText from '../common-content/ui-components/channel/props/on_chang
import OnLongPressMessage from '../common-content/ui-components/channel/props/on_long_press_message.mdx';
import OnPressInMessage from '../common-content/ui-components/channel/props/on_press_in_message.mdx';
import OnPressMessage from '../common-content/ui-components/channel/props/on_press_message.mdx';
-import OverlayReactionList from '../common-content/ui-components/overlay-provider/props/overlay_reaction_list.mdx';
+import OverlayReactionList from '../common-content/ui-components/channel/props/overlay-reaction-list.mdx';
+import OverlayReactionsAvatar from '../common-content/ui-components/channel/props/overlay-reactions-avatar.mdx';
+import OverlayReactionsItem from '../common-content/ui-components/channel/props/overlay-reactions-item.mdx';
+import OverlayReactions from '../common-content/ui-components/channel/props/overlay-reactions.mdx';
import OverrideOwnCapabilities from '../common-content/ui-components/channel/props/override_own_capabilities.mdx';
import ReactionList from '../common-content/ui-components/channel/props/reaction-list.mdx';
import Reply from '../common-content/ui-components/channel/props/reply.mdx';
@@ -639,6 +647,10 @@ Load the channel at a specified message instead of the most recent message.
+### `messageTextNumberOfLines`
+
+
+
@@ -898,8 +910,20 @@ Component to render full screen error indicator, when channel fails to load.
+
+
+### MessageActionList
+
+
+
+### MessageActionListItem
+
+
+
### MessageAvatar
@@ -932,6 +956,10 @@ Component to render full screen error indicator, when channel fails to load.
+### MessageOverlay
+
+
+
### MessagePinnedHeader
@@ -974,6 +1002,18 @@ Component to render full screen error indicator, when channel fails to load.
+### OverlayReactionsAvatar
+
+
+
+### OverlayReactionsItem
+
+
+
+### OverlayReactions
+
+
+
### ReactionList
diff --git a/docusaurus/docs/reactnative/core-components/overlay-provider.mdx b/docusaurus/docs/reactnative/core-components/overlay-provider.mdx
index 2add3b7ff6..daee8b4981 100644
--- a/docusaurus/docs/reactnative/core-components/overlay-provider.mdx
+++ b/docusaurus/docs/reactnative/core-components/overlay-provider.mdx
@@ -28,13 +28,12 @@ export const App = () => (
## Context Providers
-`OverlayProvider` contains providers for the `AttachmentPickerContext`, `ImageGalleryContext`, `MessageOverlayContext`, `OverlayContext`, `ThemeContext`, and `TranslationContext`. These can be accessed using the corresponding hooks.
+`OverlayProvider` contains providers for the `AttachmentPickerContext`, `ImageGalleryContext`, `OverlayContext`, `ThemeContext`, and `TranslationContext`. These can be accessed using the corresponding hooks.
| Context | Hook |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- |
| [`AttachmentPickerContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx) | useAttachmentPickerContext |
| [`ImageGalleryContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx) | useImageGalleryContext |
-| [`MessageOverlayContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx) | useMessageOverlayContext |
| [`OverlayContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/overlayContext/OverlayContext.tsx) | useOverlayContext |
| [`ThemeContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/themeContext/ThemeContext.tsx) | useTheme |
| [`TranslationContext`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/contexts/translationContext/TranslationContext.tsx) | useTranslationContext |
@@ -150,14 +149,6 @@ The [`SnapPoints`](https://gorhom.github.io/react-native-bottom-sheet/props#snap
| ----- | ------------------------------ |
| Array | `[0, (screenHeight * 9) / 10]` |
-### `messageTextNumberOfLines`
-
-Number of lines for the message text in the Message Overlay.
-
-| Type | Default |
-| ------ | ------- |
-| Number | 5 |
-
### `numberOfAttachmentImagesToLoadPerCall`
Number of images to load per call to [`CameraRoll.getPhotos`](https://github.com/react-native-cameraroll/react-native-cameraroll#getphotos).
@@ -289,43 +280,3 @@ Image selector component displayed in the attachment selector bar.
| Type | Default |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ComponentType | `undefined` \| [`ImageSelectorIcon`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/ImageSelectorIcon.tsx) |
-
-### `MessageActionList`
-
-Component for rendering a message action list within the message overlay.
-
-| Type | Default |
-| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | `undefined` \| [`MessageActionList`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionList.tsx) |
-
-### `MessageActionListItem`
-
-Component for rendering message action list items within a message action list.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | `undefined` \| [`MessageActionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionListItem.tsx) |
-
-### `OverlayReactionList`
-
-Reaction selector component displayed within the message overlay when user long presses a message.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | `undefined` \| [`OverlayReactionList`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionList.tsx) |
-
-### `OverlayReactions`
-
-List of reactions component within the message overlay.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | `undefined` \| [`OverlayReactions`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactions.tsx) |
-
-### `OverlayReactionsAvatar`
-
-Component for rendering an avatar in the message reaction overlay.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | `undefined` \| [`OverlayReactionsAvatar`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx) |
diff --git a/docusaurus/docs/reactnative/guides/custom-message-actions.mdx b/docusaurus/docs/reactnative/guides/custom-message-actions.mdx
index 7abc635a1c..b97dd62c76 100644
--- a/docusaurus/docs/reactnative/guides/custom-message-actions.mdx
+++ b/docusaurus/docs/reactnative/guides/custom-message-actions.mdx
@@ -45,9 +45,9 @@ You can customize each one of the default actions using props on the [`Channel c
The channel component accepts a prop called `messageActions`. You can use this prop as a callback function to render message actions selectively.
-The arguments to this function is an object with all the default message actions as [`MessageAction`](#messageaction) objects. The function should return an array of MessageAction objects to render in a [MessageActionList](../common-content/ui-components/overlay-provider/props/message_action_list.mdx) within the message overlay, that is shown when a user long presses a message in a MessageList.
+The arguments to this function is an object with all the default message actions as [`MessageAction`](#messageaction) objects. The function should return an array of MessageAction objects to render in a [MessageActionList](../common-content/ui-components/channel/props/message_action_list.mdx) within the message overlay, that is shown when a user long presses a message in a MessageList.
-You can also customize each one of the default actions using the `messageActions` prop passed to the OverlayProvider as shown in the example below. The OverlayProvider component makes these props available in the `MessageOverlayContext` context.
+You can also customize each one of the default actions using the `messageActions` prop passed to the Channel component as shown in the example below.
```tsx
messageActions={({
@@ -272,7 +272,7 @@ import { messageActions as defaultMessageActions, Mute as MuteIcon } from 'strea
## How to customize message action UI
-[`OverlayProvider`](../core-components/overlay-provider.mdx) component accepts props called - `MessageActionList` and `MessageActionListItem`. They both serve a different purpose.
+The [`Channel`](../core-components/channel.mdx) component accepts props called - `MessageActionList` and `MessageActionListItem`. They both serve a different purpose.
- `MessageActionList` - Allows full customizability of the message action list and allows users to add/define their own message action along with the style they prefer for the application.
- `MessageActionListItem` - Allows customizability of an item in a message action list.
@@ -282,15 +282,18 @@ import { messageActions as defaultMessageActions, Mute as MuteIcon } from 'strea
An example for the usage of `MessageActionList` component is as follows. You can obviously have your own logic in the component.
```tsx
-import { MessageActionListItem, OverlayProvider, useOverlayContext } from 'stream-chat-react-native';
+import { Alert } from 'react-native';
+import { Channel, MessageActionListItem, useMessageContext } from 'stream-chat-react-native';
const CustomMessageActionList = () => {
- const { setOverlay } = useOverlayContext();
+ const { dismissOverlay } = useMessageContext();
const messageActions = [
{
action: function () {
Alert.alert('Edit Message action called.');
- setOverlay('none');
+ if (dismissOverlay) {
+ dismissOverlay();
+ }
},
actionType: 'editMessage',
title: 'Edit messagee',
@@ -298,7 +301,9 @@ const CustomMessageActionList = () => {
{
action: function () {
Alert.alert('Delete message action');
- setOverlay('none');
+ if (dismissOverlay) {
+ dismissOverlay();
+ }
},
actionType: 'deleteMessage',
title: 'Delete Message',
@@ -313,9 +318,9 @@ const CustomMessageActionList = () => {
);
};
-
- {/* Underlying Channel, MessageList and Message components */}
- ;
+
+ {/* Underlying MessageList and MessageInput components */}
+ ;
```
Notice that the `MessageActionList` is a simple prop which just accepts your own component for the message action overlay. The content, styles and the action logic are all defined by the user itself.
@@ -347,7 +352,7 @@ You can use these props to provide your own component.
An example for the `MessageActionListItem` component customization is as following:
```tsx
-import { MessageActionListItem, OverlayProvider, useMessageActionAnimation } from 'stream-chat-react-native';
+import { Channel, MessageActionListItem, useMessageActionAnimation } from 'stream-chat-react-native';
const CustomMessageActionListItem = ({ action, actionType, ...rest }) => {
const { onTap } = useMessageActionAnimation({ action: action });
@@ -372,9 +377,9 @@ const CustomMessageActionListItem = ({ action, actionType, ...rest }) => {
}
};
-
- {/* Underlying Channel, MessageList and Message components */}
- ;
+
+ {/* Underlying MessageList and MessageInput components */}
+ ;
```
Note: The `useMessageActionAnimation` hook takes in the `action` for the message action and this would give some utilities like `ontap`, `animatedStyle` and the `opacity` value which would be helpful with the Animated View which can be used in support of the package `react-native-reanimated`.
@@ -413,7 +418,7 @@ Following example demonstrates how to add analytics tracking to "Copy Message" a
To disable a particular action you can return `null` for a particular action type in the MessageActionListItem prop. An example to the situation would be as follows:
```tsx
-import { MessageActionListItem, OverlayProvider, useMessageActionAnimation } from 'stream-chat-react-native';
+import { Channel, MessageActionListItem, useMessageActionAnimation } from 'stream-chat-react-native';
const CustomMessageActionListItem = ({ action, actionType, ...rest }) => {
if (actionType === 'pinMessage') {
@@ -423,7 +428,7 @@ const CustomMessageActionListItem = ({ action, actionType, ...rest }) => {
}
};
-
- {/* Underlying Channel, MessageList and Message components */}
- ;
+
+ {/* Underlying MessageList and MessageInput components */}
+ ;
```
diff --git a/docusaurus/docs/reactnative/ui-components/overlay-reaction-list.mdx b/docusaurus/docs/reactnative/ui-components/overlay-reaction-list.mdx
index 98a7e5231f..09432b5e84 100644
--- a/docusaurus/docs/reactnative/ui-components/overlay-reaction-list.mdx
+++ b/docusaurus/docs/reactnative/ui-components/overlay-reaction-list.mdx
@@ -5,7 +5,6 @@ title: OverlayReactionList
import SupportedReactions from '../common-content/ui-components/channel/props/supported_reactions.mdx';
import SetOverlay from '../common-content/contexts/overlay-context/set_overlay.mdx';
-import MessageOverlayContextData from '../common-content/contexts/message-overlay-context/data.mdx';
`OverlayReactionList` component is used to display reaction selector within message overlay.
@@ -13,10 +12,6 @@ This is the default component provided to the prop [`OverlayReactionList`](../co
## Props
-### _overrides the value from [MessageOverlayContext](../../contexts/message-overlay-context#data)_
`data` {#data}
-
-
-
### _overrides the value from [OverlayContext](../../contexts/overlay-context#setoverlay)_
`setOverlay` {#setoverlay}
diff --git a/docusaurus/sidebars-react-native.json b/docusaurus/sidebars-react-native.json
index 0b9ad84c50..4f16716750 100644
--- a/docusaurus/sidebars-react-native.json
+++ b/docusaurus/sidebars-react-native.json
@@ -101,7 +101,6 @@
"contexts/attachment-picker-context",
"contexts/image-gallery-context",
"contexts/keyboard-context",
- "contexts/message-overlay-context",
"contexts/own-capabilities-context",
"contexts/paginated-message-list-context",
"contexts/suggestions-context",
diff --git a/package/src/components/Attachment/Card.tsx b/package/src/components/Attachment/Card.tsx
index b791fbb231..99a3108e00 100644
--- a/package/src/components/Attachment/Card.tsx
+++ b/package/src/components/Attachment/Card.tsx
@@ -29,7 +29,7 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { Play } from '../../icons/Play';
import { DefaultStreamChatGenerics, FileTypes } from '../../types/types';
import { makeImageCompatibleUrl } from '../../utils/utils';
-import { ImageBackground } from '../ImageBackground';
+import { ImageBackground } from '../UIComponents/ImageBackground';
const styles = StyleSheet.create({
authorName: { fontSize: 14.5, fontWeight: '600' },
diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx
index 25f07f1063..cd0eb4af6a 100644
--- a/package/src/components/Channel/Channel.tsx
+++ b/package/src/components/Channel/Channel.tsx
@@ -7,7 +7,6 @@ import {
View,
} from 'react-native';
-import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import debounce from 'lodash/debounce';
import omit from 'lodash/omit';
import throttle from 'lodash/throttle';
@@ -577,7 +576,7 @@ const ChannelWithContext = <
MessageStatus = MessageStatusDefault,
MessageSystem = MessageSystemDefault,
MessageText,
- messageTextNumberOfLines,
+ messageTextNumberOfLines = 5,
MessageTimestamp = MessageTimestampDefault,
MoreOptionsButton = MoreOptionsButtonDefault,
myMessageTheme,
@@ -2483,9 +2482,7 @@ const ChannelWithContext = <
value={threadContext}>
value={suggestionsContext}>
value={inputMessageInputContext}>
-
- {children}
-
+ {children}
diff --git a/package/src/components/ChannelList/ChannelListFooterLoadingIndicator.tsx b/package/src/components/ChannelList/ChannelListFooterLoadingIndicator.tsx
index aaad130173..6680e948d3 100644
--- a/package/src/components/ChannelList/ChannelListFooterLoadingIndicator.tsx
+++ b/package/src/components/ChannelList/ChannelListFooterLoadingIndicator.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
-import { Spinner } from '../Spinner/Spinner';
+import { Spinner } from '../UIComponents/Spinner';
const styles = StyleSheet.create({
container: {
diff --git a/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx b/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx
index 9a4a4bd3c3..8c0d8b157e 100644
--- a/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx
+++ b/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx
@@ -13,7 +13,7 @@ import {
VideoType,
} from '../../../native';
-import { Spinner } from '../../Spinner/Spinner';
+import { Spinner } from '../../UIComponents/Spinner';
const oneEighth = 1 / 8;
diff --git a/package/src/components/Indicators/LoadingIndicator.tsx b/package/src/components/Indicators/LoadingIndicator.tsx
index abbdbdd130..689563743c 100644
--- a/package/src/components/Indicators/LoadingIndicator.tsx
+++ b/package/src/components/Indicators/LoadingIndicator.tsx
@@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
-import { Spinner } from '../Spinner/Spinner';
+import { Spinner } from '../UIComponents/Spinner';
type LoadingIndicatorWrapperProps = { text: string };
diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx
index 74b1e7a997..e5217922e9 100644
--- a/package/src/components/Message/Message.tsx
+++ b/package/src/components/Message/Message.tsx
@@ -6,7 +6,6 @@ import type { Attachment, UserResponse } from 'stream-chat';
import { useCreateMessageContext } from './hooks/useCreateMessageContext';
import { useMessageActionHandlers } from './hooks/useMessageActionHandlers';
import { useMessageActions } from './hooks/useMessageActions';
-import { useMessageActionsOverlay } from './hooks/useMessageActionsOverlay';
import { useProcessReactions } from './hooks/useProcessReactions';
import { messageActions as defaultMessageActions } from './utils/messageActions';
@@ -178,36 +177,6 @@ export type MessagePropsWithContext<
enableLongPress?: boolean;
goToMessage?: (messageId: string) => void;
isTargetedMessage?: boolean;
- /**
- * Array of allowed actions or null on message, this can also be a function returning the array.
- * If all the actions need to be disabled an empty array should be provided as value of prop
- */
- /**
- * You can call methods available on the Message
- * component such as handleEdit, handleDelete, handleAction etc.
- *
- * Source - [Message](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/Message.tsx)
- *
- * By default, we show the overlay with all the message actions on long press.
- *
- * @param message Message object which was long pressed
- * @param event Event object for onLongPress event
- **/
- onLongPress?: (payload: Partial>) => void;
-
- /**
- * You can call methods available on the Message
- * component such as handleEdit, handleDelete, handleAction etc.
- *
- * Source - [Message](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/Message.tsx)
- *
- * By default, we will dismiss the keyboard on press.
- *
- * @param message Message object which was long pressed
- * @param event Event object for onLongPress event
- * */
- onPress?: (payload: Partial>) => void;
- onPressIn?: (payload: Partial>) => void;
/**
* Handler to open the thread on message. This is callback for touch event for replies button.
*
@@ -228,15 +197,13 @@ const MessageWithContext = <
>(
props: MessagePropsWithContext,
) => {
+ const [messageOverlayVisible, setMessageOverlayVisible] = useState(false);
const [isErrorInMessage, setIsErrorInMessage] = useState(false);
const [showMessageReactions, setShowMessageReactions] = useState(true);
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
const [isEditedMessageOpen, setIsEditedMessageOpen] = useState(false);
const isMessageTypeDeleted = props.message.type === 'deleted';
- const { messageActionsBottomSheetRef, messageOverlayVisible, setMessageOverlayVisible } =
- useMessageActionsOverlay();
-
const {
channel,
chatContext,
@@ -271,10 +238,7 @@ const MessageWithContext = <
MessageOverlay,
messagesContext,
MessageSimple,
- onLongPress: onLongPressProp,
onLongPressMessage: onLongPressMessageProp,
- onPress: onPressProp,
- onPressIn: onPressInProp,
onPressInMessage: onPressInMessageProp,
onPressMessage: onPressMessageProp,
onThreadSelect,
@@ -309,7 +273,7 @@ const MessageWithContext = <
await dismissKeyboard();
};
- const closeMessageOverlay = () => {
+ const dismissOverlay = () => {
setMessageOverlayVisible(false);
};
@@ -535,7 +499,7 @@ const MessageWithContext = <
client,
deleteMessage: deleteMessageFromContext,
deleteReaction,
- dismissOverlay: closeMessageOverlay,
+ dismissOverlay,
enforceUniqueReaction,
handleBan,
handleBlock,
@@ -574,7 +538,7 @@ const MessageWithContext = <
blockUser,
copyMessage,
deleteMessage,
- dismissOverlay: closeMessageOverlay,
+ dismissOverlay,
editMessage,
error: isErrorInMessage,
flagMessage,
@@ -619,14 +583,6 @@ const MessageWithContext = <
event: payload?.event,
message,
})
- : onLongPressProp
- ? (payload?: TouchableHandlerPayload) =>
- onLongPressProp({
- actionHandlers,
- defaultHandler: payload?.defaultHandler || showMessageOverlay,
- emitter: payload?.emitter || 'message',
- event: payload?.event,
- })
: enableLongPress
? () => {
// If a message is bounced, on long press the message bounce options modal should open.
@@ -643,6 +599,7 @@ const MessageWithContext = <
actionsEnabled,
alignment,
channel,
+ dismissOverlay,
files: attachments.files,
goToMessage,
groupStyles,
@@ -680,7 +637,6 @@ const MessageWithContext = <
};
const handleOnPress = () => {
- if (onPressProp) return onPressProp(onPressArgs);
if (onPressMessageProp) return onPressMessageProp(onPressArgs);
if (payload.defaultHandler) return payload.defaultHandler();
@@ -689,23 +645,18 @@ const MessageWithContext = <
handleOnPress();
},
- onPressIn:
- onPressInProp || onPressInMessageProp
- ? (payload) => {
- const onPressInArgs = {
+ onPressIn: onPressInMessageProp
+ ? (payload) => {
+ if (onPressInMessageProp)
+ return onPressInMessageProp({
actionHandlers,
defaultHandler: payload.defaultHandler,
emitter: payload.emitter || 'message',
event: payload.event,
message,
- };
- const handleOnpressIn = () => {
- if (onPressInProp) return onPressInProp(onPressInArgs);
- if (onPressInMessageProp) return onPressInMessageProp(onPressInArgs);
- };
- handleOnpressIn();
- }
- : null,
+ });
+ }
+ : null,
otherAttachments: attachments.other,
preventPress,
reactions,
@@ -720,46 +671,46 @@ const MessageWithContext = <
if (!(isMessageTypeDeleted || messageContentOrder.length)) return null;
return (
-
+
-
+
{isBounceDialogOpen && }
{messageOverlayVisible ? (
) : null}
-
+
-
+
);
};
diff --git a/package/src/components/Message/hooks/useCreateMessageContext.ts b/package/src/components/Message/hooks/useCreateMessageContext.ts
index eedd4d561e..1fb365e0bc 100644
--- a/package/src/components/Message/hooks/useCreateMessageContext.ts
+++ b/package/src/components/Message/hooks/useCreateMessageContext.ts
@@ -10,6 +10,7 @@ export const useCreateMessageContext = <
actionsEnabled,
alignment,
channel,
+ dismissOverlay,
files,
goToMessage,
groupStyles,
@@ -62,6 +63,7 @@ export const useCreateMessageContext = <
actionsEnabled,
alignment,
channel,
+ dismissOverlay,
files,
goToMessage,
groupStyles,
diff --git a/package/src/components/Message/hooks/useMessageActionsOverlay.tsx b/package/src/components/Message/hooks/useMessageActionsOverlay.tsx
deleted file mode 100644
index 4b6fe3129c..0000000000
--- a/package/src/components/Message/hooks/useMessageActionsOverlay.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-
-import { BottomSheetModal } from '@gorhom/bottom-sheet';
-
-/**
- * Hook to manage the message actions overlay
- */
-export const useMessageActionsOverlay = () => {
- const [messageOverlayVisible, setMessageOverlayVisible] = useState(false);
-
- const messageActionsBottomSheetRef = useRef(null);
-
- /**
- * Function to open the message actions bottom sheet
- */
- const openMessageActionsBottomSheet = () => {
- if (messageActionsBottomSheetRef.current?.present) {
- messageActionsBottomSheetRef.current.present();
- } else {
- console.warn('bottom and top insets must be set for the image picker to work correctly');
- }
- };
-
- /**
- * Function to close the message actions bottom sheet
- */
- const closeMessageActionsBottomSheet = () => {
- if (messageActionsBottomSheetRef.current?.dismiss) {
- messageActionsBottomSheetRef.current.dismiss();
- }
- };
-
- /**
- * Effect to open or close the message actions bottom sheet
- */
- useEffect(() => {
- if (messageOverlayVisible) {
- openMessageActionsBottomSheet();
- } else {
- closeMessageActionsBottomSheet();
- }
- }, [messageOverlayVisible]);
-
- return {
- messageActionsBottomSheetRef,
- messageOverlayVisible,
- setMessageOverlayVisible,
- };
-};
diff --git a/package/src/components/MessageOverlay/MessageActionList.tsx b/package/src/components/MessageOverlay/MessageActionList.tsx
index 9d93e87cdb..96edc0b487 100644
--- a/package/src/components/MessageOverlay/MessageActionList.tsx
+++ b/package/src/components/MessageOverlay/MessageActionList.tsx
@@ -7,6 +7,11 @@ import { MessagesContextValue } from '../../contexts/messagesContext/MessagesCon
import { useTheme } from '../../contexts/themeContext/ThemeContext';
export type MessageActionListProps = Pick & {
+ /**
+ * Function to close the message actions bottom sheet
+ * @returns void
+ */
+ dismissOverlay?: () => void;
/**
* An array of message actions to render
*/
diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx
index 17882298fa..8e733b9f7e 100644
--- a/package/src/components/MessageOverlay/MessageOverlay.tsx
+++ b/package/src/components/MessageOverlay/MessageOverlay.tsx
@@ -1,8 +1,5 @@
-import React, { useCallback, useMemo } from 'react';
-import { StyleSheet } from 'react-native';
-
-import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
-import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
import { MessageActionType } from './MessageActionListItem';
@@ -15,6 +12,7 @@ import {
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
import { DefaultStreamChatGenerics } from '../../types/types';
+import { BottomSheetModal } from '../UIComponents/BottomSheetModal';
export type MessageOverlayProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -34,19 +32,19 @@ export type MessageOverlayProps<
* Function to close the message actions bottom sheet
* @returns void
*/
- closeMessageOverlay: () => void;
+ dismissOverlay: () => void;
/**
* An array of message actions to render
*/
messageActions: MessageActionType[];
- /**
- * Reference to the bottom sheet modal
- */
- messageActionsBottomSheetRef: React.RefObject;
/**
* Boolean to determine if there are message actions
*/
showMessageReactions: boolean;
+ /**
+ * Boolean to determine if the overlay is visible.
+ */
+ visible: boolean;
/**
* Function to handle reaction on press
* @param reactionType
@@ -61,18 +59,18 @@ export const MessageOverlay = <
props: MessageOverlayProps,
) => {
const {
- closeMessageOverlay,
+ dismissOverlay,
handleReaction,
message: propMessage,
MessageActionList: propMessageActionList,
MessageActionListItem: propMessageActionListItem,
messageActions,
- messageActionsBottomSheetRef,
OverlayReactionList: propOverlayReactionList,
OverlayReactions: propOverlayReactions,
OverlayReactionsAvatar: propOverlayReactionsAvatar,
OverlayReactionsItem: propOverlayReactionsItem,
showMessageReactions,
+ visible,
} = props;
const {
MessageActionList: contextMessageActionList,
@@ -83,7 +81,6 @@ export const MessageOverlay = <
OverlayReactionsItem: contextOverlayReactionsItem,
} = useMessagesContext();
const { message: contextMessage } = useMessageContext();
- const snapPoints = useMemo(() => ['50%', '50%'], []);
const MessageActionList = propMessageActionList ?? contextMessageActionList;
const MessageActionListItem = propMessageActionListItem ?? contextMessageActionListItem;
const OverlayReactionList = propOverlayReactionList ?? contextOverlayReactionList;
@@ -92,21 +89,9 @@ export const MessageOverlay = <
const OverlayReactionsItem = propOverlayReactionsItem ?? contextOverlayReactionsItem;
const message = propMessage ?? contextMessage;
- const handleSheetChanges = useCallback((index: number) => {
- console.log('handleSheetChanges', index);
- }, []);
-
return (
-
-
+
+
{showMessageReactions ? (
reaction.type) || []}
/>
>
)}
-
+
);
};
diff --git a/package/src/components/MessageOverlay/OverlayReactionList.tsx b/package/src/components/MessageOverlay/OverlayReactionList.tsx
index 7e2e2c0dfa..55ecbfbec8 100644
--- a/package/src/components/MessageOverlay/OverlayReactionList.tsx
+++ b/package/src/components/MessageOverlay/OverlayReactionList.tsx
@@ -93,6 +93,7 @@ const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
+ marginBottom: 16,
paddingHorizontal: 16,
},
});
diff --git a/package/src/components/UIComponents/BottomSheetModal.tsx b/package/src/components/UIComponents/BottomSheetModal.tsx
new file mode 100644
index 0000000000..40a2c34f9d
--- /dev/null
+++ b/package/src/components/UIComponents/BottomSheetModal.tsx
@@ -0,0 +1,161 @@
+import React, { PropsWithChildren, useEffect, useRef } from 'react';
+import {
+ Animated,
+ Keyboard,
+ KeyboardEvent,
+ Modal,
+ PanResponder,
+ StyleSheet,
+ useWindowDimensions,
+ View,
+} from 'react-native';
+
+import { useTheme } from '../../contexts/themeContext/ThemeContext';
+
+export type BottomSheetModalProps = {
+ /**
+ * Function to call when the modal is closed.
+ * @returns void
+ */
+ onClose: () => void;
+ /**
+ * Whether the modal is visible.
+ */
+ visible: boolean;
+ /**
+ * The height of the modal.
+ */
+ height?: number;
+};
+
+/**
+ * A modal that slides up from the bottom of the screen.
+ */
+export const BottomSheetModal = (props: PropsWithChildren) => {
+ const { height: windowHeight, width: windowWidth } = useWindowDimensions();
+ const { children, height = windowHeight / 2, onClose, visible } = props;
+ const {
+ theme: {
+ colors: { grey, overlay, white_snow },
+ },
+ } = useTheme();
+
+ const panY = useRef(new Animated.Value(windowHeight)).current;
+ const resetPositionAnim = Animated.timing(panY, {
+ duration: 300,
+ toValue: 0,
+ useNativeDriver: true,
+ });
+
+ const closeAnim = Animated.timing(panY, {
+ duration: 300,
+ toValue: height,
+ useNativeDriver: true,
+ });
+
+ const translateY = panY.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [0, 0, 1],
+ });
+
+ const handleDismiss = () => {
+ closeAnim.start(() => onClose());
+ };
+
+ useEffect(() => {
+ if (visible) {
+ resetPositionAnim.start();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [visible]);
+
+ const panResponders = useRef(
+ PanResponder.create({
+ onMoveShouldSetPanResponder: () => true,
+ onPanResponderMove: (_event, gestureState) => {
+ panY.setValue(Math.max(gestureState.dy, 0));
+ },
+ onPanResponderRelease: (_event, gestureState) => {
+ if (gestureState.dy > windowHeight / 3) {
+ handleDismiss();
+ } else {
+ resetPositionAnim.start();
+ }
+ },
+ onStartShouldSetPanResponder: () => true,
+ }),
+ ).current;
+
+ useEffect(() => {
+ const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', keyboardDidShow);
+ const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', keyboardDidHide);
+
+ return () => {
+ keyboardDidShowListener.remove();
+ keyboardDidHideListener.remove();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const keyboardDidShow = (event: KeyboardEvent) => {
+ Animated.timing(panY, {
+ duration: 250,
+ toValue: -event.endCoordinates.height,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const keyboardDidHide = () => {
+ Animated.timing(panY, {
+ duration: 250,
+ toValue: 0,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ if (!visible) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ },
+ content: {
+ flex: 1,
+ padding: 16,
+ },
+ handle: {
+ alignSelf: 'center',
+ borderRadius: 4,
+ height: 4,
+ marginVertical: 8,
+ },
+ overlay: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ },
+});
diff --git a/package/src/components/ImageBackground.tsx b/package/src/components/UIComponents/ImageBackground.tsx
similarity index 100%
rename from package/src/components/ImageBackground.tsx
rename to package/src/components/UIComponents/ImageBackground.tsx
diff --git a/package/src/components/Spinner/Spinner.tsx b/package/src/components/UIComponents/Spinner.tsx
similarity index 100%
rename from package/src/components/Spinner/Spinner.tsx
rename to package/src/components/UIComponents/Spinner.tsx
diff --git a/package/src/components/UIComponents/index.ts b/package/src/components/UIComponents/index.ts
new file mode 100644
index 0000000000..335a933cf4
--- /dev/null
+++ b/package/src/components/UIComponents/index.ts
@@ -0,0 +1,3 @@
+export * from './BottomSheetModal';
+export * from './ImageBackground';
+export * from './Spinner';
diff --git a/package/src/components/index.ts b/package/src/components/index.ts
index 0492d71211..ea51190758 100644
--- a/package/src/components/index.ts
+++ b/package/src/components/index.ts
@@ -164,7 +164,9 @@ export * from './ProgressControl/ProgressControl';
export * from './Reply/Reply';
-export * from './Spinner/Spinner';
+export * from './UIComponents/BottomSheetModal';
+export * from './UIComponents/ImageBackground';
+export * from './UIComponents/Spinner';
export * from './Thread/Thread';
export * from './Thread/components/ThreadFooterComponent';
diff --git a/package/src/contexts/messageContext/MessageContext.tsx b/package/src/contexts/messageContext/MessageContext.tsx
index 081f17a408..bcdb212bec 100644
--- a/package/src/contexts/messageContext/MessageContext.tsx
+++ b/package/src/contexts/messageContext/MessageContext.tsx
@@ -125,12 +125,21 @@ export type MessageContextValue<
reactions: ReactionSummary[];
/** React set state function to set the state of `isEditedMessageOpen` */
setIsEditedMessageOpen: React.Dispatch>;
- showMessageOverlay: (showMessageReactions?: boolean, error?: boolean) => void;
+ /**
+ * Function to show the overlay with all the message actions.
+ * @param showMessageReactions
+ * @returns void
+ */
+ showMessageOverlay: (showMessageReactions?: boolean) => void;
showMessageStatus: boolean;
/** Whether or not the Message is part of a Thread */
threadList: boolean;
/** The videos attached to a message */
videos: Attachment[];
+ /**
+ * Function to dismiss the overlay
+ */
+ dismissOverlay?: () => void;
goToMessage?: (messageId: string) => void;
/** Latest message id on current channel */
lastReceivedId?: string;
From 0e6248155bcbc84e3b17041f3816fe78e1907abd Mon Sep 17 00:00:00 2001
From: Khushal Agarwal
Date: Wed, 2 Oct 2024 12:56:50 +0530
Subject: [PATCH 10/19] fix: add opacity for the message action list item when
pressed
---
package/src/components/MessageOverlay/MessageActionListItem.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package/src/components/MessageOverlay/MessageActionListItem.tsx b/package/src/components/MessageOverlay/MessageActionListItem.tsx
index 92e7f87223..832333f9b7 100644
--- a/package/src/components/MessageOverlay/MessageActionListItem.tsx
+++ b/package/src/components/MessageOverlay/MessageActionListItem.tsx
@@ -61,7 +61,7 @@ export const MessageActionListItem = (props: MessageActionListItemProps) => {
} = useTheme();
return (
-
+ [{ opacity: pressed ? 0.5 : 1 }]}>
Date: Wed, 2 Oct 2024 20:56:14 +0530
Subject: [PATCH 11/19] fix: make modal better and change component names
---
.../reactnative/basics/troubleshooting.mdx | 2 +-
.../channel/props/handle_reaction.mdx | 2 +-
.../props/message-action-list-item.mdx | 6 +-
.../channel/props/message-action-list.mdx | 8 +-
.../{message-overlay.mdx => message-menu.mdx} | 6 +-
.../channel/props/message-reaction-picker.mdx | 5 +
....mdx => message-user-reactions-avatar.mdx} | 8 +-
...em.mdx => message-user-reactions-item.mdx} | 8 +-
.../channel/props/message-user-reactions.mdx | 5 +
.../channel/props/message_actions.mdx | 2 +-
.../channel/props/on_long_press_message.mdx | 3 +-
.../channel/props/overlay-reaction-list.mdx | 5 -
.../channel/props/overlay-reactions.mdx | 5 -
.../props/override_own_capabilities.mdx | 20 +--
.../reactnative/contexts/messages-context.mdx | 30 ++---
.../reactnative/contexts/overlay-context.mdx | 2 +-
.../reactnative/core-components/channel.mdx | 38 +++---
.../reactnative/customization/contexts.mdx | 2 -
.../guides/custom-message-actions.mdx | 14 +--
.../guides/live-location-sharing.mdx | 4 +-
.../object-types/message_action.mdx | 2 +-
.../ui-components/overlay-reaction-list.mdx | 2 +-
.../overlay-reactions-avatar.mdx | 2 +-
.../ui-components/overlay-reactions.mdx | 2 +-
package/jest-setup.js | 1 -
package/jest.config.js | 1 +
package/src/components/Channel/Channel.tsx | 44 +++----
.../Channel/hooks/useCreateMessagesContext.ts | 20 +--
package/src/components/Message/Message.tsx | 10 +-
.../Message/hooks/useMessageActions.tsx | 5 +-
.../Message/utils/messageActions.ts | 2 +-
.../MessageActionList.tsx | 0
.../MessageActionListItem.tsx | 0
.../MessageMenu.tsx} | 86 ++++++-------
.../MessageReactionPicker.tsx} | 10 +-
.../MessageUserReactions.tsx} | 23 ++--
.../MessageUserReactionsAvatar.tsx} | 6 +-
.../MessageUserReactionsItem.tsx} | 12 +-
.../ReactionButton.tsx | 0
.../__tests__/MessageActionList.test.tsx | 0
.../__tests__/MessageActionListItem.test.tsx | 0
.../__tests__/MessageReactionPicker.test.tsx} | 6 +-
.../__tests__/MessageUserReactions.test.tsx} | 24 ++--
.../MessageUserReactionsAvatar.test.tsx} | 9 +-
.../MessageUserReactionsItem.test.tsx} | 8 +-
.../__tests__/ReactionButton.test.tsx | 0
.../hooks/useFetchReactions.ts | 0
.../hooks/useMessageActionAnimation.tsx | 48 --------
.../UIComponents/BottomSheetModal.tsx | 114 +++++++++---------
package/src/components/index.ts | 13 +-
.../messageContext/MessageContext.tsx | 10 +-
.../messagesContext/MessagesContext.tsx | 54 +++++----
52 files changed, 318 insertions(+), 371 deletions(-)
rename docusaurus/docs/reactnative/common-content/ui-components/channel/props/{message-overlay.mdx => message-menu.mdx} (58%)
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-reaction-picker.mdx
rename docusaurus/docs/reactnative/common-content/ui-components/channel/props/{overlay-reactions-avatar.mdx => message-user-reactions-avatar.mdx} (56%)
rename docusaurus/docs/reactnative/common-content/ui-components/channel/props/{overlay-reactions-item.mdx => message-user-reactions-item.mdx} (56%)
create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions.mdx
delete mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
delete mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
rename package/src/components/{MessageOverlay => MessageMenu}/MessageActionList.tsx (100%)
rename package/src/components/{MessageOverlay => MessageMenu}/MessageActionListItem.tsx (100%)
rename package/src/components/{MessageOverlay/MessageOverlay.tsx => MessageMenu/MessageMenu.tsx} (54%)
rename package/src/components/{MessageOverlay/OverlayReactionList.tsx => MessageMenu/MessageReactionPicker.tsx} (88%)
rename package/src/components/{MessageOverlay/OverlayReactions.tsx => MessageMenu/MessageUserReactions.tsx} (85%)
rename package/src/components/{MessageOverlay/OverlayReactionsAvatar.tsx => MessageMenu/MessageUserReactionsAvatar.tsx} (71%)
rename package/src/components/{MessageOverlay/OverlayReactionsItem.tsx => MessageMenu/MessageUserReactionsItem.tsx} (91%)
rename package/src/components/{MessageOverlay => MessageMenu}/ReactionButton.tsx (100%)
rename package/src/components/{MessageOverlay => MessageMenu}/__tests__/MessageActionList.test.tsx (100%)
rename package/src/components/{MessageOverlay => MessageMenu}/__tests__/MessageActionListItem.test.tsx (100%)
rename package/src/components/{MessageOverlay/__tests__/OverlayReactionList.test.tsx => MessageMenu/__tests__/MessageReactionPicker.test.tsx} (95%)
rename package/src/components/{MessageOverlay/__tests__/OverlayReactions.test.tsx => MessageMenu/__tests__/MessageUserReactions.test.tsx} (87%)
rename package/src/components/{MessageOverlay/__tests__/OverlayReactionsAvatar.test.tsx => MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx} (79%)
rename package/src/components/{MessageOverlay/__tests__/OverlayReactionsItem.test.tsx => MessageMenu/__tests__/MessageUserReactionsItem.test.tsx} (93%)
rename package/src/components/{MessageOverlay => MessageMenu}/__tests__/ReactionButton.test.tsx (100%)
rename package/src/components/{MessageOverlay => MessageMenu}/hooks/useFetchReactions.ts (100%)
delete mode 100644 package/src/components/MessageOverlay/hooks/useMessageActionAnimation.tsx
diff --git a/docusaurus/docs/reactnative/basics/troubleshooting.mdx b/docusaurus/docs/reactnative/basics/troubleshooting.mdx
index fb8ca98868..48bcf13424 100644
--- a/docusaurus/docs/reactnative/basics/troubleshooting.mdx
+++ b/docusaurus/docs/reactnative/basics/troubleshooting.mdx
@@ -79,7 +79,7 @@ To do this make sure your `Channel` components are always aware of the thread st
## Image gallery not full screen
-If the image viewer or message overlay is not covering the full screen, for instance it is rendering below or behind a Header, it is likely the `OverlayProvider` is not setup in the correct location within the application.
+If the image viewer or message menu is not covering the full screen, for instance it is rendering below or behind a Header, it is likely the `OverlayProvider` is not setup in the correct location within the application.
Please refer to the [Stream Chat with Navigation](./navigation.mdx) documentation to properly place the `OverlayProvider` in relation to your navigation solution or other components.
## Image picker incorrect height
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/handle_reaction.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/handle_reaction.mdx
index 02f30f3c5e..6e1545a25d 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/handle_reaction.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/handle_reaction.mdx
@@ -1,4 +1,4 @@
-Function called when a reaction is selected in the message overlay, this is called on both the add and remove action.
+Function called when a reaction is selected in the message menu, this is called on both the add and remove action.
This function does not override the default behavior of the reaction being selected.
Please refer to [the guide on customizing message actions](../../../../guides/custom-message-actions.mdx) for details.
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
index e19fa3a21f..cd253accc7 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list-item.mdx
@@ -1,5 +1,5 @@
Component for rendering message action list items within a message action list.
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [`MessageActionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionListItem.tsx) \| `undefined` |
+| Type | Default |
+| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageActionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageActionListItem.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
index 775dad267d..b8c7102854 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-action-list.mdx
@@ -1,5 +1,5 @@
-Component for rendering a message action list within the message overlay.
+Component for rendering a message action list within the message menu.
-| Type | Default |
-| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [`MessageActionList`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageActionList.tsx) \| `undefined` |
+| Type | Default |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageActionList`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageActionList.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-menu.mdx
similarity index 58%
rename from docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-menu.mdx
index 03fda384fd..aa10a0e01b 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-overlay.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-menu.mdx
@@ -1,5 +1,5 @@
Component to customize the message contextual menu which allows users to perform actions on a message and react to messages.
-| Type | Default |
-| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [`MessageOverlay`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageOverlay/MessageOverlay.tsx) \| `undefined` |
+| Type | Default |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageMenu`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageMenu.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-reaction-picker.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-reaction-picker.mdx
new file mode 100644
index 0000000000..7dad596910
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-reaction-picker.mdx
@@ -0,0 +1,5 @@
+Reaction selector component displayed within the message menu when user long presses a message.
+
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageReactionPicker`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageMenu/MessageReactionPicker.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-avatar.mdx
similarity index 56%
rename from docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-avatar.mdx
index 8e21fa9cc0..46d80419ef 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-avatar.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-avatar.mdx
@@ -1,5 +1,5 @@
-Component for rendering an avatar in the message reaction overlay.
+Component for rendering an avatar in the message user reactions in the message menu.
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | [`OverlayReactionsAvatar`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionsAvatar.tsx) \| `undefined` |
+| Type | Default |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageUserReactionsAvatar`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageMenu/MessageUserReactionsAvatar.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-item.mdx
similarity index 56%
rename from docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx
rename to docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-item.mdx
index 94fed903f8..f6a06ebedb 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions-item.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions-item.mdx
@@ -1,5 +1,5 @@
-Component prop to customize the individual user reaction item in the overlay reactions. This includes the avatar, reaction type, and the name of the person who has reacted, etc.
+Component prop to customize the individual user reaction item in the `MessageUserReactions` component of `MessageMenu`. This includes the avatar, reaction type, and the name of the person who has reacted, etc.
-| Type | Default |
-| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ComponentType | [`OverlayReactionListItem`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionListItem.tsx) \| `undefined` |
+| Type | Default |
+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageUserReactionsItem`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageMenu/MessageUserReactionsItem.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions.mdx
new file mode 100644
index 0000000000..fdb7f75788
--- /dev/null
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-user-reactions.mdx
@@ -0,0 +1,5 @@
+List of reactions component within the message menu.
+
+| Type | Default |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ComponentType | [`MessageUserReactions`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageMenu/MessageUserReactions.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message_actions.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message_actions.mdx
index e9587fd93c..01d1de390d 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message_actions.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message_actions.mdx
@@ -1,4 +1,4 @@
-An array of, or function that returns and array of, actions that can be performed on a message shown in the message overlay.
+An array of, or function that returns and array of, actions that can be performed on a message shown in the message menu.
Please refer to [the guide on customizing message actions](../../../../guides//custom-message-actions.mdx) for details.
| Type | Default |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/on_long_press_message.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/on_long_press_message.mdx
index 8f20fe2362..e29b546cc3 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/on_long_press_message.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/on_long_press_message.mdx
@@ -1,5 +1,4 @@
-Function called when a user long presses a message.
-The default opens the message actions overlay.
+Function called when a user long presses a message. The default opens the message menu.
| Type |
| -------- |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
deleted file mode 100644
index c274f55aea..0000000000
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reaction-list.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
-Reaction selector component displayed within the message overlay when user long presses a message.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | [`OverlayReactionList`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactionList.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
deleted file mode 100644
index c4ca8da77b..0000000000
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/overlay-reactions.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
-List of reactions component within the message overlay.
-
-| Type | Default |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| ComponentType | [`OverlayReactions`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/MessageOverlay/OverlayReactions.tsx) \| `undefined` |
diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/override_own_capabilities.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/override_own_capabilities.mdx
index 65269905c0..9e1593f700 100644
--- a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/override_own_capabilities.mdx
+++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/override_own_capabilities.mdx
@@ -15,20 +15,20 @@ For example:
Available keys for the object:
-- `banChannelMembers` When false, "Block User" message action won't be available within overlay.
-- `deleteAnyMessage` When false, "Delete Message" action won't be available for received message within overlay.
-- `deleteOwnMessage` When false, "Delete Message" action won't be available for own message within overlay.
-- `flagMessage` When false, "Flag Message" message action won't be available within overlay.
-- `pinMessage` When false, "Pin Message" action won't be available within overlay.
-- `quoteMessage` When false, "Reply" message action won't be available within overlay.
+- `banChannelMembers` When false, "Block User" message action won't be available within message menu.
+- `deleteAnyMessage` When false, "Delete Message" action won't be available for received message within message menu.
+- `deleteOwnMessage` When false, "Delete Message" action won't be available for own message within message menu.
+- `flagMessage` When false, "Flag Message" message action won't be available within message menu.
+- `pinMessage` When false, "Pin Message" action won't be available within message menu.
+- `quoteMessage` When false, "Reply" message action won't be available within message menu.
- `readEvents` When false, read receipts for message won't be rendered.
- `sendLinks` When false, user will not be allowed to send URLs in message.
- `sendMessage` When false, message input component will render `SendMessageDisallowedIndicator` instead of input box.
-- `sendReaction` When false, reaction selector (`OverlayReactionList`) component won't be available within overlay.
-- `sendReply` When false, "Thread Reply" message action won't be available within overlay.
+- `sendReaction` When false, reaction selector (`OverlayReactionList`) component won't be available within message menu.
+- `sendReply` When false, "Thread Reply" message action won't be available within message menu.
- `sendTypingEvents` When false, typing events won't be sent from current user.
-- `updateAnyMessage` When false, "Edit Message" action won't be available for received messages within overlay.
-- `updateOwnMessage` When false, "Edit Message" action won't be available for own messages within overlay.
+- `updateAnyMessage` When false, "Edit Message" action won't be available for received messages within message menu.
+- `updateOwnMessage` When false, "Edit Message" action won't be available for own messages within message menu.
- `uploadFile` When false, upload file button (`AttachButton`) won't be available within `MessageInput` component.
| Type |
diff --git a/docusaurus/docs/reactnative/contexts/messages-context.mdx b/docusaurus/docs/reactnative/contexts/messages-context.mdx
index 0d25640ba6..1d660cda7c 100644
--- a/docusaurus/docs/reactnative/contexts/messages-context.mdx
+++ b/docusaurus/docs/reactnative/contexts/messages-context.mdx
@@ -54,7 +54,7 @@ import MessageEditedTimestamp from '../common-content/ui-components/channel/prop
import MessageError from '../common-content/ui-components/channel/props/message-error.mdx';
import MessageFooter from '../common-content/ui-components/channel/props/message-footer.mdx';
import MessageHeader from '../common-content/ui-components/channel/props/message-header.mdx';
-import MessageOverlay from '../common-content/ui-components/channel/props/message-overlay.mdx';
+import MessageMenu from '../common-content/ui-components/channel/props/message-menu.mdx';
import MessageReplies from '../common-content/ui-components/channel/props/message-replies.mdx';
import MessageRepliesAvatars from '../common-content/ui-components/channel/props/message-replies-avatars.mdx';
import MessageSimple from '../common-content/ui-components/channel/props/message-simple.mdx';
@@ -66,10 +66,10 @@ import MyMessageTheme from '../common-content/ui-components/channel/props/my_mes
import OnLongPressMessage from '../common-content/ui-components/channel/props/on_long_press_message.mdx';
import OnPressInMessage from '../common-content/ui-components/channel/props/on_press_in_message.mdx';
import OnPressMessage from '../common-content/ui-components/channel/props/on_press_message.mdx';
-import OverlayReactionList from '../common-content/ui-components/channel/props/overlay-reaction-list.mdx';
-import OverlayReactionsAvatar from '../common-content/ui-components/channel/props/overlay-reactions-avatar.mdx';
-import OverlayReactionsItem from '../common-content/ui-components/channel/props/overlay-reactions-item.mdx';
-import OverlayReactions from '../common-content/ui-components/channel/props/overlay-reactions.mdx';
+import MessageReactionPicker from '../common-content/ui-components/channel/props/message-reaction-picker.mdx';
+import MessageUserReactionsAvatar from '../common-content/ui-components/channel/props/message-user-reactions-avatar.mdx';
+import MessageUserReactionsItem from '../common-content/ui-components/channel/props/message-user-reactions-item.mdx';
+import MessageUserReactions from '../common-content/ui-components/channel/props/message-user-reactions.mdx';
import ReactionList from '../common-content/ui-components/channel/props/reaction-list.mdx';
import Reply from '../common-content/ui-components/channel/props/reply.mdx';
import ScrollToBottomButton from '../common-content/ui-components/channel/props/scroll-to-bottom-button.mdx';
@@ -378,9 +378,9 @@ Upserts a given message in local channel state. Please note that this function d
-### _forwarded from [Channel](../../core-components/channel#messageoverlay)_ props
MessageOverlay {#messageoverlay}
+### _forwarded from [Channel](../../core-components/channel#messagemenu)_ props
MessageMenu {#messagemenu}
-
+
2vv
zT7qbQ(wmMh%O6JCZxlZuu&>up1n($j;0J&hVq42`)OUA=NY}`de<)6-K(f;OB|7o=LGk#f)^(OZBSEIKK^gl$hMmG^#W{
z506T@i@Y;7%eKvH9~D$>$!oyk0ed4I59@7jo+Wz&H+vg1K)%xf^hZ@BWpVh)|9A>6
z`*okzc6X*pBARQMacY=;37kb(V?AfTUjzvi!uc<9BUH~%kUEoefOky%5
za#&6{dv%NQdnQCoHvY@w7H~zC-CBxF?KO!I%}NQ%J*DWwX^(mJ2oWYy!bL@zyA^9?
z?FdGi&{6w_uP*P&q{*$f#c=X&t^j!&FKj2bn0RVtSyGg&~3P+
zF_0e;R?AMe&bH>w9C&zO?f9hLopH<*f%^F8>*MbzyGxukiogCgT480%#izd<>NLnp
zQxNrO*hpT@h_n!9n~p4QqDcGPcWY;BrG@%E;$7Qp@op-E{f=)}{JQ+Cr9*F-_S|2mg
zd56aj;%W=(=xC4MK_jI~nRq3@W{^;`_uq}ez<-Y!?u-XPiz9YI?fGlN8f4Xfk+LJ(
zLJlOMGkmvrhgcTb>0w4uCl!hCxT0@xX)1e8lfibU`PsW5#~gW*>F#d1vQnaUx;C=G
zu2zFYAM{2TCD(bl=uZ_8TFSMplmkOkpQ78wZn{8vr2J*1a3dhiQVCodqZs`n^BJ98
zjN->}uxMtY@DJVPM}F;lqAW7mIH_v!pi7?QLkILD8d?ln%#^())<>JcUS@UlxK$XJSgoC#};YKIWcEZ3#lndU9W(kjXVZ%R3gqD6P$YjvSXeUUHtj>?(AhN&pv~IrG
z8?9w-%hmsmmrIQI2l-E9QI$HR_ymNE-W%#D+@&z06+EkNSf
zj}gJGFPFkXR@wFTe6)7q*R0Py{|d&q4Nu-(Cc!+=%eHwv
z(+6iHCy^&|iExVf5l%+AI)q(PP`f%q$e=4{RmQX*j@EvYmSAF}$2pzP9}N7dF&vx?
za#1^3B5)X#5bDQuSZGC|p8pASH3_S2AUmNuIsAq|#n+mL`Pha}nmdhm5#tVmt_?nC
zcCXr9(t4QTipJDJ=&=ow-8Kfk)J)K~#pI%O4)mG`F_v4w{y0r}^NTcrn5a_UE$pQF
zN148f8_hbYGNM+t2Cgd?wq3OFulfIbB`i6D0Qk`chWj{OPgMJr&Fg%E7oHQp-3_8b
zrv@NNh@Q{Y-=p*mu=ywmg0la)ecAX2IIEukOl9#pn`aDFH^ljk=|WxwL9@OXD3HAC
ztG2FeRDa}gKUY11oaM5R2M`ShK~B}CB-6~&-1Py7IyaE!I0iWj&l@Z`QVMzLo>{Tg
zopeeO7dvkU3f!r)Q+Frp71EsYxgQP1Gm6b^;K?N^^IQ%Z-txQ#K-jM1cB#o#d%d*=
zx_!E+fl;lmdAjok%~wykT6%nZlObvoeOK6H;EeKhoiz?kL>S)laXqFuN(fj>e0=)Z
z$24PgR1&SuX7H*NZ}9#o?8l78^&PB8#!-Jyz+K9*Ec@`$Ct{!FxgzqfH=9g=Wy|8s
z=?`cEF-#CDvjH+11@A<;;PYVq5yXC4o?F*n>=}Z3CEYJuSKqE40NlT4Y@YMl2v_^;
z(a&gEWMZS$k@!7sz0;R+CIeBj5Yj7bCX~P~1(okGr(rKir|6wCw{2Q7N5Y%=2cG!X
z4|IMff^iI79Qls8X$m25@$j1bl6X#p@B+qV27^gV7@of|r;6q`y5CrKmoh6b`m+p1
z=1$Ic`A%g)u~cuoJ(CC|V2-GuW(A08s`4)FQSiZ4+5C=
z5W&F`fDJg(wvPXMG$y1@AEFC^4Q`-FQX}fjBqy7?#~tYnj_Csg298QbMP5-uI$ByF
zPq~(zdFwS)t2Wan*lA><}wZsa|n3i
zV_jYEHah`Pd$8%V7lP%d6wzqc)(O5!G(Vb`rU>$dTFaNiL>x%h(<*IwK=Hd22QqxL*SaVd
z;F_h(Q4%Zh%kTLT%!qTfYA;9_v9JbzXg2@%)b4n;^ysvH;+M7M2iPyUtk&}kj5fEl
zsbk?MPWHYhIwma%sk=sDzJN@R?TB~n0OHu_{=BYpq=%bTvYs2>lZPNkkeFu{a1N`t#&UR|dN*y$}WV%gs`OsZFBwEM&7@DS}{?bf1bAI#c?eH`t6ir$4OE~>9tDSU2L3!`
zHam2#6J7V6mnBGj>@0g@+j{z>s&R2&zVTDCnPK*u-hfzLPs3MwHFJ!T;O0
zg}4f~`fY`g@~T#=esj+of&Lp`d!HQwlhSL54ZSi$Z0Akph1|PhBF*s?7-K~3*^5L|
z@a|pkYbNCug60dD`(KioyX($audm>3{QyAz31Og={9K916~gMr49t)~Y2;t^uJCkg
zwYqrRxFO`-qqqdqR?M8>L|gC?M95@J7kIH7iahMlmIIX1LsTb6MV6ibk4NL+Odxxz74y`DG)0)0sCU)T@UTGI*@1>D#`{Sy%a~b~A8nI=Q2SKY$`N9Zc
ztiW&4!WLk%Sfqh=49*Nm{w>&zWw*-ytv7v+=TY24%*qRIwuq{506+w|&O7@cD+C
zbw?iDhVDykdRR{go(9+=;vu$e{y8H!I66uDLN%nt8)cXfH^%=rN~w6_RpGnwTolDX
zDDo?iHJT_NbxM&W&`~shf?v^+WO)F?&}9YSg@}7i{#s#ZKytvx789@D$)s^75n4}h
zNryw9gk=aQxlSp(l8)OFIgO1YL95BC6ZCY-+MALTU2&cebZy>S{J9>~6Wvz_^xTf1
z0Xe&6CTUf6(WWDEVBiYCfRdB?(I2raR(%6afcX&|d+krH)HA)RJ0w(6`>-msXzkeE
zP;b-l{;>$rWG+F~z%N
zS;*bfO)eqq518AnP^n>ggOZ4y-4-(jA_b_J1(Md;j|O$ZJ!>T%I!aP9@WV;Ob<2PY
zP_7V8Q7Xb>`Y=oy;W+p`Vjo_&`5$Td806PL8z8H!hm#=?S;x;!u0
z7d0;ovLvknFZq#zC#K+-`b=i7ic0BKh7^^|HOtX(JA9J)YdT!z+9{CQ&S
zTX3e#IPE8rNibd(E~$<}>
z3m~)A>M_`!qJQI0_AqFTDn)SPhx!1d9pHVtEQ!xJ4d2V?Znu#Zay_Zj-_5Am+6MKsy{0sxNN<>Ii;oi_r_Dj#XEX#a1Ab>c;kB-WCQ&mXX!;m
z4ii!f`dt7Y;;4d3_${y8HXjaIujo?a4G90Xz|X5$DEB3JsNp6v1ko(j2a(d^<0a{`
zu0ugTY9aUX9Qg;<2(t=DPZ@&nK9=1x0cja4+b(HeOVm~=9Fpjly~tXy5r
zT1GWJ`#;}Wqe-q5tRT@_W3*bvB1c|J4rd3)VcEj=Vob;h&x8UtLukLV>dN5pBx#Le
z19_3ra3W*yFk|;JyVkN_w)=!LMAdx@Qbt1Kl^ImE^gw@Z*P4Hx{1DALA5k&jRu?^y
zG}h!I{GcNMo3l!8F!L(_e>c8pw&CK}N9BNzyNb!RjA%+qbgC|IR
z9~JbNt%*8xk{NB_IAF>LY38^clU(kj;AVs55;qG-@i8v5$FWl+D&4T#Q342HeAa{
z)ep;FP)itDj^T>CT=V+zH}mE{!~3iFj+*MSd5?9hYp?yEL3W6HJU@*V^m^S@lsQd*
z^gT%QI$ay-rXrT|=W2oYXvshc{PsdIl0|YJx2XoplA&DnK8YE}F=zd;UUR)N02&oR
z$|Ee=m%YDZ0(9+xM46xbFR!<0vTv(Z8CEwOAXQRDjLiWr0_|yxL66Vs2!Bb`v%9HD
zWx6WzF_nH{xfVG+aVm5tTwxqPix1of28w8Fjwg^d`I`ts3~Np6*na-QixLD^rtNPKgk^
z>@ldB<3=vS_gP-b6o3cxdw`UJ8qvMF{cn29e^rw|(~_A^ep&+9K2kq6*oL_&zW2a+
z6%5gPb25W0B-@cKd0{Lddtf|?FUBP76vqq9#{|;)!n?x0A>cY^L3>Fm^FT4dWw7xY
z}re2c!+Wl#Ce3^I{H9uFw_wt2Mhr#qnPlT^av@~>}
zZhH5RZI`lmAfA%J8MKN1N4J+n{m(ClM*15Q&Q7dj)lmr^iKV*VNV6~p=ok|nCws!+
zT9}*DCjNK*>O%OpzLO
zv=Hx8r9w{0WARu)Eftu0$LFLt|KjOE0}$VnPIFq`IQ1x|%Z(YXb=r8?+ZAiJ8*40epe5BnfH#Eq&b
z3fAA2grLd3z1g=}_T~OV>U)w8HbTnpz>GCHI76oLlF=(fc74X}Glw({_=~L2UkB^4
zAXE)ysDSBOA=)9{aOYXWipHBI5G>J;Q_haWm>WZXm`O2W;yhILp4JZug
zEsirxum!b6Pq#W&Av5_F5(b7&B4}l`?)iLCS6tmG8{;dqwLyrK^{iOe)N%Mb
zU!~0)(N*=7l}@VOq{;m6MS~vLjccL^$J6p-OR+O8F7Vg0u;b8?KxbF)jg(@~IMN^4mmCrt8wM#QqdpqA*JCAkikF+}LO7`RYah1}5sIU43k_XOPiN@y
zGcwAD+y>^ndi&VmM~KxY}$F>SWd5{;N3;5J>)rceKry
z)0WHJf`pByC0$UDe%44{hxLpr80GuQ+$6j%s(A>Rv$ghkk53?(4vc_3iECZ!IpueK
z6LBweaNHlV!m37|V|+D;6pm*48;)KAvFC1V)om5TQo!;c)BRkg3B&3=B3p_M==rD!
zxpP7q{?~I=Dsln4HG$t`cwrrBQniOm}h^7PDwpA)fUD&F2`#!X7
zc}L9NZ;62XmlX^bdx-09`Ef{h92aSAOL+x&p;4HZ;%eA&`)K($iQLjL{zpVaZMyY8
zA|gd0@yyy7&v^v27wYG$p|>d}EK5Ai=l#FBS=LU(wB|*l;puV5G5fb%N_ofPJ_$#G
z2*gtVAO)Q^1t$vM`eCzVy(9&p*IQ`s+9mV!LN#?Qs`u2<@}lE`c#?Y>WD+fy6a}#+
zxGh!mrK_wF1t1@cXQuxrrG5bnL=q%JJQKohAr=*XXjF?c-LxOuCIBzD990;0R4_tWeH%#05+8A_|Vak*JD1y*VIz=IIRK|xyXJRBA(qLqfW{
zJEWvR8itbYl#p(akOt}Q?oMgW=6>GiUFX9&i!ZME;6i8ip8db#cX^Kz*Hbe94?r9C~N*dSAi`!y#rZ7AtZ^NkhMBJp-V#i?7~|DEr0
zyNCDl<-oKUJZVxRfLn0u#gs0TO{Be)PFe%#2!-!0THs}S>)|8@zz%)xj46S3v@dxo}B)#Le&YQAxXSIGWw{>@5NqtA$<(qxYSH-~6od@$B>A4i5>KyJeJPEq
zBa@ZBDyvWie-2t4t%wWU{7?iZDR@-y)U9-*=hO|0SnaSrCj4~LJATj1V3IsAwe`Nw
zc$1j(Og%ci{+FR76Kg0I#$&~Kpy=zIHk~g37y%dkNmLYISalvCJ$&y?3%uTMn?7JZ
z%_Hy!
zAiro`GBH#o*tPEWbUOV$*(Fmpm?6^(-0lph_Y#$a`3;g*srjT!7*eYKt6D8x((xUU
zm(m!t>u%!t7{#PzuC2k45g9JtVzH@c-%anMYCp!6#yP89%->Bem;3I6P)}nQ0u2Vc
z$=6N;yZ8b_4hrlEhdBq)GCiiQ26LAYm1hNdsCxJw<7)iW#XYL-_BoLQ+mmCya7)YP
zylOsxv+sHG^TBb1UC!eNNnDm=3cTw+t^+ZI!wH1|`pEPclKy{L0Bjca@1UMmAQ;la
z-Ja6EL*qOe8zO=A2n^oDYq1uNot%#Q3-kIuZ-c;#3jnkb6amgKP5pHWm?YK(F+4g}
zyaY;Dl%o`sU66mfFSXRpvdwT(oOonHj3S00W&ODD^u}^4Z5*O7<
zhC@wc>rVM+6Q7qMrQr?Jzj_JTA4@hR7N4q8VrQU1WbIn<6yteNbcfUico)vz>W#c4
zN7UT@)L6Hgk)d?-8qt*!pAG-&ggvNSzn5-X{QGW+$pMQ3F~P0lY4u!+)71KM!4w>`
z$;`o-82rwpa09Mh$=}ZlKG&nBhl$#2z*kCYv)FeUISUJTkdM<}qjTU^s^xB`{dFvp
z-}+AoVmyp&1#PlGiBb+(-2iOs?-Q@_Z`p0>wSc#b^Pe%`LaYbnf5l1jLfGRXdCM0F
zzkHL;_L7|XO4c=t#nmmlU62Rf9IVl?_{)thbjv16HLg`v_5&>+AF3*5x4!h<8(5V_
z^g?+o6;G-sG)2*D5Hu03QX^^8L-u0!F+-$uDN;J9QGEQVTXNeC
zfRK1*NxfOXzQ;d4ytuKl{QBGyAwBJ25N9M)O}C-8&KJKXg%m)4=w_eooH&5DFDw53I}CR@C(825yZhCUeUs
zJ9HK|ZdWHu(0v!Yi7bAOHP_RvU3{-ptF_O8=N)y28~eE?(m#8hu+0FVhm3+
zVlIKJzBF-deY|T1jeJwLI<|LQjl&T}?I7W|&2Pa2eEmixHK6I#8Vz=M0-Z8Pfl_ThEI$K$5VrwXv21HWDk5qi!GlW*~u
zXaC(X?C9TZ?DNA|LSoQEXR8XGFco7E_UKQi#+_GY+m=3chq_aqh+lbX*oo2>NX1=;@
zV%UcZpBqJR^Vzixdc!+qH+rTU#2dbkeA;t+%a8
z>2n|!QB&0o`&^*@!vB!|uv?22NZZZAv@WqdkT217KSd*GB0&INbS(3kUbFD?L$)-^_cm|(MBPdb52r=YTq7#VK_!9~fHS*f`YMZgqT
z7f_FB9O#msYbD&SIdNmV<4?qpRR^qr%}msY9&h)oeYcREV7ib3UYj5AEfdYzpB(i6
z_ee-VNghy|TZ~l}P>lV0fVS23N)Q~t6CiuOHyd@(Jse56JJr7~Sp$AEe^sdy1dIwy
zS#Y-%?kxg|4;|$_T16tJ5Lhg>RX(%CAwRaA*KBdZoF7ny@9hqPcq@GMm&?fF6U*+w
za_WTknm;a0e(Po$O{<%C>xE~8O-}z;UIipb7AR(ZG_Xj3CLF~TQFAX7Vz4yTx&ed_
zg#@DF7mpUT3lGebWe?QT?1do%1S&gPD9HeN3eI{Dmrv`6|VG8n@WZtOFm{57iBfwk6%aRQ#k5~t4C@l)>uF0xBagY$qg0A
zL>LHHW&?>Pig({vl#D@1Q&wr2OTT|?f+B*ZAMNpfm3hVIzAS^&L}&t2!^uh9+w>h}
z8!8?xxX3OH3Ax8#Ab5&|xL_1;>+Pfki7EZ$F-(aKsoTi1umC9pUHj$$^TWxhcSOb6jP1CC^P(s-+(dUhVHylQ?&m*!L-h9
zB%`ZwkwShO`8GzgmQo3Q8VID*AxwvzV?~6HsObbv`03c!2|oS*DD&A-$MQP0fAMDv
z$xc5wUmV!|+V>laP^}eA938DZ5_W#I7QG!q(4$P=gTnWoXhq~A!
zdLwa^2`377hfk87X9$|PFH%S^=E`4Owsw3E1U3kczh};{OKh~OA#~34jdfjLQPlDc
z;~`4X2<^~QC>3pr9b8*zAB&?4^=r}9k}VGj3+wVMPhsW-Q(*zCE}`5@MYuM0pMT*x
zjGqh0&TjpWV+p8fc!5~{sWr}J*_c0dl7iraWZ4arQdv^&EuYN@l;K$%p8JX~n>3&9
z!>Y_*1Ltw$xxFa_r9V+v(awJ)rFB9h`pQGc?54~+Zn~Kk7YG)nQ+CG^wx^S$`*?hj
z5hA!4HgN4d*WFE#J;z+cKYqr~cU3QfhVA_!H|y$#&9z{SRk^{73sFS=e=iO1ScrRU
zMC1e8NkE^6P@a|b@Wn>r&F)2|mkF%u7dcWSf}{8BSMl6viV*fZgZCd9__I-`EpjJB
zCK&w>E=Pd#poT=~{HO1y{*D5W-=c=ACvXWbVhYBJ4GMyL_!O5B(X}(Lwd5Xu2Pa{0
z!VC#YX~(K_xhem#Bq4rOSfHECTXz)BLtniM8Sk2o2jTj~LCXFV9j
z-r|Ar$Eeyx#Q$6SN8SUp|KYsYJZ+OMx^rDU^B?MP7wJ;ifx91|c1P+{=6$tZpT|6L
z{LmvsDue?+i{jAjC!ncer}wF`PN1f8b_C^76I5elhA3q%5TPA+_`p_ZW8*Fu^
z_oxC+VhjNlW(|M0QD*C{Gmp`Ln!XGfnQ8kZFo2?(?7guVTLyiy9qiG8|70vZ5+A?n
zNg+xC{}o|221b(ET;tY>*CRqz^NIr}bFbk4$i7&RRjjZlFw~IBA5LZfitUDk6y*kH
zf+)d1(gSU$>Y85L58^O26>dXYfXtbN9cSr5E_YXC>%w{H9)N1Rk}-~9C7#6BZHH!p
z($1Nys8fVu+AbC4^E7t(;Tkvy{Erxp8AxOWHvnS!a%6~S1~+h;w4wx&N?0{hO`9T5?BCX4xJT#1yC5(h
z{z2%6TxiM|76D`McQnU~(nQan-&7kEjb?l;6#+%Q{cl9{kN``RFC9-#){Tuw!eOf4
zM+WH?K7k2FkCy2K8)oRAL6`R^7Tin1E}!g7c}D+_;QIvzMJ{V5P+2{F+X362nz!sy
zXKxNeiF||k-4YNM9DwUcxksQm~7buy1i3yY6eQiuRzhp*zSh)nu0p!{(f18%UO@f64jepn6nhc81
z=|2W!=bbDow)qw{dnS<2d_gWJAy=!$AwZbVCI=v&Q1QU6zx+^&Q1}4~Kt=s0#s$db%3*4(I)n
zqa)1}PY(D(2{IH@{}Xl)%6{}zsZE^PIs-g&BUw{2$OK1g|29e-A#vR57&k>*~P)jTN0yfZzpb1*j;i!-?u
zwTfil@3>$zwthr#atfXT%q&rv>!6PoVIEJI{C-6EE=Ol&I^#QYn&hz*NIfa)o-80J
zDPn#I?u59ZG=vN0(Y40*0*(hS#qLiR+jR795FsQ$Q2oOfLT1*nSqF;-jkT=;sTEGH
zKSbuO2l-J`efWl%v50F;&rds2!AvpZERl9j4z38@n$I^*_9R%e0^B
zH#Fs|T{ur4E}R-Ak2cQCDsV8Z|6Ncv_cCbGb5%bwDnNkzAO3Gww6@ONiLeI_{l!l=
z61ag-h>N()frPCnQ=V1jyAjIE)DhW|DK=}w+$1-(^_}`GZ?>om&>M$nGYSFTNmW7o
zw4xx(duojIiO2%D5mP|z8pge?B*S;&mw*F5n6m>g@XY*R5pOwiKNo4VhERkOx9R`E
z+NmNo@UHL{Q$j0F!M7ADg6!4mv}0>F1Z5}hBS~H`zFy2DCi4*Q0H=FP;UbUk0s_`;
zl{Oylc4oOpWGgoMZNi2iq)ONV;h@hs%8T7ez<>(d@@z^Rka(I0h`pci
z?f}_iW__GB22J1ZVfS!Kp6zq_uSOkj;5eGqA9MXtxS-%|Mpfzax3d<@qvPxO_^DY<
z!z>K|D=f29U|KX;TTjJ)(LV0~;nV=vpI>s;U-xuse9#?Nz%+(RyC0!qxRb%s2T7_U
z3nFQl7xe;ufW7Nas>~2v&gc-L>!2WiV9Vd$+}g1PMYETKYQ?v1;tHUqxDafm3V3h7
z;Dzrl%WZ2n9pL#0k_YI825E%G%QAgW>&;1o+=H@;a|B+K1SPUKZp3DO1vj3)XY9ia
zolVUvl#j%T2vTJ~G`OCP?4`C{R-I|mjPJtp=8*o@nyIc2va|58x
zv)i6`ldmx9w1CF_@K6V~zXd|)-p|J;w0td)v#tYIwNuzr(#O#l&M4Ri;Ptves~
zG5{Q|eglTT3Sph4NUEa1VIh!&eaoO#)ccTd{4Vglt6kjY!%1%M(ZK%_!S$0;hwnt*
zKf<#2Ky%UI)oVDjY-jaQxeSXAM
zO#?4;9oNwh(*Qeijoa_}{-yx*Lci*{9b=vKbx!s@#JIJZH@Hdn)Ya4oh_=NwrUS*m
z$65GYeLQ?89D4&~W8Gy(MyjYTN*cb5o$=B~UBok;K0t}3a!SWftz+Ic
zd$E$23Ru&1Pi9fUqfOa9Lxa2Zwrh{inkpWCq<5)0&GP}n?qBxW#K*g?3YdkP~tmGyY5-V}KOKEDhqE69?@$FI-w=C7s^M8bsF^jOc({DJhcg$}?hT
zsO&E%GRwRma{dFs4RP_M$b2W!$eohgWA@15vd~N6SuhHgapF^g5_rDonFd1I>F;2B
z84K#BWyj8s0Is=H4$yfWA9v_P(g0XL-UwKl#7p8Ygsc#?p@kJSlSMAgd#F32X|Vvl
z6@ZFkMIci~_&qJJ$1al1lY@o#4zwyi9t{>uz3-E8`##>djeeLf27v6W09Ls(!c5-9
zRO%b>QV4V1r%I)%al0G;w}1ZAoo`Q_(tn=zc)=;BUWe~C1@)xI?_>S1CMfXf;A|=k
zf2!v!HQjv?xHtaBl+(SNFc?NnVu73?oc|%wIaAj99E|h$!`4WY9=kpC3p7Vz`**8%
zR2W=nZiz(`qr7249LF1j+Pjbc6Rb>8`ZKNc*s`2Sy~gw91~$HlgNOxfUK%%ZTTcD#
zC=Ji*?Uz8u=Vl@!N1?IGd6>^1`<%NEHuj+Daz2}r!Rxzea(W)yBy0JJ48-Vie7u%7W@R
z)a}1=Azo008)Y8xkF_q1M!+L){LG)^+rvbYu{(-y>q!ZDBd5s97sf${?>1@KQTDYn
z5m(V0<%;b1mGPLpUNQf`H1jFdpe|%6wHCY%77qo6m=V!+lhr@m!~mWS+iw9kH>a5)
z6l<6r8mYzSi0#QSg@)l>n4M(9bRjPnJ`vlwigzDNI*YnXA!fbr+d+3GgtpF3nozj8
z9JYJ_Hl3nEhC{9Om(d#J@B?7fGH;JnZ$v$J
z0OS6?l(G_)^P1|xcS}S#K6XC(1n7VGwQfZ+O)2?Zt>=Y-N?w3hD$VZBok`=hA)a~_
z;@ea$$1>9vc*Bd7lLNJncJ|VNOgj7hsPsQf0*7SWx;Xc*uWg&?rMZn3lj7>>1o)~+
z8uJsTIxb3J9W*_{{9*Q0yPOw32CK>gGnYmKzmEbn#6WKAJJu%g;_|E;XU}>SM;?u7j!%EHNAj-d+
zq&oH9h=^N$FP*&a67*7G3Oz}?WC?t=dG{hSc-DQc?<;8)(+Rj5-XrFl`yq(i*qyHZ
z(eZ^|Lp1o(B6U6Qu8JKA;2&*H12ESCR6%N$6T7BW_Xyj7`@`PV$<0uHawuqQ5`RSH
zf%?5ly~n!K@&3_3uZkkx(D*|*B(3G?_P-_Ty@SpsVekG?=|bG_7BRWP65d6=#SV93
zMmgw*>TE>lW}v^C#7$sSzvK2V>}==ckY2uOf^6)hvJbCBVUq)JErsaM11vX2DX6vR
zgUU^)k9MGs|F>*1Wq<&0*BiTJotYb}nnX=4_pe+B+ce(P@&c03O}E5Dt>U0S!my>_
zE{g*#R^D^4VEyGT*d?gTQ+1WLvk~q4tXr0a&sY2}5ImNz98J@K*E@+yh;}3Ass&8L
zVJZ#)xT?`&ei!)OB;d7~i6&!g-`yE5uZ-U1sT6F$4j4OqUGW@jjv@$YrEk5d*xp(ARW>XMV7IGfrI%}VS){1W=9yIPl9KRCmRsB
zKX7?nH;xWsU+`a8K!f}b`1|y6RkYQcSYfRlzY%;NNy96h3J
z!`7Qx`Io>1{ND$XXT&KCUQRplU>`GY0A_yhc;9BcZLRbzUF0GNU61qK$38<(&3x&r
zBOS0*M7y7LMI1xn+P>7IfE|Fv|5s#
z^^HiD3H!&B2~Jx_`s<@U8o|Qz+c`L_DecgENjUrYoz4A1K70fg4pKCie_tTS{2VB1
zIQoQRam67j{|7rOv%I}rLMF#q2SV>^i{hVLn2u+iXqdj;|(_N$;s;M
zM6%?^lkd2HUW+|#z=UTNWl}v@;fqzHH7w>D7n)X7bC7>eG`w`5+tqZ6nu@?G!5eningla(&qt37?s{9($VbFz`G$smv~K*Q{p!6Zhj&
zo&BN>T>zurj);>BD}Q{w7gZ~Ev2U&?{UE;%M6k;zv<+o9iFQ78^u!pUi%;;mwj?Clurj7nHc+%r
z6BFH;8&LshhkNbAKBX?XsRqqqU5UG9ItgNMM5V=iQ;4zS
zqxXszD$A#aEF`c*A<96act{d9eJx=y9Uh2|aJJRu!zQcB{<~L(C%LGbd253AD4EI%g%}%w!mr~g-Xq>jQ
z{N=dBW{ucNc(r_3GBR!Xu3dRndyUkuFjtTpIlEd`Xo%t=SsGBaT)eVUD3xXZ8<|#{KkM8G;!?Bvn#q`4
zDQ2oKjL-Z(u#b_k*nm6*?+fNJ5qCaF)d#`uFPP~VjMv&HP1v}xs@-MUXGW^>nir={
z#zoW@i?aWVMtM$yphyDELR7i;EuxOFH3IGR#@`TmfsB
zJXz6dK?X?*FUc_wj3!*B+kZM}6K*O*AC|;R;@=PDzy66=YP*$!B~}+vBci1LJpOg4ntpmQAAiJJ8`0}(qUmBUZ-_>yho7toZd5u02scnSguhD2UjAc;K)
zrR=DhU5QVYVcgGY${C@{v;9*N-!AoUdnaDm>jyE-hm7haR^RX@)WdU`su`swsG&j4
zI+^hJ0XrJkpBo&NNPbierZK*O<;3HY%QY^ay_-}r|01P}!WpKw)z%kgoB!ymI1JNWOO{&i(
zp@>(!4@%H{SjLbRKQ+)Ro*V+>V=$*$4JB^aXwtl~_H_90PQG?ghe1d!z`#U8OhTzy
z@!<047pOaJVzPPnGG&!r)E~fJVEPhavzZ>4L@;M9Br5LEEqeH$G`TBOok|tg@MB~Y
z5xZ%4JmefAVd%5Fln@gz=}=Y2QnI%eluI9bQn(>&y4o$?BN*(oK7a0t6h4=3H`dw@
zANM+Z7N@*^HV5$|e!)Wh#}@3^fE@w*=6ry`uq#*3q{-MiTvN}K=I?Cz)7J)q^9q;r
zw~+mB_<
z9qx%N!mInM{Sk2|3mx>C4uiJ~ku;HL$DvxGl+lZH7dYk)!WwK);3cq^yM@3wfVukV
za!%7Rjj9e;&xfp=eEa1pFgpuPE{HJ0Qixn*YEa+rlfe}};^?*`+s!aK3cZ^x&D)hF
zN&1_*Ja@`kTM*0>k`Y82eaXEzbP3-O=4EMsN(g}|bPDU?cO_WlbSSaOPfhoQ^D;4P
z!KcejUHd1vbSpxGN7h6}`!#j>pQgPM&~5wYhI#@g1~q|SkyOfPhrk-$V!*9J3%Tsi
zE=#=(C>WdNvUf0VBu`9Y0da|QKjNVThYxDMVRU?kg>}t(D;Uq^Abq%VZ=CtcpKC}~
zh8Zu>v(*ikg7X`*FiZj$tQCYW8DCx>+Ap3*XIQIMDKwJRiwP6--wFDazC(HNpr*fz
z+lg*BeM5SaSGZ1Na@u!I7#DEA`hLYI^|==#+*3@Dz&~uvJX3f?{jehW5{pkZH1XI?
zfN1Ju=Anqdf5EXT$(zgs+r(?X$HPKOOjh_a`-gLuAy
zt#iZJPHpTCVVVDV;S)au6^vqdD|{JOmukP$(8KD7m`0YW#K+kxzga9s4XSEr>41OD
ztZ=0K4^{_wWu7PvnY(;k<`4HnI^A61T!vpc^74w3bS`#N{Vz;ymZ_9IUTJQE}J^7?^uZd;*DRZT|OHO?Tp
zA?l$OofLR?K*+B+eA!Q*zZJ=cbTykKv_xv^vl{$aAHB1$C3Xh{J5D$(`I*%?xx-g{
z*r_&SUwKz#S9MpVIgbIHpgWWU)t{7@M^VB>=0-I@`!Yd2o`dFPKheD@2ENbjMm9nY
z2#evk5eITvitr&}0r$IninWM4>}Vl+t^XiS^P
z2{t-#AV*1CtnA2J{iwr%#$sxbZ;9Z&B%)srN;I7{9DR;rlV2QA%1o)cD9^qxB)@?~
zyxsf`zuT+c?<^{b;LYqAEf$sG`2q5N7n*NdLC>8an3Xi&vE28jW_aJy6?J@HVAPjy
zy3&=RDf&YfV?K#9wJMbkeM2ec0zYN0xar~IpAXs7vF&^Jo5Mrg2i=^X`_OnCoK+8K
z_i}u0BO;vl&XlUXbD3M2cdz8ohINKI#jix#@5iL5XNZ6|-00ESlB%ZGL`_k&WW0DJ
z97dgcXcAv0RM^c;Wro`H=QeZ92mV826raVr7SMi1+MvqczIU40-W=(W=eAa+S$GqPC?OaH-*D=cUog|pA
zw;p=>E!J096@X%_mobKM0A0`5ZG$EXmre`&S(53Byuh~p!hX+saPV6wN#f>Ialo7ZYEs&!AMJcd4
zJ@5p8@moEuqZsKv3>cVcRe5r0M|FifZQbGF$7NJ}=MvRVdw8
z>WeiL&l?Ie9?~eI^cm%e6T3Mfp*Qo0XF$PK(z)i>4IXgP2|*p=uwknk7qAcW{PHYW
z{Abc?yw(A@p6LY>em}anjk?&N34%Vox#fEZfLh4i!y9y|s#&Sx!0;J5!6DWz^xB$0
z5Zs6*W?*qKv*d!}G=&426mEaB0GefpU!dT)p}dgW!S9(OJU7ssi^qoKXJ>K??j#h{
zu+9E@$6Xp8am&X&f2-e+n-t^)I)Ot3l9GzKY9h5t67rNlRt7>yueWrf~HB*>6TGu=tBs?IYWF8$F$7?PKHiGT^22s&Y1LyV>r2
zjiAl!dBam-J7u=>I4Q=7d1Old~XYUA0UsW^lc_2*pD`w<~C1bIk-g-=~G#OqGOpqsw^QKz@{8Ov$SBWpaM~45f*@
zr?Ds|TD^mdqA9RZsIb4VV&L?0JMYM*I;$Adb&nA8k|o|N7T#d668+PpKh9BjZfmrx
zlAX_gE;%g(Uq7#HI`;uZ2u6>~<4fLjmkdM;$G$HCmx)&lLgJIjE3~Eocl}WL#hAdG
zgYE0Uyu<6jz5xcZI0*7Sf<%$#Fe
zq~9R?A{_$B@t7BQ{%V)qs_Q9sKB(ho_nUMiVJ*!LcKV*G@4cRC_bOz*ylCbnCh=qlcE#fGOENS|O`>Gm0egEigCK&pZ
zzm9t&XcxBv1^T5^jl%lHsi5*=(tY|fZxOoxea~S<-5AgZ7$)}8fO}78OGdJ_oz$i+
zN@&6ud~*kcdxVH-DK2ps|2RyAe63lPFh*Azy<+P}9ekR;5)m#BC5K)krpFM6B>Xju
zK&I#p*-8Qr-%BSD3msi|S(23bT5)8m`iyC*=Z$I)A<&iT*-Q6ymr?q#QM;R|JzxgF
zvd@XKK86`nZ9x!=GNsoEeB}WdK5yYmBP{^KY3bQOm!af(Opr|_IsgdUbG*xA?$3Zj
zdn=!2nd9s3{RcaHqkh+C1*j|>W}QEG9xj-IHn)HQSC6T8d#0r1Ztb`*>eMySvfv#VS;7uUxA0W52RmbR1Sf)*ynKK3|_}ZN4o>1#IPHv
z#H~HMWyeV4RE)mJI@BW9#6yOM-^#Jy9_J0$j08xi}4PD3ho0RA`
zznaplP7(qET0TAj@drMv$-dw94bf7zp~zD=$o@60*W6jn$qtGB2r#8gZ^fBciP%@$
zFt1@w3|An82POXaWK@BSNOAg#7`scT~T@
zzW+u*70loOudETClWDcoe!ZvKk&4w}inM`MLWPFJt1%Q|nWOjBnCbJEweyEN!Om;J
zcP0$s
zuMfyPB&-2`u%I7MLn-uV-zJSc|2@8T2&^qC0s^VWqyPDXzXtVs+8MBqTY~O)5p0nA
zxX`&7O6())0sgi^rSIH_>t}t3-+3?j?!uq;T?2fl&u{y0&p+_ncsnj*I*3;4tyNi^
ze1qCP33-j94^!aePe}g4)JZ4(e1xOYiUw=_7_X(}9!hJOEi?@rUd%dBg`7T3U6pLO
zAP=VmSld;zH+RgAb=KA91as{sQgY(ZVmA*bG_5xDVq=BaLMKzBjL
zAmft~iavHVjdrXy+gE!4pXK4;vjf}#+!@nT{`W>|j6ETx5
z1+@T{{=6tK2P|Cs^G7276Dw76>$bbEh^pRwZArf6j^+#X4=H;J&czo)u_l5M)>C7}
zc&z(9NXj!iV|K!_&w#^S4McE{87UQldief9(l@#F8)Ykev1>%QovKTqAk0q&1|p@f8s72;w)1xTwLcF$-b&_m=H*{31&Iga*(hm>}C
zJdy~T`Dsf9;`+4-i)ueg0&zG~M(#faTz@v_Kg=8uxY-|=n&wZj
z5hJBP5)IHkJ5SXz_!s(TzWhD@7Z$zxn)uVVQ*;S;pEQgVMG`_V3mu;fF2o{s(I;Vr
z4agAP0FLxx6QE{uy_}SY5UVH#J^{PzNo4<;v8VrADoz~`>Sx<_2HEQde6!7nUD#nN
zX^vd}b}mSbbZ#l~q}`eMQ$GDYJMg&2J1+0z4<0GfH1usS)Oi7X+WqN{M1eEW^1^_^9gsE&`&@!fOJv
z&^80ViUH}zK}q$mPU8^{`8`mS;g}pX)rj(U{(TnW%P4i^9XVVq6PT1n;OU_DGw?Y%
zWj9xH&QV8Bm1a}MJnYj;bwVj{QAAT!peM_h9;n79m69rFJBX$V6Y^c0&^ih3sTx&=
z&lZg{I4ppzrs=fu$jQm+{MP||Bsw>r91IrP+s<(;?2WI3eosxUu#=cKEgtROEnk1s
z a|7@#VM#D$R|8e>^fwO;Hi|Wmm97fiCI9dmqn6uv=S5Dfyv)ij$
zI8k#Ogx}u9I!%^z=Cd#O^ile3C=1}{ca&`>(74i~;Wj$LymxS)5D_J<3CJ;q)y`bP
zA9%D=2tMPv(LpQXP+>s1d_g8Y>9_&BAAO3nYnid47OzIHW9`7Y5oPzx6`RSnZpBaj
z#+rtHX0=TVQr%SOE{K>vzWfs2p7Lu0vC$VPhI_^OGw6nW~f_J+bYX@Q&C)9?Ug9dKDJAD?J8L^
zrI#O#h(I-hFErCKq$WN!8b)7Sl8$NBS&ENvr(9eD9E9tNT1QSh0o)(h1o>AJaWnpT
zELurAZnM7TdsQl&HZD8NI~Zu)Un&9L<4_}1pbwY^X%c6}TF!T9W5p$O>JtaSb&xk4
z$ZYjK=am1PVTraA0@34yyZ|!UzFKz(y5H{m_ppNt-}dn7MB(~GQQ#Ikj2S&qKLt-e
zw>B=pqrSl-MB;#-8%gVpaxToo0Tr}}01k%uYKNUHTkB>wuUY>2n5|&wDN&=VR+Z3q
zGdpem6bQbt+AnGW4Wf?EXY*Hok+u!yfI_I5q4gmXOx=|J?F<4lFBAzbYDoUf1WKA{
zEOXSQIUl1$O=Xv$9DMkzz
zxc$7zPuS?#umdXnko-&om$1TjIIMiXQ6RFkR4}*xS{G8rQ{D56~pEx(i}DY
zTqb&-O|y3h7E8E(glh|L=e4x7cgjWA8kp9RuFHf<+@rv5swn{sLjQU+Ko0ln;oMOWmi
z5_4Z()$e+}^wcZE)<)mTm@dlB0{#W%L0w1(yP%lz(faZY6Li6-y%2(hqD!^9t2hpe
z(9wwn4hMotf~Au{}e1U??iz+G(Fen?i-&S=R7Wz{&+T_|9Y2mFamdRnhBD9
zx(Lk`0;7Bc1<866k+s9auh6{!6YD!R(I>w~w5lI%zcPREPK6lo&$ayO)Oqj-e-TPM
z5p2j?{7#Sxmwv%595cf4$S4Pcn8||2btEQ6VvA}l~
zMbe$&=^(2KpL>~R2=*;c?RdGvi1zC3%gTZX!O(=n941ph0Zh$oqm+@pRM)vtli*7(
zKTnuk{IExlF7E23d!b3BC^nU}%WIxre3Oh-t8e}{*UbDx4tXS1S}NY4X{hpe|E4}a
z?9h|f8>P=~0+xGS{Pu(1cE4aP3#!@C*o*VAL6pWcv~3qQ=AqX&xHmrs21m`utgyB3
z%|!5Ki%zyn0$;H1S4j2+VxI_AT=C2b3^sS*glIax7ef8aLW+Ht2z6oLEJ;bF@qo$m
zk#iDE4L@s3LH@TLSM&}U(a8}kGTkSwjS@tcY%?R4o%P)v|6Okj{6tPy9V|0D*wxfUc{afkC=`#Ml7TQf`RBKd+;R*tO(XT_Tv<3_
zrv^TBR){b|;&!_OtQ?*@N9bM$iCL~%_X>d$
z-iYaKom4LHV>gt{s-%JL9>_pS(Rv769VW(uic}n#p@@_Y&kw~&N>M@qLa_3tTjnVGdihC;C$jrr?l#wL
zIvY&A?Rhz4I$S4sRWyTeqJ=q8rOZ5F8=JUCIpl(KQo%WWrNrQ5M%ng|TqWVHgBMe#3Q
zzI(B&Ax@bXALZgF1_d%Y_pZUN|Ko*ZYqV{&^RB(qV#zG60TsW5fN8)0;+BX&Avn;;Z!gBm^4%Ecv}!|G!AK?d3qYpF1UG6LmQ@)0n2yOTcd2E1q7DT0Bu8DK*sdp5gb|
zNYOzq)O1%|#1W2n7sn+br+#@?&UHS_^;PwZ8vQDg^)?NMKmHjpZXt5eIx5_>RM5+TCKzH(q%
z;i%j%;4mL-Ol%auPS6nf-dJPnKT?BHnokPQ2`oV(eHr$PWX7ckNm4enqp6A?N*1NFvx1SDjK{dCx=PmfZevlkG7g0oeEuKO-ZCnx#$op!8cC7v
z?hcU#rKC$*y1QZMl5UXh7U>S@4(SGw8cIRw9N^t^-~Z=4&wI{U@0WMYXP2|~?7i9h
zir)p~K!O}xyNRpb$4LO6UN)MoJ1ZAtk}+}GfxVJ!g^avR@aZ7&t&YN7tICu1BUSPS
zRHf$)hADsb4r6Pm23x1G4*C|6giFy@l+lo!D~q9!uLN@57u>FY%@y}m!y5k@b=T%(
z^@FtYr~W;KUxWyyk$Z5*twAXE|G7Z5Xy8T96I|22r|5Vbz0w14aE&^nC7x$>S$Bj+
zX$>mCuIyK>-dZ0XrVDvgB!AF<+b05oKQr^W`Oj?OPbW~EhcnxbxsVOc|6`!Ersjwn
z_M>gartwnE8%{;7t!9B1RpJHUCqa5)B*C!Hs%&qHnwBv4v(7$?TqG~heyTyrc}zyj
zRI&l~6E>;uC=Ksaexj70V6t%eBdJobvib|c<%;QddNE`{4$?;bG|Q4uA-I2%(?#f$_OqJisf4%>
z$U6lM_F@M&V^}pW{4{O(jEa#h)()Mfp)sPXQz;})Mq+)_?jXJM04G)MBUUO&qUADr
zF8?3`#vMtf89rvo>>_;c#M#$+x8G9UcH2>|Pt+$+EL~mrsc_^Y5sy7RR4Ptt5<%t5
zo?j5vJ-;kGD5DBv(sQswT=GUNJ2}G>5D!}NCe!lsQ7WnjE02l?@Tl=5bEbLEM=^c{
zYkrl}o%yK%n=&DzldlzoYJhv2iP!MBoV)4-g<@hk
z-1=z~mCtz8@cGe-?dPMtviv#ANcT^&T>~V36*L*qb72ylPJqG-X2f$3bEjlEBFT3Y
zJ2rtN{k(S1{bJ_fS9x1ROJX>zh^RWqmQ;lh7dO1T{4UX`>c=Rat+`T%1p(DSb~n}{
zB*f2v3gmyn%g*oW`~7*wCzQq82##4+IqZ{x5COUSP{@FQD~s_dPfaQ*`)-pW9RO~gD+ep9nL@>p
z>{z=nZ6eDhD3DlJ0t8))H6XEH{!8S1ly<)6+b`c!ja633xZYFLx(~*zBkz7b=)b<|
z)+de8H4*&b@ZIfK!$(LT=g{O_fj{k(QgRH@PDg2OgO#4L%LY{8okl&Jwj-hy!Uvt+
zvD#B!nIas=!_k3|%USx|%dz%`1hU=3UGyWdl(`YZChR}+%)4KC4`~i=;4>oNW%gpt
z-S`%P;pz--24cN?`&eEzcR32O`~ueZqt@@#(uZx5lCUI6ebHW-9a>7h@jup&+MW4s
zf2>M8x!mM-E8SF&8Y#dlzthWhR(&)g{^ZgDJ)dA?*;_QhYb#C=^Py4M3=
z?yH^rCp@O7aa&A+oJJjf2y|qdl&fr0SXR3_wW7u{y9Y$MeiN*_AR0fwHG8EvDlUjg
z96ayo_&sU^HwC}f@466LE+SXIT~Iayq7wpJ3&I|GhC`4s}c=6Yj%1u%>H~oX)GS
zER62NYq)0n1g^tKe+jTtJ+v?Hy74_I686F|gi$*PAYd_dHo5S`;-an*VIYl;#$h4J
zwh#@dnA2j4TvrR58tD@IgN*Pfw_8WzTc#W!$)(IUdl_1eb&@IX6b~knOQCn*rvj-W
z6N|0oz{llz-n_He`f(=!e{mV_)k(VnNS`;PP_s;*SGDc?S(lgP;-ciPqtqNOfKF8c
z!z~?gKJ9SMeFNlFi>|!m@4TDYQ$oMR=e}(|{uz#m;p`R=1%SX_xwi%;vT+h(45vTQ
z$u`h<<0N8FOQ5^uhn{n9O?TriNcY*g(lp63$MOX0J#xz1GB{J~fZ$F2Od{uQn}_Ig
ze`tK$R*;Inq%7pKU1@@t#;^}5ds;pf|g=atC9hnDs8D2iQ}QtzjE
zmlz>Jw{aHlNNZ9L0QZ?A>^=?qln>LOa-V?oES-V-thCJp
z!P5`se@OKJEopfkD1T%v9>S!SOJId(-R1^J;U52OclXn>VZ$jkqJo6&V@~=lY+O<m{*-K@4xq)zLQk06xe1FIeZr`c8C9D
zFEsI1JKyEiSP9fgGR}W&Ow8Y&IbtA&vx}M_RqS0fM+!nZ_k9+V4_|!oN~FK_S_y(K
zNQg?fJsn}pynZC(m>MNc49*-Q@_MTfJOA>Vzy2t_Z|BnJ5Ou4qWa-wOEoD{{52RCX
zk=ka9QJ#1sFCUQ9TM5gMo;9~t^~!ps1$OhN-y7YZr_XKt@qA;H3BcW*6!PyE%IA8X
ze)p82h-|^Z>PrQLNigD*8e6V&|BEBQNBTr4$YGQh_;h#F8u-wfdjPaVN^|xFkvwY{vL2zexRQKTJJVrkBeB>RkhRA;vpce{jQvy_S@$@dR^8!w@IhMKDFHhWWv`Bu+9tru+i~xC*l^0O2HHZ;UOuxih7cd7k1$_q
z^U!aJd0IZGN@9yvL#Z`>%#ZeHa>pacxfN3-Whf)i&}eFr)18L4cQ(=u5J_i=`fc$w_#!-?LJN9;ps>8_?^TBmH?A9<|nvI1(MMkT&5hK*!R3Ao};{=9Wh3
zHv8u#1E>Az;hXasU_)l#aHHg%Uxe`S2`gZS9h-GW?*@={1Xb3y3)S}*S;OOTYk9y*
zAO3sSfZ#6Jb!@?T(eJsRX;tP0EC?I{YqxTN4_<-UI&uyDW)^aA7O7yEy9HEZr#&{d
zvAj+v(t)y{Ka6(tn9O{`NdeRZ*I8=B!hXH|N(mfGc`X4!?&n_fY;lcVFXYaGx1&r(
z@Ga<~bYF9-RF?=i_dcFex4F`#Gi__TB(d5$jKWbsXvj(0NjswH_|8avxt{}X!wJ|c
zq1b|p@Zc5DZ4o=8@`EZ|)-d779>%bOEQoo~Jv
zWIKcj456hwx>?A3hH9MTt$X20rxZiMkTM{mP8e4?E}0VAgedPPz^YtOUT%w+_`<#jNL|{J}@_YpD|J-1aDMv&H?4jaqQs>Fv
z94kzjCDHIQgf{|cOurEYZi_kXKV8sN01Hdnoa2S-*eR@1TvgSEGHe^0I+v0MrTZCo
zFcgI%3d=L#cxzd}NOwX&W$$rp5+Eaf-&Jxc&S-GmeRcGFZ`AWw#iRRnh*KGb++DTf
ze4KTj@^O~3G!$D*Kos~Bc!lyF{4T}UQAD6$lI?b&OE8*J;64vP9X8z`F_$0w
z#VLHYE73lF71@pDg*I?vj~W*PdZJ!JkQMQH0zEaSXX+j_1md;4&0vs=Kw-vC?m@(bn#DR$3Ei~fzKj?<>A@0#Dk{&C`o2tb+dC3S*4
zn0;Z2AJ@OC-lI?>G(1mES2PIourSS6@cYq2<6VoiRRTK2MX(bGNRo_xkUTP#_v+4I
zYjdX45<2XT_nhYpc)?ID)8VFR?rvEA;Y;lqqAcp-&U2St)ZQiNtj#oAcAM4)`@Q(j_glJaQ*Dl-4m
zFDfsY-A4zx(TGH1$58aFS6o_6<+XyAd#d)&+xi
zm-%p6TOT&|oz6Xz<#=Oka0091j&|XvgwEPE8hsgC_echcVV}Mk8b+%Okc?*Hjya!9
zrAtoX?aNO(Ia^WC+t66}FHx0LU)!SM4ft;}MJo9v;7l+I64B)4
zM``AZm<)O=hvs$E(-rOxkH*I@Ifam}3_?v~qR<*sDTQv$zG4-Mu>3S`>*P00V?E;o
zhbipMu}RM^=%OnY2oMC3t%kmg<6@L{kt;2NJbE|_NeS4Z)1_z{QUpgi6U*hd&P%x4
zI!f}0cx7-Rp+qs~Ml37U>0p!sUy6
z-aQ-lPgK_d%$}Um@voIIoi-vGRH|@P?UB`PpUY<#iK%fHzhD)2@C;*aEVIBVRr!O%AwjL)Ev)Qy
zKhSTrbX@M%XJ3*3!aMyO{$Tp&+>INZ)wHE*Tx9e4*Vw6sZ3oJK1SHQtsBD>mY3qT>FtQ$i+hNEevQ=+Qo_igLR;h+fklPUe0O;M6-W
zXJ24TP?D{K%toUV2p)VUsnK@bD1x1yfa-y&$q=M{p+96f08_inn=T
zeV+m=iEBflL2T2I?p1!s>tdp@V}2g4imDvHn)!BTc>o>r<>O#qfu!t>p}0J|F@!_tLCSqmS?0_yK*-YkSs#V0=#eFZa@c#pLI9
z5w?blbDU=>%z7GNE*;ryVdW=+3-6Z}e!Z{^8O-N6F=Elbq>V
z<)$CleE#?4g8;U8w+5-JI)4eab-zD&JIQS8|7GKZl4<_y;?w{n$-xfS!-Q*HM}$!}
z3(D+^>u;xHOJ{cm-(^m-+boeJ+uP~`b;>1hb!DqS@vjE8*8dgWGG&eVHXu;RM;fQ3
z8TSXNn|%RkMzb`$GDR6jL~TQZIOsR_>ujz|%sPg(bjkx@Ij_E%ASbTBU!XT5TG=o1
zQ)e`PB1)V%ggF$L|LbiraJ^X-&Joe${^3Hiv^jUjGsIll$|SV>LlCdT=MrI4O}
zz9mRTEAV6yV1fM_!0$l=Kh9ZQGj_+A@4W7%#9l6k<#>3FD*}6u@rh@4=OS}Ka
zr!%J)D{T|K#-xi=KPT-tix{;Didrjk1A)x&v)SVq_0gH&|0?GopcsUqcEDGfA}a9!
zZsm}-Gn@c)ZOcM3{kBBGr?9aQeeJ`;^`P2abWX+j^A|omn|51#PNNQc#!8lqm_lwV
z?oa=9AlTsVDuQy1Mc515bHZBhA|RUJ*cU>zbnYbNzMUZE^@U`{zQr2(CxdYR{h;a4
zpb1~ef+#l}^@2klnXoboTIZ?^>urr$IFvz34=QYNb|Y+6wc;>w!}z9}->CujEAy0C
z-uE9_hLF!eZ8s`k?;sD+Lcbn_O}4P=p?p-(0Mu%O5*X0Vv_(j^=8dl|ovs
zqjUz(L%C{;1JPQxDKYM6Fb{8c^)>CZo8grJ{Tp~AF2;%dBiBm9yh$7OL1G$H5
zhsp(go0pd`Th|e(_Q(;~LQO25jYHT)F%pW?O$@nTA8B`?D>&F9dS;-0pXOvVIOdwy
z&yG(U@wOU1C1i$hU6tVe57NWHd(%ba@Ko*aMvb@+3_qWa2b-fhP8
zyvzjEnj*Z82%JCOcEiK5U-g+JxS0~}+j;$U?nfsIZJj}R`mW88_CiFU`==P3RA^1zrLiP$r%pw+iM
z#}B>gl&=h<%Y_*jUwi8b;-QoAO(FBaoqb72nX~wU5=&XIsEqrZ6+Z=9^`o_l(i`^9u1u0f5)kss1!3C9MkwlE2%;ExG!}^pP
zxd8oRO5YoYA_YpH0&3daQ+dxE{}9@r_7kw`jvssNXghU^7Hb&8x`c0-aP42cMob0i
ze~m^hI>5AXZjY)=5>yG!ma{hJGQ)`ZoALZve4Akrjx{&1z5*(|AojFvS0z3Jdnk!;
z1Gyq96@b(~f#79lOiv_$7tTXa1WNhECgXh7ZxD8+#A*E~m9Bc-QmzB;t0U;;4l0uD!|jPqfqdw6zYdCx
z&Uc^d=hOw4VEaJKFo;a^@x{{Xm^~bs|8jc1=zZDakv)dXlB+uq^IyLOPB6uttf0ca
zTvY&0BZT3>!8!c1Q{PzOuUgBep>)7WXPlbQz6Hv?o9J{4F{>F|@*ge8P(FMr7zr66
zYHEwp#tkwO3*wy`w20t7oTI*PS(Bx#^rH~v$+3-?bwxH`n-ElM0je-x^Z&aF^9AP%
zPKGq12(g7U?nOe4SW?k3zENc2mr=X!&leP*o09`0Ac5di}R+@zHQ)6#R^%oJ@qh
z`i8l5+?k`sAz!N^x%h4gp3B1Gm?8#ZN%DiaQU*rR*kI0ev722#UR=TG_LV}S#+e@-
zw8TziF^!`+&f2j56!>rD5M9^mr|HkJt$uMibC2g|?|$F7oj{xVGi1;7x6ldg{fKX1OQe89QLDA2
zx@ZWqnSbGoR@VQwmy60zBLZi-pkM{}Sq!#SAvJc~4yuySj`^U~fS59?|B57X
z!JY$Smj0y@bVg@+dR^rgnB0vp_E#;>MzXdG5X#JHv$~q`Vwk6*Cw{9b8SE{+1&iO_
z1KG?Rj&H|2&wpUp`Sf`4r(67b%~aT9QdKZ2pN)m<(>5|%`5}Z!%lS@;F`Vk1cqm^5
z{W6by{DLNrD|-(V(*PZB&y*IQ)>v>P*=jpQ`JTI0zje0wM-I8eT^*&v5k;6jPSN`l|(z1lPl<5JXmFpQgyJ0mlW-*H45{Hl5Y=B>3lfR)#|it@ZHCg;mQK>D3r
z`>x{;ta+#RG?_Zv9Dc5$P?yX$9T(L=%z9!>@|pyUH4nY5qqD
zzh1S<&m?Gv@rYor^Gck4+vxKXhSv?3+JRTvhF_RUYQaOS$V$I?^Y0XW0RFg;+CFTW
z9=2wY?Q;9PXY^Eh1XMc#3sbgN+e!hFpOYpq%kW~)3XQDscbbS0mQHx65<2Rhk!zhR
z!|tz8tLBroBoQM!d+R4@j(sD0=FQ@4mn#cNcRi~8K=&);SWF}uS!W0ayH8QgD8dvG
zkNpn)N>)Z8R_wquT}ir_G)90#M=|!H?s)gH`-y(kh107xJ+9VqEcg(5gJM2+#M7zF
zxXH7m*-WoLK-;18ABL8!_`Mn+Q}^V+*k>bW|^c1-lZP_6RoN1cYqlE
zW|>mkHT|QjT*vKBjG!~gIt=NMEB=$U#2Wsv6X1K18Ycdup8^yi2kSRKoF#>xp%-1)
zk0IzBipx(sYnFZ&`;#&N<-uc76BAazP`Mov9S81%zhA@OaoHs_Tjo(egGoY?phIzj
z!&xrM3tekT|8{bu*jKSgPO2{cw(JK&f9(g-UWLBJpZ&1IwoUtT4UdR+ZzfGk8s%V%
zzM$#!A-|vrkc~lS69jxvbCJ1Tv&{~(D;9wbFDnM$W9#2PWH@xQTsbYTQKLJZv+&-1
zPp9v?)vXOXpOII{5R^&o0i?@Jv4_>^GTteHfh$TFeT~DbeZt;Bmr94)wF1Xi`=iTH
zWPp}2?39`BL!r#U5;^U?%v$bb00>JSM|+&l&&MViq>qN%fF8Uwsv4<-lNWG*1Pk;U
zOXh!46)<`_Pk-QivocLpS}t}~aTKNEXL{c#{?K^&_%~4eq<)0r=!FXSozJ*NUUI;V
zSK#u&bm=tcsK{TKAmZ}E9lX^`zg?4GBsD34+AeYTPf>|CheGZ
zAMbC1nN$3TN2-4VwFUPX_^s0lZ1!=RkN1bXNgO?B>}DY7Df|MO^O5@dLGRPENm6Et(JpI;*t^^yoDrF21}LDn4I>#m%setO9sm972d$y{lFsw~M~)kQ
zwH~w6(HCyps@OzJ)T$iVm?k5K_8)+raukGs^XINl8~wZai2N^DRtSSV$r1xQJ6JEkn!CkCUR4)g+_?^&HWWydB<^7o|dzvg~^IE%_;dVMJxPcl$OU5
z98`+AxDhJyaNH<5=Csx{_BXxW2k2sS9Y^32%waBXCU#$J{QZ8Jyr(8ho;n4dCJtau
zR!IF3<2yk5b4#OW)S)R~udo*m*4gU%;xhfmCBZjm9!ng0IykIRfVWQ@FF^BnM)_Qy
zFoYeDN&Uo6jJ+Tsd{_`V=azRt#`zQP95%TVRW3g*{I~NGX13OZEV4Qcl&`?HC)6B<
zfCPdjqR2AS4Ob)g-H6KUnD{k2-18W@nVmG7%Bm#Fg&W&n`CsFLGl*FqzvQ4{dna>C
zunU-7s!cM8nUDR&lit(5B|_;P>tU(8Tm>~Z)N;?-yvBESpCfd%7VqsN
zrE~#nW|(KYv>b}0B(Vwb6QfvKZU)$m6Jm61IKYIB`6}@@hj6t|t8Sc)iIfdY+VU`S-gE=kfaHQww2_gGVTE)Ch}F
zhc^3ndG`PK9{*u||EIn_xf2*K&WVQ&Go{x04WaMC(hZmO3I5*=w$y1WMLAxpf2JjK
z-+KFxfj95{>LSN&N-l!J=gT_Ky7`;&v`zU$o$Ed)7hrP>J
zv|lxQd8OGYW4s{~B*n}AoLD5HEm-+Npl@(gwPm(R+IkXVbo14qmrDa*RwVw8GM^a5
z^lADtbG{^!{3vO)0PU`Ap-YDfWw`~dkdj42jf?r{r+Wkmn3)qhmUc{3Q5YP9McH99
z8>;Ml9E0K$P&fAzRYLkAg}RLlgL_JGg65bD+!6!-e7%@4*Y*M#Eq4@9)pSY%}elUr`UvKOS}p@w5y+(ukf7;yyl+vx;dM
zkGcpZEfNkz`?`E|+#8eQ&_{c^p?vy#$ryOfctku64xiY1qvENqb7zZK`9AsthVbiI
zh^mI6O<@SUqffZXr;Bvb^?(%a6Cad}@0ODhXxuQF&%7m!5WtT`U7Q91PZCD4Fak?o
ztgQcfHYMG{viaQYMN99o4y8Ee3X{GS+95GM%z%Kb9Sdyft24HepqEzgSW15>7mMj5
z786mEK#)mJbeqS9iM3*u94HG_fCp6VrzXMl;ZT|&k*pf6U^sERUP~{oSY!7aaXg26
z!-><23#o5So=yXR2!6eOy&g;Rr;f1|8r9Q&S|)~@V~sp=
zVD)fJ!Ei^~c2)wrA27qpE(=gZN44!zR+#0j^1RD78#_is|7?dzrQQg>2F7rH-XHVA)=98L=KUYgn8}CJo({k(Zc(&f70{~OHBZcj67`K
zJY`sf%}we|HIsfXMi-w<&a&7T{h215uB9^BxtVnUDU$;ZGYYVEar&S?ag3X=F+h
z1S6OJ{IkSn2n)8cKS*8N5e}&c6$ts_s6!p#+#Mh;_EfACW84f`g$fAYB|+s7qH2a{
zS^)|4NEPL`z;Z`y)U;}P3dJyJj2p*cP7Xaz%atW^j@9spdrA8G#K|aJDLc?+DuX9X
zMaxTTR1x7_Wly`tCkRtit+l4b#e5%&N
z_>C@2rGAA$^=rQg1k82nhu|6Y*qs%m-7Q802*Bg5{ns%;R+$xdBSgneyA`O0FI9~E
zR{T4l#^ejB-+Lei7+7OH!+(Nu&f8Q5?q?7oKKHRUp-ZIUc?5PeL?$sZ#k3@$;h6DQ
zV0^^GHB>P(#m_EhQUPaFv}6I*hee{0!vVkySV=dO^#8GRHf)KlVVvs|Vf<9^_Olkz
z`1#alr|UK2q_LbSvGcz9pb4bzQ&CJTVA%VzpRkkbq~SM*A!J9i$)>>rxU}CRLQNPS
z#LU7@$lw$>#yQO{u-g@Mze2c07WAf-30gVWRy003P@52?u8pSHi#(z#`8&HZG>n&$
zF!r#Nll~GJ?<-Mr1_hjpR|*{QHq~RsJ4$#i$YPUHk#*fl#dI4|g>_}1<4%D_8%z{a
z`q~Z!kU2&M?dZz6y~lVM4z`yqt<4Zf3g+c*OH;TnXWyCWkBfPFZE%S@Ri6k|UBs=&
zXxHydXG`$P_!rGQWLkH%fch7aD2o4zxzFycE{P#weL-^6r`6p{j|OSK2za{4=NwWW
z(PD!a+>V_P&r7oJt>rn}F>ob_O{;isR|RPsa75hGMmFn~@+Jc+S)H+c8a%s-=e5Vh
zUSELHB<=}{qA@BH(WiP+)L7j2LEQH{g>i4Sw)o`E20|V2B_y-au(4fZ`)D7-JN^`2
zzj1qke45=7e&20E`I84($$gi%3GRTnkWUixe1P(4rpIaNC6E@b3zZ4t+;y5rjUsnn
zv+F$pr<8ce-i_4C=+)4?xw9J6eUj&N&8&vB+tIV
zy*exgt$*TLLVrmDulQk#8XfDU+w2Rp-dCYTNR*@vJG=|IRv*JKNkEj9&4;9X(WD&~
zDD9=k1L9-dzw5|*r(D}^EOWFb&ueH9_)Uvfb$*t{R>BdE?bQ`}B>e_-Iv9aA{o?}=
zegA-XJGBE;C^5B=ggb_a5WPu^5a;larH9%`vH-
zMviZ9W?HnL45FaZMgTtH#)yvKbN9PsHH8lf<{NO%JEU)i!uqA^fPTg3R_7ss6K
zng3E&9o@j}G*otD$syXV3423eeR&JwPj^Fj46A(DNa_mez5`V#CgL}Hn?q7I19#Eq
zuEv^@>K3N3UcniLO#c4SvM0AqZvNG7Y0~D7k+UxK(>Ol!gnk}s+%aL*8moyN{qwlV
zPW*qI0&jA|Me&%AYuMjps=rurfMI2K%4FlNW#ZVI9usdU}}LLAvKc+*#gmeLz?HrRuL6l
zG+Ga8h}G{~JEVk()DsWO|LGZc^Q?N=N-B@5`hwC?BZ9~zWu#w(vWUc1u2e|$G0wD^
z7ouyQ76vzR4>udEmg46R*P+ByihLa>?AZ>kQ7Ax*hHU4zZiZQr|NU!f*#me7OP2dk
z;uumqJb_Tm+oy$>L`2vkCU*dkZ`7#o>UKn2?4;-UX_uI_S;iPsDkuj$D@|B>)ARf&
z$N9q$!SUphBX(LGk7vS+|L_LFM!Dzfb|;VbVnXA?JlN6Pr2s0=fw({zPjrXp^ZoB7
zv83{yrV&n}A=nk(07~yXJZSahcVQUE%XowHwqw@mm|E>`IW&wz0wHR8{7TWFAl~8d
z6qXO+9Ro948g+-Xv{}P(5FsL3u9?{vlmaOu{B3%}pj%Y2mQ_TwoU4=c-h+LCW$xW}
z>%9lp%-8uKIr63*Q|b!PelE*|;(f`CKM;gE-&A_2-n_5A+(Hp=>ox|f*Rl9(()7~H
zaj2ZzzW$j#?kmk&Wr+CMNDn&=2*VEN1{$IWb~w*F>TZ2Q%Hvfl&5=`JDE3&IV|0R-
z_xqI+SXhyue9qc1ry}~65|j@lmz?#^CdBSY69euI9+JI=sVOC6a?sHEGZ3fdc+{G?
zja~DMjTOwYz9eP!BXLl;Q?mx>*?TamoI+59hGfyD9(+y+&7gf47*_|%Y?u0%k#n+QfCp9TWa#=mA^%+LecpZ!vZTGz
zf+lp)kT0q>K|IA=IzJ5S=#L8S;B
zZ(Bx=cA%127e)J&W%$HmD`4%yu52$yR?cR;+4bb-ALy=hj~U{x4e{DB?g)dhWj_~h
zmvVL#PGMti%#1yc7Gno7_@5RiKxe)^k&Fd%o1?(gS6ZT?ArrjVzkXJ;q)MIN;?0v
zW%M3rmx-g`g~!BPudUgw1hl3Ui~o`}O=2M!46$boDwb~^_1CZU`VIFXo(pJ|$FYCL
zZU6mG8{j+57Rv9@BM-F=iXl3lX`|~TTstxPP27tp{ucjrYnF%MQ2m~^_;GcbJwpH8q5lm
zd#B8-B*V%s=qMLst5YHn-tJVc-uvmHq_H@%YsYxpee)L>nbpI@ul!dFF5Amj-|GC5
z)DKnMVDl}s6Olj{bXVFly09X+>&b-B(GkSRi5PT5zswXRpoAaGgy#zqdqZYZFIgEE
z;8npQNO~TEjLlycVK~OI+zputt!8&it2lB#S)2H*-4xrHXV$*?u9tM7R4=VLDAOs<
z7Z)0ZgeKznDoRB8H=gnt(2xUcd|xRlvPI0Votmy0v$}2GKhGD;(^(ts#*4>;fgaqM
z-n#08%XW$2C6*9?Ba9{qN6q2Df0xh!V9X4}J`u{UcJ}I^V~XiXDA-to?wg{t?`IG&
ziH|;MH|!qoHiCimW7?`-?|UuCk3_l3)ORTk1t0sGx|h6m+eofYV%`0Y@EGwq@mp%#
zk~j>(LVArlvR>NS|D62a#Q=Mx&h)};X)=g(C!K>ml(@JjkL%mZi~MuOA0S15X%kzo}HpeeXf9KK-q_c-vVWN%}d5s3Yb;K?X*w4P$SjUzM*?75Kh
zk1JJ4#;!)i?t_7I$?Bk>-ywr{@{V&-F!Ax?pQ4L%>z428Pj`>{QmERjy70=p
z-U4z&6&lcf6^73XB(N$-g7WfwRPJcbYCJaIOzan-1f<&QvFERpx0{pCm%am3mh&tD
zuFL{M{pjJ%pm;bua*B6}KfXgoKV%XhZ>!WUoVk7k48mKc$2)d-Ykg4K?P@rR(&FnB3
zDuDv-KvM#^>Ey!&3K56vwm@OcNq@}ct7P*4lhwPjwdVoTtsLUg``N1`9=5dp`h603
zq^9aRGEm&hSR173RIrQ@*N8@=xT+
z&)Y*@{4|A2GXVwg^9jPFQK|UFuJA(sE#SY3{~Ju~W5)udB!c_YV*MZ>$H|gLmYYx*
z&x{~T--~WH&dh}%6Zg-5Y$V<&31#8ycB!W^#xl=yhr7tQ36+mgb54~j{}yE633->l
z?i*dS)cQyBs`cJvJXZ^o17$rv)7W=|W$>Xc&PQgi8AdP+2C*lf=}Joz{%@$T&g8Rq
zAT1XMR0krgELY}v7pqZ8fgO>hwkq6TKtvwahO(zg)HVYO6m$mNw6$N^j6(1ghv8Bxb2u&mzx*
z+&kq<*%}RlE>ylKOPD_ry=ltLsoQrZKyQS>n8|FUE1R`oJ6orB*IN6ilYeT!EZaQ%
zex8~ob1GMm3lG>j0tk7`B3o-14roLI>6F2TY&;#`k3#n(g@$7M|Bp({U|bhMHA4*}
z`y)@#tm_NQ7TEfSd>re5aE{_3t9gnlKYp}bXP8_PvjxNnJ*I~t2193Vlvtz*%
zFnnadPE}Cl`E+6jm>fy#W=|1Iq%SGpdA{<3h>O3BzPKC;p(P`B2HP;JCcjO%Z!jsb
zB+GG$JL|WQxV}S>Cv;yeu`pv>vr6A)rSnwbFj|yJ{Y%>&>g;K5Dl_TA4#Yyq>;x~TLqThnkM
zE-dksM8kVL?pwH{c(DR2=7)y)W+(c1yo8FJW8U%C4#g&8tVFxy&gx}u?QR=zZf_!`
zxpypPvhUSk--`$5^c*5rQ7jIjqzq3g8vZ}r0YO<2zBS#0O^3SkMz@htOj?SG@Wq-{@0E9q+Ueu*}1C0l-8-YtueoH>+%bPIIZ;FGS5WsAWxA^ap~}$Ofshb3hRs2Ya8B
z$WZAjx#6Jj(Il=-c)UpDBtGUE0*}^4kJQ&nVJC#ZH#;HvGZB9NErbo~f3;6=YrPm^
z;hYpg`dUk-zG_x0&&~PP^5QD+S44X(fv{Z7Y?Njz0PWL7T=Pq*Z=sAIdRIdI*0GuK
z0C)j*m`Fnk-w|yu=)K^e0enoL!|d_4nF^qH*%+ToN{nk}Bw7`#tdlc>1x5gEcGuD%
zJru<|?kx%-mmogxrPKr7u*GU?Sk9%@z@Ng|s;#@112awdc*|6;UtaPxExu#YB44L{
z`~%L=$0KETCJ4cyPioxxCvJcPCk}D#r+KP({kBJ37-epQv>B0%cvG|kA|
z&$euuFi*}n4QI=^#bFF;7ExrVl#$@im_ao1#ewgH#uO&FvK&0w09tG6lIasCOPb>c
z>$H7aS%pzj`0W&z<{7XWPxV($ftJDIQr;9Zc!2UWz#urrg{;^>dE|fmEg-Nixm=m8
zLgV1&cjJt!T&-&kPok&SyWx;rCSgrvG`!trnLdhl(-xUt8)`9#ydKLySXJ@dBVw
zCg1U&_P&stxX^Tbd-sYf4wC?Rxoz#vOY0gxk6u4|RJB0jy)wHHp46~rCbRL5mFdjt
zRu}~*jhMk5_&u
zEv$)OY!;gTss*6_zM?iUE-Lz}Kb^^IX}Nm3`^t|qp5S#lpDX_~|8VD#c9-7NA0BXN
zir`mz(UY0+50GdI*Mx^#b+kffD5OMAfwP4ufh_H(?d4SK}gQ^p+%k#X#o!j_oY?SAN
z$&~km4AgWHzFi$Dim0zt>CV%4-)ObWV7!}$fIa(G`puj>;9aQJL2}5-GSps6jo>(2
z=NZ?I8#M51C&iZvp0
zB;^!5agjqUIKG9eUTkc3v(xuIg*Cok9w%84pWbO)4BlWqoTd$8DY^5SS|d#HzOxBm
z;(BGp^<8O*MLw>0$i*3af9kRKPggq#bugRGLF#iL)Rt}}1M`mYZk%79G%7arM_Ok$
zX?mbqy0{+zR;S*gK^EzTc&Ciyy~-cX{C2%d$H=Y2l7F<4St72L0$qMEoB
zirgFFKTKob^KWwZ(70ia-ur$o#t}*C+GhJ~(i60f4>&$kiC%c5tQh>Jdc2JbyjgG^
z6G9PR?25_tKRAOiV)xkn0>Io?|2#QW;=!H6q_^ed_aBfUb+JUs>K8vO6Aadzq9*tL
zjt}L?<$VZ8&mku~QLWh9Wdw
zCaGb)X`-a!!YeW%#>0dmTjoE#h$b7s4AzrDK4Z)08#y$R7#^?5;*V!rUI
z?Uk+y^QM_vG8q&n9V(DDY^x94c(_F1Pol0~-9>cnd5^0Tpa=oHmUz*c
zLFbmA=18u&{~h%GSG1d2RrL*G_z}9O3n3!Mrw>7#B8VY&HwfWdfb+Kh8Wu_R(jU$$
z1#>u4M_c)3!`w)GIN}&UZ{!2Omd5xSO8MsQ6Ld?OXU?Ix$Iv7UC3Et6cmesB>z)pw
zzC(YQN;i5kjVLr&WsefpcLcznW7=;2`I2?6I7CWs07A-c+Pb5spZeZ$&!0(`Xk~HA
zE88Whry=@1-;XE@gksFkZ%mvkC@9rjusyNyu*T&Qs;_1TWK5X3ui7dP2P59QJAK#5
z)n?_*k=06x_t{$f%DyJ0)om1kWb_68(>vs@@k`pig>D|mMv%(B_K|2_k!FUNrHNfUdc}o25lkXG{9%Gd*yyGD{
zFx5Skf>9L)jS&asT~9ubUyUj%>sg-!#d?h5ek&EY6_7^s#wX7Kp^O_9>!MYzfPXlU
z8NvxA&6}5|xSY+uivVb%K^GBS^q%(tK0SfRuK()OGH!U>&CK`w6h5u
ztOfm_#Q7_6T6Z!M){~NA^c?l~wH$L4C$#Hfcgjqe-dC~eYD@a@LoRn#)K;8`=j;TM
zUp(DNTh?L@$70^w=$}Bpkv-f}{8qt|OPDT{j(;zYTulFqLgbJ|Y6f2i=*s~3sgLYa
zq3FHa%gCIh9fOR3B$A71FjKbH${0Vc4N**emMtxOKcQI(_Aa;i@8s|j-oP$ifQ5>YNJSwst?`uqIk6Khf%|Q%uS=%P2nDCaIRF0#
z^Ce3uEDk>fo>}m0j<9z4!?w*VIxwsj7nRQK?h>UgRL__@s?vbnBdRw+91l?ZlLr6l
zH@b)Zi3!||WKe2Y|BE%ykVd8T`ld2}ASGHiM~^aZ)aVR$tnzS92R^c)K>M5$G%Us-h_riP3h>maC?D}0Tv^Pk74FGjRgTwa&7hg@5
z-cag>_#<%zDSp;~76xnNKq7BH^j;dH0u%`PuG0$Rw;EJTdWQu0mn0Qu7Ipp)1h*g0
zIR^{V$Pql?Jt>4qS@?1p@4i^t=ZcV_zA{2MS-Z5wL@et7o==LUfEz6{(o_ryb8Lz$
zmLI44B;D*gECY?ZWoV}KlM{pBi>M*fS1Rc>G*V1vXVi%~%n}HP7rigoJ*}H(6IdmF
z^Sf>xhDQ+i9;P0>TmIx&(kO&p6xvAr6^y?+DPuwUs#jcigV=f
zMTj&~eHL6w<6kzCEsmFTI
z$&*84?SftY*|akh4QXC&imN|N;oe*Y9L-^UFuPpFwTyv*S^BGKef~?XvO>Nk`HkHK
zfR!~UgP-%e-uNbyDolxnW`49ik!H`F9Ee>#e4F5QGOqai9Qt!VYvEVYuuR7<=?#z4
zwT^GsruZ@6zuyJ?IY1zLFNOFr)0{zfL~ihG_u&lm0}hcUc?)#;KCACV$qP8T_>#q+
z!OD+G7Url~Tfrpg0c~ha*x=FC9@77ZvbPM1EBf{}ad&qKmIQ~!-Q9w_ySoH;Z6G8N
z8i!!P-QB&>MiboKf&`e(|IU4D?prf8Gu0paOV>W9*V*U!J$tQU`K^i9m!M9|*I6h4
z8xY}7+Y2mh^O^7Y9$Ub9B%h2Ugoojb6}