diff --git a/docs/.vuepress/navbar/index.ts b/docs/.vuepress/navbar/index.ts index c71e9fe9c..f494f0c2f 100644 --- a/docs/.vuepress/navbar/index.ts +++ b/docs/.vuepress/navbar/index.ts @@ -72,66 +72,66 @@ export const zhNavbar = navbar([ }, ] }, - // { - // text: 'UIKit', - // children: [ - // { - // text: '平台', - // children: [ - // { - // text: 'Android', - // icon: '/icon-Android.svg', - // link: '/uikit/android/overview.html' - // }, - // { - // text: 'iOS', - // icon: '/icon-iOS.svg', - // link: '/uikit/ios/overview.html' - // }, - // // { - // // text: 'Web', - // // icon: '/icon-web.svg', - // // link: '/uikit/web/overview.html' - // // }, - // // { - // // text: 'Windows', - // // icon: '/icon-windows.svg', - // // link: '/uikit/windows/overview.html' - // // } - // ] - // }, - // { - // text: '框架', - // children: [ - // { - // text: 'React Native', - // icon: '/icon-ReactNative.svg', - // link: '/uikit/react-native/overview.html' - // }, - // { - // text: 'Flutter', - // icon: '/icon-flutter.svg', - // link: '/uikit/flutter/overview.html' - // }, - // // { - // // text: 'Unity', - // // icon: '/icon-unity.svg', - // // link: '/uikit/unity/overview.html' - // // }, - // // { - // // text: '小程序', - // // icon: '/icon-mini-program.svg', - // // link: '/uikit/applet/overview.html' - // // }, - // // { - // // text: 'uni-app', - // // icon: '/icon-uni-app.svg', - // // link: '/uikit/applet/uniapp.html' - // // }, - // ] - // }, - // ] - // }, + { + text: 'UIKit', + children: [ + { + text: '平台', + children: [ + { + text: 'Android', + icon: '/icon-Android.svg', + link: '/uikit/android/overview.html' + }, + { + text: 'iOS', + icon: '/icon-iOS.svg', + link: '/uikit/ios/overview.html' + }, + // { + // text: 'Web', + // icon: '/icon-web.svg', + // link: '/uikit/web/overview.html' + // }, + // { + // text: 'Windows', + // icon: '/icon-windows.svg', + // link: '/uikit/windows/overview.html' + // } + ] + }, + { + text: '框架', + children: [ + { + text: 'React Native', + icon: '/icon-ReactNative.svg', + link: '/uikit/react-native/overview.html' + }, + { + text: 'Flutter', + icon: '/icon-flutter.svg', + link: '/uikit/flutter/overview.html' + }, + // { + // text: 'Unity', + // icon: '/icon-unity.svg', + // link: '/uikit/unity/overview.html' + // }, + // { + // text: '小程序', + // icon: '/icon-mini-program.svg', + // link: '/uikit/applet/overview.html' + // }, + // { + // text: 'uni-app', + // icon: '/icon-uni-app.svg', + // link: '/uikit/applet/uniapp.html' + // }, + ] + }, + ] + }, { text: 'API 参考', children: [ diff --git a/docs/.vuepress/sidebar/document.ts b/docs/.vuepress/sidebar/document.ts index 825f9fb3a..b62a0d0ed 100644 --- a/docs/.vuepress/sidebar/document.ts +++ b/docs/.vuepress/sidebar/document.ts @@ -103,7 +103,7 @@ const documentSidebar = [ text: '其他', children: [ { text: '错误码', link: 'error.html' }, - { text: 'EaseIMKit 使用指南', link: 'easeimkit.html', except: ['web', 'windows', 'react-native', 'flutter', 'unity'] }, + //{ text: 'EaseIMKit 使用指南', link: 'easeimkit.html', except: ['web', 'windows', 'react-native', 'flutter', 'unity'] }, { text: 'EaseCallKit 使用指南', link: 'easecallkit.html', except: ['web', 'windows', 'react-native', 'flutter', 'unity'] }, ], except: ['applet', 'server-side'] diff --git a/docs/.vuepress/sidebar/uikit.ts b/docs/.vuepress/sidebar/uikit.ts index c01824227..80fb32ec4 100644 --- a/docs/.vuepress/sidebar/uikit.ts +++ b/docs/.vuepress/sidebar/uikit.ts @@ -18,11 +18,20 @@ const uikitSidebar = [ collapsible: 子菜单是否允许展开/收起,true: 允许; false: 不允许。请参考「子菜单示例」 children: 子菜单。请参考「子菜单示例」 */ - text: '快速开始', - children: [ - { text: 'UIKit 集成', link: 'overview.html' }, - ] + text: 'UIKit 介绍', link: 'overview.html', + except: ['android', 'ios'] }, + { + text: '快速开始', link: 'quickstart.html', + }, + { + text: '集成聊天页面', link: 'key_function_chat_page.html', + except: ['android', 'ios'] + }, + { + text: '集成会话列表页面', link: 'key_function_conversation_list.html', + except: ['android', 'ios'] + } ] function buildDocSidebar() { diff --git a/docs/document/android/easeimkit.md b/docs/document/android/easeimkit.md deleted file mode 100644 index 572314c07..000000000 --- a/docs/document/android/easeimkit.md +++ /dev/null @@ -1,1261 +0,0 @@ -# EaseIMKit 使用指南 - - - -在您阅读此文档时,我们假定您已经具备了基础的 Android 应用开发经验,并能够理解相关基础概念。此文档是针对导入 EaseIMKit 库的快速集成文档,如果只是导入 SDK 集成使用,请参考 [环信即时通讯 IM Android 快速开始](quickstart.html)。 - -## 简介 - -EaseIMKit 是什么? - -**EaseIMKit** 是 EaseUI 的升级版,是基于环信 IM SDK 的一款 UI 组件库,它提供了一些通用的 UI 组件,例如‘会话列表’、‘聊天界面’和‘联系人列表’等,开发者可根据实际业务需求通过该组件库快速地搭建自定义 IM 应用。EaseIMKit 中的组件在实现 UI 功能的同时,调用 IM SDK 相应的接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 EaseIMKit 时只需关注自身业务或个性化扩展即可。 - -EaseIMKit 源码地址 - -- [EaseIMKit](https://github.com/easemob/easeui/tree/EaseIMKit) - -使用 EaseIMKIt 的环信 IM APP 源码地址: - -- [环信 IM](https://github.com/easemob/chat-android) - -## 导入 EaseIMKit - -### 开发环境要求 - -- Android Studio 3.2 以上 -- Gradle 4.6 以上 -- targetVersion 26 以上 -- Android SDK API 19 以上 -- Java JDK 1.8 以上 - -### 集成说明 - -EaseIMKit 支持 Gradle 接入和 Module 源码集成 - -#### Gradle 接入集成 - -:::notice 重大变动 -远程仓库统一由 JCenter 迁移到 `MavenCentral`,依赖库的域名由 “com.hyphenate” 修改为 “io.hyphenate”,详见 [环信即时通讯 IM Android 快速开始](quickstart.html)。 -::: - -```gradle -implementation 'io.hyphenate:ease-im-kit:xxx版本' -implementation 'io.hyphenate:hyphenate-chat:xxx版本' -``` - -**EaseIMKit 必须依赖环信 IM SDK,因而在使用 EaseIMKit 时必须同时添加环信 IM SDK 依赖。** - -:::notice - -1. IM SDK **3.8.0** 版本以后,远程依赖的 `artifactId` 修改为 `hyphenate-chat`,且该版本以后中不再包含音视频相关逻辑。 -2. IM SDK **3.8.0** 以下,远程依赖,包含音视频的 `artifactId` 为 `hyphenate-sdk`,不包含音视频的 `artifactId` 为 `hyphenate-sdk-lite`。如果想使用不包含音视频通话的 SDK,用 `implementation 'io.hyphenate:hyphenate-sdk-lite:xxx版本`'。 - -版本号参考 [Android SDK 更新日志](releasenote.html)。 -::: - -#### Module 源码集成 - -```gradle -implementation project(':ease-im-kit') -``` - -#### 依赖的第三方库 - -- Glide: 图片处理库,显示用户头像时用到。 -- BaiduLBS_Android.jar: 百度地图定位库。 - -#### 关于位置消息说明 - -EaseIMKit 中位置消息使用的是百度地图定位 jar 包,为了防止出现百度地图类冲突的问题,EaseIMKit 库打包时对百度地图定位 jar 包采用了只编译的方式,这就要求如果开发者想要使用位置消息,需要在自己的项目中添加百度地图定位的 jar 包及其 so 文件。 - -### 初始化 - -在 application 的 onCreate 下调用初始化 EaseIMKit 的方法。 - -:::notice -EaseIMKit 初始化里已包含 SDK 的初始化,不需要再去调用 SDK 的初始化。 -::: - -```java -public class DemoApplication extends Application { - @Override - public void onCreate() { - super.onCreate(); - - //EaseIM 初始化 - if(EaseIM.getInstance().init(context, options)){ - //在做打包混淆时,关闭 debug 模式,避免消耗不必要的资源 - EMClient.getInstance().setDebugMode(true); - //EaseIM 初始化成功之后再调用注册消息监听的代码 ... - } - } -} -``` - -## 快速搭建 - -EaseIMKit 封装了常用 IM 功能,提供了会话,聊天及联系人等基本的 fragment,旨在帮助开发者快速集成环信 SDK。 - -### 创建会话列表界面 - -EaseIMKit 提供了 EaseConversationListFragment,需要将其或者其子类添加到 Activity 中。开发者需要对刷新事件(新消息,删除消息,删除会话等)进行处理。 - -![img](@static/images/android/easeim.jpeg) - -:::notice -要实现自定义头像及昵称,请参考 [设置头像和昵称](userprofile.html#设置当前用户的属性)。 -::: - -### 创建聊天界面 - -EaseIMKit 提供了 EaseChatFragment,添加到 Activity 中并传递相应的参数即可用。 -必须向 EaseChatFragment 传递的参数为: - -- `conversationId`——会话 ID,单聊时指对方 ID,群聊和聊天室时指群和聊天室 ID; -- `chatType`——聊天类型,整型,分别为单聊(1)、群聊(2)和聊天室(3); - -可选传递参数为: - -- `history_msg_id`——消息 ID,用于查询历史记录时的定位消息 ID; -- `isRoaming`——是否开启漫游,布尔类型,用于标记是否优先从服务器拉取消息。 - -```java -public class ChatActivity extends BaseActivity { - private EaseChatFragment chatFragment; - - @Override - protected void onCreate(Bundle arg0) { - super.onCreate(arg0); - setContentView(R.layout.em_activity_chat); - //use EaseChatFratFragment - chatFragment = new EaseChatFragment(); - //pass parameters to chat fragment - chatFragment.setArguments(getIntent().getExtras()); - getSupportFragmentManager().beginTransaction().add(R.id.container, chatFragment).commit(); - } -} -``` - -![img](@static/images/android/easeim1.jpeg) - -### 添加联系人界面 - -EaseIMKit 提供了 EaseContactListFragment,添加其及其子类到 Activity 中。开发者需要对刷新事件(添加联系人,删除联系人等)进行处理。 - -![img](@static/images/android/easeim2.jpeg) - -## 设置样式 - -### 设置标题栏 - -EaseIMKit 提供了自定义的标题栏控件 EaseTitleBar。 - -![img](@static/images/android/easeim-titlebar.jpeg) - -标题栏除了做为 View 所具有的属性功能外,还可以设置标题的位置等。 - -xml 中设置如下: - -```xml - -``` - -其中 `titleBarDisplayHomeAsUpEnabled` 属性为设置返回按钮是否可见,设置标题位置可设置 `titleBarTitlePosition`,可选值为 `center`,`left` 和 `right`。 - -也可进行代码设置,如下: - -```java -EaseTitleBar titleBarMessage = findViewById(R.id.title_bar_message); -//设置右侧菜单图标 -titleBarMessage.setRightImageResource(R.drawable.chat_user_info); -//设置标题 -titleBarMessage.setTitle("标题"); -//设置标题位置 -titleBarMessage.setTitlePosition(EaseTitleBar.TitlePosition.Left); -//设置右侧菜单图标的点击事件 -titleBarMessage.setOnRightClickListener(this); -//设置返回按钮的点击事件 -titleBarMessage.setOnBackPressListener(this); -``` - -当然设置右侧菜单,您也可以通过 Android 提供的添加 `menu xml` 的形式实现。修改按钮图标,可以调用 `titleBarMenuResource` 属性进行设置。 - -### 设置会话列表 - -会话列表可以修改如下样式: - -- 头像:头像大小,头像形状(方形,带圆角的方形,圆形),描边 -- 标题、内容、时间等文字:字体大小,字体颜色 -- 未读消息:可设置是否展示,展示位置(左式和右式) - -在 `EaseConversationListFragment` 及其子类中可以直接获取到 `EaseConversationListLayout` 这个控件,然后通过这个控件进行设置。 - -代码如下: - -```java -//设置头像尺寸 -conversationListLayout.setAvatarSize(EaseCommonUtils.dip2px(mContext, 50)); -//设置头像样式:0 为默认,1 为圆形,2 为方形(设置方形时,需要配合设置 avatarRadius,默认的 avatarRadius 为 50dp) -conversationListLayout.setAvatarShapeType(2); -//设置圆角半径 -conversationListLayout.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5)); -//设置标题字体的颜色 -conversationListLayout.setTitleTextColor(ContextCompat.getColor(mContext, R.color.red)); -//设置是否隐藏未读消息数,默认为不隐藏 -conversationListLayout.hideUnreadDot(false); -//设置未读消息数展示位置,默认为左侧 -conversationListLayout.showUnreadDotPosition(EaseConversationSetStyle.UnreadDotPosition.LEFT); -``` - -效果如下图: - -![img](@static/images/android/easeim3.jpeg) -更多样式请参考 EaseContactListLayout 控件。 - -#### 增加长按菜单项 - -`EaseConversationListLayout` 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 -示例代码如下: - -```java -@Override -public void initView(Bundle savedInstanceState) { - super.initView(savedInstanceState); - ...... - conversationListLayout.addItemMenu(Menu.NONE, R.id.action_con_delete, 4, getString(R.string.ease_conversation_menu_delete)); -} - -...... - -@Override -public boolean onMenuItemClick(MenuItem item, int position) { - EaseConversationInfo info = conversationListLayout.getItem(position); - Object object = info.getInfo(); - if(object instanceof EMConversation) { - switch (item.getItemId()) { - case R.id.action_con_make_top : - // 将会话置顶 - conversationListLayout.makeConversationTop(position, info); - return true; - case R.id.action_con_cancel_top : - // 取消会话置顶 - conversationListLayout.cancelConversationTop(position, info); - return true; - case R.id.action_con_delete : - showDeleteDialog(position, info); - return true; - } - } - return super.onMenuItemClick(item, position); -} -``` - -### 设置聊天窗口 - -聊天窗口包括标题栏(不包含在 EaseChatFragment 中),聊天区,输入区及扩展展示区,如下图所示: - -![img](@static/images/android/easeim4.png) - -标题区 EaseTitleBar 的具体布局及实现不在 EaseIMKit 库的聊天控件及 fragment 中,需要你自己去实现。 -开发者可以在 EaseChatFragment 中获取到 EaseChatLayout 这个控件,然后通过这个控件进一步获取到获取其他控件,代码如下: - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//获取到菜单输入父控件 -EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); -//获取到菜单输入控件 -IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); -//获取到扩展区域控件 -IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu(); -//获取到表情区域控件 -IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu(); -``` - -#### 修改聊天列表样式 - -聊天列表区域可以修改背景,文字,气泡,是否展示昵称及聊天展示样式等,更多设置请参考 IChatMessageItemSet。 - -#### 修改聊天列表背景 - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//设置聊天列表背景 -messageListLayout.setBackground(new ColorDrawable(Color.parseColor("#DA5A4D"))); -``` - -效果如下图: - -![img](@static/images/android/easeim5.jpeg) - -#### 修改头像属性 - -开发者可以设置默认头像和头像形状。 - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//设置默认头像 -messageListLayout.setAvatarDefaultSrc(ContextCompat.getDrawable(mContext, R.drawable.ease_default_avatar)); -//设置头像形状:0 为默认,1 为圆形,2 为方形 -messageListLayout.setAvatarShapeType(1); -``` - -效果如下图: - -![img](@static/images/android/easeim6.jpeg) - -#### 修改聊天文本 - -开发者可以修改聊天文本的字体大小及字体颜色,发送方及接收方需保持一致。 - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//设置文本字体大小 -messageListLayout.setItemTextSize((int) EaseCommonUtils.sp2px(mContext, 18)); -//设置文本字体颜色 -messageListLayout.setItemTextColor(ContextCompat.getColor(mContext, R.color.red)); -``` - -效果如下图: - -![img](@static/images/android/easeim7.jpeg) - -#### 修改时间线样式 - -开发者可以修改时间线的背景,文字的大小及颜色。 - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//设置时间线的背景 -messageListLayout.setTimeBackground(ContextCompat.getDrawable(mContext, R.color.gray_normal)); -//设置时间线的文本大小 -messageListLayout.setTimeTextSize((int) EaseCommonUtils.sp2px(mContext, 18)); -//设置时间线的文本颜色 -messageListLayout.setTimeTextColor(ContextCompat.getColor(mContext, R.color.black)); -``` - -效果如下图: - -![img](@static/images/android/easeim8.jpeg) - -#### 修改聊天列表展示样式 - -开发者可以设置聊天列表的样式,发送方和接收方位于两侧还是位于一侧。 - -```java -//获取到聊天列表控件 -EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); -//设置聊天列表样式:两侧及均位于左侧 -messageListLayout.setItemShowType(EaseChatMessageListLayout.ShowType.LEFT); -``` - -效果如下图: - -![img](@static/images/android/easeim9.jpeg) - -#### 修改输入区样式 - -输入区控件为 EaseChatInputMenu,它由输入控件 EaseChatPrimaryMenu,扩展控件 EaseChatExtendMenu 和表情控件 EaseEmojiconMenu 组成。 - -```java -//获取到菜单输入父控件 -EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); -//获取到菜单输入控件 -IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); -//获取到扩展区域控件 -IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu(); -//获取到表情区域控件 -IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu(); -``` - -开发者可以修改菜单输入控件的样式,其有 5 种模式,即 `完整模式`,`不可用语音模式`,`不可用表情模式`,`不可用语音和表情模式` 和 `只有文本输入模式`。 - -```java -//获取到菜单输入父控件 -EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); -//获取到菜单输入控件 -IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); -if(primaryMenu != null) { - //设置菜单样式为不可用语音模式 - primaryMenu.setMenuShowType(EaseInputMenuStyle.DISABLE_VOICE); -} -``` - -效果(EaseInputMenuStyle.DISABLE_VOICE)如下图: - -![img](@static/images/android/easeim10.jpeg) - -其他样式为: - -完整模式(EaseInputMenuStyle.All): - -![img](@static/images/android/easeim11.jpeg) - -不可用表情模式(EaseInputMenuStyle.DISABLE_EMOJICON): - -![img](@static/images/android/easeim12.jpeg) - -不可用语音和表情模式(EaseInputMenuStyle.DISABLE_VOICE_EMOJICON): - -![img](@static/images/android/easeim13.jpeg) - -只有文本输入模式(EaseInputMenuStyle.ONLY_TEXT): - -![img](@static/images/android/easeim14.jpeg) - -#### 增加自定义消息类型及其布局 - -EaseIMKit 中已经为八种基本消息类型文本,表情,图片,视频,语音,文件,定位及 Custom 提供了基本的布局,ViewHolder 及 Delegate,开发者可以直接使用。但是这些类型很可能不能满足开发者的需求,那么就需要添加新的消息类型及其布局和逻辑。 -使用 EaseIMKit 只需按照以下 5 步即可快速添加自定义消息类型。 -下面我们以自定一个新的文本消息为例: - -1、新建 ChatTxtNewAdapterDelegate 继承 EaseMessageAdapterDelegate。 - -```java -public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate { - @Override - protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) { - return null; - } - - @Override - protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) { - return null; - } -} -``` - -2、新建 ChatRowTxtNew 继承 EaseChatRow 并实现相关方法 - -```java -public class ChatRowTxtNew extends EaseChatRow { - private TextView contentView; - - public ChatRowTxtNew(Context context, boolean isSender) { - super(context, isSender); - } - - @Override - protected void onInflateView() { - inflater.inflate(isSender ? R.layout.ease_row_sent_txt_new : R.layout.ease_row_received_txt_new, this); - } - - @Override - protected void onFindViewById() { - contentView = (TextView) findViewById(com.hyphenate.easeui.R.id.tv_chatcontent); - } - - @Override - protected void onSetUpView() { - EMTextMessageBody txtBody = (EMTextMessageBody) message.getBody(); - contentView.setText(txtBody.getMessage()); - } -} -``` - -布局文件以 R.layout.ease_row_sent_txt_new 为例,如下: - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -其中 ID 为 bubble 控件外的控件为发送端布局的基本组成,需要开发者拷贝进去。一般而言,开发者需要添加的控件,放在 bubble 控件内即可。如上所示,开发者只需在 `onFindViewById()` 方法中找到自己添加的控件,并在 `onSetUpView()` 方法内处理展示逻辑即可。 - -3、新建 ChatTxtNewViewHolder 继承 EaseChatRowViewHolder 并实现相关方法 - -```java -public class ChatTxtNewViewHolder extends EaseChatRowViewHolder { - public ChatTxtNewViewHolder(@NonNull View itemView, MessageListItemClickListener itemClickListener) { - super(itemView, itemClickListener); - } - - @Override - public void onBubbleClick(EMMessage message) { - super.onBubbleClick(message); - // 实现相关点击方法 - } -} -``` - -自定义的 ChatTxtNewViewHolder 可以复写实现 onBubbleClick(EMMessage message) 及 onResendClick(EMMessage message) 方法。需要注意的是,如果在其他地方设置了 MessageListItemClickListener 监听,并将相应的方法实现并返回 true 以后,ViewHolder 中的这两个方法会被拦截。 自定义的 ViewHolder 可以复写 onDetachedFromWindow() 方法,可以做一些释放资源的处理。 自定义的 ViewHolder 需要根据消息的方向(发送发或者接收方)对消息进行处理,可以分别复写 handleSendMessage(final EMMessage message) 或者 handleReceiveMessage(EMMessage message)。 - -4、补全 ChatTxtNewAdapterDelegate 并重写 isForViewType 方法 - -```java -public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate { - @Override - public boolean isForViewType(EMMessage item, int position) { - return item.getType() == EMMessage.Type.TXT && item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false); - } - - @Override - protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) { - return new ChatRowTxtNew(parent.getContext(), isSender); - } - - @Override - protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) { - return new ChatTxtNewViewHolder(view, itemClickListener); - } -} -``` - -:::notice -(1)相同的消息类型(比如例子中消息类型是 EMMessage.Type.TXT)且通过标记判断类型时,在第 5 步注册对话类型时,应将该对话类型注册于基类的对话类型之前(即 ChatTxtNewAdapterDelegate 注册应在 EaseTextAdapterDelegate 之前)。 - -(2)对于 `item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false)` 可以理解为一种标记,在发送消息时设置,如下; -::: - -```java -/** - * 发送新文本消息 - * @param content - */ -public void sendTxtNewMessage(String content) { - EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername); - message.setAttribute(DemoConstant.MESSAGE_ATTR_IS_TXT_NEW, true); - EMClient.getInstance().chatManager().sendMessage(message); -} -``` - -其中 `DemoConstant.MESSAGE_ATTR_IS_TXT_NEW` 为定义的一个常量,通常为字符串类型。 - -5、注册 ChatTxtNewAdapterDelegate 对话类型(通过 EaseMessageTypeSetManager 进行注册) - -```java -/** - * 注册对话类型 - */ -private void registerConversationType() { - EaseMessageTypeSetManager.getInstance() - .addConversationType(EaseExpressionAdapterDelegate.class) //自定义表情 - .addConversationType(EaseFileAdapterDelegate.class) //文件 - ...... - .addConversationType(ChatTxtNewAdapterDelegate.class) //新文本消息 - .setDefaultConversionType(EaseTextAdapterDelegate.class); //文本 -} -``` - -需要开发者注意的是,自定义的消息类型需要注册到 EaseMessageTypeSetManager 中,具体用法可以参考环信 App 中的 [DemoHelper](https://github.com/easemob/chat-android/blob/master/app/src/main/java/com/hyphenate/easeim/DemoHelper.java) 类中的 `registerConversationType()` 方法,并在初始化时调用 `registerConversationType()` 方法。 - -开发者在注册消息类型时,一定要在最后设置默认项(即调用 `setDefaultConversionType()`),并建议将 `EaseTextAdapterDelegate` 设置默认项。如果没有符合的消息类型,EaseIMKit 会选择默认的消息类型进行展示(注:需要展示的消息类型也需要符合默认消息的消息类型,否则会造成 EMMessageBody 强转时报错)。 - -#### 增加长按菜单项 - -EaseChatLayout 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 - -示例代码如下: - -```java -@Override -public void initView(Bundle savedInstanceState) { - super.initView(savedInstanceState); - ...... - // 构建菜单项 model 并通过 EaseChatLayout 添加 - MenuItemBean itemMenu = new MenuItemBean(0, R.id.action_chat_forward, 11, getString(R.string.action_forward)); - itemMenu.setResourceId(R.drawable.ease_chat_item_menu_forward); - chatLayout.addItemMenu(itemMenu ); -} - -...... - -@Override -public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { - switch (item.getItemId()) { - case R.id.action_chat_forward : - // 增加实现逻辑,并返回 true - return true; - } - return false; -} -``` - -#### 增加更多扩展功能 - -EaseIMKit 提供了常用的一些扩展功能,比如发送图片,发送文件,发送定位等,但是实际开发中可能满足不了开发者的需求,EaseIMKit 提供了增加扩展功能的接口,以实现发送短视频消息、音视频通话等。 - -以添加视频通话和音视频会议为例,示例代码如下: - -```java -private void resetChatExtendMenu() { - // 获取到扩展功能控件 - IChatExtendMenu chatExtendMenu = chatLayout.getChatInputMenu().getChatExtendMenu(); - if(chatExtendMenu == null) { - return; - } - // 清除所有的扩展项 - chatExtendMenu.clear(); - // 添加自己需要的扩展功能 - chatExtendMenu.registerMenuItem(R.string.attach_picture, R.drawable.ease_chat_image_selector, EaseChatExtendMenu.ITEM_PICTURE); - chatExtendMenu.registerMenuItem(R.string.attach_take_pic, R.drawable.ease_chat_takepic_selector, EaseChatExtendMenu.ITEM_TAKE_PICTURE); - // 根据消息类型添加不同的扩展功能 - if(chatType == EaseConstant.CHATTYPE_SINGLE){ - chatExtendMenu.registerMenuItem(R.string.attach_media_call, R.drawable.em_chat_video_call_selector, ITEM_VIDEO_CALL); - } - if (chatType == EaseConstant.CHATTYPE_GROUP) { // 音视频会议 - chatExtendMenu.registerMenuItem(R.string.voice_and_video_conference, R.drawable.em_chat_video_call_selector, ITEM_CONFERENCE_CALL); - } -} - -...... - -@Override -public void onChatExtendMenuItemClick(View view, int itemId) { - super.onChatExtendMenuItemClick(view, itemId); - // 只需实现不满足需求或者开发者自己添加的扩展功能 - switch (itemId) { - case ITEM_VIDEO_CALL: - // 实现条目点击事件 - break; - case ITEM_CONFERENCE_CALL: - // 实现条目点击事件 - break; - } -} -``` - -#### 增加自定义表情 - -EaseIMKit 也提供了增加自定义表情的接口,开发者可仿照 EmojiconExampleGroupData 进行定义类型的表情类,然后调用相应接口加入即可。 - -代码如下: - -```java -// 添加扩展表情 -chatLayout.getChatInputMenu().getEmojiconMenu().addEmojiconGroup(EmojiconExampleGroupData.getData()); -``` - -### 设置联系人列表 - -通讯录列表界面可以设置如下样式: - -- 展示模式:分为简洁模式和常规模式 -- 条目:设置条目背景,条目高度及 header 背景 -- 头像:设置默认头像,头像样式及头像大小等 - -在 EaseContactListFragment 及其子类中可以直接获取到 EaseContactLayout 这个控件,然后通过这个控件进行设置。 - -代码如下: - -```java -// 获取列表控件 -EaseContactListLayout contactList = contactLayout.getContactList(); -// 设置条目高度 -contactList.setItemHeight((int) EaseCommonUtils.dip2px(mContext, 80)); -// 设置条目背景 -contactList.setItemBackGround(ContextCompat.getDrawable(mContext, R.color.gray)); -// 设置头像样式:0 为默认,1 为圆形,2 为方形(设置方形时,需要配合设置 avatarRadius,默认的 avatarRadius 为 50dp) -contactList.setAvatarShapeType(2); -// 设置头像圆角 -contactList.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5)); -// 设置 header 背景 -contactList.setHeaderBackGround(ContextCompat.getDrawable(mContext, R.color.white)); -``` - -效果如图: - -![img](@static/images/android/easeim15.jpeg) - -设置简洁模式 - -```java -//设置为简洁模式 -contactLayout.showSimple(); -``` - -效果如图: - -![img](@static/images/android/easeim16.jpeg) - -#### 增加长按菜单项 - -EaseContactListLayout 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 - -示例代码如下: - -```java -// 通过增加 `OnPopupMenuPreShowListener` 监听,并在 `onMenuPreShow` 中增加菜单项更简单 -@Override -public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) { - super.onMenuPreShow(menuHelper, position); - // 增加需要的菜单项,其中传入的第三项 order 决定了菜单项的位置 - menuHelper.addItemMenu(1, R.id.action_friend_block, 2, getString(R.string.em_friends_move_into_the_blacklist_new)); - menuHelper.addItemMenu(1, R.id.action_friend_delete, 1, getString(R.string.ease_friends_delete_the_contact)); -} - -...... - -@Override -public boolean onMenuItemClick(MenuItem item, int position) { - EaseUser user = contactLayout.getContactList().getItem(position); - switch (item.getItemId()) { - case R.id.action_friend_block : - // 增加处理逻辑并返回 `true` - return true; - case R.id.action_friend_delete: - // 增加处理逻辑并返回 `true` - return true; - } - return super.onMenuItemClick(item, position); -} -``` - -#### 增加头布局 - -EaseIMKit 默认是不再通讯录列表之前增加头布局的,但是内部预设了添加头布局的逻辑,开发者可通过 EaseContactListLayout 提供的 API 快速的增加一个或者多个头布局。 - -示例代码如下: - -```java -/** - * 添加头布局 - */ -public void addHeader() { - // 增加多个头布局 - contactLayout.getContactList().addCustomItem(CUSTOM_NEW_CHAT, R.drawable.em_friends_new_chat, getString(R.string.em_friends_new_chat)); - contactLayout.getContactList().addCustomItem(CUSTOM_GROUP_LIST, R.drawable.em_friends_group_chat, getString(R.string.em_friends_group_chat)); - contactLayout.getContactList().addCustomItem(CUSTOM_CHAT_ROOM_LIST, R.drawable.em_friends_chat_room, getString(R.string.em_friends_chat_room)); -} - -...... - -@Override -public void initListener() { - super.initListener(); - ...... - contactLayout.getContactList().setOnCustomItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - EaseContactCustomBean item = contactLayout.getContactList().getCustomAdapter().getItem(position); - switch (item.getId()) { - case CUSTOM_NEW_CHAT : - // 增加实现逻辑 - break; - case CUSTOM_GROUP_LIST : - // 增加实现逻辑 - break; - case CUSTOM_CHAT_ROOM_LIST : - // 增加实现逻辑 - break; - } - } - }); -} -``` - -### 设置用户头像和昵称属性 - -#### 设置头像和昵称 - -环信 IM SDK 不做用户信息存储,如果用户想要展示自定义的头像及昵称,可以通过 EaseUserProfileProvider 进行提供。 - -首先需要在合适的时机去设置 EaseUserProfileProvider,例如: - -```java -EaseIM.getInstance().setUserProvider(new EaseUserProfileProvider() { - @Override - public EaseUser getUser(String username) { - //根据 username,从数据库中或者内存中取出之前保存的用户信息,如从数据库中取出的用户对象为 DemoUserBean - DemoUserBean bean = getUserFromDbOrMemery(username); - EaseUser user = new EaseUser(username); - ...... - //设置用户昵称 - user.setNickname(bean.getNickname()); - //设置头像地址 - user.setAvatar(bean.getAvatar()); - //最后返回构建的 EaseUser 对象 - return user; - } -}); -``` - -EaseIMKit 中会话列表,聊天列表及联系人列表,内部已经添加 EaseUserProfileProvider 的判断,当展示数据时优先从 EaseUserProfileProvider 获取头像和昵称数据,如果有则展示,如果没有头像采用默认头像,昵称展示为环信 ID。 - -:::notice 建议方案 -开发者先将相关用户信息从服务器中获取并存储到数据库中,在 getUser(String username) 方法调用时,从数据库中根据 username(环信 ID)取出相应的用户数据,生成 EaseUser 对象 user,并给 user 赋值 nickname 及 avatar 属性,最后返回这个 user 即可。 -::: - -#### 统一设置头像样式 - -EaseIMKit 提供了 `EaseAvatarOptions` 这个类用于全局配置头像的样式,包括形状,圆角半径,描边宽度及描边颜色。会话,聊天及联系人中已经增加了对于 EaseAvatarOptions 的支持。 - -示例代码如下: - -```java -//设置头像配置属性 -EaseIM.getInstance().setAvatarOptions(getAvatarOptions()); -...... -/** - * 统一配置头像 - * @return - */ -private EaseAvatarOptions getAvatarOptions() { - EaseAvatarOptions avatarOptions = new EaseAvatarOptions(); - //设置头像形状为圆形,1 代表圆形,2 代表方形 - avatarOptions.setAvatarShape(1); - return avatarOptions; -} -``` - -使用时可以直接调用 EaseUserUtils 中的 `setUserAvatarStyle(EaseImageView imageView)` 方法即可设置。 - -## 事件处理 - -EaseIMKit 还帮助开发者实现了一系列的事件监听接口,比如条目的点击事件,条目的长按事件,菜单项的点击事件等。 - -### 会话列表 - -#### 条目点击事件 - -开发者如果使用 `EaseConversationListFragment` 及其子类,可以重写 `onItemClick(View view, int position)` 方法即可。 - -代码如下: - -```java -@Override -public void onItemClick(View view, int position) { - super.onItemClick(view, position); - //添加点击事件实现逻辑 -} -``` - -开发者如果直接使用的 `EaseConversationListLayout` 控件,则可通过该控件设置条目的点击事件。 - -代码如下: - -```java -conversationListLayout.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - //添加点击事件实现逻辑 - } -}); -``` - -#### 长按事件及弹出菜单点击事件 - -`EaseConversationListLayout` 中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。 - -如果开发者使用的是 `EaseConversationListFragment` 及其子类,则直接重写 `onMenuItemClick(MenuItem item, int position)` 即可。 - -代码如下: - -```java -@Override -public boolean onMenuItemClick(MenuItem item, int position) { - //添加具体的点击事件实现逻辑,并返回 true - return super.onMenuItemClick(item, position); -} -``` - -开发者如果直接使用的 EaseConversationListLayout 控件,则可通过该控件设置弹出菜单的点击事件。 - -代码如下: - -```java -conversationListLayout.setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item, int position) { - //添加具体的点击事件实现逻辑,并返回 true - return false; - } -}); -``` - -如果开发者需要自己实现弹出菜单,通过 EaseConversationListLayout 控件添加条目的长按监听并实现即可。 - -代码如下: - -```java -conversationListLayout.setOnItemLongClickListener(new OnItemLongClickListener() { - @Override - public boolean onItemLongClick(View view, int position) { - //添加弹出菜单的逻辑,并返回 true - return false; - } -}); -``` - -### 聊天区域 - -#### 聊天列表事件 - -开发者如果使用的是 EaseChatFragment 及其子类,则可以根据需要重写相关的事件方法即可。聊天列表中的常用监听事件均封装到了 OnChatLayoutListener 接口中,EaseChatFragment 已经设置了该监听。 - -`OnChatLayoutListener` 中有如下事件监听: - -```java -public interface OnChatLayoutListener { - /** - * 点击消息 bubble 区域 - * @param message - * @return - */ - boolean onBubbleClick(EMMessage message); - - /** - * 长按消息 bubble 区域 - * @param v - * @param message - * @return - */ - boolean onBubbleLongClick(View v, EMMessage message); - - /** - * 点击头像 - * @param username - */ - void onUserAvatarClick(String username); - - /** - * 长按头像 - * @param username - */ - void onUserAvatarLongClick(String username); - - /** - * 条目点击 - * @param view - * @param itemId - */ - void onChatExtendMenuItemClick(View view, int itemId); - - /** - * EditText 文本变化监听 - * @param s - * @param start - * @param before - * @param count - */ - void onTextChanged(CharSequence s, int start, int before, int count); - - /** - * 聊天中错误信息 - * @param code - * @param errorMsg - */ - void onChatError(int code, String errorMsg); - - /** - * 用于监听其他人正在数据事件 - * @param action 输入事件 TypingBegin为开始 TypingEnd 为结束 - */ - default void onOtherTyping(String action){} - -} -``` - -如果开发者使用的是 EaseChatLayout 控件,则通过该控件实现 OnChatLayoutListener 接口即可。 - -#### 长按事件及弹出菜单点击事件 - -EaseChatLayout 中已经实现了一套默认的长按弹出菜单逻辑,并对默认的菜单项进行了处理,如果开发者对默认菜单项有其他实现需求,只需实现弹出菜单的点击事件即可。 - -如果开发者使用的是 EaseChatFragment 及其子类,则直接重写 onMenuItemClick(MenuItemBean item, EMMessage message) 即可。 - -代码如下: - -```java -@Override -public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { - //添加菜单条目点击事件实现逻辑,并返回 true - //返回 true 表示不采用默认的实现逻辑 - return false; -} -``` - -如果开发者需要在弹出菜单展示前对菜单项进行处理,重写 `onPreMenu(EasePopupWindowHelper helper, EMMessage message)` 并处理即可。 - -示例代码如下: - -```java -@Override -public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) { - // 默认两分钟后,即不可撤回 - if(System.currentTimeMillis() - message.getMsgTime() > 2 * 60 * 1000) { - helper.findItemVisible(R.id.action_chat_recall, false); - } - EMMessage.Type type = message.getType(); - helper.findItemVisible(R.id.action_chat_forward, false); - switch (type) { - case TXT: - if(!message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VIDEO_CALL, false) - && !message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VOICE_CALL, false)) { - helper.findItemVisible(R.id.action_chat_forward, true); - } - break; - case IMAGE: - helper.findItemVisible(R.id.action_chat_forward, true); - break; - } - - if(chatType == DemoConstant.CHATTYPE_CHATROOM) { - helper.findItemVisible(R.id.action_chat_forward, true); - } -} -``` - -开发者如果直接使用的 EaseChatLayout 控件,则可通过该控件设置弹出菜单的点击事件。 - -代码如下: - -```java -chatLayout.setOnPopupWindowItemClickListener(new OnMenuChangeListener() { - @Override - public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) { - // 菜单预处理逻辑 - } - @Override - public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { - // 菜单项点击事件,如果默认实现不满足,则自己实现并返回 true。 - return false; - } -}); -``` - -如果开发者需要自己实现弹出菜单,通过 EaseChatLayout 控件找到 EaseChatMessageListLayout 并添加条目的长按监听并实现即可。 - -代码如下: - -```java -chatLayout.getChatMessageListLayout().setOnItemLongClickListener(new OnItemLongClickListener() { - @Override - public boolean onItemLongClick(View view, int position) { - //添加弹出菜单的逻辑,并返回 true - return false; - } -}); -``` - -### 通讯录列表 - -#### 条目点击事件 - -开发者如果使用 EaseContactListFragment 及其子类,可以重写 `onItemClick(View view, int position)` 方法即可。 - -代码如下: - -```java -@Override -public void onItemClick(View view, int position) { - super.onItemClick(view, position); - //添加点击事件实现逻辑 -} -``` - -开发者如果直接使用的 EaseContactLayout 控件,则可通过该控件找到 EaseContactListLayout 并设置条目的点击事件。 - -代码如下: - -```java -contactLayout.getContactList().setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - //添加点击事件实现逻辑 - } -}); -``` - -#### 长按事件及弹出菜单点击事件 - -EaseContactListLayout 中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。 - -如果开发者使用的是 EaseContactListFragment 及其子类,则直接重写 `onMenuItemClick(MenuItem item, int position)` 即可。 - -代码如下: - -```java -@Override -public boolean onMenuItemClick(MenuItem item, int position) { - // 添加具体的点击事件实现逻辑,并返回 'true' - return super.onMenuItemClick(item, position); -} -``` - -如果开发者需要在弹出菜单展示前对菜单项进行处理,重写 `onMenuPreShow(EasePopupWindowHelper helper, EMMessage message)` 并处理即可。 - -开发者如果直接使用的 EaseContactLayout 控件,则可通过该控件找到 EaseContactListLayout 并设置弹出菜单的点击事件。 - -代码如下: - -```java -contactLayout.getContactList().setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item, int position) { - //添加具体的点击事件实现逻辑,并返回 true - return false; - } -}); -``` - -相应的菜单项预处理,需要通过 EaseContactListLayout 设置菜单预处理监听事件。 代码如下: - -```java -contactLayout.getContactList().setOnPopupMenuPreShowListener(new OnPopupMenuPreShowListener() { - @Override - public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) { - //菜单预处理逻辑 - } -}); -``` - -如果开发者需要自己实现弹出菜单,通过 EaseContactListLayout 控件添加条目的长按监听并实现即可。 - -代码如下: - -```java -contactLayout.getContactList().setOnItemLongClickListener(new OnItemLongClickListener() { - @Override - public boolean onItemLongClick(View view, int position) { - //添加弹出菜单的逻辑,并返回 true - return false; - } -}); -``` - -## 功能扩展 - -### 系统消息 - -EaseIMKit 中 EaseConversationListLayout 已经封装了 IM 通知的展示逻辑,但是需要开发者将 IM 的通知封装成系统消息并保存到本地数据库。为了方便开发者封装成符合 EaseIMKit 能够使用的系统消息,EaseIMKit 中提供了 EaseSystemMsgManager 管理类,开发者可通过该管理类,方便的封装及更新系统消息。 - -EaseIMKit 可处理的系统消息有如下要求: - -```java -// 设置为文本消息 -EMMessage emMessage = EMMessage.createReceiveMessage(EMMessage.Type.TXT); -// 设置 from 为固定的 "em_system" -emMessage.setFrom(EaseConstant.DEFAULT_SYSTEM_MESSAGE_ID); -emMessage.setMsgId(UUID.randomUUID().toString()); -emMessage.setStatus(EMMessage.Status.SUCCESS); -``` - -当然 EaseSystemMsgManager 管理类已经做了默认处理,开发者只需传入文本内容(会话列表中展示的内容)及扩展内容 ext(Map)即可。 示例如下: - -```java -@Override -public void onFriendRequestDeclined(String username) { - EMLog.i("ChatContactListener", "onFriendRequestDeclined"); - Map ext = EaseSystemMsgManager.getInstance().createMsgExt(); - ext.put(DemoConstant.SYSTEM_MESSAGE_FROM, username); - ext.put(DemoConstant.SYSTEM_MESSAGE_STATUS, InviteMessageStatus.BEREFUSED.name()); - EMMessage message = EaseSystemMsgManager.getInstance().createMessage(PushAndMessageHelper.getSystemMessage(ext), ext); - ...... -} -``` - -同时 EaseConversationListLayout 提供了是否展示系统消息的 API,showSystemMessage(boolean show) 用于控制是否展示系统消息。 diff --git a/docs/document/ios/easeimkit.md b/docs/document/ios/easeimkit.md deleted file mode 100644 index b3e5e2317..000000000 --- a/docs/document/ios/easeimkit.md +++ /dev/null @@ -1,1058 +0,0 @@ -# EaseIMKit 使用指南 - - - -仍在使用旧版 EaseUI 的用户可参考旧版 EaseUI 的文档,旧版已不再维护。 旧版文档地址:[EaseUI 集成](https://docs-im.easemob.com/im/ios/other/easeui) - -## 简介 - -EaseIMKit 是什么? - -EaseIMKit 是基于环信 IM SDK 的一款 UI 组件库,它提供了一些通用的 UI 组件,例如 ‘会话列表’、‘聊天界面’ 和 ‘联系人列表’ 等,开发者可根据实际业务需求通过该组件库快速地搭建自定义 IM 应用。EaseIMKit 中的组件在实现 UI 功能的同时,调用 IM SDK 相应的接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 EaseIMKit 时只需关注自身业务或个性化扩展即可。 - -EaseIMKit 源码地址 - -- [EaseIMKit 工程](https://github.com/easemob/easeui_ios/tree/EaseIMKit) - -使用 EaseIMKit 环信 IM App 地址: - -- [环信 IM](https://github.com/easemob/chat-ios) - -## 导入 - -### 支持系统版本要求 - -- EaseIMKit 支持 iOS 10.0 及以上系统版本 -- EaseIM 支持 iOS 11.0 及以上系统版本 - -## 快速集成 - -### pod 方式集成 - -``` -pod 'EaseIMKit' -``` - -需要在 `Podfile` 文件加上 `use_frameworks!` - -:::notice -EaseIMKit: 对应 HyphenateChat SDK(HyphenateChat 不包含实时音视频,EaseIMKit 不包含音视频,EaseIM 依赖音视频库 EaseCallKit 后实现了音视频功能) - -EaseIMKit 中包含了拍照,发语音,发图片,发视频,发位置,发文件的功能,需要使用录音,摄像头,相册,地理位置的权限。需要在您项目的 info.plist 中添加对应权限。 -::: - -### 源码集成 - -- [Github 下载源码](https://github.com/easemob/easeui_ios.git) - -执行命令:`git clone https://github.com/easemob/easeui_ios.git` - -- 创建 `Podfile` 文件并添加 EaseIMKit 源码依赖 - - 1. 项目 `Podfile` 文件 和 `ProjectName.xcodeproj` 文件应在同一目录,如下图所示: - - ![img](@static/images/ios/easeimkit1.png) - - Podfile 文件示例: - - ``` - platform :ios, '11.0' - - source 'https://github.com/CocoaPods/Specs.git' - - target 'ProjectName' do - pod 'EaseIMKit', :path => "../EaseUI/EaseIMKit" - pod 'HyphenateChat', '3.8.4' - end - ``` - - 2. EaseIMKit path 路径(如:pod 'EaseIMKit', :path ⇒ “../EaseUI/EaseIMKit”)需指向 EaseIMKit.podspec 文件所在目录,如下图所示: - - ![img](@static/images/ios/easeimkit2.png) - -- 项目集成本地 EaseIMKit 源码 - - 1. 终端 cd 到 Podfile 文件所在目录,执行 pod install 命令在项目中安装 EaseIMKit 本地源码 - 2. 执行完成后,则在 Xcode 项目目录 Pods/Development Pods/ 可找到 EaseIMKit 源码,如下图所示: - - ![img](@static/images/ios/easeimkit3.png) - - 3. 可对源码进行符合自己项目目标的自定义修改 - -- 成为社区贡献者 - -如果在源码自定义过程中有任何通用自定义都可以给我们 [Github 仓库](https://github.com/easemob/easeui_ios.git) 提交代码成为社区贡献者! - -### 初始化 - -第 1 步:引入相关头文件 - -```objectivec -#import -``` - -第 2 步:在在工程的 AppDelegate 中的以下方法中调用 EaseIMKitManager 的初始化方法一并初始化环信 SDK。(注: 此方法不需要重复调用) - -```objectivec -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - EMOptions *options = [EMOptions optionsWithAppkey:@"您的APPKEY"]; - [EaseIMKitManager initWithEMOptions:options]; - //再做登录操作,可准确接收到系统通知 - return YES; -} -``` - -EaseIMKitManager 主要包含系统通知(好友申请,群邀请/申请)回调,未读总数回调等方法。 用户需要注册自己的类到 EaseIMKitManagerDelegate 才可收到未读总数变化回调。 用户需要添加 EaseIMKitSystemNotiDelegate 代理才可收到系统通知相关回调。 - -系统通知相关回调接口,系统通知构造成了一个本地会话,每个新通知构造为一条本地消息。 系统通知所构造的会话的 conversationId 为 @“emsystemnotificationid”。 - -#### 是否需要系统通知 - -```objectivec -/*! - @method - @brief 是否需要系统通知:好友/群 申请等 - @discussion 默认需要系统通知 - @result 返回: YES:需要; NO:不需要; - */ -- (BOOL)isNeedsSystemNotification; -``` - -#### 收到系统通知所展示信息回调接口 - -```objectivec -/*! - @method - @brief 收到请求返回展示信息 - @param conversationId 会话 ID。 - * 对于单聊类型,会话 ID 同时也是对方用户的名称。 - * 对于群聊类型,会话 ID 同时也是对方群组的 ID,并不同于群组的名称。 - * 对于聊天室类型,会话 ID 同时也是聊天室的 ID,并不同于聊天室的名称。 - - @param requestUser 请求方。 - @param reason 请求原因。 - @result 返回系统通知所展示信息。 - */ -- (NSString *)requestDidReceiveShowMessage:(NSString *)conversationId requestUser:(NSString *)requestUser reason:(EaseIMKitCallBackReason)reason; -``` - -对于返回值处理:空字符串不产生新 message / nil:使用默认实现 / 非空字符串且长度大于 0,使用该字符串产生新 message - -#### 收到系统通知扩展信息回调接口 - -```objectivec -/*! - @method - @brief 收到请求返回扩展信息 - @param conversationId 会话 ID。 - * 对于单聊类型,会话 ID 同时也是对方用户的名称。 - * 对于群聊类型,会话 ID 同时也是对方群组的 ID,并不同于群组的名称。 - * 对于聊天室类型,会话 ID 同时也是聊天室的 ID,并不同于聊天室的名称。 - - @param requestUser 请求方。 - @param reason 请求原因。 - @result 返回系统通知携带扩展信息字典。 - */ -- (NSDictionary *)requestDidReceiveConversationExt:(NSString *)conversationId requestUser:(NSString *)requestUser reason:(EaseIMKitCallBackReason)reason; -``` - -#### 未读总数变化回调接口 - -```objectivec -/*! - @method - @brief 会话未读总数变化。 - @param unreadCount 当前会话列表的总未读数。 - */ -- (void)conversationsUnreadCountUpdate:(NSInteger)unreadCount; -``` - -## 快速搭建 - -### 聊天会话快速搭建 - -导入 EaseIMKit 头文件 - -```objectivec -#import -``` - -EaseIMKit 提供现成的聊天会话 ViewController,可以通过创建 EaseChatViewController 对象实例,嵌入进自己的聊天控制器方式(参考 EaseIM 中 EMChatViewController)实现对 EaseIMKit 聊天会话的集成。 创建聊天会话页实例,需传递用户‘环信 ID’或‘群 ID’ ,会话类型(EMConversationType)以及必须传入聊天视图配置数据模型 EaseChatViewModel 实例。 - -```objectivec -EaseChatViewModel *viewModel = [[EaseChatViewModel alloc]init]; -EaseChatViewController *chatController = [EaseChatViewController initWithConversationId:@"custom" - conversationType:EMConversationTypeChat - chatViewModel:viewModel]; -[self addChildViewController:chatController]; -[self.view addSubview:chatController.view]; -chatController.view.frame = self.view.bounds; -``` - -聊天控制器嵌入自己的聊天页后还需传入消息列表 messageList 以供 EaseChatViewController 展示使用 - -```objectivec -//isScrollBottom 是否滑动到页面底部 -- (void)loadData:(BOOL)isScrollBottom -{ - __weak typeof(self) weakself = self; - void (^block)(NSArray *aMessages, EMError *aError) = ^(NSArray *aMessages, EMError *aError) { - dispatch_async(dispatch_get_main_queue(), ^{ - //给 EaseChatViewController 传入消息列表messageList - //isScrollBottom 是否滑动到页面底部 isInsertBottom 消息数据集是否插入消息列表底部 - //在传入消息列表之前不需要对列表做任何处理,只需传入列表数据即可,否则会刷新 UI 失败 - [weakself.chatController refreshTableViewWithData:aMessages isInsertBottom:NO isScrollBottom:isScrollBottom]; - }); - }; - [self.conversation loadMessagesStartFromId:nil count:50 searchDirection:EMMessageSearchDirectionUp completion:block]; -} -``` - -### 会话列表快速搭建 - -导入 EaseIMKit 头文件 - -```objectivec -#import -``` - -在自己聊天控制器内可嵌入 EaseIMKit 的会话列表页,创建会话列表实例,实例化会话列表必须传入会话列表视图数据配置模型 EaseConversationViewModel 实例。 - -```objectivec -EaseConversationViewModel *viewModel = [[EaseConversationViewModel alloc] init]; - -EaseConversationsViewController *easeConvsVC = [[EaseConversationsViewController alloc] initWithModel:viewModel]; -easeConvsVC.delegate = self; -[self addChildViewController:easeConvsVC]; -[self.view addSubview:easeConvsVC.view]; -[easeConvsVC.view mas_makeConstraints:^(MASConstraintMaker *make) { - make.size.equalTo(self.view); -}]; -``` - -### 通讯录快速搭建 - -导入 EaseIMKit 头文件 - -```objectivec -#import -``` - -在自己聊天控制器内可嵌入 EaseIMKit 的会话列表页,创建通讯录实例,必须传入通讯录视图数据模型 EaseContactsViewModel 实例以构建通讯录 UI 界面。 - -```objectivec -EaseContactsViewModel *model = [[EaseContactsViewModel alloc] init]; -EaseContactsViewController *contactsVC = [[EaseContactsViewController alloc] initWithModel:model]; -//通讯录头部功能区(加好友/群聊/聊天室 入口) -contactsVC.customHeaderItems = [self items]; -contactsVC.delegate = self; -[self addChildViewController:contactsVC]; -[self.view addSubview:contactsVC.view]; -[contactsVC.view mas_makeConstraints:^(MASConstraintMaker *make) { - make.size.equalTo(self.view); -}]; -//设置通讯录头部功能区实例(EaseIM APP 有效): -- (NSArray *)items { - EMContactModel *newFriends = [[EMContactModel alloc] init]; - newFriends.huanXinId = @"newFriend"; - newFriends.nickname = @"新的好友"; - newFriends.avatar = [UIImage imageNamed:@"newFriend"]; - EMContactModel *groups = [[EMContactModel alloc] init]; - groups.huanXinId = @"groupList"; - groups.nickname = @"群聊"; - groups.avatar = [UIImage imageNamed:@"groupchat"]; - EMContactModel *chatooms = [[EMContactModel alloc] init]; - chatooms.huanXinId = @"chatroomList"; - chatooms.nickname = @"聊天室"; - chatooms.avatar = [UIImage imageNamed:@"chatroom"]; - return (NSArray *)@[newFriends, groups, chatooms]; -} -``` - -## 设置样式 - -### 聊天会话样式配置 - -聊天会话可配置参数如下: - -```objectivec -@property (nonatomic, strong) UIColor *chatViewBgColor; //聊天页背景色 -@property (nonatomic, strong) UIColor *chatBarBgColor; //输入区背景色 -@property (nonatomic, strong) EaseExtFuncModel *extFuncModel; //输入区扩展功能数据模型 -@property (nonatomic, strong) UIColor *msgTimeItemBgColor; //时间线背景色 -@property (nonatomic, strong) UIColor *msgTimeItemFontColor; //时间线字体颜色 -@property (nonatomic, strong) UIImage *receiveBubbleBgPicture; //所接收信息的气泡 -@property (nonatomic, strong) UIImage *sendBubbleBgPicture; //所发送信息的气泡 -@property (nonatomic, strong) UIColor *contentFontColor; //文本消息字体颜色 -@property (nonatomic) CGFloat contentFontSize; //文本消息字体大小 -@property (nonatomic) UIEdgeInsets bubbleBgEdgeInset; //消息气泡背景图保护区域 -@property (nonatomic) EaseInputBarStyle inputBarStyle; //输入区类型:(全部功能,无语音,无表情,无表情和语音,纯文本) -@property (nonatomic) EaseAvatarStyle avatarStyle; //头像风格 -@property (nonatomic) CGFloat avatarCornerRadius; //头像圆角大小 默认:0 (只有头像类型是圆角生效) -//仅群聊可设置 -@property (nonatomic) EaseAlignmentStyle msgAlignmentStyle; //聊天区域消息排列方式 -``` - -其中参数:EaseExtFuncModel 输入区扩展功能数据配置模型(聊天会话页相机,相册,音视频等区域)内含可配参数: - -```objectivec -@property (nonatomic, strong) UIColor *iconBgColor;//图标所在 view 背景色 -@property (nonatomic, strong) UIColor *viewBgColor;//视图背景色 -@property (nonatomic, strong) UIColor *fontColor;//字体颜色 -@property (nonatomic, assign) CGFloat fontSize;//字体大小 -@property (nonatomic, assign) CGSize collectionViewSize;//视图尺寸 -``` - -其中参数:inputBarStyle(输入区)包含五种类型: - -```objectivec -typedef NS_ENUM(NSInteger, EaseInputBarStyle) { - EaseInputBarStyleAll = 1, //全部功能 - EaseInputBarStyleNoAudio, //无语音 - EaseInputBarStyleNoEmoji, //无表情 - EaseInputBarStyleNoAudioAndEmoji, //无表情和语音 - EaseInputBarStyleOnlyText, //纯文本 -}; -``` - -其中参数:EaseAlignmentStyle (消息排列方式,仅群聊可生效)包含两种类型 - -```objectivec -typedef enum { - EaseAlignmentNormal = 1, //左右排列 - EaseAlignmentlLeft, //居左排列 -} EaseAlignmentStyle; -``` - -实例化的聊天控制器可通过重置视图 UI 配置模型刷新页面 - -```objectivec -//重置聊天控制器 -- (void)resetChatVCWithViewModel:(EaseChatViewModel *)viewModel; -``` - -聊天页背景色,输入区颜色配置示例: - -![背景色,输入区颜色](@static/images/ios/easeimkit4.png) - -聊天会话输入区类型参数配置示例: - -![全部功能,语音不可用,表情不可用,语音和表情不可用,纯文本](@static/images/ios/easeimkit5.png) - -输入区扩展功能参数配置示例: - -![输入区扩展](@static/images/ios/easeimkit6.jpeg) - -聊天会话群聊消息同左排列,时间线背景色,时间字体颜色配置示例: - -![群聊消息同左排列,时间线背景色,时间字体颜色](@static/images/ios/easeimkit7.jpeg) - -### 会话列表样式配置 - -会话列表可配置参数如下: - -```objectivec -@property (nonatomic) EaseAvatarStyle avatarType; // 头像样式 -@property (nonatomic, strong) UIImage *defaultAvatarImage; // 默认头像 -@property (nonatomic) CGSize avatarSize; // 头像尺寸 -@property (nonatomic) UIEdgeInsets avatarEdgeInsets; // 头像位置 -@property (nonatomic, strong) UIFont *nameLabelFont; // 昵称字体 -@property (nonatomic, strong) UIColor *nameLabelColor; // 昵称颜色 -@property (nonatomic) UIEdgeInsets nameLabelEdgeInsets; // 昵称位置 -@property (nonatomic, strong) UIFont *detailLabelFont; // 详情字体 -@property (nonatomic, strong) UIColor *detailLabelColor; // 详情字色 -@property (nonatomic) UIEdgeInsets detailLabelEdgeInsets; // 详情位置 -@property (nonatomic, strong) UIFont *timeLabelFont; // 时间字体 -@property (nonatomic, strong) UIColor *timeLabelColor; // 时间字色 -@property (nonatomic) UIEdgeInsets timeLabelEdgeInsets; // 时间位置 -@property (nonatomic) EMUnReadCountViewPosition badgeLabelPosition; // 未读数显示风格 -@property (nonatomic, strong) UIFont *badgeLabelFont; // 未读数字体 -@property (nonatomic, strong) UIColor *badgeLabelTitleColor; // 未读数字色 -@property (nonatomic, strong) UIColor *badgeLabelBgColor; // 未读数背景色 -@property (nonatomic) CGFloat badgeLabelHeight; // 未读数角标高度 -@property (nonatomic) CGVector badgeLabelCenterVector; // 未读数中心位置偏移 -@property (nonatomic) int badgeMaxNum; // 未读数显示上限, 超过上限后会显示 xx+ -``` - -会话列表以及联系人列表共用其父类可配置参数如下: - -```objectivec -@property (nonatomic) BOOL canRefresh; // 是否可下拉刷新 -@property (nonatomic, strong) UIView *bgView; // tableView 背景图 -@property (nonatomic, strong) UIColor *cellBgColor; // UITableViewCell 背景色 -@property (nonatomic) UIEdgeInsets cellSeparatorInset; // UITableViewCell 下划线位置 -@property (nonatomic, strong) UIColor *cellSeparatorColor; // UITableViewCell 下划线颜色 -``` - -### 通讯录样式配置 - -通讯录可配置参数如下: - -```objectivec -@property (nonatomic) EaseAvatarStyle avatarType; // 头像样式 -@property (nonatomic, strong) UIImage *defaultAvatarImage; // 默认头像 -@property (nonatomic) CGSize avatarSize; // 头像尺寸 -@property (nonatomic) UIEdgeInsets avatarEdgeInsets; // 头像位置 -@property (nonatomic, strong) UIFont *nameLabelFont; // 昵称字体 -@property (nonatomic, strong) UIColor *nameLabelColor; // 昵称颜色 -@property (nonatomic) UIEdgeInsets nameLabelEdgeInsets; // 昵称位置 -@property (nonatomic, strong) UIFont *sectionTitleFont; // section title 字体 -@property (nonatomic, strong) UIColor *sectionTitleColor; // section title 颜色 -@property (nonatomic, strong) UIColor *sectionTitleBgColor; // section title 背景 -@property (nonatomic) UIEdgeInsets sectionTitleEdgeInsets; // section title 位置 -// section title label 高度 (section title view = sectionTitleLabelHeight + sectionTitleEdgeInsets.top + sectionTitleEdgeInsets.bottom) -@property (nonatomic) CGFloat sectionTitleLabelHeight; -``` - -以及通讯录和会话列表共用的参数配置 - -```objectivec -@property (nonatomic) BOOL canRefresh; // 是否可下拉刷新 -@property (nonatomic, strong) UIView *bgView; // tableView 背景图 -@property (nonatomic, strong) UIColor *cellBgColor; // UITableViewCell 背景色 -@property (nonatomic) UIEdgeInsets cellSeparatorInset; // UITableViewCell 下划线位置 -@property (nonatomic, strong) UIColor *cellSeparatorColor; // UITableViewCell 下划线颜色 -``` - -通讯录添加头部功能区:新的好友,群聊,聊天室示意图: - -![头部功能区:新的好友,群聊,聊天室以及联系人列表](@static/images/ios/easeimkit8.png) - -## 自定义功能扩展 - -### 聊天会话自定义功能扩展 - -实例化 EaseChatViewController 之后,可选择实现 EaseChatViewControllerDelegate 协议(聊天控制器回调代理),接收 EaseChatViewController 的回调并做进一步的自定义实现。 - -EaseChatViewControllerDelegate - -#### 下拉加载更多消息回调 - -下拉加载更多消息回调(可得到当前第一条消息 ID 作为下次加载更多消息的参考 ID;当前消息列表) - -```objectivec -/** - * 下拉加载更多消息回调 - * - * @param firstMessageId 第一条消息 ID - * @param messageList 当前消息列表 - */ -- (void)loadMoreMessageData:(NSString *)firstMessageId currentMessageList:(NSArray *)messageList; -``` - -#### 自定义 cell - -通过实现聊天控制回调获取自定义消息 cell,根据 messageModel,用户自己判断是否显示自定义消息 cell。如果返回 nil 会显示默认;如果返回 cell 会显示用户自定义消息 cell。 - -```objectivec -/*! - @method - @brief 获取消息自定义 cell - @discussion 用户根据 messageModel 判断是否显示自定义 cell。返回 nil 显示默认 cell,否则显示用户自定义 cell - @param tableView 当前消息视图的 tableView - @param messageModel 消息的数据模型 - @result 返回用户自定义 cell - */ -- (UITableViewCell *)cellForItem:(UITableView *)tableView messageModel:(EaseMessageModel *)messageModel; -``` - -具体创建自定义 Cell 的示例: - -```objectivec -//自定义通话记录cell -- (UITableViewCell *)cellForItem:(UITableView *)tableView messageModel:(EaseMessageModel *)messageModel -{ - //当消息是单聊音视频通话消息时,EaseIM 自定义通话记录 cell - if (messageModel.type == EMMessageTypePictMixText) { - EMMessageCell *cell = [[EMMessageCell alloc]initWithDirection:messageModel.direction type:messageModel.type]; - cell.model = messageModel; - cell.delegate = self; - return cell; - } - return nil; -} -``` - -通过自定义 cell 展示单聊音视频通话记录的效果图: - -![自定义 cell 展示单聊音视频通话记录](@static/images/ios/easeimkit9.png) - -#### 选中消息的回调 - -选中消息的回调(EaseIMKit 没有对于自定义 cell 的选中事件回调,需用户自定义实现选中响应) - -```objectivec -/*! - @method - @brief 消息点击事件 - @discussion 用户根据 messageModel 判断,是否自定义处理消息选中时间。返回 NO 为自定义处理,返回 YES 为默认处理 - @param message 当前点击的消息 - @param userData 当前点击的消息携带的用户资料 - @result 是否采用默认事件处理 -*/ -- (BOOL)didSelectMessageItem:(EMMessage*)message userData:(id)userData; -``` - -EaseIMKit 选中是消息气泡,自定义 cell 的点击事件需自定义实现,例:EaseIM 单聊通话记录 cell 点击事件再次发起通话 - -```objectivec -- (void)messageCellDidSelected:(EMMessageCell *)aCell -{ - //使用‘通知’的方式发起通话,其中所定义的宏仅在 EaseIM APP 中生效 - NSString *callType = nil; - NSDictionary *dic = aCell.model.message.ext; - if ([[dic objectForKey:EMCOMMUNICATE_TYPE] isEqualToString:EMCOMMUNICATE_TYPE_VOICE]) - callType = EMCOMMUNICATE_TYPE_VOICE; - if ([[dic objectForKey:EMCOMMUNICATE_TYPE] isEqualToString:EMCOMMUNICATE_TYPE_VIDEO]) - callType = EMCOMMUNICATE_TYPE_VIDEO; - if ([callType isEqualToString:EMCOMMUNICATE_TYPE_VOICE]) - [[NSNotificationCenter defaultCenter] postNotificationName:CALL_MAKE1V1 object:@{CALL_CHATTER:aCell.model.message.conversationId, CALL_TYPE:@(EMCallTypeVoice)}]; - if ([callType isEqualToString:EMCOMMUNICATE_TYPE_VIDEO]) - [[NSNotificationCenter defaultCenter] postNotificationName:CALL_MAKE1V1 object:@{CALL_CHATTER:aCell.model.message.conversationId, CALL_TYPE:@(EMCallTypeVideo)}]; -} -``` - -#### 用户资料回调 - -用户资料回调(头像昵称等) - -```objectivec -/*! - @method - @brief 返回用户资料 - @discussion 用户根据 huanxinID 在自己的用户体系中匹配对应的用户资料,并返回相应的信息,否则默认实现 - @param huanxinID 环信 ID - @result 返回与当前环信 ID 关联的用户资料 - */ -- (id)userData:(NSString*)huanxinID; -``` - -#### 用户选中头像的回调 - -```objectivec -/*! - @method - @brief 点击消息头像 - @discussion 获取用户点击头像回调 - @param userData 当前点击的头像所指向的用户资料 - */ - -- (void)avatarDidSelected:(id)userData; -``` - -获取用户选中头像回调的样例: - -```objectivec -//头像点击 -- (void)avatarDidSelected:(id)userData -{ - //EMPersonalDataViewController 用户自定义的个人信息视图 - //样例逻辑是选中消息头像后,进入该消息发送者的个人信息页 - if (userData && userData.easeId) { - EMPersonalDataViewController *controller = [[EMPersonalDataViewController alloc]initWithNickName:userData.easeId]; - [self.navigationController pushViewController:controller animated:YES]; - } -} -``` - -#### 用户长按头像的回调 - -```objectivec -/*! - @method - @brief 点击消息头像 - @discussion 获取用户长按头像回调 - @param userData 当前长按的头像所指向的用户资料 - */ - -- (void)avatarDidLongPress:(id)userData; -``` - -#### 群通知回执详情 - -```objectivec -/*! - @method - @brief 群通知回执详情 - @discussion 获取用户点击群通知的回调(仅在群聊中并且是点击用户群主有效) - @param message 当前群通知消息 - @param groupId 当前消息所属群 ID - */ - -- (void)groupMessageReadReceiptDetail:(EMMessage*)message groupId:(NSString*)groupId; -``` - -获取用户点击群通知回执详情的样例: - -```objectivec -//群通知阅读回执详情 -- (void)groupMessageReadReceiptDetail:(EMMessage *)message groupId:(NSString *)groupId -{ - //EMReadReceiptMsgViewController用户自定义群通知阅读回执详情页(在 EaseIM APP 中有效) - EMReadReceiptMsgViewController *readReceiptControl = [[EMReadReceiptMsgViewController alloc] initWithMessage:message groupId:groupId]; - readReceiptControl.modalPresentationStyle = 0; - [self presentViewController:readReceiptControl animated:YES completion:nil]; -} -``` - -#### 输入区回调 - -当前会话输入扩展区数据模型组(UI 配置可在聊天视图配置数据模型中设置) - -```objectivec -/*! - @method - @brief 当前会话输入扩展区数据模型组 - @param defaultInputBarItems 默认功能数据模型组 (默认有序:相册,相机,位置,文件) - @param conversationType 当前会话类型:单聊,群聊,聊天室 - @result 返回一组输入区扩展功能 - */ - -- (NSMutableArray*)inputBarExtMenuItemArray: - (NSMutableArray*)defaultInputBarItems - conversationType:(EMConversationType)conversationType; -``` - -当前会话输入扩展区数据模型组回调示例(EaseIM APP 有效): - -```objectivec -- (NSMutableArray *)inputBarExtMenuItemArray:(NSMutableArray *)defaultInputBarItems conversationType:(EMConversationType)conversationType -{ -NSMutableArray *menuArray = [[NSMutableArray alloc]init]; -//相册 -[menuArray addObject:[defaultInputBarItems objectAtIndex:0]]; -//相机 -[menuArray addObject:[defaultInputBarItems objectAtIndex:1]]; -//音视频 -__weak typeof(self) weakself = self; -if (conversationType != EMConversationTypeChatRoom) { - EaseExtMenuModel *rtcMenu = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"video_conf"] funcDesc:@"音视频" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { - if (isExecuted) { - [weakself chatSealRtcAction]; - } - }]; - [menuArray addObject:rtcMenu]; -} -//位置 -[menuArray addObject:[defaultInputBarItems objectAtIndex:2]]; -//文件 -[menuArray addObject:[defaultInputBarItems objectAtIndex:3]]; -//群组回执 -if (conversationType == EMConversationTypeGroupChat) { - if ([[EMClient.sharedClient.groupManager getGroupSpecificationFromServerWithId:_conversation.conversationId error:nil].owner isEqualToString:EMClient.sharedClient.currentUsername]) { - EaseExtMenuModel *groupReadReceiptExtModel = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"pin_readReceipt"] funcDesc:@"群组回执" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { - //群组回执发送消息页 - [weakself groupReadReceiptAction]; - }]; - [menuArray addObject:groupReadReceiptExtModel]; - } -} -return menuArray; -} -``` - -#### 键盘输入变化回调 - -```objectivec -/*! - @method - @brief 输入区键盘输入变化回调 例:@群成员 - @result 返回键入内容是否有效 - */ -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; -``` - -输入区键盘回调示例(EaseIM APP 有效): - -```objectivec -//@群成员 -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text -{ - if (self.conversation.type == EMConversationTypeGroupChat) { - if ([text isEqualToString:@"@"]) { - [self _willInputAt:textView]; - } else if ([text isEqualToString:@""]) { - __block BOOL isAt = NO; - [textView.attributedText enumerateAttributesInRange:NSMakeRange(0, textView.text.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { - NSString *atUser = attrs[@"AtInfo"]; - if (atUser) { - if (textView.selectedRange.location == range.location + range.length) { - isAt = YES; - NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithAttributedString:textView.attributedText]; - [result deleteCharactersInRange:range]; - textView.attributedText = result; - if ([atUser isEqualToString:@"All"]) { - [self.chatController removeAtAll]; - } else { - [self.chatController removeAtUser:atUser]; - } - *stop = YES; - } - } - }]; - return !isAt; - } - } - return YES; -} -``` - -#### 输入框选中回调 - -```objectivec -/** - * 输入区选中范围变化回调 例:@群成员 - */ -- (void)textViewDidChangeSelection:(UITextView *)textView; -``` - -输入区选中范围变化回调示例(EaseIM APP 有效): - -```objectivec -- (void)textViewDidChangeSelection:(UITextView *)textView -{ - [textView.attributedText enumerateAttributesInRange:NSMakeRange(0, textView.text.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { - if (attrs[@"AtInfo"]) { - NSUInteger min = textView.selectedRange.location; - NSUInteger max = textView.selectedRange.location + textView.selectedRange.length; - if (min > range.location && min <= range.location + range.length) { - NSUInteger location = range.location + range.length; - NSUInteger length = 0; - if (textView.selectedRange.location + textView.selectedRange.length > location) { - length = textView.selectedRange.location + textView.selectedRange.length - location; - } - textView.selectedRange = NSMakeRange(location, length); - *stop = YES; - } else if (max > range.location && max <= range.location + range.length) { - NSUInteger location = min; - NSUInteger length = textView.selectedRange.length - (max - range.location - range.length); - textView.selectedRange = NSMakeRange(location, length); - *stop = YES; - } - } - }]; -} -``` - -#### 对方正在输入状态回调 - -对方正在输入状态回调(单聊有效) - -```objectivec -/** - 对方正在输入 -*/ -- (void)beginTyping; -``` - -对方结束输入回调(单聊有效) - -```objectivec -/** - 对方结束输入 -*/ -- (void)endTyping; -``` - -#### 消息长按事件回调 - -默认消息 cell 长按回调 - -```objectivec -/*! - @method - @brief 默认消息 cell 长按回调 - @param defaultLongPressItems 默认长按扩展区功能数据模型组(默认共有:复制,删除,撤回发送消息时 间距当前时间小于 2 分钟) - @param message 当前长按的消息 - @result 返回默认消息长按扩展功能组 - */ -- (NSMutableArray*)messageLongPressExtMenuItemArray:(NSMutableArray*)defaultLongPressItems message:(EMMessage*)message; -``` - -默认消息 cell 长按回调示例(EaseIM APP 有效): - -```objectivec -//添加转发消息 -- (NSMutableArray *)messageLongPressExtMenuItemArray:(NSMutableArray *)defaultLongPressItems message:(EMMessage *)message -{ - NSMutableArray *menuArray = [[NSMutableArray alloc]initWithArray:defaultLongPressItems]; - //转发 - __weak typeof(self) weakself = self; - if (message.body.type == EMMessageBodyTypeText || message.body.type == EMMessageBodyTypeImage || message.body.type == EMMessageBodyTypeLocation || message.body.type == EMMessageBodyTypeVideo) { - EaseExtMenuModel *forwardMenu = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"forward"] funcDesc:@"转发" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { - //用户可自定义转发 CallBack - if (isExecuted) { - [weakself forwardMenuItemAction:message]; - } - }]; - [menuArray addObject:forwardMenu]; - } - return menuArray; -} -``` - -#### 自定义 cell 长按回调 - -用户自定义消息 cell 长按事件回调 - -```objectivec -/*! - @method - @brief 当前所长按的 自定义 cell 的扩展区数据模型组 - @param defaultLongPressItems 默认长按扩展区功能数据模型组。默认共有:复制,删除,撤回(发送消息时 间距当前时间小于 2 分钟) - @param customCell 当前长按的自定义 cell - @result 返回默认消息长按扩展功能组 - */ -/** - * - * - * @param defaultLongPressItems 默认长按扩展区功能数据模型组 默认共有:复制,删除,撤回(发送消息时 间距当前时间小于 2 分钟)) - * @param customCell 当前长按的自定义 cell - */ -- (NSMutableArray*)customCellLongPressExtMenuItemArray:(NSMutableArray*)defaultLongPressItems customCell:(UITableViewCell*)customCell; -``` - -### 会话列表自定义功能扩展 - -实例化 `EaseConversationsViewController` 之后,可选择实现 `EaseConversationsViewControllerDelegate` 协议(会话列表回调代理),接收 `EaseConversationsViewController` 的回调并做进一步的自定义实现。 - -`EaseConversationsViewControllerDelegate` - -#### 自定义 cell - -通过实现会话列表回调获取自定义消息 cell 如果返回 nil 会显示默认;如果返回 cell 则会显示用户自定义 cell。 - -```objectivec -/*! - @method - @brief 获取消息自定义 cell - @discussion 返回 nil 显示默认 cell,否则显示用户自定义 cell - @param tableView 当前消息视图的 tableView - @param indexPath 当前所要展示 cell 的 indexPath - @result 返回用户自定义cell - */ -- (UITableViewCell *)easeTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; -``` - -#### 会话列表 cell 选中回调 - -```objectivec -/*! - @method - @brief 会话列表 cell 选中回调 - @param tableView 当前消息视图的 tableView - @param indexPath 当前所要展示 cell 的 indexPath - */ -- (void)easeTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; -``` - -会话列表 cell 选中回调示例(EaseIM APP 有效): - -```objectivec -- (void)easeTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - EaseConversationCell *cell = (EaseConversationCell*)[tableView cellForRowAtIndexPath:indexPath]; - //系统通知 - if ([cell.model.easeId isEqualToString:@"emsystemnotificationid"]) { - //此实例仅为 EaseIM APP 展示系统通知 - EMNotificationViewController *controller = [[EMNotificationViewController alloc] initWithStyle:UITableViewStylePlain]; - [self.navigationController pushViewController:controller animated:YES]; - return; - } - //跳转至聊天页 - [[NSNotificationCenter defaultCenter] postNotificationName:CHAT_PUSHVIEWCONTROLLER object:cell.model]; -} -``` - -#### 会话列表用户资料回调 - -```objectivec -/*! - @method - @brief 会话列表用户资料回调 - @discussion 可根据 conversationId 或 type 返回对应的用户资料数据集 - @param conversationId 当前会话列表 cell 所拥有的会话 ID - @param type 当前会话列表 cell 所拥有的会话类型 - */ -- (id)easeUserDelegateAtConversationId:(NSString *)conversationId - conversationType:(EMConversationType)type; -``` - -会话列表用户资料回调实例(EaseIM APP 有效) - -```objectivec -- (id)easeUserDelegateAtConversationId:(NSString *)conversationId conversationType:(EMConversationType)type -{ - //EMConversationUserDataModel 为自定义用户资料数据模型,实现 EaseUserDelegate 接口返回参数 - //@property (nonatomic, copy, readonly) NSString *easeId; // 环信 ID - //@property (nonatomic, copy, readonly) UIImage *defaultAvatar; // 默认头像显示 - EMConversationUserDataModel *userData = [[EMConversationUserDataModel alloc]initWithEaseId:conversationId conversationType:type]; - return userData; -} -``` - -#### 会话列表 cell 侧滑项回调 - -```objectivec -/*! - @method - @brief 会话列表 cell 侧滑项回调 - @param tableView 当前消息视图的 tableView - @param indexPath 当前所要侧滑 cell 的 indexPath - @param actions 返回侧滑项集合 - */ -- (NSArray *)easeTableView:(UITableView *)tableView - trailingSwipeActionsForRowAtIndexPath:(NSIndexPath *)indexPath - actions:(NSArray *)actions; -``` - -会话列表 cell 侧滑项回调示例(EaseIM APP 有效) - -```objectivec -- (NSArray *)easeTableView:(UITableView *)tableView trailingSwipeActionsForRowAtIndexPath:(NSIndexPath *)indexPath actions:(NSArray *)actions -{ - NSMutableArray *array = [[NSMutableArray alloc]init]; - __weak typeof(self) weakself = self; - UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal - title:@"删除" - handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) - { - //删除操作 - }]; - deleteAction.backgroundColor = [UIColor redColor]; - [array addObject:deleteAction]; - //返回的 actions 有序:删除,置顶 - [array addObject:actions[1]]; - return [array copy]; -} -``` - -### 通讯录自定义功能扩展 - -#### 获取用户联系人资料 - -获取用户自己的联系人列表填充到 EaseIMKit 通讯录中 - -```objectivec -- (void)setContacts:(NSArray * _Nonnull)contacts; -``` - -实例化 EaseContactsViewController 之后,可选择实现 EaseContactsViewControllerDelegate 协议(通讯录代理),接收 EaseContactsViewController 的回调并做进一步的自定义实现。 - -EaseConversationsViewControllerDelegate - -#### 即将刷新通讯录填充数据 - -```objectivec -- (void)willBeginRefresh; -``` - -即将刷新通讯录填充数据示例(EaseIM APP 有效): - -```objectivec -- (void)willBeginRefresh { - //从服务器获取当前登录账户的联系人列表 - [EMClient.sharedClient.contactManager getContactsFromServerWithCompletion:^(NSArray *aList, EMError *aError) { - if (!aError) { - self->_contacts = [aList mutableCopy]; - NSMutableArray *contacts = [NSMutableArray array]; - for (NSString *username in aList) { - EMContactModel *model = [[EMContactModel alloc] init]; - model.huanXinId = username; - [contacts addObject:model]; - } - //填充联系人列表集合到 EaseIMKit 通讯录实例中 - [self->_contactsVC setContacts:contacts]; - } - [self->_contactsVC endRefresh]; - }]; -} -``` - -#### 通讯录自定义 cell - -```objectivec -/*! -@method -@brief 获取通讯录自定义 cell -@discussion 返回 nil 显示默认 cell,否则显示用户自定义 cell -@param tableView 当前消息视图的 tableView -@param contact 当前所要展示 cell 所拥有的数据模型 -@result 返回用户自定义 cell -*/ -- (UITableViewCell *)easeTableView:(UITableView *)tableView cellForRowAtContactModel:(EaseContactModel *) contact; -``` - -#### 通讯录 cell 条目选中回调 - -```objectivec -/*! -@method -@brief 通讯录 cell 条目选中回调 -@param tableView 当前消息视图的 tableView -@param contact 当前所选中 cell 所拥有的数据模型 -@result 返回用户自定义 cell -*/ -- (void)easeTableView:(UITableView *)tableView didSelectRowAtContactModel:(EaseContactModel *) contact; -``` - -通讯录 cell 条目选中回调示例: - -```objectivec -- (void)easeTableView:(UITableView *)tableView didSelectRowAtContactModel:(EaseContactModel *)contact { - //跳转加好友页 - if ([contact.easeId isEqualToString:@"newFriend"]) { - EMInviteFriendViewController *controller = [[EMInviteFriendViewController alloc] init]; - [self.navigationController pushViewController:controller animated:YES]; - return; - } - //跳转群组列表页 - if ([contact.easeId isEqualToString:@"groupList"]) { - [[NSNotificationCenter defaultCenter] postNotificationName:GROUP_LIST_PUSHVIEWCONTROLLER object:@{NOTIF_NAVICONTROLLER:self.navigationController}]; - return; - } - //跳转聊天室列表页 - if ([contact.easeId isEqualToString:@"chatroomList"]) { - [[NSNotificationCenter defaultCenter] postNotificationName:CHATROOM_LIST_PUSHVIEWCONTROLLER object:@{NOTIF_NAVICONTROLLER:self.navigationController}]; - return; - } - //跳转好友个人资料页 - [self personData:contact.easeId]; -} -``` - -#### 通讯录 cell 侧滑回调 - -```objectivec -/*! - @method - @brief 会话列表 cell 侧滑项回调 - @param tableView 当前消息视图的 tableView - @param contact 当前所侧滑 cell 拥有的联系人数据 - @param actions 返回侧滑项集合 - */ -- (NSArray *)easeTableView:(UITableView *)tableView - trailingSwipeActionsForRowAtContactModel:(EaseContactModel *) contact - actions:(NSArray * __nullable)actions; -``` - -通讯录 cell 侧滑回调示例: - -```objectivec -- (NSArray *)easeTableView:(UITableView *)tableView trailingSwipeActionsForRowAtContactModel:(EaseContactModel *)contact actions:(NSArray *)actions -{ - //通讯录头部非联系人列表禁止侧滑 - if ([contact.easeId isEqualToString:@"newFriend"] || [contact.easeId isEqualToString:@"groupList"] || [contact.easeId isEqualToString:@"chatroomList"]) { - return nil; - } - __weak typeof(self) weakself = self; - UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive - title:@"删除" - handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) - { - //删除联系人操作 - }]; - return @[deleteAction]; -} -``` diff --git a/docs/uikit/android/overview.md b/docs/uikit/android/overview.md index 8013c93ef..572314c07 100644 --- a/docs/uikit/android/overview.md +++ b/docs/uikit/android/overview.md @@ -1 +1,1261 @@ -## UIKit 集成文档 +# EaseIMKit 使用指南 + + + +在您阅读此文档时,我们假定您已经具备了基础的 Android 应用开发经验,并能够理解相关基础概念。此文档是针对导入 EaseIMKit 库的快速集成文档,如果只是导入 SDK 集成使用,请参考 [环信即时通讯 IM Android 快速开始](quickstart.html)。 + +## 简介 + +EaseIMKit 是什么? + +**EaseIMKit** 是 EaseUI 的升级版,是基于环信 IM SDK 的一款 UI 组件库,它提供了一些通用的 UI 组件,例如‘会话列表’、‘聊天界面’和‘联系人列表’等,开发者可根据实际业务需求通过该组件库快速地搭建自定义 IM 应用。EaseIMKit 中的组件在实现 UI 功能的同时,调用 IM SDK 相应的接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 EaseIMKit 时只需关注自身业务或个性化扩展即可。 + +EaseIMKit 源码地址 + +- [EaseIMKit](https://github.com/easemob/easeui/tree/EaseIMKit) + +使用 EaseIMKIt 的环信 IM APP 源码地址: + +- [环信 IM](https://github.com/easemob/chat-android) + +## 导入 EaseIMKit + +### 开发环境要求 + +- Android Studio 3.2 以上 +- Gradle 4.6 以上 +- targetVersion 26 以上 +- Android SDK API 19 以上 +- Java JDK 1.8 以上 + +### 集成说明 + +EaseIMKit 支持 Gradle 接入和 Module 源码集成 + +#### Gradle 接入集成 + +:::notice 重大变动 +远程仓库统一由 JCenter 迁移到 `MavenCentral`,依赖库的域名由 “com.hyphenate” 修改为 “io.hyphenate”,详见 [环信即时通讯 IM Android 快速开始](quickstart.html)。 +::: + +```gradle +implementation 'io.hyphenate:ease-im-kit:xxx版本' +implementation 'io.hyphenate:hyphenate-chat:xxx版本' +``` + +**EaseIMKit 必须依赖环信 IM SDK,因而在使用 EaseIMKit 时必须同时添加环信 IM SDK 依赖。** + +:::notice + +1. IM SDK **3.8.0** 版本以后,远程依赖的 `artifactId` 修改为 `hyphenate-chat`,且该版本以后中不再包含音视频相关逻辑。 +2. IM SDK **3.8.0** 以下,远程依赖,包含音视频的 `artifactId` 为 `hyphenate-sdk`,不包含音视频的 `artifactId` 为 `hyphenate-sdk-lite`。如果想使用不包含音视频通话的 SDK,用 `implementation 'io.hyphenate:hyphenate-sdk-lite:xxx版本`'。 + +版本号参考 [Android SDK 更新日志](releasenote.html)。 +::: + +#### Module 源码集成 + +```gradle +implementation project(':ease-im-kit') +``` + +#### 依赖的第三方库 + +- Glide: 图片处理库,显示用户头像时用到。 +- BaiduLBS_Android.jar: 百度地图定位库。 + +#### 关于位置消息说明 + +EaseIMKit 中位置消息使用的是百度地图定位 jar 包,为了防止出现百度地图类冲突的问题,EaseIMKit 库打包时对百度地图定位 jar 包采用了只编译的方式,这就要求如果开发者想要使用位置消息,需要在自己的项目中添加百度地图定位的 jar 包及其 so 文件。 + +### 初始化 + +在 application 的 onCreate 下调用初始化 EaseIMKit 的方法。 + +:::notice +EaseIMKit 初始化里已包含 SDK 的初始化,不需要再去调用 SDK 的初始化。 +::: + +```java +public class DemoApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + + //EaseIM 初始化 + if(EaseIM.getInstance().init(context, options)){ + //在做打包混淆时,关闭 debug 模式,避免消耗不必要的资源 + EMClient.getInstance().setDebugMode(true); + //EaseIM 初始化成功之后再调用注册消息监听的代码 ... + } + } +} +``` + +## 快速搭建 + +EaseIMKit 封装了常用 IM 功能,提供了会话,聊天及联系人等基本的 fragment,旨在帮助开发者快速集成环信 SDK。 + +### 创建会话列表界面 + +EaseIMKit 提供了 EaseConversationListFragment,需要将其或者其子类添加到 Activity 中。开发者需要对刷新事件(新消息,删除消息,删除会话等)进行处理。 + +![img](@static/images/android/easeim.jpeg) + +:::notice +要实现自定义头像及昵称,请参考 [设置头像和昵称](userprofile.html#设置当前用户的属性)。 +::: + +### 创建聊天界面 + +EaseIMKit 提供了 EaseChatFragment,添加到 Activity 中并传递相应的参数即可用。 +必须向 EaseChatFragment 传递的参数为: + +- `conversationId`——会话 ID,单聊时指对方 ID,群聊和聊天室时指群和聊天室 ID; +- `chatType`——聊天类型,整型,分别为单聊(1)、群聊(2)和聊天室(3); + +可选传递参数为: + +- `history_msg_id`——消息 ID,用于查询历史记录时的定位消息 ID; +- `isRoaming`——是否开启漫游,布尔类型,用于标记是否优先从服务器拉取消息。 + +```java +public class ChatActivity extends BaseActivity { + private EaseChatFragment chatFragment; + + @Override + protected void onCreate(Bundle arg0) { + super.onCreate(arg0); + setContentView(R.layout.em_activity_chat); + //use EaseChatFratFragment + chatFragment = new EaseChatFragment(); + //pass parameters to chat fragment + chatFragment.setArguments(getIntent().getExtras()); + getSupportFragmentManager().beginTransaction().add(R.id.container, chatFragment).commit(); + } +} +``` + +![img](@static/images/android/easeim1.jpeg) + +### 添加联系人界面 + +EaseIMKit 提供了 EaseContactListFragment,添加其及其子类到 Activity 中。开发者需要对刷新事件(添加联系人,删除联系人等)进行处理。 + +![img](@static/images/android/easeim2.jpeg) + +## 设置样式 + +### 设置标题栏 + +EaseIMKit 提供了自定义的标题栏控件 EaseTitleBar。 + +![img](@static/images/android/easeim-titlebar.jpeg) + +标题栏除了做为 View 所具有的属性功能外,还可以设置标题的位置等。 + +xml 中设置如下: + +```xml + +``` + +其中 `titleBarDisplayHomeAsUpEnabled` 属性为设置返回按钮是否可见,设置标题位置可设置 `titleBarTitlePosition`,可选值为 `center`,`left` 和 `right`。 + +也可进行代码设置,如下: + +```java +EaseTitleBar titleBarMessage = findViewById(R.id.title_bar_message); +//设置右侧菜单图标 +titleBarMessage.setRightImageResource(R.drawable.chat_user_info); +//设置标题 +titleBarMessage.setTitle("标题"); +//设置标题位置 +titleBarMessage.setTitlePosition(EaseTitleBar.TitlePosition.Left); +//设置右侧菜单图标的点击事件 +titleBarMessage.setOnRightClickListener(this); +//设置返回按钮的点击事件 +titleBarMessage.setOnBackPressListener(this); +``` + +当然设置右侧菜单,您也可以通过 Android 提供的添加 `menu xml` 的形式实现。修改按钮图标,可以调用 `titleBarMenuResource` 属性进行设置。 + +### 设置会话列表 + +会话列表可以修改如下样式: + +- 头像:头像大小,头像形状(方形,带圆角的方形,圆形),描边 +- 标题、内容、时间等文字:字体大小,字体颜色 +- 未读消息:可设置是否展示,展示位置(左式和右式) + +在 `EaseConversationListFragment` 及其子类中可以直接获取到 `EaseConversationListLayout` 这个控件,然后通过这个控件进行设置。 + +代码如下: + +```java +//设置头像尺寸 +conversationListLayout.setAvatarSize(EaseCommonUtils.dip2px(mContext, 50)); +//设置头像样式:0 为默认,1 为圆形,2 为方形(设置方形时,需要配合设置 avatarRadius,默认的 avatarRadius 为 50dp) +conversationListLayout.setAvatarShapeType(2); +//设置圆角半径 +conversationListLayout.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5)); +//设置标题字体的颜色 +conversationListLayout.setTitleTextColor(ContextCompat.getColor(mContext, R.color.red)); +//设置是否隐藏未读消息数,默认为不隐藏 +conversationListLayout.hideUnreadDot(false); +//设置未读消息数展示位置,默认为左侧 +conversationListLayout.showUnreadDotPosition(EaseConversationSetStyle.UnreadDotPosition.LEFT); +``` + +效果如下图: + +![img](@static/images/android/easeim3.jpeg) +更多样式请参考 EaseContactListLayout 控件。 + +#### 增加长按菜单项 + +`EaseConversationListLayout` 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 +示例代码如下: + +```java +@Override +public void initView(Bundle savedInstanceState) { + super.initView(savedInstanceState); + ...... + conversationListLayout.addItemMenu(Menu.NONE, R.id.action_con_delete, 4, getString(R.string.ease_conversation_menu_delete)); +} + +...... + +@Override +public boolean onMenuItemClick(MenuItem item, int position) { + EaseConversationInfo info = conversationListLayout.getItem(position); + Object object = info.getInfo(); + if(object instanceof EMConversation) { + switch (item.getItemId()) { + case R.id.action_con_make_top : + // 将会话置顶 + conversationListLayout.makeConversationTop(position, info); + return true; + case R.id.action_con_cancel_top : + // 取消会话置顶 + conversationListLayout.cancelConversationTop(position, info); + return true; + case R.id.action_con_delete : + showDeleteDialog(position, info); + return true; + } + } + return super.onMenuItemClick(item, position); +} +``` + +### 设置聊天窗口 + +聊天窗口包括标题栏(不包含在 EaseChatFragment 中),聊天区,输入区及扩展展示区,如下图所示: + +![img](@static/images/android/easeim4.png) + +标题区 EaseTitleBar 的具体布局及实现不在 EaseIMKit 库的聊天控件及 fragment 中,需要你自己去实现。 +开发者可以在 EaseChatFragment 中获取到 EaseChatLayout 这个控件,然后通过这个控件进一步获取到获取其他控件,代码如下: + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//获取到菜单输入父控件 +EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); +//获取到菜单输入控件 +IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); +//获取到扩展区域控件 +IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu(); +//获取到表情区域控件 +IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu(); +``` + +#### 修改聊天列表样式 + +聊天列表区域可以修改背景,文字,气泡,是否展示昵称及聊天展示样式等,更多设置请参考 IChatMessageItemSet。 + +#### 修改聊天列表背景 + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//设置聊天列表背景 +messageListLayout.setBackground(new ColorDrawable(Color.parseColor("#DA5A4D"))); +``` + +效果如下图: + +![img](@static/images/android/easeim5.jpeg) + +#### 修改头像属性 + +开发者可以设置默认头像和头像形状。 + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//设置默认头像 +messageListLayout.setAvatarDefaultSrc(ContextCompat.getDrawable(mContext, R.drawable.ease_default_avatar)); +//设置头像形状:0 为默认,1 为圆形,2 为方形 +messageListLayout.setAvatarShapeType(1); +``` + +效果如下图: + +![img](@static/images/android/easeim6.jpeg) + +#### 修改聊天文本 + +开发者可以修改聊天文本的字体大小及字体颜色,发送方及接收方需保持一致。 + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//设置文本字体大小 +messageListLayout.setItemTextSize((int) EaseCommonUtils.sp2px(mContext, 18)); +//设置文本字体颜色 +messageListLayout.setItemTextColor(ContextCompat.getColor(mContext, R.color.red)); +``` + +效果如下图: + +![img](@static/images/android/easeim7.jpeg) + +#### 修改时间线样式 + +开发者可以修改时间线的背景,文字的大小及颜色。 + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//设置时间线的背景 +messageListLayout.setTimeBackground(ContextCompat.getDrawable(mContext, R.color.gray_normal)); +//设置时间线的文本大小 +messageListLayout.setTimeTextSize((int) EaseCommonUtils.sp2px(mContext, 18)); +//设置时间线的文本颜色 +messageListLayout.setTimeTextColor(ContextCompat.getColor(mContext, R.color.black)); +``` + +效果如下图: + +![img](@static/images/android/easeim8.jpeg) + +#### 修改聊天列表展示样式 + +开发者可以设置聊天列表的样式,发送方和接收方位于两侧还是位于一侧。 + +```java +//获取到聊天列表控件 +EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout(); +//设置聊天列表样式:两侧及均位于左侧 +messageListLayout.setItemShowType(EaseChatMessageListLayout.ShowType.LEFT); +``` + +效果如下图: + +![img](@static/images/android/easeim9.jpeg) + +#### 修改输入区样式 + +输入区控件为 EaseChatInputMenu,它由输入控件 EaseChatPrimaryMenu,扩展控件 EaseChatExtendMenu 和表情控件 EaseEmojiconMenu 组成。 + +```java +//获取到菜单输入父控件 +EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); +//获取到菜单输入控件 +IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); +//获取到扩展区域控件 +IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu(); +//获取到表情区域控件 +IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu(); +``` + +开发者可以修改菜单输入控件的样式,其有 5 种模式,即 `完整模式`,`不可用语音模式`,`不可用表情模式`,`不可用语音和表情模式` 和 `只有文本输入模式`。 + +```java +//获取到菜单输入父控件 +EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu(); +//获取到菜单输入控件 +IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu(); +if(primaryMenu != null) { + //设置菜单样式为不可用语音模式 + primaryMenu.setMenuShowType(EaseInputMenuStyle.DISABLE_VOICE); +} +``` + +效果(EaseInputMenuStyle.DISABLE_VOICE)如下图: + +![img](@static/images/android/easeim10.jpeg) + +其他样式为: + +完整模式(EaseInputMenuStyle.All): + +![img](@static/images/android/easeim11.jpeg) + +不可用表情模式(EaseInputMenuStyle.DISABLE_EMOJICON): + +![img](@static/images/android/easeim12.jpeg) + +不可用语音和表情模式(EaseInputMenuStyle.DISABLE_VOICE_EMOJICON): + +![img](@static/images/android/easeim13.jpeg) + +只有文本输入模式(EaseInputMenuStyle.ONLY_TEXT): + +![img](@static/images/android/easeim14.jpeg) + +#### 增加自定义消息类型及其布局 + +EaseIMKit 中已经为八种基本消息类型文本,表情,图片,视频,语音,文件,定位及 Custom 提供了基本的布局,ViewHolder 及 Delegate,开发者可以直接使用。但是这些类型很可能不能满足开发者的需求,那么就需要添加新的消息类型及其布局和逻辑。 +使用 EaseIMKit 只需按照以下 5 步即可快速添加自定义消息类型。 +下面我们以自定一个新的文本消息为例: + +1、新建 ChatTxtNewAdapterDelegate 继承 EaseMessageAdapterDelegate。 + +```java +public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate { + @Override + protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) { + return null; + } + + @Override + protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) { + return null; + } +} +``` + +2、新建 ChatRowTxtNew 继承 EaseChatRow 并实现相关方法 + +```java +public class ChatRowTxtNew extends EaseChatRow { + private TextView contentView; + + public ChatRowTxtNew(Context context, boolean isSender) { + super(context, isSender); + } + + @Override + protected void onInflateView() { + inflater.inflate(isSender ? R.layout.ease_row_sent_txt_new : R.layout.ease_row_received_txt_new, this); + } + + @Override + protected void onFindViewById() { + contentView = (TextView) findViewById(com.hyphenate.easeui.R.id.tv_chatcontent); + } + + @Override + protected void onSetUpView() { + EMTextMessageBody txtBody = (EMTextMessageBody) message.getBody(); + contentView.setText(txtBody.getMessage()); + } +} +``` + +布局文件以 R.layout.ease_row_sent_txt_new 为例,如下: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +其中 ID 为 bubble 控件外的控件为发送端布局的基本组成,需要开发者拷贝进去。一般而言,开发者需要添加的控件,放在 bubble 控件内即可。如上所示,开发者只需在 `onFindViewById()` 方法中找到自己添加的控件,并在 `onSetUpView()` 方法内处理展示逻辑即可。 + +3、新建 ChatTxtNewViewHolder 继承 EaseChatRowViewHolder 并实现相关方法 + +```java +public class ChatTxtNewViewHolder extends EaseChatRowViewHolder { + public ChatTxtNewViewHolder(@NonNull View itemView, MessageListItemClickListener itemClickListener) { + super(itemView, itemClickListener); + } + + @Override + public void onBubbleClick(EMMessage message) { + super.onBubbleClick(message); + // 实现相关点击方法 + } +} +``` + +自定义的 ChatTxtNewViewHolder 可以复写实现 onBubbleClick(EMMessage message) 及 onResendClick(EMMessage message) 方法。需要注意的是,如果在其他地方设置了 MessageListItemClickListener 监听,并将相应的方法实现并返回 true 以后,ViewHolder 中的这两个方法会被拦截。 自定义的 ViewHolder 可以复写 onDetachedFromWindow() 方法,可以做一些释放资源的处理。 自定义的 ViewHolder 需要根据消息的方向(发送发或者接收方)对消息进行处理,可以分别复写 handleSendMessage(final EMMessage message) 或者 handleReceiveMessage(EMMessage message)。 + +4、补全 ChatTxtNewAdapterDelegate 并重写 isForViewType 方法 + +```java +public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate { + @Override + public boolean isForViewType(EMMessage item, int position) { + return item.getType() == EMMessage.Type.TXT && item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false); + } + + @Override + protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) { + return new ChatRowTxtNew(parent.getContext(), isSender); + } + + @Override + protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) { + return new ChatTxtNewViewHolder(view, itemClickListener); + } +} +``` + +:::notice +(1)相同的消息类型(比如例子中消息类型是 EMMessage.Type.TXT)且通过标记判断类型时,在第 5 步注册对话类型时,应将该对话类型注册于基类的对话类型之前(即 ChatTxtNewAdapterDelegate 注册应在 EaseTextAdapterDelegate 之前)。 + +(2)对于 `item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false)` 可以理解为一种标记,在发送消息时设置,如下; +::: + +```java +/** + * 发送新文本消息 + * @param content + */ +public void sendTxtNewMessage(String content) { + EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername); + message.setAttribute(DemoConstant.MESSAGE_ATTR_IS_TXT_NEW, true); + EMClient.getInstance().chatManager().sendMessage(message); +} +``` + +其中 `DemoConstant.MESSAGE_ATTR_IS_TXT_NEW` 为定义的一个常量,通常为字符串类型。 + +5、注册 ChatTxtNewAdapterDelegate 对话类型(通过 EaseMessageTypeSetManager 进行注册) + +```java +/** + * 注册对话类型 + */ +private void registerConversationType() { + EaseMessageTypeSetManager.getInstance() + .addConversationType(EaseExpressionAdapterDelegate.class) //自定义表情 + .addConversationType(EaseFileAdapterDelegate.class) //文件 + ...... + .addConversationType(ChatTxtNewAdapterDelegate.class) //新文本消息 + .setDefaultConversionType(EaseTextAdapterDelegate.class); //文本 +} +``` + +需要开发者注意的是,自定义的消息类型需要注册到 EaseMessageTypeSetManager 中,具体用法可以参考环信 App 中的 [DemoHelper](https://github.com/easemob/chat-android/blob/master/app/src/main/java/com/hyphenate/easeim/DemoHelper.java) 类中的 `registerConversationType()` 方法,并在初始化时调用 `registerConversationType()` 方法。 + +开发者在注册消息类型时,一定要在最后设置默认项(即调用 `setDefaultConversionType()`),并建议将 `EaseTextAdapterDelegate` 设置默认项。如果没有符合的消息类型,EaseIMKit 会选择默认的消息类型进行展示(注:需要展示的消息类型也需要符合默认消息的消息类型,否则会造成 EMMessageBody 强转时报错)。 + +#### 增加长按菜单项 + +EaseChatLayout 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 + +示例代码如下: + +```java +@Override +public void initView(Bundle savedInstanceState) { + super.initView(savedInstanceState); + ...... + // 构建菜单项 model 并通过 EaseChatLayout 添加 + MenuItemBean itemMenu = new MenuItemBean(0, R.id.action_chat_forward, 11, getString(R.string.action_forward)); + itemMenu.setResourceId(R.drawable.ease_chat_item_menu_forward); + chatLayout.addItemMenu(itemMenu ); +} + +...... + +@Override +public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { + switch (item.getItemId()) { + case R.id.action_chat_forward : + // 增加实现逻辑,并返回 true + return true; + } + return false; +} +``` + +#### 增加更多扩展功能 + +EaseIMKit 提供了常用的一些扩展功能,比如发送图片,发送文件,发送定位等,但是实际开发中可能满足不了开发者的需求,EaseIMKit 提供了增加扩展功能的接口,以实现发送短视频消息、音视频通话等。 + +以添加视频通话和音视频会议为例,示例代码如下: + +```java +private void resetChatExtendMenu() { + // 获取到扩展功能控件 + IChatExtendMenu chatExtendMenu = chatLayout.getChatInputMenu().getChatExtendMenu(); + if(chatExtendMenu == null) { + return; + } + // 清除所有的扩展项 + chatExtendMenu.clear(); + // 添加自己需要的扩展功能 + chatExtendMenu.registerMenuItem(R.string.attach_picture, R.drawable.ease_chat_image_selector, EaseChatExtendMenu.ITEM_PICTURE); + chatExtendMenu.registerMenuItem(R.string.attach_take_pic, R.drawable.ease_chat_takepic_selector, EaseChatExtendMenu.ITEM_TAKE_PICTURE); + // 根据消息类型添加不同的扩展功能 + if(chatType == EaseConstant.CHATTYPE_SINGLE){ + chatExtendMenu.registerMenuItem(R.string.attach_media_call, R.drawable.em_chat_video_call_selector, ITEM_VIDEO_CALL); + } + if (chatType == EaseConstant.CHATTYPE_GROUP) { // 音视频会议 + chatExtendMenu.registerMenuItem(R.string.voice_and_video_conference, R.drawable.em_chat_video_call_selector, ITEM_CONFERENCE_CALL); + } +} + +...... + +@Override +public void onChatExtendMenuItemClick(View view, int itemId) { + super.onChatExtendMenuItemClick(view, itemId); + // 只需实现不满足需求或者开发者自己添加的扩展功能 + switch (itemId) { + case ITEM_VIDEO_CALL: + // 实现条目点击事件 + break; + case ITEM_CONFERENCE_CALL: + // 实现条目点击事件 + break; + } +} +``` + +#### 增加自定义表情 + +EaseIMKit 也提供了增加自定义表情的接口,开发者可仿照 EmojiconExampleGroupData 进行定义类型的表情类,然后调用相应接口加入即可。 + +代码如下: + +```java +// 添加扩展表情 +chatLayout.getChatInputMenu().getEmojiconMenu().addEmojiconGroup(EmojiconExampleGroupData.getData()); +``` + +### 设置联系人列表 + +通讯录列表界面可以设置如下样式: + +- 展示模式:分为简洁模式和常规模式 +- 条目:设置条目背景,条目高度及 header 背景 +- 头像:设置默认头像,头像样式及头像大小等 + +在 EaseContactListFragment 及其子类中可以直接获取到 EaseContactLayout 这个控件,然后通过这个控件进行设置。 + +代码如下: + +```java +// 获取列表控件 +EaseContactListLayout contactList = contactLayout.getContactList(); +// 设置条目高度 +contactList.setItemHeight((int) EaseCommonUtils.dip2px(mContext, 80)); +// 设置条目背景 +contactList.setItemBackGround(ContextCompat.getDrawable(mContext, R.color.gray)); +// 设置头像样式:0 为默认,1 为圆形,2 为方形(设置方形时,需要配合设置 avatarRadius,默认的 avatarRadius 为 50dp) +contactList.setAvatarShapeType(2); +// 设置头像圆角 +contactList.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5)); +// 设置 header 背景 +contactList.setHeaderBackGround(ContextCompat.getDrawable(mContext, R.color.white)); +``` + +效果如图: + +![img](@static/images/android/easeim15.jpeg) + +设置简洁模式 + +```java +//设置为简洁模式 +contactLayout.showSimple(); +``` + +效果如图: + +![img](@static/images/android/easeim16.jpeg) + +#### 增加长按菜单项 + +EaseContactListLayout 提供了增加菜单项的 API,开发者可方便的增加更多的菜单功能。 + +示例代码如下: + +```java +// 通过增加 `OnPopupMenuPreShowListener` 监听,并在 `onMenuPreShow` 中增加菜单项更简单 +@Override +public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) { + super.onMenuPreShow(menuHelper, position); + // 增加需要的菜单项,其中传入的第三项 order 决定了菜单项的位置 + menuHelper.addItemMenu(1, R.id.action_friend_block, 2, getString(R.string.em_friends_move_into_the_blacklist_new)); + menuHelper.addItemMenu(1, R.id.action_friend_delete, 1, getString(R.string.ease_friends_delete_the_contact)); +} + +...... + +@Override +public boolean onMenuItemClick(MenuItem item, int position) { + EaseUser user = contactLayout.getContactList().getItem(position); + switch (item.getItemId()) { + case R.id.action_friend_block : + // 增加处理逻辑并返回 `true` + return true; + case R.id.action_friend_delete: + // 增加处理逻辑并返回 `true` + return true; + } + return super.onMenuItemClick(item, position); +} +``` + +#### 增加头布局 + +EaseIMKit 默认是不再通讯录列表之前增加头布局的,但是内部预设了添加头布局的逻辑,开发者可通过 EaseContactListLayout 提供的 API 快速的增加一个或者多个头布局。 + +示例代码如下: + +```java +/** + * 添加头布局 + */ +public void addHeader() { + // 增加多个头布局 + contactLayout.getContactList().addCustomItem(CUSTOM_NEW_CHAT, R.drawable.em_friends_new_chat, getString(R.string.em_friends_new_chat)); + contactLayout.getContactList().addCustomItem(CUSTOM_GROUP_LIST, R.drawable.em_friends_group_chat, getString(R.string.em_friends_group_chat)); + contactLayout.getContactList().addCustomItem(CUSTOM_CHAT_ROOM_LIST, R.drawable.em_friends_chat_room, getString(R.string.em_friends_chat_room)); +} + +...... + +@Override +public void initListener() { + super.initListener(); + ...... + contactLayout.getContactList().setOnCustomItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + EaseContactCustomBean item = contactLayout.getContactList().getCustomAdapter().getItem(position); + switch (item.getId()) { + case CUSTOM_NEW_CHAT : + // 增加实现逻辑 + break; + case CUSTOM_GROUP_LIST : + // 增加实现逻辑 + break; + case CUSTOM_CHAT_ROOM_LIST : + // 增加实现逻辑 + break; + } + } + }); +} +``` + +### 设置用户头像和昵称属性 + +#### 设置头像和昵称 + +环信 IM SDK 不做用户信息存储,如果用户想要展示自定义的头像及昵称,可以通过 EaseUserProfileProvider 进行提供。 + +首先需要在合适的时机去设置 EaseUserProfileProvider,例如: + +```java +EaseIM.getInstance().setUserProvider(new EaseUserProfileProvider() { + @Override + public EaseUser getUser(String username) { + //根据 username,从数据库中或者内存中取出之前保存的用户信息,如从数据库中取出的用户对象为 DemoUserBean + DemoUserBean bean = getUserFromDbOrMemery(username); + EaseUser user = new EaseUser(username); + ...... + //设置用户昵称 + user.setNickname(bean.getNickname()); + //设置头像地址 + user.setAvatar(bean.getAvatar()); + //最后返回构建的 EaseUser 对象 + return user; + } +}); +``` + +EaseIMKit 中会话列表,聊天列表及联系人列表,内部已经添加 EaseUserProfileProvider 的判断,当展示数据时优先从 EaseUserProfileProvider 获取头像和昵称数据,如果有则展示,如果没有头像采用默认头像,昵称展示为环信 ID。 + +:::notice 建议方案 +开发者先将相关用户信息从服务器中获取并存储到数据库中,在 getUser(String username) 方法调用时,从数据库中根据 username(环信 ID)取出相应的用户数据,生成 EaseUser 对象 user,并给 user 赋值 nickname 及 avatar 属性,最后返回这个 user 即可。 +::: + +#### 统一设置头像样式 + +EaseIMKit 提供了 `EaseAvatarOptions` 这个类用于全局配置头像的样式,包括形状,圆角半径,描边宽度及描边颜色。会话,聊天及联系人中已经增加了对于 EaseAvatarOptions 的支持。 + +示例代码如下: + +```java +//设置头像配置属性 +EaseIM.getInstance().setAvatarOptions(getAvatarOptions()); +...... +/** + * 统一配置头像 + * @return + */ +private EaseAvatarOptions getAvatarOptions() { + EaseAvatarOptions avatarOptions = new EaseAvatarOptions(); + //设置头像形状为圆形,1 代表圆形,2 代表方形 + avatarOptions.setAvatarShape(1); + return avatarOptions; +} +``` + +使用时可以直接调用 EaseUserUtils 中的 `setUserAvatarStyle(EaseImageView imageView)` 方法即可设置。 + +## 事件处理 + +EaseIMKit 还帮助开发者实现了一系列的事件监听接口,比如条目的点击事件,条目的长按事件,菜单项的点击事件等。 + +### 会话列表 + +#### 条目点击事件 + +开发者如果使用 `EaseConversationListFragment` 及其子类,可以重写 `onItemClick(View view, int position)` 方法即可。 + +代码如下: + +```java +@Override +public void onItemClick(View view, int position) { + super.onItemClick(view, position); + //添加点击事件实现逻辑 +} +``` + +开发者如果直接使用的 `EaseConversationListLayout` 控件,则可通过该控件设置条目的点击事件。 + +代码如下: + +```java +conversationListLayout.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + //添加点击事件实现逻辑 + } +}); +``` + +#### 长按事件及弹出菜单点击事件 + +`EaseConversationListLayout` 中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。 + +如果开发者使用的是 `EaseConversationListFragment` 及其子类,则直接重写 `onMenuItemClick(MenuItem item, int position)` 即可。 + +代码如下: + +```java +@Override +public boolean onMenuItemClick(MenuItem item, int position) { + //添加具体的点击事件实现逻辑,并返回 true + return super.onMenuItemClick(item, position); +} +``` + +开发者如果直接使用的 EaseConversationListLayout 控件,则可通过该控件设置弹出菜单的点击事件。 + +代码如下: + +```java +conversationListLayout.setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item, int position) { + //添加具体的点击事件实现逻辑,并返回 true + return false; + } +}); +``` + +如果开发者需要自己实现弹出菜单,通过 EaseConversationListLayout 控件添加条目的长按监听并实现即可。 + +代码如下: + +```java +conversationListLayout.setOnItemLongClickListener(new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(View view, int position) { + //添加弹出菜单的逻辑,并返回 true + return false; + } +}); +``` + +### 聊天区域 + +#### 聊天列表事件 + +开发者如果使用的是 EaseChatFragment 及其子类,则可以根据需要重写相关的事件方法即可。聊天列表中的常用监听事件均封装到了 OnChatLayoutListener 接口中,EaseChatFragment 已经设置了该监听。 + +`OnChatLayoutListener` 中有如下事件监听: + +```java +public interface OnChatLayoutListener { + /** + * 点击消息 bubble 区域 + * @param message + * @return + */ + boolean onBubbleClick(EMMessage message); + + /** + * 长按消息 bubble 区域 + * @param v + * @param message + * @return + */ + boolean onBubbleLongClick(View v, EMMessage message); + + /** + * 点击头像 + * @param username + */ + void onUserAvatarClick(String username); + + /** + * 长按头像 + * @param username + */ + void onUserAvatarLongClick(String username); + + /** + * 条目点击 + * @param view + * @param itemId + */ + void onChatExtendMenuItemClick(View view, int itemId); + + /** + * EditText 文本变化监听 + * @param s + * @param start + * @param before + * @param count + */ + void onTextChanged(CharSequence s, int start, int before, int count); + + /** + * 聊天中错误信息 + * @param code + * @param errorMsg + */ + void onChatError(int code, String errorMsg); + + /** + * 用于监听其他人正在数据事件 + * @param action 输入事件 TypingBegin为开始 TypingEnd 为结束 + */ + default void onOtherTyping(String action){} + +} +``` + +如果开发者使用的是 EaseChatLayout 控件,则通过该控件实现 OnChatLayoutListener 接口即可。 + +#### 长按事件及弹出菜单点击事件 + +EaseChatLayout 中已经实现了一套默认的长按弹出菜单逻辑,并对默认的菜单项进行了处理,如果开发者对默认菜单项有其他实现需求,只需实现弹出菜单的点击事件即可。 + +如果开发者使用的是 EaseChatFragment 及其子类,则直接重写 onMenuItemClick(MenuItemBean item, EMMessage message) 即可。 + +代码如下: + +```java +@Override +public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { + //添加菜单条目点击事件实现逻辑,并返回 true + //返回 true 表示不采用默认的实现逻辑 + return false; +} +``` + +如果开发者需要在弹出菜单展示前对菜单项进行处理,重写 `onPreMenu(EasePopupWindowHelper helper, EMMessage message)` 并处理即可。 + +示例代码如下: + +```java +@Override +public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) { + // 默认两分钟后,即不可撤回 + if(System.currentTimeMillis() - message.getMsgTime() > 2 * 60 * 1000) { + helper.findItemVisible(R.id.action_chat_recall, false); + } + EMMessage.Type type = message.getType(); + helper.findItemVisible(R.id.action_chat_forward, false); + switch (type) { + case TXT: + if(!message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VIDEO_CALL, false) + && !message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VOICE_CALL, false)) { + helper.findItemVisible(R.id.action_chat_forward, true); + } + break; + case IMAGE: + helper.findItemVisible(R.id.action_chat_forward, true); + break; + } + + if(chatType == DemoConstant.CHATTYPE_CHATROOM) { + helper.findItemVisible(R.id.action_chat_forward, true); + } +} +``` + +开发者如果直接使用的 EaseChatLayout 控件,则可通过该控件设置弹出菜单的点击事件。 + +代码如下: + +```java +chatLayout.setOnPopupWindowItemClickListener(new OnMenuChangeListener() { + @Override + public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) { + // 菜单预处理逻辑 + } + @Override + public boolean onMenuItemClick(MenuItemBean item, EMMessage message) { + // 菜单项点击事件,如果默认实现不满足,则自己实现并返回 true。 + return false; + } +}); +``` + +如果开发者需要自己实现弹出菜单,通过 EaseChatLayout 控件找到 EaseChatMessageListLayout 并添加条目的长按监听并实现即可。 + +代码如下: + +```java +chatLayout.getChatMessageListLayout().setOnItemLongClickListener(new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(View view, int position) { + //添加弹出菜单的逻辑,并返回 true + return false; + } +}); +``` + +### 通讯录列表 + +#### 条目点击事件 + +开发者如果使用 EaseContactListFragment 及其子类,可以重写 `onItemClick(View view, int position)` 方法即可。 + +代码如下: + +```java +@Override +public void onItemClick(View view, int position) { + super.onItemClick(view, position); + //添加点击事件实现逻辑 +} +``` + +开发者如果直接使用的 EaseContactLayout 控件,则可通过该控件找到 EaseContactListLayout 并设置条目的点击事件。 + +代码如下: + +```java +contactLayout.getContactList().setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + //添加点击事件实现逻辑 + } +}); +``` + +#### 长按事件及弹出菜单点击事件 + +EaseContactListLayout 中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。 + +如果开发者使用的是 EaseContactListFragment 及其子类,则直接重写 `onMenuItemClick(MenuItem item, int position)` 即可。 + +代码如下: + +```java +@Override +public boolean onMenuItemClick(MenuItem item, int position) { + // 添加具体的点击事件实现逻辑,并返回 'true' + return super.onMenuItemClick(item, position); +} +``` + +如果开发者需要在弹出菜单展示前对菜单项进行处理,重写 `onMenuPreShow(EasePopupWindowHelper helper, EMMessage message)` 并处理即可。 + +开发者如果直接使用的 EaseContactLayout 控件,则可通过该控件找到 EaseContactListLayout 并设置弹出菜单的点击事件。 + +代码如下: + +```java +contactLayout.getContactList().setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item, int position) { + //添加具体的点击事件实现逻辑,并返回 true + return false; + } +}); +``` + +相应的菜单项预处理,需要通过 EaseContactListLayout 设置菜单预处理监听事件。 代码如下: + +```java +contactLayout.getContactList().setOnPopupMenuPreShowListener(new OnPopupMenuPreShowListener() { + @Override + public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) { + //菜单预处理逻辑 + } +}); +``` + +如果开发者需要自己实现弹出菜单,通过 EaseContactListLayout 控件添加条目的长按监听并实现即可。 + +代码如下: + +```java +contactLayout.getContactList().setOnItemLongClickListener(new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(View view, int position) { + //添加弹出菜单的逻辑,并返回 true + return false; + } +}); +``` + +## 功能扩展 + +### 系统消息 + +EaseIMKit 中 EaseConversationListLayout 已经封装了 IM 通知的展示逻辑,但是需要开发者将 IM 的通知封装成系统消息并保存到本地数据库。为了方便开发者封装成符合 EaseIMKit 能够使用的系统消息,EaseIMKit 中提供了 EaseSystemMsgManager 管理类,开发者可通过该管理类,方便的封装及更新系统消息。 + +EaseIMKit 可处理的系统消息有如下要求: + +```java +// 设置为文本消息 +EMMessage emMessage = EMMessage.createReceiveMessage(EMMessage.Type.TXT); +// 设置 from 为固定的 "em_system" +emMessage.setFrom(EaseConstant.DEFAULT_SYSTEM_MESSAGE_ID); +emMessage.setMsgId(UUID.randomUUID().toString()); +emMessage.setStatus(EMMessage.Status.SUCCESS); +``` + +当然 EaseSystemMsgManager 管理类已经做了默认处理,开发者只需传入文本内容(会话列表中展示的内容)及扩展内容 ext(Map)即可。 示例如下: + +```java +@Override +public void onFriendRequestDeclined(String username) { + EMLog.i("ChatContactListener", "onFriendRequestDeclined"); + Map ext = EaseSystemMsgManager.getInstance().createMsgExt(); + ext.put(DemoConstant.SYSTEM_MESSAGE_FROM, username); + ext.put(DemoConstant.SYSTEM_MESSAGE_STATUS, InviteMessageStatus.BEREFUSED.name()); + EMMessage message = EaseSystemMsgManager.getInstance().createMessage(PushAndMessageHelper.getSystemMessage(ext), ext); + ...... +} +``` + +同时 EaseConversationListLayout 提供了是否展示系统消息的 API,showSystemMessage(boolean show) 用于控制是否展示系统消息。 diff --git a/docs/uikit/flutter/key_function_chat_page.md b/docs/uikit/flutter/key_function_chat_page.md new file mode 100644 index 000000000..a2be054a7 --- /dev/null +++ b/docs/uikit/flutter/key_function_chat_page.md @@ -0,0 +1,334 @@ +# 聊天页面 + +用户可以在聊天页面中进行单聊、群聊或聊天室聊天。该页面分为消息列表和消息输入区域。 + +## 创建聊天界面 + +flutter_chat_uikit 提供了 `ChatMessagesView`,添加到 `build` 中并传入相应的参数即可用。 + +| 参数 | 类型 | 是否必需 | 描述 | +| :------------- | :-----| :----- | :-------- | +| `conversation` | `EMConversation` | 是 | `ChatMessagesView` 对应的会话对象。 | +| `inputBarTextEditingController` | | 否 | 输入框中的 `TextField` 对应的 Controller。| +| `background` | | 否 | `ChatMessagesView` 的背景图。| +| `inputBar` | | 否 | 输入框组件。 如不设置,默认使用 `ChatInputBar`。| +| `onTap`| | 否 | 消息气泡点击事件。 | +| `onBubbleLongPress` | | 否 | 消息气泡长按事件。| +| `onBubbleDoubleTap`| | 否 | 消息气泡双击事件。| +| `avatarBuilder` | | 否 | 头像 widget builder。| +| `nicknameBuilder` | | 否 | 昵称 Widget builder。| +| `itemBuilder`| | 否 | 消息气泡 Widget builder。| +| `moreItems` | | 否 | 长按消息气泡后显示的操作项。如果在 `onBubbleLongPress` 中返回 `null`,将使用 `moreItems`。默认显示三个操作:复制、删除和消息撤回。 | +| `messageListViewController` | | 否 | 消息列表 Controller, 详见 `ChatMessageListController`。| +| `willSendMessage` | | 否 | 消息将要发送的事件,需要返回一个 `EMMessage` 对象。 | +| `onError` | |否|错误回调,如权限错误等。| +| `enableScrollBar` | | 否 | 是否启用滚动条。默认启用。 | +| `needDismissInputWidget` | | 否 |用于取消输入 Widget 的回调。如果使用自定义输入 Widget,则在接收回调时取消输入 Widget,例如,通过调用 `FocusNode.unfocus`。详见 `ChatInputBar`。 | +| `inputBarMoreActionsOnTap` | | 否 | 单击输入框旁边的 `+` 的回调。需要返回 `ChatBottomSheetItems` 列表。 | + +```dart +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + ), + ), + ); + } +} +``` + + + +## 自定义实现 + +### 设置主题颜色 + +可以通过修改主题中的属性来改变消息页面中的颜色和字体。 + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + builder: (context, child) { + // ChatUIKit 需要在你使用 `flutter_chat_uikit` widget 的根节点上。 + return ChatUIKit( + // ChatUIKitTheme 主题。 + theme: ChatUIKitTheme(), + child: child!, + ); + }, + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} +``` + +参数详情如下表所示: + +| 参数 | 类型 | 是否必需 | 描述 | +| :------------- | :-----| :----- | :-------- | +| `badgeColor` | `Color` | 否 | 未读数角标颜色。 | +| `badgeBorderColor` | `Color` | 否 | 未读数 `border` 颜色。| +| `badgeTextStyle` | `TextStyle` | 否 | 未读数角标的字体。| +| `sendVoiceItemIconColor` | `Color` | 否 | 发送的语音消息 `bubble` 中图标的颜色。| +| `receiveVoiceItemIconColor` | `Color` | 否 | 接收的语音消息 `bubble` 中图标的颜色。| +| `sendBubbleColor` | `Color` | 否 | 发送消息的气泡颜色。| +| `receiveBubbleColor` | `Color` | 否 | 收到消息的气泡颜色。| +| `sendTextStyle` | `TextStyle` | 否 | 发送文字消息的字体。| +| `receiveTextStyle` | `TextStyle` | 否 | 接收文字消息的字体。| +| `conversationListItemTitleStyle` | `TextStyle` | 否 | 会话列表条目标题的字体。| +| `conversationListItemSubTitleStyle` | `TextStyle` | 否 | 会话列表条目副标题的字体。| +| `conversationListItemTsStyle` | `TextStyle` | 否 | 会话列表条目时间的字体。| +| `messagesListItemTsStyle` | `TextStyle` | 否 | 消息列表条目中时间的字体。| +| `inputWidgetSendBtnColor` | `Color` | 否 | 表情键盘中发送按钮的颜色。| +| `inputWidgetSendBtnStyle` | `TextStyle` | 否 | 表情键盘中发送按钮的字体。| + +### 添加头像 + +通过设置 `ChatMessagesView` 中的 `avatarBuilder` 实现自定义头像的功能。 + +```dart +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + avatarBuilder: (context, userId) { + // 返回你要显示的头像 Widget。 + return Container( + width: 30, + height: 30, + color: Colors.red, + ); + }, + ), + ), + ); + } +} +``` + +效果如下图所示: + + + +### 添加昵称 + +通过设置 `ChatMessagesView` 中的 `nicknameBuilder` 实现自定义昵称的功能。 + +```dart +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + // 返回你要显示的昵称 widget。 + nicknameBuilder: (context, userId) { + return Text(userId); + }, + ), + ), + ); + } +} +``` + +效果如下图所示: + + + +### 添加气泡点击事件 + +通过设置 `ChatMessagesView` 中的 `onTap` 实现自定义点击功能。 + +```dart +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + // 条目点击事件。 + onTap: (context, message) { + bubbleClicked(message); + return true; + }, + ), + ), + ); + } + + void bubbleClicked(EMMessage message) { + SnackBar bar = const SnackBar( + content: Text('气泡被点击'), + duration: Duration(milliseconds: 1000), + ); + ScaffoldMessenger.of(context).showSnackBar(bar); + } +} +``` + +效果如下图所示: + + + +### 自定义消息气泡样式 + +通过设置 `ChatMessagesView` 中的 `itemBuilder` 实现自定义气泡样式。 + +```dart +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + itemBuilder: (context, model) { + if (model.message.body.type == MessageType.TXT) { + // 自定义消息气泡。 + return CustomTextItemWidget( + model: model, + onTap: (context, message) { + bubbleClicked(message); + return true; + }, + ); + } + }, + ), + ), + ); + } + + void bubbleClicked(EMMessage message) { + SnackBar bar = const SnackBar( + content: Text('气泡被点击'), + duration: Duration(milliseconds: 1000), + ); + ScaffoldMessenger.of(context).showSnackBar(bar); + } +} + + +//自定义消息气泡。 +class CustomTextItemWidget extends ChatMessageListItem { + const CustomTextItemWidget({super.key, required super.model, super.onTap}); + + @override + Widget build(BuildContext context) { + EMTextMessageBody body = model.message.body as EMTextMessageBody; + + Widget content = Text( + body.content, + style: const TextStyle( + color: Colors.black, + fontSize: 50, + fontWeight: FontWeight.w400, + ), + ); + return getBubbleWidget(content); + } +} +``` + +效果如下图所示: + + + +### 自定义输入框样式 + +通过设置 `ChatMessagesView` 中的 `inputBar` 实现自定义输入框,同时用过实现 `ChatMessageListController` 发送信息。 + +```dart +class _MessagesPageState extends State { + late ChatMessageListController _msgController; + final TextEditingController _textController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + @override + void initState() { + super.initState(); + _msgController = ChatMessageListController(widget.conversation); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.conversation.id)), + body: SafeArea( + // UIKit 中的聊天页面。 + child: ChatMessagesView( + conversation: widget.conversation, + messageListViewController: _msgController, + inputBar: customInputWidget(), + needDismissInputWidget: () { + _focusNode.unfocus(); + }, + ), + ), + ); + } + + // 自定义输入 Widget。 + Widget customInputWidget() { + return SizedBox( + height: 50, + child: Row( + children: [ + Expanded( + child: TextField( + focusNode: _focusNode, + controller: _textController, + ), + ), + ElevatedButton( + onPressed: () { + final msg = EMMessage.createTxtSendMessage( + targetId: widget.conversation.id, + content: _textController.text); + _textController.text = ''; + _msgController.sendMessage(msg); + }, + child: const Text('Send')) + ], + ), + ); + } +} +``` + +效果如下图所示: + + diff --git a/docs/uikit/flutter/key_function_conversation_list.md b/docs/uikit/flutter/key_function_conversation_list.md new file mode 100644 index 000000000..e57cc194a --- /dev/null +++ b/docs/uikit/flutter/key_function_conversation_list.md @@ -0,0 +1,157 @@ +# 会话列表页面 + +会话列表页面展示了用户当前所有的单聊、群聊和聊天室会话。你可以创建会话列表界面,并设置自定义头像、昵称和界面的展示样式,添加自定义点击事件。 + +## 创建会话列表界面 + +flutter_chat_uikit 提供了 `ChatConversationsView`,添加到 `build` 中并传入相应的参数。 + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_chat_uikit/flutter_chat_uikit.dart'; + +class ConversationsPage extends StatefulWidget { + const ConversationsPage({super.key}); + + @override + State createState() => _ConversationsPageState(); +} + +class _ConversationsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ConversationPage'), + ), + body: ChatConversationsView(), + ); + } +} +``` + +效果图如下所示: + + + +参数详情如下表所示: + +通过 `ChatConversationsView`,你可以快速显示和管理当前会话。 + +| 属性 | 描述 | +| :-------------- | :----- | +| `controller` | `ChatConversationsView` Controller。 | +| `itemBuilder` | 会话列表条目 builder。若需进行自定义,会返回 widget。 | +| `avatarBuilder` | 头像 builder。若该属性未实现或你返回了 `null`,会使用默认头像。| +| `nicknameBuilder` | 昵称 builder。若你未设置该属性或返回了 `null`,会显示会话 ID。 | +| `onItemTap` | 会话列表条目的点击事件的回调。 | +| `backgroundWidgetWhenListEmpty` | 列表为空时为背景 widget。 | + +## 自定义实现 + +### 设置会话头像 + +通过设置 `ChatConversationsView` 中的 `avatarBuilder` 实现自定义头像的功能。 + +```dart +class _ConversationsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ConversationPage'), + ), + body: ChatConversationsView( + avatarBuilder: (context, conversation) { + return const CircleAvatar( + child: Text('Avatar'), + ); + }, + ), + ); + } +} +``` + +效果如下图所示: + + + +### 设置会话名称 + +通过设置 `ChatConversationsView` 中的 `nicknameBuilder` 实现显示昵称功能。 + +```dart +class _ConversationsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ConversationPage'), + ), + body: ChatConversationsView( + nicknameBuilder: (context, conversation) { + return Text('nickname'); + }, + ), + ); + } +} +``` + +效果如下图所示: + + + +### 自定义显示样式 + +通过设置 `ChatConversationsView` 中的 `itemBuilder` 实现自定义显示会话。 + +```dart +class _ConversationsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ConversationPage'), + ), + body: ChatConversationsView( + itemBuilder: (context, index, conversation) { + return ListTile( + title: Text(conversation.id), + subtitle: Text('subtitle'), + ); + }, + ), + ); + } +} +``` +效果如下图所示: + + + +### 自定义点击事件 + +通过设置 `ChatConversationsView` 中的 `onItemTap` 实现会话列表点击。 + +```dart +class _ConversationsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ConversationPage'), + ), + body: ChatConversationsView( + onItemTap: (conversation) { + print(conversation.id); + }, + ), + ); + } +} +``` +效果如下图所示: + + diff --git a/docs/uikit/flutter/overview.md b/docs/uikit/flutter/overview.md index 8013c93ef..9c901181e 100644 --- a/docs/uikit/flutter/overview.md +++ b/docs/uikit/flutter/overview.md @@ -1 +1,30 @@ -## UIKit 集成文档 +# 概述 + +flutter_chat_uikit 是基于环信 IM SDK 的一款 UI 组件库,提供通用的 UI 组件,例如会话列表、聊天界面。利用该组件库,开发者可根据实际业务需求快速地搭建自定义 IM 应用。flutter_chat_uikit 中的组件在实现 UI 功能的同时,调用 IM SDK 相关接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 flutter_chat_uikit 时只需关注自身业务或个性化扩展即可。 + +查看 flutter_chat_uikit 源码,请点击[这里](https://github.com/easemob/flutter_chat_uikit)。 + +## 功能 + +目前,flutter_chat_uikit 提供以下功能: + +- 快速搭建聊天页面。 +- 快速搭建会话列表页面。 + +flutter_chat_uikit 在单聊、群聊和聊天室会话中提供下列特性,提升聊天体验。 + +| 特性 | 描述 | +| :-------------- | :----------------------------- | +| 文本消息 | 发送和接收文本消息。 | +| 图片消息 | 发送和接收图片消息。 | +| 语音消息 | 发送和接收语音消息。 | +| 文件消息 | 发送和接收文件消息。 | +| 已读回执 | 提示接收方的已读状态。该功能仅适用于单聊会话。 | +| 未读消息数 | 显示收到消息的未读数。 | +| 消息表情回复 | 回复消息表情。 | +| 消息删除 | 删除本地消息。 | +| 消息撤回 | 在指定时间内对已发送消息撤回。 | + + + + diff --git a/docs/uikit/flutter/quickstart.md b/docs/uikit/flutter/quickstart.md new file mode 100644 index 000000000..00bc54d0c --- /dev/null +++ b/docs/uikit/flutter/quickstart.md @@ -0,0 +1,157 @@ +# 快速开始 + +利用 flutter_chat_uikit 提供的 UI 组件,你可以轻松实现应用内的聊天。flutter_chat_uikit 支持单聊、群聊和聊天室会话。本文介绍如何实现在单聊会话中发送消息。 + +   + + +## 前提条件 + +集成 flutter_chat_uikit 前,你的开发环境需要满足以下条件: + +1. 有效的环信即时通讯 IM 开发者账号,创建应用并获取 App Key。 +2. 在[环信控制台](https://console.easemob.com/index)[创建两个用户用于聊天](/product/enable_and_configure_IM.html#创建-im-用户)。 + +### Android 平台 + +- Flutter 2.5.0 或以上版本 +- Dart 2.19.1 或以上版本 +- macOS 或 Windows 系统 +- 支持 JDK 1.8 或以上版本的 Android Studio 4.0 或以上版本 +- 运行 Android SDK API 级别 21 或以上的 Android 模拟器或真机 + +### iOS 平台 + +- Flutter 2.5.0 或以上版本 +- Dart 2.19.1 或以上版本 +- macOS +- 安装有 Xcode 命令行工具的 Xcode 12.4 或以上版本 +- CocoaPods +- 运行 iOS 10.0 或以上版本的 iOS 模拟器或真机 + +## 所需权限 + +### Android + +```xml + + + + + + + +``` + +### iOS + +| 键 | 值 | +| :------------ | :------- | +| `Privacy - Microphone Usage Description` | 麦克风权限 | +| `Privacy - Camera Usage Description` | 摄像头权限 | +| `Privacy - Photo Library Usage Description` | 相册权限 | + +## 防止代码混淆 + +安卓中需要配置防止代码混淆,在 `proguard-rules.pro` 文件中添加以下代码: + +``` +-keep class com.hyphenate.** {*;} +-dontwarn com.hyphenate.** +``` + +## 发送第一条消息 + +### 第一步 集成 flutter_chat_uikit + +flutter_chat_uikit 支持 pub.dev 接入和本地源码集成。 + +- pub.dev 接入集成: + +```dart +flutter pub add flutter_chat_uikit +flutter pub get +``` + +- 本地源码集成: + +```dart +dependencies: + flutter_chat_uikit: + path: `<#uikit path#>` +``` + +flutter_chat_uikit 使用了以下第三方依赖库: + +```dart +dependencies: + intl: ^0.18.0 + image_picker: ^0.8.6+4 + file_picker: ^4.6.1 + record: ^4.4.4 + audioplayers: ^3.0.1 + im_flutter_sdk: ^4.1.0 +``` + +### 第二步 初始化即时通讯 IM SDK + +在 app 的 `main` 下调用 SDK 初始化方法。 + +:::notice +flutter_chat_uikit 不包含 IM SDK 的初始化和登录,使用时确保已完成 SDK 初始化和登录。 +::: + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + final options = ChatOptions( + appKey: <#Your AppKey#>, + ); + await EMClient.getInstance.init(options); + runApp(const MyApp()); +} +``` + +#### 第三步 创建聊天界面 + +flutter_chat_uikit 提供了 `ChatMessagesView`,添加到 `build` 中,传入必填参数 `conversation` 及所需的可选参数即可。详见[聊天界面参数描述](key_function_chat_page.html#创建聊天界面)。 + +1. 通过 IM SDK 获取一个本地会话。 + +```dart +// targetId: 接收方的用户 ID。单聊为对方的用户 ID,群聊为群组 ID,聊天室为聊天 室 ID。 +// type: 单聊为 `EMConversationType.Chat`, 群聊为 `EMConversationType.GroupChat`, 聊天室为 `EMConversationType.ChatRoom`。 +EMConversation conversation = await EMClient.getInstance.chatManager.getConversation(targetId, type: EMConversationType.Chat); +``` + +2. 将会话传递给 `ChatMessagesView`。 + +```dart +class MessagesPage extends StatefulWidget { + const MessagesPage(this.conversation, {super.key}); + + final EMConversation conversation; + + @override + State createState() => _MessagesPageState(); +} + +class _MessagesPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.conversation.id), + ), + body: SafeArea( + // UIKit 中的聊天界面。 + child: ChatMessagesView( + conversation: widget.conversation, + ), + ), + ); + } +} +``` + + diff --git a/docs/uikit/ios/overview.md b/docs/uikit/ios/overview.md index 8013c93ef..b3e5e2317 100644 --- a/docs/uikit/ios/overview.md +++ b/docs/uikit/ios/overview.md @@ -1 +1,1058 @@ -## UIKit 集成文档 +# EaseIMKit 使用指南 + + + +仍在使用旧版 EaseUI 的用户可参考旧版 EaseUI 的文档,旧版已不再维护。 旧版文档地址:[EaseUI 集成](https://docs-im.easemob.com/im/ios/other/easeui) + +## 简介 + +EaseIMKit 是什么? + +EaseIMKit 是基于环信 IM SDK 的一款 UI 组件库,它提供了一些通用的 UI 组件,例如 ‘会话列表’、‘聊天界面’ 和 ‘联系人列表’ 等,开发者可根据实际业务需求通过该组件库快速地搭建自定义 IM 应用。EaseIMKit 中的组件在实现 UI 功能的同时,调用 IM SDK 相应的接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 EaseIMKit 时只需关注自身业务或个性化扩展即可。 + +EaseIMKit 源码地址 + +- [EaseIMKit 工程](https://github.com/easemob/easeui_ios/tree/EaseIMKit) + +使用 EaseIMKit 环信 IM App 地址: + +- [环信 IM](https://github.com/easemob/chat-ios) + +## 导入 + +### 支持系统版本要求 + +- EaseIMKit 支持 iOS 10.0 及以上系统版本 +- EaseIM 支持 iOS 11.0 及以上系统版本 + +## 快速集成 + +### pod 方式集成 + +``` +pod 'EaseIMKit' +``` + +需要在 `Podfile` 文件加上 `use_frameworks!` + +:::notice +EaseIMKit: 对应 HyphenateChat SDK(HyphenateChat 不包含实时音视频,EaseIMKit 不包含音视频,EaseIM 依赖音视频库 EaseCallKit 后实现了音视频功能) + +EaseIMKit 中包含了拍照,发语音,发图片,发视频,发位置,发文件的功能,需要使用录音,摄像头,相册,地理位置的权限。需要在您项目的 info.plist 中添加对应权限。 +::: + +### 源码集成 + +- [Github 下载源码](https://github.com/easemob/easeui_ios.git) + +执行命令:`git clone https://github.com/easemob/easeui_ios.git` + +- 创建 `Podfile` 文件并添加 EaseIMKit 源码依赖 + + 1. 项目 `Podfile` 文件 和 `ProjectName.xcodeproj` 文件应在同一目录,如下图所示: + + ![img](@static/images/ios/easeimkit1.png) + + Podfile 文件示例: + + ``` + platform :ios, '11.0' + + source 'https://github.com/CocoaPods/Specs.git' + + target 'ProjectName' do + pod 'EaseIMKit', :path => "../EaseUI/EaseIMKit" + pod 'HyphenateChat', '3.8.4' + end + ``` + + 2. EaseIMKit path 路径(如:pod 'EaseIMKit', :path ⇒ “../EaseUI/EaseIMKit”)需指向 EaseIMKit.podspec 文件所在目录,如下图所示: + + ![img](@static/images/ios/easeimkit2.png) + +- 项目集成本地 EaseIMKit 源码 + + 1. 终端 cd 到 Podfile 文件所在目录,执行 pod install 命令在项目中安装 EaseIMKit 本地源码 + 2. 执行完成后,则在 Xcode 项目目录 Pods/Development Pods/ 可找到 EaseIMKit 源码,如下图所示: + + ![img](@static/images/ios/easeimkit3.png) + + 3. 可对源码进行符合自己项目目标的自定义修改 + +- 成为社区贡献者 + +如果在源码自定义过程中有任何通用自定义都可以给我们 [Github 仓库](https://github.com/easemob/easeui_ios.git) 提交代码成为社区贡献者! + +### 初始化 + +第 1 步:引入相关头文件 + +```objectivec +#import +``` + +第 2 步:在在工程的 AppDelegate 中的以下方法中调用 EaseIMKitManager 的初始化方法一并初始化环信 SDK。(注: 此方法不需要重复调用) + +```objectivec +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + EMOptions *options = [EMOptions optionsWithAppkey:@"您的APPKEY"]; + [EaseIMKitManager initWithEMOptions:options]; + //再做登录操作,可准确接收到系统通知 + return YES; +} +``` + +EaseIMKitManager 主要包含系统通知(好友申请,群邀请/申请)回调,未读总数回调等方法。 用户需要注册自己的类到 EaseIMKitManagerDelegate 才可收到未读总数变化回调。 用户需要添加 EaseIMKitSystemNotiDelegate 代理才可收到系统通知相关回调。 + +系统通知相关回调接口,系统通知构造成了一个本地会话,每个新通知构造为一条本地消息。 系统通知所构造的会话的 conversationId 为 @“emsystemnotificationid”。 + +#### 是否需要系统通知 + +```objectivec +/*! + @method + @brief 是否需要系统通知:好友/群 申请等 + @discussion 默认需要系统通知 + @result 返回: YES:需要; NO:不需要; + */ +- (BOOL)isNeedsSystemNotification; +``` + +#### 收到系统通知所展示信息回调接口 + +```objectivec +/*! + @method + @brief 收到请求返回展示信息 + @param conversationId 会话 ID。 + * 对于单聊类型,会话 ID 同时也是对方用户的名称。 + * 对于群聊类型,会话 ID 同时也是对方群组的 ID,并不同于群组的名称。 + * 对于聊天室类型,会话 ID 同时也是聊天室的 ID,并不同于聊天室的名称。 + + @param requestUser 请求方。 + @param reason 请求原因。 + @result 返回系统通知所展示信息。 + */ +- (NSString *)requestDidReceiveShowMessage:(NSString *)conversationId requestUser:(NSString *)requestUser reason:(EaseIMKitCallBackReason)reason; +``` + +对于返回值处理:空字符串不产生新 message / nil:使用默认实现 / 非空字符串且长度大于 0,使用该字符串产生新 message + +#### 收到系统通知扩展信息回调接口 + +```objectivec +/*! + @method + @brief 收到请求返回扩展信息 + @param conversationId 会话 ID。 + * 对于单聊类型,会话 ID 同时也是对方用户的名称。 + * 对于群聊类型,会话 ID 同时也是对方群组的 ID,并不同于群组的名称。 + * 对于聊天室类型,会话 ID 同时也是聊天室的 ID,并不同于聊天室的名称。 + + @param requestUser 请求方。 + @param reason 请求原因。 + @result 返回系统通知携带扩展信息字典。 + */ +- (NSDictionary *)requestDidReceiveConversationExt:(NSString *)conversationId requestUser:(NSString *)requestUser reason:(EaseIMKitCallBackReason)reason; +``` + +#### 未读总数变化回调接口 + +```objectivec +/*! + @method + @brief 会话未读总数变化。 + @param unreadCount 当前会话列表的总未读数。 + */ +- (void)conversationsUnreadCountUpdate:(NSInteger)unreadCount; +``` + +## 快速搭建 + +### 聊天会话快速搭建 + +导入 EaseIMKit 头文件 + +```objectivec +#import +``` + +EaseIMKit 提供现成的聊天会话 ViewController,可以通过创建 EaseChatViewController 对象实例,嵌入进自己的聊天控制器方式(参考 EaseIM 中 EMChatViewController)实现对 EaseIMKit 聊天会话的集成。 创建聊天会话页实例,需传递用户‘环信 ID’或‘群 ID’ ,会话类型(EMConversationType)以及必须传入聊天视图配置数据模型 EaseChatViewModel 实例。 + +```objectivec +EaseChatViewModel *viewModel = [[EaseChatViewModel alloc]init]; +EaseChatViewController *chatController = [EaseChatViewController initWithConversationId:@"custom" + conversationType:EMConversationTypeChat + chatViewModel:viewModel]; +[self addChildViewController:chatController]; +[self.view addSubview:chatController.view]; +chatController.view.frame = self.view.bounds; +``` + +聊天控制器嵌入自己的聊天页后还需传入消息列表 messageList 以供 EaseChatViewController 展示使用 + +```objectivec +//isScrollBottom 是否滑动到页面底部 +- (void)loadData:(BOOL)isScrollBottom +{ + __weak typeof(self) weakself = self; + void (^block)(NSArray *aMessages, EMError *aError) = ^(NSArray *aMessages, EMError *aError) { + dispatch_async(dispatch_get_main_queue(), ^{ + //给 EaseChatViewController 传入消息列表messageList + //isScrollBottom 是否滑动到页面底部 isInsertBottom 消息数据集是否插入消息列表底部 + //在传入消息列表之前不需要对列表做任何处理,只需传入列表数据即可,否则会刷新 UI 失败 + [weakself.chatController refreshTableViewWithData:aMessages isInsertBottom:NO isScrollBottom:isScrollBottom]; + }); + }; + [self.conversation loadMessagesStartFromId:nil count:50 searchDirection:EMMessageSearchDirectionUp completion:block]; +} +``` + +### 会话列表快速搭建 + +导入 EaseIMKit 头文件 + +```objectivec +#import +``` + +在自己聊天控制器内可嵌入 EaseIMKit 的会话列表页,创建会话列表实例,实例化会话列表必须传入会话列表视图数据配置模型 EaseConversationViewModel 实例。 + +```objectivec +EaseConversationViewModel *viewModel = [[EaseConversationViewModel alloc] init]; + +EaseConversationsViewController *easeConvsVC = [[EaseConversationsViewController alloc] initWithModel:viewModel]; +easeConvsVC.delegate = self; +[self addChildViewController:easeConvsVC]; +[self.view addSubview:easeConvsVC.view]; +[easeConvsVC.view mas_makeConstraints:^(MASConstraintMaker *make) { + make.size.equalTo(self.view); +}]; +``` + +### 通讯录快速搭建 + +导入 EaseIMKit 头文件 + +```objectivec +#import +``` + +在自己聊天控制器内可嵌入 EaseIMKit 的会话列表页,创建通讯录实例,必须传入通讯录视图数据模型 EaseContactsViewModel 实例以构建通讯录 UI 界面。 + +```objectivec +EaseContactsViewModel *model = [[EaseContactsViewModel alloc] init]; +EaseContactsViewController *contactsVC = [[EaseContactsViewController alloc] initWithModel:model]; +//通讯录头部功能区(加好友/群聊/聊天室 入口) +contactsVC.customHeaderItems = [self items]; +contactsVC.delegate = self; +[self addChildViewController:contactsVC]; +[self.view addSubview:contactsVC.view]; +[contactsVC.view mas_makeConstraints:^(MASConstraintMaker *make) { + make.size.equalTo(self.view); +}]; +//设置通讯录头部功能区实例(EaseIM APP 有效): +- (NSArray *)items { + EMContactModel *newFriends = [[EMContactModel alloc] init]; + newFriends.huanXinId = @"newFriend"; + newFriends.nickname = @"新的好友"; + newFriends.avatar = [UIImage imageNamed:@"newFriend"]; + EMContactModel *groups = [[EMContactModel alloc] init]; + groups.huanXinId = @"groupList"; + groups.nickname = @"群聊"; + groups.avatar = [UIImage imageNamed:@"groupchat"]; + EMContactModel *chatooms = [[EMContactModel alloc] init]; + chatooms.huanXinId = @"chatroomList"; + chatooms.nickname = @"聊天室"; + chatooms.avatar = [UIImage imageNamed:@"chatroom"]; + return (NSArray *)@[newFriends, groups, chatooms]; +} +``` + +## 设置样式 + +### 聊天会话样式配置 + +聊天会话可配置参数如下: + +```objectivec +@property (nonatomic, strong) UIColor *chatViewBgColor; //聊天页背景色 +@property (nonatomic, strong) UIColor *chatBarBgColor; //输入区背景色 +@property (nonatomic, strong) EaseExtFuncModel *extFuncModel; //输入区扩展功能数据模型 +@property (nonatomic, strong) UIColor *msgTimeItemBgColor; //时间线背景色 +@property (nonatomic, strong) UIColor *msgTimeItemFontColor; //时间线字体颜色 +@property (nonatomic, strong) UIImage *receiveBubbleBgPicture; //所接收信息的气泡 +@property (nonatomic, strong) UIImage *sendBubbleBgPicture; //所发送信息的气泡 +@property (nonatomic, strong) UIColor *contentFontColor; //文本消息字体颜色 +@property (nonatomic) CGFloat contentFontSize; //文本消息字体大小 +@property (nonatomic) UIEdgeInsets bubbleBgEdgeInset; //消息气泡背景图保护区域 +@property (nonatomic) EaseInputBarStyle inputBarStyle; //输入区类型:(全部功能,无语音,无表情,无表情和语音,纯文本) +@property (nonatomic) EaseAvatarStyle avatarStyle; //头像风格 +@property (nonatomic) CGFloat avatarCornerRadius; //头像圆角大小 默认:0 (只有头像类型是圆角生效) +//仅群聊可设置 +@property (nonatomic) EaseAlignmentStyle msgAlignmentStyle; //聊天区域消息排列方式 +``` + +其中参数:EaseExtFuncModel 输入区扩展功能数据配置模型(聊天会话页相机,相册,音视频等区域)内含可配参数: + +```objectivec +@property (nonatomic, strong) UIColor *iconBgColor;//图标所在 view 背景色 +@property (nonatomic, strong) UIColor *viewBgColor;//视图背景色 +@property (nonatomic, strong) UIColor *fontColor;//字体颜色 +@property (nonatomic, assign) CGFloat fontSize;//字体大小 +@property (nonatomic, assign) CGSize collectionViewSize;//视图尺寸 +``` + +其中参数:inputBarStyle(输入区)包含五种类型: + +```objectivec +typedef NS_ENUM(NSInteger, EaseInputBarStyle) { + EaseInputBarStyleAll = 1, //全部功能 + EaseInputBarStyleNoAudio, //无语音 + EaseInputBarStyleNoEmoji, //无表情 + EaseInputBarStyleNoAudioAndEmoji, //无表情和语音 + EaseInputBarStyleOnlyText, //纯文本 +}; +``` + +其中参数:EaseAlignmentStyle (消息排列方式,仅群聊可生效)包含两种类型 + +```objectivec +typedef enum { + EaseAlignmentNormal = 1, //左右排列 + EaseAlignmentlLeft, //居左排列 +} EaseAlignmentStyle; +``` + +实例化的聊天控制器可通过重置视图 UI 配置模型刷新页面 + +```objectivec +//重置聊天控制器 +- (void)resetChatVCWithViewModel:(EaseChatViewModel *)viewModel; +``` + +聊天页背景色,输入区颜色配置示例: + +![背景色,输入区颜色](@static/images/ios/easeimkit4.png) + +聊天会话输入区类型参数配置示例: + +![全部功能,语音不可用,表情不可用,语音和表情不可用,纯文本](@static/images/ios/easeimkit5.png) + +输入区扩展功能参数配置示例: + +![输入区扩展](@static/images/ios/easeimkit6.jpeg) + +聊天会话群聊消息同左排列,时间线背景色,时间字体颜色配置示例: + +![群聊消息同左排列,时间线背景色,时间字体颜色](@static/images/ios/easeimkit7.jpeg) + +### 会话列表样式配置 + +会话列表可配置参数如下: + +```objectivec +@property (nonatomic) EaseAvatarStyle avatarType; // 头像样式 +@property (nonatomic, strong) UIImage *defaultAvatarImage; // 默认头像 +@property (nonatomic) CGSize avatarSize; // 头像尺寸 +@property (nonatomic) UIEdgeInsets avatarEdgeInsets; // 头像位置 +@property (nonatomic, strong) UIFont *nameLabelFont; // 昵称字体 +@property (nonatomic, strong) UIColor *nameLabelColor; // 昵称颜色 +@property (nonatomic) UIEdgeInsets nameLabelEdgeInsets; // 昵称位置 +@property (nonatomic, strong) UIFont *detailLabelFont; // 详情字体 +@property (nonatomic, strong) UIColor *detailLabelColor; // 详情字色 +@property (nonatomic) UIEdgeInsets detailLabelEdgeInsets; // 详情位置 +@property (nonatomic, strong) UIFont *timeLabelFont; // 时间字体 +@property (nonatomic, strong) UIColor *timeLabelColor; // 时间字色 +@property (nonatomic) UIEdgeInsets timeLabelEdgeInsets; // 时间位置 +@property (nonatomic) EMUnReadCountViewPosition badgeLabelPosition; // 未读数显示风格 +@property (nonatomic, strong) UIFont *badgeLabelFont; // 未读数字体 +@property (nonatomic, strong) UIColor *badgeLabelTitleColor; // 未读数字色 +@property (nonatomic, strong) UIColor *badgeLabelBgColor; // 未读数背景色 +@property (nonatomic) CGFloat badgeLabelHeight; // 未读数角标高度 +@property (nonatomic) CGVector badgeLabelCenterVector; // 未读数中心位置偏移 +@property (nonatomic) int badgeMaxNum; // 未读数显示上限, 超过上限后会显示 xx+ +``` + +会话列表以及联系人列表共用其父类可配置参数如下: + +```objectivec +@property (nonatomic) BOOL canRefresh; // 是否可下拉刷新 +@property (nonatomic, strong) UIView *bgView; // tableView 背景图 +@property (nonatomic, strong) UIColor *cellBgColor; // UITableViewCell 背景色 +@property (nonatomic) UIEdgeInsets cellSeparatorInset; // UITableViewCell 下划线位置 +@property (nonatomic, strong) UIColor *cellSeparatorColor; // UITableViewCell 下划线颜色 +``` + +### 通讯录样式配置 + +通讯录可配置参数如下: + +```objectivec +@property (nonatomic) EaseAvatarStyle avatarType; // 头像样式 +@property (nonatomic, strong) UIImage *defaultAvatarImage; // 默认头像 +@property (nonatomic) CGSize avatarSize; // 头像尺寸 +@property (nonatomic) UIEdgeInsets avatarEdgeInsets; // 头像位置 +@property (nonatomic, strong) UIFont *nameLabelFont; // 昵称字体 +@property (nonatomic, strong) UIColor *nameLabelColor; // 昵称颜色 +@property (nonatomic) UIEdgeInsets nameLabelEdgeInsets; // 昵称位置 +@property (nonatomic, strong) UIFont *sectionTitleFont; // section title 字体 +@property (nonatomic, strong) UIColor *sectionTitleColor; // section title 颜色 +@property (nonatomic, strong) UIColor *sectionTitleBgColor; // section title 背景 +@property (nonatomic) UIEdgeInsets sectionTitleEdgeInsets; // section title 位置 +// section title label 高度 (section title view = sectionTitleLabelHeight + sectionTitleEdgeInsets.top + sectionTitleEdgeInsets.bottom) +@property (nonatomic) CGFloat sectionTitleLabelHeight; +``` + +以及通讯录和会话列表共用的参数配置 + +```objectivec +@property (nonatomic) BOOL canRefresh; // 是否可下拉刷新 +@property (nonatomic, strong) UIView *bgView; // tableView 背景图 +@property (nonatomic, strong) UIColor *cellBgColor; // UITableViewCell 背景色 +@property (nonatomic) UIEdgeInsets cellSeparatorInset; // UITableViewCell 下划线位置 +@property (nonatomic, strong) UIColor *cellSeparatorColor; // UITableViewCell 下划线颜色 +``` + +通讯录添加头部功能区:新的好友,群聊,聊天室示意图: + +![头部功能区:新的好友,群聊,聊天室以及联系人列表](@static/images/ios/easeimkit8.png) + +## 自定义功能扩展 + +### 聊天会话自定义功能扩展 + +实例化 EaseChatViewController 之后,可选择实现 EaseChatViewControllerDelegate 协议(聊天控制器回调代理),接收 EaseChatViewController 的回调并做进一步的自定义实现。 + +EaseChatViewControllerDelegate + +#### 下拉加载更多消息回调 + +下拉加载更多消息回调(可得到当前第一条消息 ID 作为下次加载更多消息的参考 ID;当前消息列表) + +```objectivec +/** + * 下拉加载更多消息回调 + * + * @param firstMessageId 第一条消息 ID + * @param messageList 当前消息列表 + */ +- (void)loadMoreMessageData:(NSString *)firstMessageId currentMessageList:(NSArray *)messageList; +``` + +#### 自定义 cell + +通过实现聊天控制回调获取自定义消息 cell,根据 messageModel,用户自己判断是否显示自定义消息 cell。如果返回 nil 会显示默认;如果返回 cell 会显示用户自定义消息 cell。 + +```objectivec +/*! + @method + @brief 获取消息自定义 cell + @discussion 用户根据 messageModel 判断是否显示自定义 cell。返回 nil 显示默认 cell,否则显示用户自定义 cell + @param tableView 当前消息视图的 tableView + @param messageModel 消息的数据模型 + @result 返回用户自定义 cell + */ +- (UITableViewCell *)cellForItem:(UITableView *)tableView messageModel:(EaseMessageModel *)messageModel; +``` + +具体创建自定义 Cell 的示例: + +```objectivec +//自定义通话记录cell +- (UITableViewCell *)cellForItem:(UITableView *)tableView messageModel:(EaseMessageModel *)messageModel +{ + //当消息是单聊音视频通话消息时,EaseIM 自定义通话记录 cell + if (messageModel.type == EMMessageTypePictMixText) { + EMMessageCell *cell = [[EMMessageCell alloc]initWithDirection:messageModel.direction type:messageModel.type]; + cell.model = messageModel; + cell.delegate = self; + return cell; + } + return nil; +} +``` + +通过自定义 cell 展示单聊音视频通话记录的效果图: + +![自定义 cell 展示单聊音视频通话记录](@static/images/ios/easeimkit9.png) + +#### 选中消息的回调 + +选中消息的回调(EaseIMKit 没有对于自定义 cell 的选中事件回调,需用户自定义实现选中响应) + +```objectivec +/*! + @method + @brief 消息点击事件 + @discussion 用户根据 messageModel 判断,是否自定义处理消息选中时间。返回 NO 为自定义处理,返回 YES 为默认处理 + @param message 当前点击的消息 + @param userData 当前点击的消息携带的用户资料 + @result 是否采用默认事件处理 +*/ +- (BOOL)didSelectMessageItem:(EMMessage*)message userData:(id)userData; +``` + +EaseIMKit 选中是消息气泡,自定义 cell 的点击事件需自定义实现,例:EaseIM 单聊通话记录 cell 点击事件再次发起通话 + +```objectivec +- (void)messageCellDidSelected:(EMMessageCell *)aCell +{ + //使用‘通知’的方式发起通话,其中所定义的宏仅在 EaseIM APP 中生效 + NSString *callType = nil; + NSDictionary *dic = aCell.model.message.ext; + if ([[dic objectForKey:EMCOMMUNICATE_TYPE] isEqualToString:EMCOMMUNICATE_TYPE_VOICE]) + callType = EMCOMMUNICATE_TYPE_VOICE; + if ([[dic objectForKey:EMCOMMUNICATE_TYPE] isEqualToString:EMCOMMUNICATE_TYPE_VIDEO]) + callType = EMCOMMUNICATE_TYPE_VIDEO; + if ([callType isEqualToString:EMCOMMUNICATE_TYPE_VOICE]) + [[NSNotificationCenter defaultCenter] postNotificationName:CALL_MAKE1V1 object:@{CALL_CHATTER:aCell.model.message.conversationId, CALL_TYPE:@(EMCallTypeVoice)}]; + if ([callType isEqualToString:EMCOMMUNICATE_TYPE_VIDEO]) + [[NSNotificationCenter defaultCenter] postNotificationName:CALL_MAKE1V1 object:@{CALL_CHATTER:aCell.model.message.conversationId, CALL_TYPE:@(EMCallTypeVideo)}]; +} +``` + +#### 用户资料回调 + +用户资料回调(头像昵称等) + +```objectivec +/*! + @method + @brief 返回用户资料 + @discussion 用户根据 huanxinID 在自己的用户体系中匹配对应的用户资料,并返回相应的信息,否则默认实现 + @param huanxinID 环信 ID + @result 返回与当前环信 ID 关联的用户资料 + */ +- (id)userData:(NSString*)huanxinID; +``` + +#### 用户选中头像的回调 + +```objectivec +/*! + @method + @brief 点击消息头像 + @discussion 获取用户点击头像回调 + @param userData 当前点击的头像所指向的用户资料 + */ + +- (void)avatarDidSelected:(id)userData; +``` + +获取用户选中头像回调的样例: + +```objectivec +//头像点击 +- (void)avatarDidSelected:(id)userData +{ + //EMPersonalDataViewController 用户自定义的个人信息视图 + //样例逻辑是选中消息头像后,进入该消息发送者的个人信息页 + if (userData && userData.easeId) { + EMPersonalDataViewController *controller = [[EMPersonalDataViewController alloc]initWithNickName:userData.easeId]; + [self.navigationController pushViewController:controller animated:YES]; + } +} +``` + +#### 用户长按头像的回调 + +```objectivec +/*! + @method + @brief 点击消息头像 + @discussion 获取用户长按头像回调 + @param userData 当前长按的头像所指向的用户资料 + */ + +- (void)avatarDidLongPress:(id)userData; +``` + +#### 群通知回执详情 + +```objectivec +/*! + @method + @brief 群通知回执详情 + @discussion 获取用户点击群通知的回调(仅在群聊中并且是点击用户群主有效) + @param message 当前群通知消息 + @param groupId 当前消息所属群 ID + */ + +- (void)groupMessageReadReceiptDetail:(EMMessage*)message groupId:(NSString*)groupId; +``` + +获取用户点击群通知回执详情的样例: + +```objectivec +//群通知阅读回执详情 +- (void)groupMessageReadReceiptDetail:(EMMessage *)message groupId:(NSString *)groupId +{ + //EMReadReceiptMsgViewController用户自定义群通知阅读回执详情页(在 EaseIM APP 中有效) + EMReadReceiptMsgViewController *readReceiptControl = [[EMReadReceiptMsgViewController alloc] initWithMessage:message groupId:groupId]; + readReceiptControl.modalPresentationStyle = 0; + [self presentViewController:readReceiptControl animated:YES completion:nil]; +} +``` + +#### 输入区回调 + +当前会话输入扩展区数据模型组(UI 配置可在聊天视图配置数据模型中设置) + +```objectivec +/*! + @method + @brief 当前会话输入扩展区数据模型组 + @param defaultInputBarItems 默认功能数据模型组 (默认有序:相册,相机,位置,文件) + @param conversationType 当前会话类型:单聊,群聊,聊天室 + @result 返回一组输入区扩展功能 + */ + +- (NSMutableArray*)inputBarExtMenuItemArray: + (NSMutableArray*)defaultInputBarItems + conversationType:(EMConversationType)conversationType; +``` + +当前会话输入扩展区数据模型组回调示例(EaseIM APP 有效): + +```objectivec +- (NSMutableArray *)inputBarExtMenuItemArray:(NSMutableArray *)defaultInputBarItems conversationType:(EMConversationType)conversationType +{ +NSMutableArray *menuArray = [[NSMutableArray alloc]init]; +//相册 +[menuArray addObject:[defaultInputBarItems objectAtIndex:0]]; +//相机 +[menuArray addObject:[defaultInputBarItems objectAtIndex:1]]; +//音视频 +__weak typeof(self) weakself = self; +if (conversationType != EMConversationTypeChatRoom) { + EaseExtMenuModel *rtcMenu = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"video_conf"] funcDesc:@"音视频" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { + if (isExecuted) { + [weakself chatSealRtcAction]; + } + }]; + [menuArray addObject:rtcMenu]; +} +//位置 +[menuArray addObject:[defaultInputBarItems objectAtIndex:2]]; +//文件 +[menuArray addObject:[defaultInputBarItems objectAtIndex:3]]; +//群组回执 +if (conversationType == EMConversationTypeGroupChat) { + if ([[EMClient.sharedClient.groupManager getGroupSpecificationFromServerWithId:_conversation.conversationId error:nil].owner isEqualToString:EMClient.sharedClient.currentUsername]) { + EaseExtMenuModel *groupReadReceiptExtModel = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"pin_readReceipt"] funcDesc:@"群组回执" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { + //群组回执发送消息页 + [weakself groupReadReceiptAction]; + }]; + [menuArray addObject:groupReadReceiptExtModel]; + } +} +return menuArray; +} +``` + +#### 键盘输入变化回调 + +```objectivec +/*! + @method + @brief 输入区键盘输入变化回调 例:@群成员 + @result 返回键入内容是否有效 + */ +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; +``` + +输入区键盘回调示例(EaseIM APP 有效): + +```objectivec +//@群成员 +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text +{ + if (self.conversation.type == EMConversationTypeGroupChat) { + if ([text isEqualToString:@"@"]) { + [self _willInputAt:textView]; + } else if ([text isEqualToString:@""]) { + __block BOOL isAt = NO; + [textView.attributedText enumerateAttributesInRange:NSMakeRange(0, textView.text.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + NSString *atUser = attrs[@"AtInfo"]; + if (atUser) { + if (textView.selectedRange.location == range.location + range.length) { + isAt = YES; + NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithAttributedString:textView.attributedText]; + [result deleteCharactersInRange:range]; + textView.attributedText = result; + if ([atUser isEqualToString:@"All"]) { + [self.chatController removeAtAll]; + } else { + [self.chatController removeAtUser:atUser]; + } + *stop = YES; + } + } + }]; + return !isAt; + } + } + return YES; +} +``` + +#### 输入框选中回调 + +```objectivec +/** + * 输入区选中范围变化回调 例:@群成员 + */ +- (void)textViewDidChangeSelection:(UITextView *)textView; +``` + +输入区选中范围变化回调示例(EaseIM APP 有效): + +```objectivec +- (void)textViewDidChangeSelection:(UITextView *)textView +{ + [textView.attributedText enumerateAttributesInRange:NSMakeRange(0, textView.text.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + if (attrs[@"AtInfo"]) { + NSUInteger min = textView.selectedRange.location; + NSUInteger max = textView.selectedRange.location + textView.selectedRange.length; + if (min > range.location && min <= range.location + range.length) { + NSUInteger location = range.location + range.length; + NSUInteger length = 0; + if (textView.selectedRange.location + textView.selectedRange.length > location) { + length = textView.selectedRange.location + textView.selectedRange.length - location; + } + textView.selectedRange = NSMakeRange(location, length); + *stop = YES; + } else if (max > range.location && max <= range.location + range.length) { + NSUInteger location = min; + NSUInteger length = textView.selectedRange.length - (max - range.location - range.length); + textView.selectedRange = NSMakeRange(location, length); + *stop = YES; + } + } + }]; +} +``` + +#### 对方正在输入状态回调 + +对方正在输入状态回调(单聊有效) + +```objectivec +/** + 对方正在输入 +*/ +- (void)beginTyping; +``` + +对方结束输入回调(单聊有效) + +```objectivec +/** + 对方结束输入 +*/ +- (void)endTyping; +``` + +#### 消息长按事件回调 + +默认消息 cell 长按回调 + +```objectivec +/*! + @method + @brief 默认消息 cell 长按回调 + @param defaultLongPressItems 默认长按扩展区功能数据模型组(默认共有:复制,删除,撤回发送消息时 间距当前时间小于 2 分钟) + @param message 当前长按的消息 + @result 返回默认消息长按扩展功能组 + */ +- (NSMutableArray*)messageLongPressExtMenuItemArray:(NSMutableArray*)defaultLongPressItems message:(EMMessage*)message; +``` + +默认消息 cell 长按回调示例(EaseIM APP 有效): + +```objectivec +//添加转发消息 +- (NSMutableArray *)messageLongPressExtMenuItemArray:(NSMutableArray *)defaultLongPressItems message:(EMMessage *)message +{ + NSMutableArray *menuArray = [[NSMutableArray alloc]initWithArray:defaultLongPressItems]; + //转发 + __weak typeof(self) weakself = self; + if (message.body.type == EMMessageBodyTypeText || message.body.type == EMMessageBodyTypeImage || message.body.type == EMMessageBodyTypeLocation || message.body.type == EMMessageBodyTypeVideo) { + EaseExtMenuModel *forwardMenu = [[EaseExtMenuModel alloc]initWithData:[UIImage imageNamed:@"forward"] funcDesc:@"转发" handle:^(NSString * _Nonnull itemDesc, BOOL isExecuted) { + //用户可自定义转发 CallBack + if (isExecuted) { + [weakself forwardMenuItemAction:message]; + } + }]; + [menuArray addObject:forwardMenu]; + } + return menuArray; +} +``` + +#### 自定义 cell 长按回调 + +用户自定义消息 cell 长按事件回调 + +```objectivec +/*! + @method + @brief 当前所长按的 自定义 cell 的扩展区数据模型组 + @param defaultLongPressItems 默认长按扩展区功能数据模型组。默认共有:复制,删除,撤回(发送消息时 间距当前时间小于 2 分钟) + @param customCell 当前长按的自定义 cell + @result 返回默认消息长按扩展功能组 + */ +/** + * + * + * @param defaultLongPressItems 默认长按扩展区功能数据模型组 默认共有:复制,删除,撤回(发送消息时 间距当前时间小于 2 分钟)) + * @param customCell 当前长按的自定义 cell + */ +- (NSMutableArray*)customCellLongPressExtMenuItemArray:(NSMutableArray*)defaultLongPressItems customCell:(UITableViewCell*)customCell; +``` + +### 会话列表自定义功能扩展 + +实例化 `EaseConversationsViewController` 之后,可选择实现 `EaseConversationsViewControllerDelegate` 协议(会话列表回调代理),接收 `EaseConversationsViewController` 的回调并做进一步的自定义实现。 + +`EaseConversationsViewControllerDelegate` + +#### 自定义 cell + +通过实现会话列表回调获取自定义消息 cell 如果返回 nil 会显示默认;如果返回 cell 则会显示用户自定义 cell。 + +```objectivec +/*! + @method + @brief 获取消息自定义 cell + @discussion 返回 nil 显示默认 cell,否则显示用户自定义 cell + @param tableView 当前消息视图的 tableView + @param indexPath 当前所要展示 cell 的 indexPath + @result 返回用户自定义cell + */ +- (UITableViewCell *)easeTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; +``` + +#### 会话列表 cell 选中回调 + +```objectivec +/*! + @method + @brief 会话列表 cell 选中回调 + @param tableView 当前消息视图的 tableView + @param indexPath 当前所要展示 cell 的 indexPath + */ +- (void)easeTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; +``` + +会话列表 cell 选中回调示例(EaseIM APP 有效): + +```objectivec +- (void)easeTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + EaseConversationCell *cell = (EaseConversationCell*)[tableView cellForRowAtIndexPath:indexPath]; + //系统通知 + if ([cell.model.easeId isEqualToString:@"emsystemnotificationid"]) { + //此实例仅为 EaseIM APP 展示系统通知 + EMNotificationViewController *controller = [[EMNotificationViewController alloc] initWithStyle:UITableViewStylePlain]; + [self.navigationController pushViewController:controller animated:YES]; + return; + } + //跳转至聊天页 + [[NSNotificationCenter defaultCenter] postNotificationName:CHAT_PUSHVIEWCONTROLLER object:cell.model]; +} +``` + +#### 会话列表用户资料回调 + +```objectivec +/*! + @method + @brief 会话列表用户资料回调 + @discussion 可根据 conversationId 或 type 返回对应的用户资料数据集 + @param conversationId 当前会话列表 cell 所拥有的会话 ID + @param type 当前会话列表 cell 所拥有的会话类型 + */ +- (id)easeUserDelegateAtConversationId:(NSString *)conversationId + conversationType:(EMConversationType)type; +``` + +会话列表用户资料回调实例(EaseIM APP 有效) + +```objectivec +- (id)easeUserDelegateAtConversationId:(NSString *)conversationId conversationType:(EMConversationType)type +{ + //EMConversationUserDataModel 为自定义用户资料数据模型,实现 EaseUserDelegate 接口返回参数 + //@property (nonatomic, copy, readonly) NSString *easeId; // 环信 ID + //@property (nonatomic, copy, readonly) UIImage *defaultAvatar; // 默认头像显示 + EMConversationUserDataModel *userData = [[EMConversationUserDataModel alloc]initWithEaseId:conversationId conversationType:type]; + return userData; +} +``` + +#### 会话列表 cell 侧滑项回调 + +```objectivec +/*! + @method + @brief 会话列表 cell 侧滑项回调 + @param tableView 当前消息视图的 tableView + @param indexPath 当前所要侧滑 cell 的 indexPath + @param actions 返回侧滑项集合 + */ +- (NSArray *)easeTableView:(UITableView *)tableView + trailingSwipeActionsForRowAtIndexPath:(NSIndexPath *)indexPath + actions:(NSArray *)actions; +``` + +会话列表 cell 侧滑项回调示例(EaseIM APP 有效) + +```objectivec +- (NSArray *)easeTableView:(UITableView *)tableView trailingSwipeActionsForRowAtIndexPath:(NSIndexPath *)indexPath actions:(NSArray *)actions +{ + NSMutableArray *array = [[NSMutableArray alloc]init]; + __weak typeof(self) weakself = self; + UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal + title:@"删除" + handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) + { + //删除操作 + }]; + deleteAction.backgroundColor = [UIColor redColor]; + [array addObject:deleteAction]; + //返回的 actions 有序:删除,置顶 + [array addObject:actions[1]]; + return [array copy]; +} +``` + +### 通讯录自定义功能扩展 + +#### 获取用户联系人资料 + +获取用户自己的联系人列表填充到 EaseIMKit 通讯录中 + +```objectivec +- (void)setContacts:(NSArray * _Nonnull)contacts; +``` + +实例化 EaseContactsViewController 之后,可选择实现 EaseContactsViewControllerDelegate 协议(通讯录代理),接收 EaseContactsViewController 的回调并做进一步的自定义实现。 + +EaseConversationsViewControllerDelegate + +#### 即将刷新通讯录填充数据 + +```objectivec +- (void)willBeginRefresh; +``` + +即将刷新通讯录填充数据示例(EaseIM APP 有效): + +```objectivec +- (void)willBeginRefresh { + //从服务器获取当前登录账户的联系人列表 + [EMClient.sharedClient.contactManager getContactsFromServerWithCompletion:^(NSArray *aList, EMError *aError) { + if (!aError) { + self->_contacts = [aList mutableCopy]; + NSMutableArray *contacts = [NSMutableArray array]; + for (NSString *username in aList) { + EMContactModel *model = [[EMContactModel alloc] init]; + model.huanXinId = username; + [contacts addObject:model]; + } + //填充联系人列表集合到 EaseIMKit 通讯录实例中 + [self->_contactsVC setContacts:contacts]; + } + [self->_contactsVC endRefresh]; + }]; +} +``` + +#### 通讯录自定义 cell + +```objectivec +/*! +@method +@brief 获取通讯录自定义 cell +@discussion 返回 nil 显示默认 cell,否则显示用户自定义 cell +@param tableView 当前消息视图的 tableView +@param contact 当前所要展示 cell 所拥有的数据模型 +@result 返回用户自定义 cell +*/ +- (UITableViewCell *)easeTableView:(UITableView *)tableView cellForRowAtContactModel:(EaseContactModel *) contact; +``` + +#### 通讯录 cell 条目选中回调 + +```objectivec +/*! +@method +@brief 通讯录 cell 条目选中回调 +@param tableView 当前消息视图的 tableView +@param contact 当前所选中 cell 所拥有的数据模型 +@result 返回用户自定义 cell +*/ +- (void)easeTableView:(UITableView *)tableView didSelectRowAtContactModel:(EaseContactModel *) contact; +``` + +通讯录 cell 条目选中回调示例: + +```objectivec +- (void)easeTableView:(UITableView *)tableView didSelectRowAtContactModel:(EaseContactModel *)contact { + //跳转加好友页 + if ([contact.easeId isEqualToString:@"newFriend"]) { + EMInviteFriendViewController *controller = [[EMInviteFriendViewController alloc] init]; + [self.navigationController pushViewController:controller animated:YES]; + return; + } + //跳转群组列表页 + if ([contact.easeId isEqualToString:@"groupList"]) { + [[NSNotificationCenter defaultCenter] postNotificationName:GROUP_LIST_PUSHVIEWCONTROLLER object:@{NOTIF_NAVICONTROLLER:self.navigationController}]; + return; + } + //跳转聊天室列表页 + if ([contact.easeId isEqualToString:@"chatroomList"]) { + [[NSNotificationCenter defaultCenter] postNotificationName:CHATROOM_LIST_PUSHVIEWCONTROLLER object:@{NOTIF_NAVICONTROLLER:self.navigationController}]; + return; + } + //跳转好友个人资料页 + [self personData:contact.easeId]; +} +``` + +#### 通讯录 cell 侧滑回调 + +```objectivec +/*! + @method + @brief 会话列表 cell 侧滑项回调 + @param tableView 当前消息视图的 tableView + @param contact 当前所侧滑 cell 拥有的联系人数据 + @param actions 返回侧滑项集合 + */ +- (NSArray *)easeTableView:(UITableView *)tableView + trailingSwipeActionsForRowAtContactModel:(EaseContactModel *) contact + actions:(NSArray * __nullable)actions; +``` + +通讯录 cell 侧滑回调示例: + +```objectivec +- (NSArray *)easeTableView:(UITableView *)tableView trailingSwipeActionsForRowAtContactModel:(EaseContactModel *)contact actions:(NSArray *)actions +{ + //通讯录头部非联系人列表禁止侧滑 + if ([contact.easeId isEqualToString:@"newFriend"] || [contact.easeId isEqualToString:@"groupList"] || [contact.easeId isEqualToString:@"chatroomList"]) { + return nil; + } + __weak typeof(self) weakself = self; + UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive + title:@"删除" + handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) + { + //删除联系人操作 + }]; + return @[deleteAction]; +} +``` diff --git a/docs/uikit/react-native/key_function_chat_page.md b/docs/uikit/react-native/key_function_chat_page.md new file mode 100644 index 000000000..730aa11af --- /dev/null +++ b/docs/uikit/react-native/key_function_chat_page.md @@ -0,0 +1,279 @@ +# 聊天页面 + +聊天组件提供了丰富的功能,支持文本、表情、图片、语音、文件等多种类型消息的输入,支持显示消息列表、自定义头像、自定义消息状态、自定义消息气泡,并可以修改消息状态。 + +## 集成聊天页面 + +在项目中集成聊天页面组件 `ChatFragment`,并传入相应的参数即可使用。 + +| 参数 | 是否必需 | 描述 | +| :------------- | :-----| :----- | +| `chatId` | 是 | 会话 ID。 | +| `chatType` | 是 | - `0`:单聊;
- `1`:群聊;
- `2`:聊天室。 | +| `propsRef` | 否 | 设置聊天组件控制器。 | +| `screenParams` | 否 | 设置聊天组件的参数。 | +| `messageBubbleList` | 否 | 设置自定义消息气泡组件。 | +| `onUpdateReadCount` | 否 | 未读消息计数更新时发生。 | +| `onClickMessageBubble` | 否 | 单击消息气泡通知时发生。 | +| `onLongPressMessageBubble` | 否 | 按住消息气泡时发生。 | +| `onClickInputMoreButton` | 否 | 按下**更多**按钮时发生。 | +| `onPressInInputVoiceButton` | 否 | 按下语音按钮时发生。 | +| `onPressOutInputVoiceButton` | 否 | 释放语音按钮时发生。 | +| `onSendMessage` | 否 | 消息开始发送时发生。 | +| `onSendMessageEnd` | 否 | 消息发送完成时发生。 | +| `onVoiceRecordEnd` | 否 | 语音消息录制完成时发生。 | + +```typescript +import * as React from "react"; +import { ChatFragment, ScreenContainer } from "react-native-chat-uikit"; +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; // 会话 ID,String 类型。 + const chatType = 0; + return ( + + + + ); +} +``` +效果如下图所示: + + + + +聊天页面相关的方法如下表所示: + +| 方法 | 描述 | +| :------- | :----- | +| `sendTextMessage` | 发送文本消息。 | +| `sendImageMessage` | 发送图片消息。 | +| `sendVoiceMessage` | 发送语音消息。 | +| `sendCustomMessage` | 发送自定义消息。 | +| `sendFileMessage` | 发送文件消息。 | +| `sendVideoMessage` | 发送视频消息。 | +| `sendLocationMessage` | 发送位置消息。 | +| `loadHistoryMessage` | 加载历史消息。 | +| `deleteLocalMessage` | 删除本地消息。 | +| `resendMessage` | 重发发送失败的消息。 | +| `downloadAttachment` | 下载附件。 | + +例如,你可以自行录制语音,然后调用 `sendVoiceMessage` 方法发送。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + chatRef.current.sendVoiceMessage(params); + }} + /> + + ); +} +``` + +效果如下图所示: + + + +## 自定义实现 + +### 自定义聊天气泡 + +当默认的聊天气泡无法满足需求时,你可以自行设计聊天气泡的样式。 + +`MessageBubbleList` 为自定义聊天气泡列表组件,可以修改任何气泡内容,例如:头像、文字、图片、语音样式、消息状态等。该组件提供子组件实现文本、图片、音视频等消息的样式: + +- `TextMessageItem`:自定义文本消息样式。 +- `LocationMessageItem`:自定义位置消息样式。 +- `CustomMessageItem`:自定义自定义消息样式。 +- `ImageMessageItem`:自定义图片消息样式。 +- `VideoMessageItem`:自定义视频消息样式。 +- `VoiceMessageItem`:自定义语音消息样式。 +- `FileMessageItem`:自定义文件消息样式。 + +```typescript +import type { MessageBubbleListProps } from "../fragments/MessageBubbleList"; +import MessageBubbleList from "../fragments/MessageBubbleList"; +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + + + ); +} +``` + +效果如下图所示: + +   +   + + +### 自定义背景色 + +你可以设置 `MessageBubbleListPropsP` 中的 `backgroundColor` 自定义背景色。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + + + ); +} +``` + +效果如下图所示: + + + +### 自定义时间标签显示和隐藏 + +你可以设置 `messageBubbleList` 中的 `showTimeLabel` 自定义时间标签显示和隐藏。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + + + ); +} +``` + +效果如下图所示: + + + +### 自定义点击消息气泡回调 + +你可以设置 `ChatFragment` 中的 `onClickMessageBubble` 自定义点击消息气泡回调,例如:播放语音、预览图片等。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:对于语音消息,进行播放;对于图片消息,进行预览。 + }} + /> + + ); +} +``` + +### 自定义长按消息气泡回调 + +你可以设置 `ChatFragment` 中的 `onLongPressMessageBubble` 自定义长按消息气泡回调,例如,可以显示不同右键菜单。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:显示右键菜单。例如,消息转发、消息删除和消息重发等。 + }} + /> + + ); +} +``` + +效果如下图所示: + + + +### 自定义发送消息前回调 + +你可以设置 `ChatFragment` 中的 `onSendMessage` 自定义发送消息前回调。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO: 更新消息。 + }} + /> + + ); +} +``` + +### 自定义发送消息完成回调 + +你可以设置 `ChatFragment` 中的 `onSendMessageEnd` 自定义发送消息完成,例如,更新消息发送状态。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:更新消息发送状态,即发送成功或失败。 + }} + /> + + ); +} +``` + + + + + diff --git a/docs/uikit/react-native/key_function_conversation_list.md b/docs/uikit/react-native/key_function_conversation_list.md new file mode 100644 index 000000000..a6e2e57b6 --- /dev/null +++ b/docs/uikit/react-native/key_function_conversation_list.md @@ -0,0 +1,176 @@ +# 会话列表页面 + +会话列表组件支持创建、更新、删除会话、会话样式修改、会话状态改变(如未读消息数)等。 + +## 集成会话列表页面 + +在项目中集成会话列表页面组件 `ConversationListFragment`,并传入相应的参数即可使用。 + +| 参数 | 是否必需 | 描述 | +| :------------- | :-----| :----- | +| `chatId ` | 是 | 会话 ID。 | +| `chatType` | 是 | 会话类型。
- `0`:单聊;
- `1`:群聊;
- `2`:聊天室。 | +| `propsRef` | 否 | 设置会话列表控制器。通过会话列表控制器可以实现以下会话操作:
- 创建会话;
- 更新会话,如更新会话排序、会话样式和消息未读数;
- 删除会话;
- `updateRead`:将会话设置为已读;
- `updateExtension`:设置会话自定义字段。| +| `onLongPress` | 否 | 按住会话列表项时发生。 | +| `onPress` | 否 | 单击会话列表项时发生。 | +| `onUpdateReadCount` | 否 | 更新会话列表项的消息未读数时发生。 | +| `sortPolicy` | 否 | 设置会话列表项的排序规则。 | +| `RenderItem` | 否 | 自定义会话列表项的样式。 | + +```typescript +import * as React from "react"; +import { + ConversationListFragment, + ScreenContainer, +} from "react-native-chat-uikit"; +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; // 会话 ID,String 类型。 + const chatType = 0; + return ( + + + + ); +} +``` + +效果如下图所示: + + + +## 自定义实现 +### 自定义会话样式 + +你可以设置 `ConversationListFragment` 中的 `RenderItem` 自定义会话样式,例如自定义头像、消息未读数以及消息时间戳等。 + +:::notice +如果开启侧滑功能,则需要设置侧滑组件的宽度。 +::: + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + return ; + }} + /> + + ); +} +``` + +效果如下图所示: + +   +   + + +### 自定义会话排序 + +会话默认按会话 ID `convId` 排序。你可以设置 `ConversationListFragment` 中的 `sortPolicy` 自定义会话排序,如置顶会话或按会话中的最新一条消息的时间戳排序。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + if (a.key > b.key) { + return 1; + } else if (a.key < b.key) { + return -1; + } else { + return 0; + } + }} + /> + + ); +} +``` + +效果如下图所示: + + + +### 自定义会话的未读消息数 + +很多组件需要关注会话的未读消息数的通知来改变消息提醒的状态。 + +你可以设置 `ConversationListFragment` 中的 `onUpdateReadCount` 自定义会话的未读消息数。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:显示未读消息数 + }} + /> + + ); +} +``` + +效果如下图所示: + + + +### 自定义会话点击事件 + +你可以设置 `ConversationListFragment` 中的 `onPress` 自定义会话点击事件。例如,你可以点击会话列表项,进入聊天页面。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:进入聊天页面。 + }} + /> + + ); +} +``` + +### 自定义会话列表项长按事件 + +你可以设置 `ConversationListFragment` 中的 `onLongPress` 自定义会话列表项,例如长按后显示该项目的右键菜单。 + +```typescript +export default function ChatScreen(): JSX.Element { + const chatId = "xxx"; + const chatType = 0; + return ( + + { + // TODO:显示自定义菜单。 + }} + /> + + ); +} +``` + +效果如下图所示: + + + + + + + + diff --git a/docs/uikit/react-native/overview.md b/docs/uikit/react-native/overview.md index 8013c93ef..e18b23a07 100644 --- a/docs/uikit/react-native/overview.md +++ b/docs/uikit/react-native/overview.md @@ -1 +1,30 @@ -## UIKit 集成文档 +# 概述 + +react-native-chat-uikit 是基于环信 IM SDK 的一款 UI 组件库,提供通用的 UI 组件,例如会话列表、聊天界面。利用该组件库,开发者可根据实际业务需求快速地搭建自定义 IM 应用。react-native-chat-uikit 中的组件在实现 UI 功能的同时,调用 IM SDK 相关接口实现 IM 相关逻辑和数据的处理,因而开发者在使用 react-native-chat-uikit 时只需关注自身业务或个性化扩展即可。 + +查看 react-native-chat-uikit 源码,请点击[这里](https://github.com/easemob/react-native-chat-library/tree/dev/packages/react-native-chat-uikit)。 + +## 功能 + +目前,react-native-chat-uikit 提供以下功能: + +- 快速搭建聊天页面。 +- 快速搭建会话列表页面。 + +react-native-chat-uikit 在单聊、群聊和聊天室会话中提供下列特性,提升聊天体验。 + +| 特性 | 描述 | +| :-------------- | :----------------------------- | +| 文本消息 | 发送和接收文本消息。 | +| 图片消息 | 发送和接收图片消息。 | +| 语音消息 | 发送和接收语音消息。 | +| 文件消息 | 发送和接收文件消息。 | +| 已读回执 | 提示接收方的已读状态。该功能仅适用于单聊会话。 | +| 未读消息数 | 显示收到消息的未读数。 | +| 消息表情回复 | 回复消息表情。 | +| 消息删除 | 删除本地消息。 | +| 消息撤回 | 在指定时间内对已发送消息撤回。 | + + + + diff --git a/docs/uikit/react-native/quickstart.md b/docs/uikit/react-native/quickstart.md new file mode 100644 index 000000000..b7a01e55c --- /dev/null +++ b/docs/uikit/react-native/quickstart.md @@ -0,0 +1,203 @@ +# 快速开始 + +利用 react-native-chat-uikit 提供的 UI 组件,你可以轻松实现应用内的聊天。react-native-chat-uikit 支持单聊、群聊和聊天室会话,本文介绍如何实现在单聊会话中发送消息。 + +## 前提条件 + +集成 react-native-chat-uikit 前,你的开发环境需要满足以下条件: + +1. 有效的环信即时通讯 IM 开发者账号,创建应用并获取 App Key。 +2. 在[环信控制台](https://console.easemob.com/index)[创建两个用户用于聊天](/product/enable_and_configure_IM.html#创建-im-用户)。 + +### iOS 平台 + +- MacOS 10.15.7 或以上版本 +- Xcode 12.4 或以上版本,包括命令行工具 +- React Native 0.63.4 或以上版本 +- NodeJs 16 或以上版本,包含 npm 包管理工具 +- CocoaPods 包管理工具 +- Yarn 编译运行工具 +- Watchman 调试工具 +- 运行环境真机或模拟器 iOS 10.0 或以上版本 + +### Android 平台 + +- MacOS 10.15.7 或以上版本 +- Android Studio 4.0 或以上版本,包括 JDK 1.8 或以上版本 +- React Native 0.63.4 或以上版本 +- 如果用 Macos 系统开发,需要 CocoaPods 包管理工具 +- NodeJs 16 或以上版本,包含 npm 包管理工具 +- Yarn 编译运行工具 +- Watchman 调试工具 +- 运行环境真机或模拟器 Android 6.0 或以上版本 + +## 发送第一条消息 + +### 第一步 创建 RN 示例项目 + +```sh +npx react-native init RNUIkitQuickExample --version 0.71.11 +``` + +### 第二步 初始化项目 + +在生成的 `env` 中配置 `appkey` 等信息。 + +```sh +yarn && yarn run env +``` + +### 第三步 添加依赖到该项目 + +其中 `react-native-chat-sdk` 为即时通讯 IM SDK, `react-native-chat-uikit` 为 UIkit,其它为三方依赖。 + +```json +{ + "dependencies": { + "@react-native-async-storage/async-storage": "^1.17.11", + "@react-native-camera-roll/camera-roll": "^5.6.0", + "@react-native-clipboard/clipboard": "^1.11.2", + "@react-native-firebase/app": "^18.0.0", + "@react-native-firebase/messaging": "^18.0.0", + "react-native-audio-recorder-player": "^3.5.3", + "react-native-chat-sdk": "^1.2.0", + "react-native-chat-uikit": "^1.0.0", + "react-native-create-thumbnail": "^1.6.4", + "react-native-document-picker": "^9.0.1", + "react-native-fast-image": "^8.6.3", + "react-native-file-access": "^3.0.4", + "react-native-get-random-values": "~1.8.0", + "react-native-image-picker": "^5.4.2", + "react-native-permissions": "^3.8.0", + "react-native-safe-area-context": "4.5.0", + "react-native-screens": "^3.20.0", + "react-native-video": "^5.2.1" + } +} +``` + +### 第四步 配置 iOS + +添加以下内容到 `ios/Podfile` 文件。 + +```ruby +target 'RNUIkitQuickExample' do + + pod 'GoogleUtilities', :modular_headers => true + pod 'FirebaseCore', :modular_headers => true + + permissions_path = File.join(File.dirname(`node --print "require.resolve('react-native-permissions/package.json')"`), "ios") + pod 'Permission-Camera', :path => "#{permissions_path}/Camera" + pod 'Permission-MediaLibrary', :path => "#{permissions_path}/MediaLibrary" + pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone" + pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications" + pod 'Permission-PhotoLibrary', :path => "#{permissions_path}/PhotoLibrary" + +end +``` + +添加以下内容到 `ios/RNUIkitQuickExample/Info.plist` 文件。 + +```xml + + NSCameraUsageDescription + + NSMicrophoneUsageDescription + + NSPhotoLibraryUsageDescription + + +``` + +### 第五步 配置 Android + +添加以下内容到 `android/build.gradle` 文件。 + +```groovy +buildscript { + ext { + kotlinVersion = '1.6.10' + if (findProperty('android.kotlinVersion')) { + kotlinVersion = findProperty('android.kotlinVersion') + } + } + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} +``` + +添加以下内容到 `android/app/src/main/AndroidManifest.xml` 文件。 + +```xml + + + + + + + + + +``` + +### 第六步 初始化 UIKit 和进入聊天页面 + +必要的代码包括初始化 react-native-chat-uikit、登录服务器、进入聊天页面开始聊天。下面添加了简单的页面路由和跳转用于演示目的。 + +1. 初始化 react-native-chat-uikit: + +```typescript +export const App = () => { + return ( + + + + + + + + + ); +}; +``` + +2. 添加聊天详情页面: + +```typescript +export function ChatScreen({ + route, +}: NativeStackScreenProps): JSX.Element { + return ( + + + + ); +} +``` + +### 第七步 运行示例项目 + +```sh +yarn run ios +# or +yarn run android +``` + +### 第八步 发送消息 + + + +## 示例源码参考 + +[查看示例完整源码](https://github.com/easemob/react-native-chat-library/tree/dev/examples/uikit-quick-start)。 diff --git a/static/images/uikitflutter/ChatConversationsView.png b/static/images/uikitflutter/ChatConversationsView.png new file mode 100644 index 000000000..f836c13fd Binary files /dev/null and b/static/images/uikitflutter/ChatConversationsView.png differ diff --git a/static/images/uikitflutter/ChatConversationsView_avatar.png b/static/images/uikitflutter/ChatConversationsView_avatar.png new file mode 100644 index 000000000..591235df4 Binary files /dev/null and b/static/images/uikitflutter/ChatConversationsView_avatar.png differ diff --git a/static/images/uikitflutter/ChatConversationsView_click.png b/static/images/uikitflutter/ChatConversationsView_click.png new file mode 100644 index 000000000..914b14c72 Binary files /dev/null and b/static/images/uikitflutter/ChatConversationsView_click.png differ diff --git a/static/images/uikitflutter/ChatConversationsView_custom.png b/static/images/uikitflutter/ChatConversationsView_custom.png new file mode 100644 index 000000000..644dc4a68 Binary files /dev/null and b/static/images/uikitflutter/ChatConversationsView_custom.png differ diff --git a/static/images/uikitflutter/ChatConversationsView_nickname.png b/static/images/uikitflutter/ChatConversationsView_nickname.png new file mode 100644 index 000000000..f52fa4cf2 Binary files /dev/null and b/static/images/uikitflutter/ChatConversationsView_nickname.png differ diff --git a/static/images/uikitflutter/ChatMessagesView.png b/static/images/uikitflutter/ChatMessagesView.png new file mode 100644 index 000000000..a182ae598 Binary files /dev/null and b/static/images/uikitflutter/ChatMessagesView.png differ diff --git a/static/images/uikitflutter/MessagesPage.png b/static/images/uikitflutter/MessagesPage.png new file mode 100644 index 000000000..d1b2c3c76 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage.png differ diff --git a/static/images/uikitflutter/MessagesPage_custom_avatar.png b/static/images/uikitflutter/MessagesPage_custom_avatar.png new file mode 100644 index 000000000..1f47bdfd5 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage_custom_avatar.png differ diff --git a/static/images/uikitflutter/MessagesPage_custom_bubble.png b/static/images/uikitflutter/MessagesPage_custom_bubble.png new file mode 100644 index 000000000..5e78aeef6 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage_custom_bubble.png differ diff --git a/static/images/uikitflutter/MessagesPage_custom_bubble_click.png b/static/images/uikitflutter/MessagesPage_custom_bubble_click.png new file mode 100644 index 000000000..d1436ec19 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage_custom_bubble_click.png differ diff --git a/static/images/uikitflutter/MessagesPage_custom_input.png b/static/images/uikitflutter/MessagesPage_custom_input.png new file mode 100644 index 000000000..083004df1 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage_custom_input.png differ diff --git a/static/images/uikitflutter/MessagesPage_custom_nickname.png b/static/images/uikitflutter/MessagesPage_custom_nickname.png new file mode 100644 index 000000000..0b5c7d757 Binary files /dev/null and b/static/images/uikitflutter/MessagesPage_custom_nickname.png differ diff --git a/static/images/uikitrn/chat_detail_bg.png b/static/images/uikitrn/chat_detail_bg.png new file mode 100644 index 000000000..e8291d298 Binary files /dev/null and b/static/images/uikitrn/chat_detail_bg.png differ diff --git a/static/images/uikitrn/chat_detail_msg_list_item_custom_1.png b/static/images/uikitrn/chat_detail_msg_list_item_custom_1.png new file mode 100644 index 000000000..85ddcf491 Binary files /dev/null and b/static/images/uikitrn/chat_detail_msg_list_item_custom_1.png differ diff --git a/static/images/uikitrn/chat_detail_msg_list_item_custom_2.png b/static/images/uikitrn/chat_detail_msg_list_item_custom_2.png new file mode 100644 index 000000000..5d8400db0 Binary files /dev/null and b/static/images/uikitrn/chat_detail_msg_list_item_custom_2.png differ diff --git a/static/images/uikitrn/chat_detail_msg_list_item_custom_3.png b/static/images/uikitrn/chat_detail_msg_list_item_custom_3.png new file mode 100644 index 000000000..b356df900 Binary files /dev/null and b/static/images/uikitrn/chat_detail_msg_list_item_custom_3.png differ diff --git a/static/images/uikitrn/chat_detail_msg_list_item_long_press.png b/static/images/uikitrn/chat_detail_msg_list_item_long_press.png new file mode 100644 index 000000000..05ec3a425 Binary files /dev/null and b/static/images/uikitrn/chat_detail_msg_list_item_long_press.png differ diff --git a/static/images/uikitrn/chat_detail_overview.png b/static/images/uikitrn/chat_detail_overview.png new file mode 100644 index 000000000..afb3db12c Binary files /dev/null and b/static/images/uikitrn/chat_detail_overview.png differ diff --git a/static/images/uikitrn/chat_detail_send_voice_msg.png b/static/images/uikitrn/chat_detail_send_voice_msg.png new file mode 100644 index 000000000..c20641190 Binary files /dev/null and b/static/images/uikitrn/chat_detail_send_voice_msg.png differ diff --git a/static/images/uikitrn/chat_detail_time_label.png b/static/images/uikitrn/chat_detail_time_label.png new file mode 100644 index 000000000..1ba9dc82a Binary files /dev/null and b/static/images/uikitrn/chat_detail_time_label.png differ diff --git a/static/images/uikitrn/conv_list_custom_1.png b/static/images/uikitrn/conv_list_custom_1.png new file mode 100644 index 000000000..67c7f2e40 Binary files /dev/null and b/static/images/uikitrn/conv_list_custom_1.png differ diff --git a/static/images/uikitrn/conv_list_custom_2.png b/static/images/uikitrn/conv_list_custom_2.png new file mode 100644 index 000000000..a65ae0bdb Binary files /dev/null and b/static/images/uikitrn/conv_list_custom_2.png differ diff --git a/static/images/uikitrn/conv_list_long_press.png b/static/images/uikitrn/conv_list_long_press.png new file mode 100644 index 000000000..fd52121ff Binary files /dev/null and b/static/images/uikitrn/conv_list_long_press.png differ diff --git a/static/images/uikitrn/conv_list_overview.png b/static/images/uikitrn/conv_list_overview.png new file mode 100644 index 000000000..aa12df8e7 Binary files /dev/null and b/static/images/uikitrn/conv_list_overview.png differ diff --git a/static/images/uikitrn/conv_list_sort.png b/static/images/uikitrn/conv_list_sort.png new file mode 100644 index 000000000..45a65c637 Binary files /dev/null and b/static/images/uikitrn/conv_list_sort.png differ diff --git a/static/images/uikitrn/conv_list_unread_count.png b/static/images/uikitrn/conv_list_unread_count.png new file mode 100644 index 000000000..805853265 Binary files /dev/null and b/static/images/uikitrn/conv_list_unread_count.png differ diff --git a/static/images/uikitrn/conversation_list_slide_menu.png b/static/images/uikitrn/conversation_list_slide_menu.png new file mode 100644 index 000000000..e49471ad1 Binary files /dev/null and b/static/images/uikitrn/conversation_list_slide_menu.png differ diff --git a/static/images/uikitrn/uikit_quick_start.png b/static/images/uikitrn/uikit_quick_start.png new file mode 100644 index 000000000..4a3575635 Binary files /dev/null and b/static/images/uikitrn/uikit_quick_start.png differ