聊天页面

大约 11 分钟

聊天页面

聊天页面组件 ConversationDetail 主要由输入组件、消息列表组件、菜单组件和导航栏组件组成。该组件支持以下功能:

  • 发送和接收消息, 包括文本、表情、图片、语音、视频、文件和名片消息。
  • 对消息进行复制、引用、撤回、删除、编辑、重新发送和审核。
  • 从服务器拉取漫游消息。
  • 清除本地消息。
  • 消息翻译。
  • 消息表情回复。
  • 消息话题。
  • 消息转发。

该组件支持多种模式:包括聊天模式、搜索结果显示模式、创建话题模式、话题模式。 通过 ConversationDetailProps.type 属性来区分。详见 ConversationDetailModelType 类型。

消息相关功能,详见功能介绍文档

示例代码如下:

import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import type { RootParamsName, RootScreenParamsList } from "../routes";
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const name = route.name as RootParamsName;

  // 必须填写的参数
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;

  // 搜索模式
  const messageId = ((route.params as any)?.params as any)?.messageId;

  // 是否是多选模式
  const selectType = ((route.params as any)?.params as any)?.selectType;

  // 话题模式的参数
  const thread = ((route.params as any)?.params as any)?.thread;
  const firstMessage = ((route.params as any)?.params as any)?.firstMessage;

  // 组件模式
  const comType = React.useRef<ConversationDetailModelType>(
    name === "ConversationDetail"
      ? "chat"
      : name === "MessageThreadDetail"
      ? "thread"
      : name === "MessageHistory"
      ? "search"
      : "create_thread"
  ).current;

  return (
    <SafeAreaView
      style={{
        flex: 1,
      }}
    >
      <ConversationDetail
        type={comType}
        convId={convId}
        convType={convType}
        msgId={messageId}
        thread={thread}
        firstMessage={firstMessage}
        selectType={selectType}
      />
    </SafeAreaView>
  );
}

聊天页面的核心属性

ConversationDetail 组件的核心属性介绍如下:

属性类型是否必选描述
typeConversationDetailModelType组件模式。包括聊天模式、搜索模式、创建话题模式和话题模式。
convIdstring会话 ID。
convTypeChatConversationType会话类型。
convNamestring会话名称。
containerStyleobject修改组件样式。
inputobject输入组件属性、引用属性、自定义组件。详见输入组件介绍
listobject消息列表组件属性、引用属性、自定义组件。详见列表组件介绍
onClickedAvatarfunction点击会话头像的回调。
NavigationBarfunction自定义导航栏组件。
enableNavigationBarboolean是否激活导航栏。如果为 false,则不显示导航栏。
selectTypeConversationSelectModeType选择模式。包括普通模式和多选模式。
threadChatMessageThread话题模式参数。话题对象。
firstMessageChatMessageThread话题模式参数。话题消息对象。
msgIdstring创建话题模式或者搜索模式的参数。话题消息的 ID,或者是搜索模式的消息关键字。
parentIdstring创建话题模式参数。该话题的所在群组 ID。
newThreadNamestring创建话题模式参数。该话题的名称。
onCreateThreadResultstring创建话题模式参数。创建话题的结果回调通知。
onClickedThreadfunction点击消息气泡,打开话题页面的回调通知。例如,进行路由跳转。
onClickedVoicefunction点击导航栏音频按钮的回调通知。例如,进行路由跳转。
onClickedVideofunction点击导航栏视频按钮的回调通知。例如,进行路由跳转。
onThreadDestroyedfunction话题销毁的回调通知。例如,进行路由跳转。
onThreadKickedfunction离开话题的回调通知。例如,进行路由跳转。
onForwardMessagefunction转发消息的回调通知。例如,进行路由跳转。

自定义导航栏

导航栏组件为通用组件,布局为左中右。自定义方式和方法与会话列表类似,详见会话列表页面的自定义导航栏部分

输入组件

MessageInput 组件主要发送消息。默认支持发送文本、表情、图片、语音、视频、文件、自定义等消息。还支持发送复合类型消息,例如:引用消息、修改消息、名片消息等。

自定义输入组件

MessageInput 组件提供自定义菜单,可以添加自定义项,实现发送自定义消息。

核心属性如下:

属性类型是否必选描述
convIdstring会话 ID。
convTypeChatConversationType会话类型。
selectTypeConversationSelectModeType选择模式。包括普通模式和多选模式。
topnumber若使用了 SafeAreaView 组件,需要设置 top
bottomnumber若使用了 SafeAreaView 组件,需要设置 bottom
numberOfLinesnumber输入文本组件的最大行数。默认 4 行。
onClickedSendfunction点击发送按钮的回调。
onEditMessageFinishedfunction编辑消息完成的回调。
onClickedCardMenufunction点击名片消息的回调。
onInitMenufunction注册初始化菜单的回调。
emojiListstring[]自定义表情列表。如果不设置,使用默认列表。
multiSelectCountnumber多选模式下的选择消息数量。
onClickedMultiSelectDeleteButtonfunction点击删除按钮的回调通知。 内部跳转。
onClickedMultiSelectShareButtonfunction点击转发按钮的回调通知。内部路由。
unreadCountnumber消息未读数。
onClickedUnreadCountfunction消息未读数的回调通知。

引用对象的核心方法如下:

方法描述
close关闭输入组件键盘,切换到一般模式。
quoteMessage回复消息。
editMessage编辑消息。
showMultiSelect打开多选模式。消息列表出现复选框。
hideMultiSelect取消多选模式。消息列表隐藏复选框。

消息列表组件

MessageList 组件主要显示和管理消息列表,支持添加、编辑和删除消息列表项。

  • 发送消息会显示在消息列表,消息发送到服务器会修改消息状态,对方已读会修改状态。
  • 发送消息后,在规定时间内可以撤销、编辑和回复。
  • 接收的消息可以设置已读状态,附件类型消息支持附件下载。
  • 消息菜单支持更新或者添加自定义项。
  • 消息列表项支持修改样式、布局和显示隐藏。

自定义消息列表组件

核心属性如下:

属性类型是否必选描述
convIdstring会话 ID。
convTypeChatConversationType会话类型。
selectTypeConversationSelectModeType选择模式。包括普通模式和多选模式。
containerStyleobject修改组件样式。
threadChatMessageThread话题模式参数。话题对象。
msgIdstring创建话题模式或者搜索模式的参数。话题消息的 ID,或者是搜索模式的消息关键字。
parentIdstring创建话题模式参数。该话题的所在群组 ID。
newThreadNamestring创建话题模式参数。该话题的名称。
firstMessageChatMessageThread话题模式参数。话题消息对象。
onClickedfunction点击消息列表的回调。
onClickedItemfunction点击消息的回调。
onLongPressItemfunction长按消息的回调。
onClickedItemAvatarfunction点击消息头像的回调。
onClickedItemQuotefunction点击消息的回复的回调。
onQuoteMessageForInputfunction回复消息的回调。
onEditMessageForInputfunction修改消息的回调。
reportMessageCustomListarray自定义消息举报的否的列表项。
listItemRenderPropsMessageListItemRenders消息列表项的组件的自定义。还支持内部组件的自定义。
recvMessageAutoScrollboolean收到消息是否滚动到列表最下面。
messageLayoutTypeMessageLayoutType消息布局靠左还是靠右。
onInitMenufunction注册初始化菜单的回调。
onCopyFinishedfunction复制完成的回调。
recvMessageAutoScrollboolean收到新消息是否自动滚动屏幕消息列表。
messageLayoutTypeMessageLayoutType消息列表是居左还是居右。默认接收消息在左边,发送消息在右边。
messageLayoutTypeMessageLayoutType消息列表是居左还是居右。默认接收消息在左边,发送消息在右边。
onNoMoreMessagefunction已经没有更多消息的回调通知。可能多次通知注意去重。
onCreateThreadfunction请求创建话题的回调通知。例如,进行路由跳转。
onOpenThreadfunction打开话题的回调通知。例如,进行路由跳转。
onCreateThreadResultfunction创建话题结果的回调通知。例如,进行路由跳转。
onClickedEditThreadNamefunction编辑话题名称的回调通知。例如,进行路由跳转。
onClickedOpenThreadMemberListfunction查看话题成员列表的回调通知。例如,进行路由跳转。
onClickedLeaveThreadfunction离开话题的回调通知。例如,进行路由跳转。
onClickedDestroyThreadfunction销毁话题的回调通知。例如,进行路由跳转。
onClickedMultiSelectedfunction点击多选菜单的回调通知。
onChangeMultiItemsfunction多选结果的回调通知。
onClickedSingleSelectfunction点击转发的回调通知。
onClickedHistoryDetailfunction点击历史消息的回调通知。例如,进行路由跳转。
onChangeUnreadCountfunction未读数发生变更的回调通知。例如,进行路由跳转。

对象引用的方法如下:

方法描述
addSendMessage发送消息。
addSendMessageToUI发送消息到 UI,不会发送到服务器。
sendMessageToServer发送到服务器。
removeMessage移除消息。
recallMessage撤回消息。
updateMessage更新消息。
loadHistoryMessage加载历史消息。
onInputHeightChange输入组件的高度变化。
editMessageFinished编辑消息完成。
scrollToBottom滚动到消息底部。
startShowThreadMoreMenu显示底部话题菜单。
cancelMultiSelected取消多选。
removeMultiSelected移除已经选择的消息。
getMultiSelectedMessages获取已经选择的消息列表。

消息列表的头像和昵称

MessageList 组件内部并没有头像和昵称的默认值,需要用户提供。若未提供,则展示默认头像和用户 ID。

可通过以下方式提供头像和昵称:

  • 注册回调:使用 Container 组件的 onRequestMultiData 属性实现。
  • 主动调用:使用 ChatService.updateDataList 方法实现。调用该方法会触发内部事件分发,刷新已加载的组件页面。
  • 消息携带:优先使用消息携带的头像和昵称。

设置消息列表的背景颜色

type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;

  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          containerStyle: { backgroundColor: 'red' },
        },
      }}
    />
  );
}

设置消息列表的背景图片

type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;

  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          backgroundImage: 'https://img.yzcdn.cn/vant/cat.jpeg',
        },
      }}
    />
  );
}

自定义消息时间戳

设置消息气泡下面的时间戳,需要在初始化部分进行。 示例代码如下:

export function App() {
  const { getOptions } = useApp();

  return (
    <UIKitContainer
      options={getOptions()}
      formatTime={{
        locale: enAU,
        conversationDetailCallback(timestamp, enAU) {
          return format(timestamp, 'yyyy-MM-dd HH:mm:ss', { locale: enAU });
        },
      }}
    >
      {/* sub component */}
    </UIKitContainer>
  );
}

自定义消息列表项样式

对于消息列表项,可以设置头像、昵称、气泡布局、样式、事件等。

export function MyMessageContent(props: MessageContentProps) {
  const { msg, layoutType, isSupport, contentMaxWidth } = props;
  if (msg.body.type === ChatMessageType.TXT) {
    // todo: 如果是文本类型消息,则使用该样式进行显示。
    return (
      <MessageText
        msg={msg}
        layoutType={layoutType}
        isSupport={isSupport}
        maxWidth={contentMaxWidth}
      />
    );
  }
  return <MessageContent {...props} />;
}

type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;

  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          listItemRenderProps: {
            MessageContent: MyMessageContent,
          },
        },
      }}
    />
  );
}

自定义消息列表项,例如:隐藏左边消息的头像。

其它自定义的内容,可以参考 MessageViewProps 属性。

export function MyMessageView(props: MessageViewProps) {
  if (props.model.layoutType === 'left') {
    // todo: 如果是左边的消息,则不显示头像
    return <MessageView {...props} avatarIsVisible={false} />;
  }
  return MessageView(props);
}

type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;

  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          listItemRenderProps: {
            MessageView: MyMessageView,
          },
        },
      }}
    />
  );
}

自定义消息上下文菜单样式

messageMenuStyle 支持三种模式: bottom-sheetcontextcustombottom-sheet 模式通过页面组件底部弹出菜单,context 模式类似微信,通过消息位置和点击位置弹出菜单,custom 模式通过用户自定组件实现,需要遵守 MessageCustomLongPressMenu 约束。

custom 模式示例代码如下:

  1. 在全局设置属性 Container.messageMenuStylecustom。其他模式不需要用户设置 MessageCustomLongPressMenu
export function App() {
  return (
    <UIKitContainer messageMenuStyle={'custom'}>
      {/* sub component */}
    </UIKitContainer>
  );
}
  1. ConversationDetail 组件中设置属性 MessageCustomLongPressMenu
export const MyMessageContextNameMenu = React.forwardRef<
  ContextNameMenuRef,
  ContextNameMenuProps
>(function (
  props: ContextNameMenuProps,
  ref?: React.ForwardedRef<ContextNameMenuRef>
) {
  const {} = props;
  React.useImperativeHandle(
    ref,
    () => {
      return {
        startShow: () => {},
        startHide: (_onFinished?: () => void) => {},
        startShowWithInit: (_initItems: InitMenuItemsType[], _?: any) => {},
        startShowWithProps: (_props: ContextNameMenuProps) => {},
        getData: () => {
          return undefined;
        },
      };
    },
    []
  );
  ref;
  return <View style={{ width: 100, height: 44, backgroundColor: 'red' }} />;
});

type Props = NativeStackScreenProps<RootScreenParamsList>;
export function MyConversationDetailScreen(props: Props) {
  const { route } = props;

  return (
    <SafeAreaViewFragment>
      <ConversationDetail
        MessageCustomLongPressMenu={MyMessageContextNameMenu}
      />
    </SafeAreaViewFragment>
  );
}

自定义发送消息附件菜单样式

messageInputBarStyle 支持两种模式: bottom-sheetextensionbottom-sheet 模式通过页面组件底部弹出菜单, extension 模式通过布局组件实现。

在全局通过设置属性 Container.messageInputBarStyle 属性决定菜单样式。

export function App() {
  return (
    <UIKitContainer messageInputBarStyle={'extension'}>
      {/* sub component */}
    </UIKitContainer>
  );
}

事件通知

事件通知在列表中已经实现,收到对应事件会更新列表。通常情况下,不需要开发者关注。

示例应用

实现的核心是,自定义输入组件的菜单,点击自定义项发送自定义消息,自定义渲染组件来显示自定义消息。

示例如下:

export function MyMessageContent(props: MessageContentProps) {
  const { msg } = props;
  if (msg.body.type === ChatMessageType.CUSTOM) {
    return (
      <View>
        <Text>{(msg.body as ChatCustomMessageBody).params?.test}</Text>
      </View>
    );
  }
  return <MessageContent {...props} />;
}
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { navigation, route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  const listRef = React.useRef<MessageListRef>({} as any);
  const inputRef = React.useRef<MessageInputRef>({} as any);
  const { top, bottom } = useSafeAreaInsets();
  const im = useChatContext();

  return (
    <SafeAreaView
      style={{
        flex: 1,
      }}
    >
      <ConversationDetail
        type={"chat"}
        convId={convId}
        convType={convType}
        input={{
          ref: inputRef,
          props: {
            top,
            bottom,
            onInitMenu: (menu) => {
              return [
                ...menu,
                {
                  name: "test",
                  isHigh: false,
                  icon: "bell",
                  onClicked: () => {
                    listRef.current?.addSendMessage({
                      type: "custom",
                      msg: ChatMessage.createCustomMessage(convId, "test", 1, {
                        params: { test: "111" },
                      }),
                    });
                  },
                },
              ];
            },
          },
        }}
        list={{
          ref: listRef,
          props: {
            listItemRenderProps: {
              MessageContent: MyMessageContent,
            },
          },
        }}
      />
    </SafeAreaView>
  );
}