update flutter uikit to 1.6.0
This commit is contained in:
parent
7f3360ead1
commit
c859e4e65c
|
|
@ -1,4 +1,9 @@
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
|
||||||
|
* Add: `scrollToConversation` on `TIMUIKitConversationController`. You can now easily navigate to a specific conversation in the conversation list and move to the next unread conversation by double-clicking the tab bar, [refers to our sample app](https://github.com/TencentCloud/chat-demo-flutter/blob/main/lib/src/conversation.dart).
|
||||||
|
* Optimize: The performance of the history message list while scrolling over a large distance.
|
||||||
|
|
||||||
## 1.5.0+1
|
## 1.5.0+1
|
||||||
|
|
||||||
* Fix: Video message oversize.
|
* Fix: Video message oversize.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,513 @@
|
||||||
|
|
||||||
|
# 社交场景最佳实践方案
|
||||||
|
|
||||||
|
社交模块是目前主流应用程序最常见的功能之一。有了社交模块,用户在您的应用内,可以自由的交流互动,并添加好友,关注其他用户等等。
|
||||||
|
|
||||||
|
这可在很大程度上,促进您应用程序的活跃度,吸引用户留存,获取更多新用户,并可拓展您应用的业务范围。让更多用户花更多时间在您的应用程序上。
|
||||||
|
|
||||||
|
例如,
|
||||||
|
|
||||||
|
- 交友软件,其核心便是社交聊天模块,用于匹配对话及用户关系链维护,让更多的用户相聚与相识;
|
||||||
|
- 音乐软件,可用社交模块让乐迷及粉丝群体实时沟通,打造音乐社区文化;
|
||||||
|
- 教育软件,可用社交模块打通 "学校-教师-家长" 循环,促进家校互动,形成家校社三合力,更大程度发挥教育影响作用,保证教育的一致性与连贯性;
|
||||||
|
- 电子游戏,特别是RPG类型,内置的聊天模块让玩家能在线组局,一起作战,并组建工会,创造游戏内社区,提升游戏社交属性,丰富体验,提升活跃度;
|
||||||
|
- 医疗软件,聊天及社群模块让患者间得以互助交流,一起战胜病魔,走出病情,让大家看到希望;
|
||||||
|
- 导航软件,堵车交流群,让归心似箭的旅行者们,交流前方最新情报,不再无聊干等,发发段子调侃一下,为拥堵的出行,带来一些希望与欢乐。
|
||||||
|
|
||||||
|
因此,社交聊天模块可谓是您应用程序不可或缺的能力。
|
||||||
|
|
||||||
|
本文将以腾讯云IM为基础,梳理现有应用在接入社交场景过程中常见需求,给出解决实现方案。以及可能遇到的问题、需要注意的细节点等,希望能帮助客户朋友们快速的理解业务、实现需求。
|
||||||
|
|
||||||
|
>? 本文档主要介绍社交场景的通用SDK实现方案,文中示例截图来自于我们的[Flutter TUIKit](https://cloud.tencent.com/document/product/269/70747),您可根据需要,选用我们提供的全部平台SDK或TUIKit。
|
||||||
|
|
||||||
|
## 用户
|
||||||
|
|
||||||
|
腾讯云IM支持托管维护用户信息与用户资料。您可直接将您应用的用户资料存储与我们的服务内,并通过相关API进行读取/更新/维护操作。
|
||||||
|
|
||||||
|
对于社交场景,常见用户资料可分为基本信息资料和其他信息资料。
|
||||||
|
|
||||||
|
|基本信息|其他信息|
|
||||||
|
|---|---|
|
||||||
|
|用户名,性别,生日,所在地,个性签名,昵称等|其他社交模块内需要的资料|
|
||||||
|
|
||||||
|
### 导入现有用户数据
|
||||||
|
|
||||||
|
如果您需要给您现有应用,添加社交能力。可直接使用我们的服务端API,快速将您现有用户数据,完整导入至腾讯云IM中。
|
||||||
|
|
||||||
|
导入完成后,现有用户可直接使用其原有身份数据,和其他用户发起会话,一起聊天,开启社交之旅。
|
||||||
|
|
||||||
|
[导入多个账号 - 服务端API](https://cloud.tencent.com/document/product/269/4919)
|
||||||
|
|
||||||
|
### 用户在线状态
|
||||||
|
|
||||||
|
腾讯云IM支持自动上报并让其他用户获取[在线状态信息](https://cloud.tencent.com/document/product/269/3665)。
|
||||||
|
|
||||||
|
状态包括:前台运行状态 / 后台运行状态 / 未登录状态。
|
||||||
|
|
||||||
|
利用这一能力,您可让用户看到其他用户的在线状态,增强互动性。
|
||||||
|
|
||||||
|
此外,您还可使用这一能力,针对您的业务场景,做许多功能拓展。例如,交友软件,能够优先推荐匹配在线的用户,让聊天可进行的更顺畅,缘分更快相聚。
|
||||||
|
|
||||||
|
| 会话列表用户在线状态 | 通讯录用户在线状态 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/ca1c22cc3452f77186dcadde610626af.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/90a6a5ac3a54b4289f3ae473f61c1ed2.png" /> |
|
||||||
|
|
||||||
|
#### 获取用户在线状态
|
||||||
|
|
||||||
|
在客户端上, 您可调用 [`getUserStatus`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMManager/getUserStatus.html) 方法,[批量查询其他用户的在线状态](https://cloud.tencent.com/document/product/269/75511#.E6.9F.A5.E8.AF.A2.E7.94.A8.E6.88.B7.E7.8A.B6.E6.80.81)。
|
||||||
|
|
||||||
|
此外,在服务端上,也可[通过REST API,获取用户状态](https://cloud.tencent.com/document/product/269/2566)。
|
||||||
|
|
||||||
|
#### 订阅用户在线状态变更
|
||||||
|
|
||||||
|
其他用户的在线状态总是实时在变化的,您可在客户端上,调用 [`subscribeUserStatus`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMManager/subscribeUserStatus.html) 方法,[批量订阅其他用户的在线状态](https://cloud.tencent.com/document/product/269/75511#.E8.AE.A2.E9.98.85.E7.94.A8.E6.88.B7.E7.8A.B6.E6.80.81)。当发生变化时,将通过回调函数通知您,您可根据其,修改界面UI并完成其他您的业务操作。
|
||||||
|
|
||||||
|
## 好友
|
||||||
|
|
||||||
|
好友管理,又称关系链管理,是社交场景的基础。众多会话/聊天特性,都要依赖于好友关系状态。有了好友关系链能力,众多的用户才能得以串联起来,互动形成整体。
|
||||||
|
|
||||||
|
例如微信/QQ只允许好友间发起一对一单聊;交友软件则常常可在非好友的情况下,进行有限度的聊天;在线娱乐社区软件,则常常不需要好友关系即可会话。
|
||||||
|
|
||||||
|
因此,您需要根据您的应用使用场景,确定好友及关系链管理的用法。
|
||||||
|
|
||||||
|
| 通讯录 |
|
||||||
|
|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/47db4f2a1f6ba187ab9f14e1ce3aa470.png" /> |
|
||||||
|
|
||||||
|
### 好友关系
|
||||||
|
|
||||||
|
腾讯云IM支持单个用户添加最多3000个好友。
|
||||||
|
|
||||||
|
#### 好友关系类型
|
||||||
|
|
||||||
|
好友关系类别包含单向好友和双向好友。
|
||||||
|
|
||||||
|
- 双向好友:用户 A 的好友表中有用户 B,B 的好友表中也有 A。
|
||||||
|
- 单向好友:用户 A 的好友表中有用户 B,但 B 的好友表中却没有 A。
|
||||||
|
|
||||||
|
#### 添加好友验证方式
|
||||||
|
|
||||||
|
一回合加好友:如果帐号 A 设置的加好友验证方式是 [AllowType_Type_AllowAny](https://cloud.tencent.com/document/product/269/1500#.E6.A0.87.E9.85.8D.E8.B5.84.E6.96.99.E5.AD.97.E6.AE.B5),那么任何人添加 A 为好友都可直接添加成功,这种一个请求就添加好友成功的场景称作一回合加好友。
|
||||||
|
|
||||||
|
两回合加好友:如果帐号 A 设置的加好友验证方式是 [AllowType_Type_NeedConfirm](https://cloud.tencent.com/document/product/269/1500#.E6.A0.87.E9.85.8D.E8.B5.84.E6.96.99.E5.AD.97.E6.AE.B5),那么任何人添加 A,A 都会收到一个请求加好友验证消息,这是第一个回合,然后 A 对这个请求加好友验证消息进行同意操作时,这是第二个回合,这种需要验证的加好友场景就被称为两回合加好友。
|
||||||
|
|
||||||
|
### 非好友发消息
|
||||||
|
|
||||||
|
对于某些场景,需要非好友关系也能发送消息。例如对于交友软件,常常允许匹配到的陌生人,发送若干条消息打招呼。
|
||||||
|
|
||||||
|
这需要您在[腾讯云IM的控制台](https://console.cloud.tencent.com/im/login-message),关闭 “好友关系检查” 功能。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果您需要针对陌生人发消息的数量加以限制,可在您的业务层实现。发送若干条消息后,不再允许发送即可。
|
||||||
|
|
||||||
|
## 群组
|
||||||
|
|
||||||
|
有的时候,仅一对一单聊无法满足您的社交场景要求。
|
||||||
|
|
||||||
|
例如对于音乐类型app,优质的乐迷群及粉丝群,有助于打造音乐社区文化,吸引用户留存。
|
||||||
|
|
||||||
|
### 群组类型
|
||||||
|
|
||||||
|
腾讯云IM支持多种类型的群,为便于理解,在这里以常见的群聊举例。
|
||||||
|
|
||||||
|
#### 微信群 - 好友工作群 Work
|
||||||
|
|
||||||
|
Work群,类似普通微信群。创建后仅支持已在群内的好友邀请加群,且无需被邀请方同意或群主审批。
|
||||||
|
|
||||||
|
该群适合用于打造好友间互动拉入,产生的群。
|
||||||
|
|
||||||
|
#### QQ群 - 陌生人社交群 Public
|
||||||
|
|
||||||
|
Public群,类似QQ群。创建后群主可指定群管理员,用户搜索群 ID 发起加群申请后,需要群主或管理员审批通过(可选)才能入群。
|
||||||
|
|
||||||
|
该群适合用于打造兴趣社区,用户在您的app中,发现好玩的兴趣群组,可按需主动加入。
|
||||||
|
|
||||||
|
#### 会议群 - Meeting
|
||||||
|
|
||||||
|
创建后可随意进出,且支持查看入群前消息。
|
||||||
|
|
||||||
|
适合用于音视频会议场景、在线教育场景等与实时音视频产品结合的场景。
|
||||||
|
|
||||||
|
#### 直播群 - AVChatRoom
|
||||||
|
|
||||||
|
创建后可随意进出,没有群成员数量上限,但不支持历史消息存储。
|
||||||
|
|
||||||
|
适合与直播产品结合,用于弹幕聊天场景。
|
||||||
|
|
||||||
|
#### Discord - 社群 Community
|
||||||
|
|
||||||
|
创建后可随意进出,最多支持10w人,支持历史消息存储,用户搜索群 ID 发起加群申请后,无需管理员审批即可进群。[详情可查看此文档](https://cloud.tencent.com/document/product/269/83870)。
|
||||||
|
|
||||||
|
### 群资料
|
||||||
|
|
||||||
|
群资料主要包括 群组本身的资料 和 群成员资料。
|
||||||
|
|
||||||
|
#### 群组本身的资料
|
||||||
|
|
||||||
|
群组资料是指单个群组维度的属性,包括群名称、简介、公告、群主等,以及群组维度自定义字段。
|
||||||
|
|
||||||
|
| Meeting 群 | Public 群 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/80ce0f7c90b3a2116d00a21154a5cc4c.jpg" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/f2c6b0d3badea438b15a49ad5cb6933b.jpg" /> |
|
||||||
|
|
||||||
|
##### 获取群资料
|
||||||
|
|
||||||
|
在客户端上,可调用 [`getGroupsInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupsInfo.html) 方法,获取特定群组资料详情。
|
||||||
|
|
||||||
|
##### 修改群资料
|
||||||
|
|
||||||
|
可修改群组名称、群组简介、群组公告、群组头像、群名片,修改加群选项、群纬度自定义字段、用户群内身份、群成员维度自定义字段和接收群消息选项等信息。
|
||||||
|
|
||||||
|
在客户端上,可调用 [`setGroupInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/setGroupInfo.html) 方法,修改特定群组资料详情。
|
||||||
|
|
||||||
|
#### 群成员资料
|
||||||
|
|
||||||
|
群成员资料主要包括,特定成员,在群内的备注名/角色/禁言状态/自定义字段信息。在强社交场景的群内,会非常实用。
|
||||||
|
|
||||||
|
##### 获取群成员资料
|
||||||
|
|
||||||
|
可通过客户端 [`getGroupMembersInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupMembersInfo.html) 方法或 [`getGroupMemberList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupMemberList.html) 方法获取此信息。
|
||||||
|
|
||||||
|
##### 修改群成员资料
|
||||||
|
|
||||||
|
不同的群成员资料,调用不同的API方法修改。[详情可查看此处](https://cloud.tencent.com/document/product/269/37411#.E7.BE.A4.E6.88.90.E5.91.98)。
|
||||||
|
|
||||||
|
## 会话
|
||||||
|
|
||||||
|
一个会话,您可理解为同某个特定用户的单聊,或一个群聊的消息集合。当用户创建了一个单聊或群聊,当其中有消息的收发时,对应的会话就随之创建。
|
||||||
|
|
||||||
|
在腾讯云IM层面,每个会话都是一个 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 类的实例,包括了 `会话类型 / 会话ID / 用户ID / 群ID / 显示名称 / 头像 / 最后一条消息 / 草稿 / 群聊类型 / 消息接收方式 / 群 @ 信息列表 / 是否置顶 / 标记列表 / 所属分组信息 / 自定义数据` 信息。
|
||||||
|
|
||||||
|
### 会话列表
|
||||||
|
|
||||||
|
会话列表,您可以理解成微信软件的首页。即,所有会话的集合。方便用户找到目标会话。
|
||||||
|
|
||||||
|
会话列表功能主要分为获取会话列表、处理会话列表更新。
|
||||||
|
|
||||||
|
| 会话列表 |
|
||||||
|
|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0f7ab6ee29f36a1faff6f933a06223d9.jpg" /> |
|
||||||
|
|
||||||
|
#### 获取会话列表
|
||||||
|
|
||||||
|
您可在客户端上调用 [`getConversationList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationList.html)。该接口获取的是本地缓存的会话,如果服务器会话有更新,SDK 内部会自动同步,然后在 [`V2TIMConversationListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimConversationListener.html) 通知回调。
|
||||||
|
|
||||||
|
如果您的应用场景会产生较多的会话数,考虑到加载效率、网络省流,我们建议您采用分页拉取的方式。每次分页拉取的数量建议不超过 100 个。[具体方案可参考此处。](https://cloud.tencent.com/document/product/269/75366#.E5.88.86.E9.A1.B5.E6.8B.89.E5.8F.96)
|
||||||
|
|
||||||
|
#### 会话列表实时更新
|
||||||
|
|
||||||
|
当会话信息发生变化,例如收到一条新消息/设置消息草稿/出现一个新的会话,都会导致会话列表发生更新。
|
||||||
|
|
||||||
|
如果需要实时获取更新信息,需要通过客户端的 [`addConversationListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/addConversationListener.html) 方法,添加会话监听器。收到更新触发后,更改UI。
|
||||||
|
|
||||||
|
### 会话草稿
|
||||||
|
|
||||||
|
在发送消息时,可能会遇到消息尚未编辑完,就要切换至其它聊天窗口的情况。
|
||||||
|
|
||||||
|
这些未编辑完的消息可通过 [`setConversationDraft`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/setConversationDraft.html) 接口保存,以便于下次回到这个聊天界面时,通过 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 对象的 [`draftText`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html#drafttext) 字段,获取到尚未编辑完的内容,继续编辑。
|
||||||
|
|
||||||
|
此类草稿信息,仅保存在本地。
|
||||||
|
|
||||||
|
### 置顶会话
|
||||||
|
|
||||||
|
会话置顶指的是把单聊或者群聊会话固定在会话列表的最顶部,不会被其他会话更新挤到底部,方便用户查找。
|
||||||
|
|
||||||
|
在社交场景中,用户常常需要将一些重要的人或群置顶。这在我们使用微信的过程中,很普遍。
|
||||||
|
|
||||||
|
置顶状态会存储在服务器,切换终端设备后,置顶状态会同步到新设备上。
|
||||||
|
|
||||||
|
| 置顶的会话,注意最上方 “Yifan” 会话 |
|
||||||
|
|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0f7ab6ee29f36a1faff6f933a06223d9.jpg" /> |
|
||||||
|
|
||||||
|
置顶会话,通过客户端 [`pinConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/pinConversation.html) 即可。
|
||||||
|
|
||||||
|
调用 [`getConversationList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationList.html) 获取会话列表时,该接口返回的会话列表中,置顶的会话在前,未置顶的会话在后。您可以通过 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 对象的 [`isPinned`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html#ispinned) 字段,检查会话有没有置顶。
|
||||||
|
|
||||||
|
### 会话标记
|
||||||
|
|
||||||
|
在某些社交场景下,您可能需要对会话进行标记,例如 "会话标星"、"会话折叠"、"会话隐藏"、“会话标记未读”。
|
||||||
|
|
||||||
|
直接在客户端调用 [`markConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/markConversation.html) 方法,即可标记,或取消标记一条会话。
|
||||||
|
|
||||||
|
标记后,在后续通过 [`getConversationListByFilter`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationListByFilter.html) 方法请求会话列表时,可按照标记,过滤获取指定会话。
|
||||||
|
|
||||||
|
## 消息
|
||||||
|
|
||||||
|
消息是社交模块的灵魂。众多各种类型的消息,组成了一个个会话,使得用户与用户之间,紧密的串联在一起。
|
||||||
|
|
||||||
|
腾讯云IM中,一对一单聊消息与群聊消息,用法在大部分场景中都类似,下面着重介绍几点。
|
||||||
|
|
||||||
|
### 消息类型
|
||||||
|
|
||||||
|
腾讯云IM支持多种类型的消息,如下:
|
||||||
|
|
||||||
|
| 功能类型 | 功能描述 |
|
||||||
|
| ------------ | ------------------------------------------------------------ |
|
||||||
|
| 文本消息 | 消息内容是普通文本 |
|
||||||
|
| 表情消息 | 表情消息为开发者自定义,可传入表情资源路径 |
|
||||||
|
| 地理位置消息 | 消息内容为地理位置标题、经度、纬度信息 |
|
||||||
|
| 图片消息 | 消息内容为图片的 URL 地址、尺寸、图片大小等信息,最大支持大小为28M的图片 |
|
||||||
|
| 语音消息 | 消息内容为语音文件的 URL 地址、大小、时长等信息,最大支持大小为28M的语音文件 |
|
||||||
|
| 文件消息 | 消息内容为文件的 URL 地址、大小、格式等信息,格式不限,最大支持大小为100M的文件 |
|
||||||
|
| 短视频消息 | 消息内容为短视频文件的 URL 地址、时长、大小、格式等信息,最大支持大小为100M的短视频文件 |
|
||||||
|
| 自定义消息 | 开发者自定义的消息类型,例如红包消息、石头剪刀布等形式的消息 |
|
||||||
|
|
||||||
|
### 消息回复
|
||||||
|
|
||||||
|
回复一条消息,既支持使用文字内容,发一条新消息,引用原消息;也支持使用[Emoji表情回应](https://cloud.tencent.com/document/product/269/85906)。
|
||||||
|
|
||||||
|
| 引用回复文本 | [表情回应](https://cloud.tencent.com/document/product/269/85906) |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/826d1f6c230b29aadcf62b0b1a53b3c6.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/d9b075b14d89d5394d22c01e43cd7e05.jpg" /> |
|
||||||
|
|
||||||
|
#### 引用回复文本
|
||||||
|
|
||||||
|
此方案效果和微信中,长按一条消息,选择 “引用”,效果一致。
|
||||||
|
|
||||||
|
引用消息,实际上,在腾讯云IM SDK层面,也是一条普通文本消息。文本消息的主体,则是回复的文字内容。
|
||||||
|
|
||||||
|
为了展示原消息的引用,需要在发送文本消息的时候,将原消息的信息,传入新消息的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中。例如,我们的TUIKit,为了实现这个功能,传入了如下JSON。
|
||||||
|
|
||||||
|
```json
|
||||||
|
"messageReply": {
|
||||||
|
"messageID": 原消息的ID,
|
||||||
|
"messageAbstract": 原消息的描述,用于显示在消息列表气泡中,
|
||||||
|
"messageSender": 原消息的发送者,建议使用备注名或昵称,
|
||||||
|
"messageType": 原消息类型,
|
||||||
|
"version": 协议版本
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在消息列表中展示时,从 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中,提取出上述JSON信息,直接用于拼接展示 `"${messageSender}: ${messageAbstract}"` 即可。
|
||||||
|
|
||||||
|
如需支持点击展示引用消息的区域,跳转至被引用原始消息。可根据上述JSON中的 `messageID` 字段,在消息列表中,找到这条消息,跳转即可。如果消息当前不存在于数组中,可直接调用 [`getHistoryMessageList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getHistoryMessageList.html),参数传入 `messageID`,获取本条消息及其上下文消息。
|
||||||
|
|
||||||
|
#### 表情回应
|
||||||
|
|
||||||
|
在回复特定的单条消息时,你不仅可以直接引用原消息并回复,还可使用Emoji表情回应,高效表达“好的”、“赞同”、“很棒”、“哭笑不得”、“加油”等多种信息,大大降低沟通成本,解决多人聊天中消息冗杂的问题。
|
||||||
|
|
||||||
|
通常,若干个用户,可对同一条消息,或多条消息,点击一个或若干个回应表情。[在显示上](https://cloud.tencent.com/document/product/269/85906#.E5.B1.95.E7.A4.BA.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94),这些回应信息,常常承载在不同的气泡中,以表情为首,后面跟着若干个名字。如本章节图片所示。
|
||||||
|
|
||||||
|
这些名称,需要支持点击,并跳转至用户Profile详情页中。若名字过多,还需要加以折叠,[通过新窗口详情页展示](https://cloud.tencent.com/document/product/269/85906#.E5.B1.95.E7.A4.BA.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94)。
|
||||||
|
|
||||||
|
[发送表情回应](https://cloud.tencent.com/document/product/269/85906#.E5.8F.91.E9.80.81.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94),则可放置于消息的长按菜单中。
|
||||||
|
|
||||||
|
| 发送表情回应 [TUIKit](https://cloud.tencent.com/document/product/269/85906) | 表情回应详情 [TUIKit](https://cloud.tencent.com/document/product/269/85906) |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/d9b075b14d89d5394d22c01e43cd7e05.jpg" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/678ae70ae8f810b50b0e57946306a329.jpg" /> |
|
||||||
|
|
||||||
|
**下面介绍实施细节:**
|
||||||
|
|
||||||
|
表情回应的数据,存储于消息的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中,可以如下JSON格式示例。其中的 `key` 字段,如采用Emoji Unicode表情字符,可直接传入 Unicode;若采用图片小表情,可传入路径或文件名。
|
||||||
|
|
||||||
|
```json
|
||||||
|
messageReactions: [
|
||||||
|
{
|
||||||
|
key: "表情名称1",
|
||||||
|
users: ["用户1", "用户2", ...]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "表情名称2",
|
||||||
|
users: ["用户1", "用户2", ...]
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
本部分代码可根据您的需求,加以修改。
|
||||||
|
|
||||||
|
展示时,渲染遍历上述结构体即可。
|
||||||
|
|
||||||
|
发送表情回应,则直接在客户端调用 [`modifyMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/modifyMessage.html) 方法,修改消息本身的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段即可。将当前用户头像,添加或从上述结构体中移除,完成回应或取消回应。
|
||||||
|
|
||||||
|
调用方法修改后,所有用户的 [`V2TimAdvancedMsgListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimAdvancedMsgListener.html) => [`onRecvMessageModified`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageModified.html) 监听器会触发,您可依此修改消息UI,展示最新表情回应内容。
|
||||||
|
|
||||||
|
### 删除消息
|
||||||
|
|
||||||
|
删除消息分为两种:删除本地消息和删除云端消息。
|
||||||
|
|
||||||
|
删除云端消息会在删除本地消息的基础上,同步删除云端存储的消息,且无法恢复。
|
||||||
|
|
||||||
|
删除本地消息,在客户端调用 [`deleteMessageFromLocalStorage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/deleteMessageFromLocalStorage.html) 方法。需要注意的是,如果程序卸载重装,依旧能获取到被删除的消息。
|
||||||
|
|
||||||
|
删除云端存储的消息,在客户端调用 [`deleteMessages`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/deleteMessages.html) 方法。此方法不支持多端同步,即无法自动删除,其他设备上,已经存在的消息。
|
||||||
|
|
||||||
|
### 搜索消息
|
||||||
|
|
||||||
|
搜索能力是社交场景中的重要一环。
|
||||||
|
|
||||||
|
用户们常常需要,在特定会话中,亦或是全局中,快速准确搜索到某条消息。此外,也可作为社交活动运营工具,增加相关内容的引导,简洁高效。
|
||||||
|
|
||||||
|
您可在客户端,调用 [`searchLocalMessages`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/searchLocalMessages.html) 方法,并传入以 [`V2TIMMessageSearchParam`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html) 对象封装的关键词信息,即可完成搜索。
|
||||||
|
|
||||||
|
如果您希望在全部会话范围内搜索,只需要将 [`V2TIMMessageSearchParam`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html) 中的 [`conversationID`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html#conversationid) 设置为空或者不设置即可。
|
||||||
|
|
||||||
|
| 全局搜索 | 会话内搜索 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/3ae930db6fae191337885529f3e94543.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/e4b17eff7775be1f2e12efa7ff92d309.png" /> |
|
||||||
|
|
||||||
|
### 转发消息
|
||||||
|
|
||||||
|
在日常生活聊天或工作场景中,将一个会话中的消息,合并或逐条转发至另一个会话,是个非常高频且基础的操作。
|
||||||
|
|
||||||
|
| 合并转发消息 | 合并消息详细内容 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/8d569af68336d64b0e64ca294048fa10.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/a8aa1f4c3c11713a2f9e19fdfae8bc8a.png" /> |
|
||||||
|
|
||||||
|
逐条转发消息,需要先在客户端调用 [`createForwardMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createForwardMessage.html) 方法创建一条和原消息内容完全一样的转发消息,再调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 方法把转发消息发送出去。
|
||||||
|
|
||||||
|
合并转发消息,同样需要先创建后转发。需要额外注意的是,在客户端调用 [`createMergerMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createMergerMessage.html) 方法创建一条合并消息时,需要设置原始消息列表,合并消息标题、合并消息摘要等信息。
|
||||||
|
|
||||||
|
若想转发至多个接收者,遍历调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 方法即可。
|
||||||
|
|
||||||
|
对于接收者端,若想展示上方右侧图片的合并消息详情,需要当用户点击合并消息的时候再调用 [`downloadMergerMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/downloadMergerMessage.html) 方法下载合并消息列表 UI 展示。
|
||||||
|
|
||||||
|
### 撤回消息
|
||||||
|
|
||||||
|
消息撤回是目前社交软件中必备的功能。
|
||||||
|
|
||||||
|
发送方可撤回一条已经发送成功的消息。默认情况下,发送者只能撤回 2 分钟以内的消息,此配置可按需修改。
|
||||||
|
|
||||||
|
撤回方在客户端,调用 [`revokeMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/revokeMessage.html) 方法,接收方会收到消息撤回通知 [`onRecvMessageRevoked`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageRevokedCallback.html)。通知中包含了撤回消息的 msgID,您可根据这个 msgID 判断 UI 层是哪一条消息撤回了,然后把对应的消息气泡切换成 "消息已被撤回" 状态。
|
||||||
|
|
||||||
|
### 消息翻译
|
||||||
|
|
||||||
|
对于国际化的聊天场景,消息翻译功能必不可少,可大大提升跨语言交流效率。社交场景中,大型群聊内,有不同语言的交流存在,是非常之常见的。
|
||||||
|
|
||||||
|
| 消息翻译 |
|
||||||
|
|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/d0e184a60525316d067e100702b7a8a9.png" /> |
|
||||||
|
|
||||||
|
对于文本类型的消息,您可在客户端上调用 [`translateText`](https://im.sdk.qcloud.com/doc/zh-cn/classcom_1_1tencent_1_1imsdk_1_1v2_1_1V2TIMMessageManager.html#a1e1806c27bc7b76a3b816492ed9cbe5c) 方法,将待翻译文本列表和目标语言传至我们的服务端。原语言可由您自行判断,也可由我们判断。
|
||||||
|
|
||||||
|
翻译结果返回后,我们建议您将其通过客户端的 [`setLocalCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/setLocalCustomData.html) 方法,存放于消息的 [`localCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#localcustomdata) 字段中,以便于后续直接展示,避免用户重复翻译,多次发送翻译请求。
|
||||||
|
|
||||||
|
我们支持众多语言的互相翻译,[所有支持的语言可查看此处](https://cloud.tencent.com/document/product/269/85380#.E6.96.87.E6.9C.AC.E7.BF.BB.E8.AF.91.E8.AF.AD.E8.A8.80.E6.94.AF.E6.8C.81)。
|
||||||
|
|
||||||
|
### 消息已读回执
|
||||||
|
|
||||||
|
单聊和群聊均支持消息已读回执功能,操作步骤一致。
|
||||||
|
|
||||||
|
| 单聊,[TUIKit](https://cloud.tencent.com/document/product/269/85905)中以文字承载 | 群聊,[TUIKit](https://cloud.tencent.com/document/product/269/85905)中以圆圈承载 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/48a728ec352b4d593a9e39878caea189.jpg" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/2c35eb7b775c974bda6420193feb4615.jpg" /> |
|
||||||
|
|
||||||
|
|
||||||
|
是否启用此功能,可根据您的社交业务需求决定。
|
||||||
|
|
||||||
|
例如对于类似微信的熟人社交,已读回执的用处可能不是非常大;但是对于陌生人交友场景,已读回执则十分重要,帮助用户来确认,对方是否愿意跟自己聊下去,是否已读不回;对于工作聊天场景,群已读回执还能发挥更大的作用,可便捷看到群内哪些人已读哪些人未读,帮助发送者确认信息传递效率。
|
||||||
|
|
||||||
|
**具体用法如下:**
|
||||||
|
|
||||||
|
发送端创建消息后,先通过消息对象 [`V2TIMMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html) 的 [`needReadReceipt`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#needreadreceipt) 字段设置这条消息需要已读回执,再发送消息到会话中。
|
||||||
|
|
||||||
|
接收端收到消息后,根据消息对象 [`V2TIMMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html) 的 [`needReadReceipt`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#needreadreceipt) 字段判断消息是否需要已读回执。如果需要,当用户查看消息后,调用 `sendMessageReadReceipts` 方法发送消息已读回执。
|
||||||
|
|
||||||
|
接收端发送消息已读回执后,发送端可在 [`V2TIMAdvancedMsgListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimAdvancedMsgListener.html) 的 [`onRecvMessageReadReceipts`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageReadReceipts.html) 中收到已读回执通知,在通知中更新 UI,例如更新某条消息为 “2 人已读”。
|
||||||
|
|
||||||
|
此外,发送端也可主动请求消息已读回执信息。发送端从其他界面进入消息列表后,先请求获取历史消息,再调用 [`getMessageReadReceipts`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getMessageReadReceipts.html) 方法请求获取消息已读回执信息。
|
||||||
|
|
||||||
|
群聊场景的消息已读回执,通常需要能够查看详情,显示群内哪些人已读,哪些人未读。当用户点击已读回执角标时,可调用 [`getGroupMessageReadMemberList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getGroupMessageReadMemberList.html) 方法分页拉取消息已读或未读群成员列表。
|
||||||
|
|
||||||
|
| 已读 群成员 | 未读 群成员 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/a10c1e9b99b6b422b6c18c9800ce5bb1.jpg" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/c561fd2902d946f98a992eb1f7335b92.jpg" /> |
|
||||||
|
|
||||||
|
### 群内@消息
|
||||||
|
|
||||||
|
相信大家已经很熟悉,在群聊交流过程中,如果需要提及或提醒某些群成员,我们可直接 @ 他们。所有的社交聊天软件,都有这个基础功能。
|
||||||
|
|
||||||
|
当用户输入 @ 字符后,弹出群成员选择界面。选择完需要 @ 的成员后以 “@A @B @C......” 形式显示在输入框,并可继续编辑消息内容,完成消息发送。
|
||||||
|
|
||||||
|
| 监听 @ 字符选择群成员 | 编辑群 @ 消息发送 | 收到群 @ 消息 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/2cabd456df97fa93080f715962ea0d0b.jpg" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/6de66fb0bbdeb28013f2e16e01a05908.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/9bd023628e15570e07250f5e7782387a.png" /> |
|
||||||
|
|
||||||
|
>? 图一:在聊天界面监听到输入框输入 "@" 字符后,可跳转到群成员选择界面,选择需要 @ 的群成员。
|
||||||
|
>图二:在群成员选择完成后,重新返回聊天界面,继续编辑群 @ 消息发送。
|
||||||
|
>图三:如果有消息 @ 我,自己会收到会话更新,可在会话 Cell 展示 “有人@我” 信息。
|
||||||
|
|
||||||
|
由于实现方案内容较多,[您可查看此文档](https://cloud.tencent.com/document/product/269/75349),获取详情。
|
||||||
|
|
||||||
|
### 消息漫游
|
||||||
|
|
||||||
|
如果用户有多台设备,或者同时使用电脑和手机登录您的应用程序,用户们希望看到,无论在哪一端,历史消息都能尽可能完整。能从提供的历史消息上下文中,快速无障碍的加入聊天,满足社交场景高频率聊天的要求。
|
||||||
|
|
||||||
|
为了保证交流的连续性与流畅性,我们提供一套消息漫游存储能力,即用户更换终端的情况下,也可以获取到跟其他用户或者某个群的聊天记录,可以达到类似QQ软件的效果。
|
||||||
|
|
||||||
|
默认情况下,单聊消息和群聊消息有7天漫游,超过漫游时长的消息会被删除。此外,还支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。
|
||||||
|
|
||||||
|
以下截图演示了消息在手机和电脑之间漫游。*图片来自Flutter TUIKit,一套代码完成电脑桌面端/Web端/移动端应用的开发。*
|
||||||
|
|
||||||
|
| 电脑端 | 手机移动端 |
|
||||||
|
|---------|---------|
|
||||||
|
| <img style="width:600px" src="https://qcloudimg.tencent-cloud.cn/raw/8644dd2335809cebc8058b75dfc8f14e.png" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/8df8e5b3204b22555ddd022c1a1c5715.jpg" /> |
|
||||||
|
|
||||||
|
### 更多丰富的消息形态
|
||||||
|
|
||||||
|
我们默认提供的消息类型,可满足您大部分的聊天场景需求。但是对于社交软件来说,仅有这些还远远不够。
|
||||||
|
|
||||||
|
红包/送礼物/投票/发送匹配度/闪照等等一系列创新玩法,让您app的社交场景模块变得更加丰富多彩。
|
||||||
|
|
||||||
|
因此,您可使用我们提供的**自定义消息**能力,来发挥您的想象力,尽情创造激动人心的玩法及贴合您业务需求的功能。
|
||||||
|
|
||||||
|
发送自定义消息分两步:
|
||||||
|
|
||||||
|
- 调用 [`createCustomMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createCustomMessage.html) 创建自定义消息,传入消息体。
|
||||||
|
- 调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 发送消息。
|
||||||
|
|
||||||
|
消息体中,您可以JSON格式,传入任何符合您需求的数据。例如,包含一个字段控制消息形态类型,还包含一个字段控制消息当前数据。
|
||||||
|
|
||||||
|
如果是投票/红包等类型的消息,如果您想实时更新投票数据/红包领取信息,可将此类信息放在消息体中,在客户端上,通过 [`modifyMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/modifyMessage.html) 方法实时修改。
|
||||||
|
|
||||||
|
## 更多能力
|
||||||
|
|
||||||
|
### 内容审核
|
||||||
|
|
||||||
|
在社交场景中,用户很可能会发送不合规的消息。特别是陌生人交友软件,黄色不良内容消息更是频频出现。诱导性暗示图片/裸聊等令人反感的内容,不仅严重损害了用户们的身心健康,更很有可能违法并导致应用被监管部门查封。
|
||||||
|
|
||||||
|
我们支持内容审核(反垃圾信息)功能,可针对不安全、不适宜的内容进行自动识别、处理,为您的产品体验和业务安全保驾护航。
|
||||||
|
|
||||||
|
目前有以下三种内容审核方式。
|
||||||
|
|
||||||
|
| 方式 | 介绍 |
|
||||||
|
|---------|---------|
|
||||||
|
| [本地审核](https://cloud.tencent.com/document/product/269/79139#bdsh) | 在客户端本地检测由即时通信 SDK 发送的文本内容,支持对已配置的敏感词进行拦截或者替换处理。此功能通过在 IM 开启服务并配置词库的方式实现。 |
|
||||||
|
| [云端审核](https://cloud.tencent.com/document/product/269/79139#ydsh) | 在服务端检测由单聊、群聊、资料场景中产生的文本、图片、音频、视频内容,支持针对不同场景的不同内容分别配置审核策略,并对识别出的不安全内容进行拦截。 |
|
||||||
|
| [第三方回调服务](https://cloud.tencent.com/document/product/269/79139#dsf) | 如果您已接入第三方内容审核服务,您可以使用 第三方回调配置 来实现。 |
|
||||||
|
|
||||||
|
您可按需使用如上三种内容审核工具,保证业务安全运行。
|
||||||
|
|
||||||
|
### 离线推送
|
||||||
|
|
||||||
|
社交场景下,用户需要随时都能够得知最新的消息,以加快聊天效率,促进社交关系的形成。
|
||||||
|
|
||||||
|
由于移动端设备的性能与电量有限,当 App 处于后台时,为了避免维持长连接而导致的过多资源消耗,我们推荐您使用各厂商提供的系统级推送通道来进行消息通知。系统级的推送通道相比第三方推送拥有更稳定的系统级长连接,可以做到随时接受推送消息,且资源消耗大幅降低。
|
||||||
|
|
||||||
|
我们目前原生支持的厂商系统有:苹果iOS/Google FCM/OPPO/VIVO/华为/小米/魅族/荣耀。
|
||||||
|
|
||||||
|
理论上,集成系统原生的离线推送,需要手动对接各个厂商的SDK,手动注册服务/获取Token/承载点击回调页面,非常之复杂。
|
||||||
|
|
||||||
|
因此,我们提供了如下几款离线推送插件,封装了上述厂商的原生SDK,大大降低了使用上手成本。您可直接按照文档配置。开箱即用。
|
||||||
|
|
||||||
|
- [Native](https://cloud.tencent.com/document/product/269/74285)
|
||||||
|
- [Flutter](https://cloud.tencent.com/document/product/269/74605)
|
||||||
|
- [uni-app](https://cloud.tencent.com/document/product/269/79124)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 音视频通话
|
||||||
|
|
||||||
|
许多时候,仅靠文字和图片还是不足以抒发我们内心的情感,可能打字聊天一小时,也比不是直接打一通视频电话来的爽快。
|
||||||
|
|
||||||
|
特别是对于社交场景下的用户们,他们一定有很多想法想要交流与诉说。
|
||||||
|
|
||||||
|
因此,我们也强烈推荐您,再继承我们的音视频通话能力,一步到位,完成一对一或多人群组的音频/视频通话,并且支持离线唤起能力。
|
||||||
|
|
||||||
|
[详情可查看此文档](https://cloud.tencent.com/document/product/269/84296)。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
以上,就是使用腾讯云IM实现社交场景常见需求的解决方案。快来使用腾讯云IM打造属于您的社交产品吧~
|
||||||
|
|
@ -109,10 +109,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_android
|
name: camera_android
|
||||||
sha256: "4cef01e8e78fe27c809a429bf74352ab94ab76b0c980e3ec708f1414614e3d9f"
|
sha256: e491c836147f60dd8a54cf3895fd2960e13b21b78a9d15b563a1b6c70894f142
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.3"
|
version: "0.10.4"
|
||||||
camera_avfoundation:
|
camera_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -125,10 +125,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_platform_interface
|
name: camera_platform_interface
|
||||||
sha256: "0eedd642d905ca24f1c483fe9ea0d0e7287b86a402845c28d24df28cc7b0ee6e"
|
sha256: b632be28e61d00a233f67d98ea90fd7041956f27a1c65500188ee459be60e15f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.4"
|
version: "2.4.0"
|
||||||
camera_web:
|
camera_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -189,10 +189,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: f71079978789bc2fe78d79227f1f8cfe195b31bbd8db2399b0d15a4b96fb843b
|
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+2"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -513,10 +513,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "39c013200046d14c58b71dc4fa3d00e425fc9c699d589136cd3ca018727c0493"
|
sha256: "8ffb14b43713d7c43fb21299cc18181cc5b39bd3ea1cc427a085c6400fe5aa52"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.6+6"
|
version: "0.8.6+7"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1080,23 +1080,23 @@ packages:
|
||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.5.0+1"
|
version: "1.6.0"
|
||||||
tencent_extended_text:
|
tencent_extended_text:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: tencent_extended_text
|
name: tencent_extended_text
|
||||||
sha256: "2904d064eeb9d3395f7d31bdc9f168ee75e7783f20161b1a6eb4647677a56721"
|
sha256: "27a2f7ee58ada480e295102471f1733a7402178a239d0c80a7aa33a134c641ef"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.2"
|
||||||
tencent_extended_text_field:
|
tencent_extended_text_field:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: tencent_extended_text_field
|
name: tencent_extended_text_field
|
||||||
sha256: "4bb5bb3863792b7cee48d76cd100b0084906baa2bf4e1a917283f5de62076b0b"
|
sha256: d311c240983dbf78e31b58f91e425920a40d6564942813e692a3419bf5c9deb0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
tencent_extended_text_library:
|
tencent_extended_text_library:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1229,10 +1229,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809"
|
sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.8"
|
version: "6.1.9"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1245,10 +1245,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3
|
sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.18"
|
version: "6.1.0"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1442,5 +1442,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.0 <4.0.0"
|
dart: ">=2.19.0 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.7.0"
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,21 @@ class TUIConversationViewModel extends ChangeNotifier {
|
||||||
static V2TimConversation? _selectedConversation;
|
static V2TimConversation? _selectedConversation;
|
||||||
bool _haveMoreData = true;
|
bool _haveMoreData = true;
|
||||||
int _totalUnReadCount = 0;
|
int _totalUnReadCount = 0;
|
||||||
|
String? _scrollToConversation;
|
||||||
String _nextSeq = "0";
|
String _nextSeq = "0";
|
||||||
ConversationLifeCycle? _lifeCycle;
|
ConversationLifeCycle? _lifeCycle;
|
||||||
|
|
||||||
|
String? get scrollToConversation => _scrollToConversation;
|
||||||
|
|
||||||
|
set scrollToConversation(String? value) {
|
||||||
|
_scrollToConversation = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearScrollToConversation(){
|
||||||
|
_scrollToConversation = null;
|
||||||
|
}
|
||||||
|
|
||||||
List<V2TimConversation?> get conversationList {
|
List<V2TimConversation?> get conversationList {
|
||||||
if (PlatformUtils().isWeb) {
|
if (PlatformUtils().isWeb) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -113,7 +124,7 @@ class TUIConversationViewModel extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadInitConversation() async {
|
loadInitConversation() async {
|
||||||
await loadData(count: 20);
|
await loadData(count: 40);
|
||||||
_chatGlobalModel.initMessageMapFromLocalDatabase(_conversationList);
|
_chatGlobalModel.initMessageMapFromLocalDatabase(_conversationList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class TIMUIKitConversationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the conversation list to UI
|
/// Load the conversation list to UI
|
||||||
loadData({int count = 20}) {
|
loadData({int count = 40}) {
|
||||||
model.loadData(count: count);
|
model.loadData(count: count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,4 +63,10 @@ class TIMUIKitConversationController {
|
||||||
dispose() {
|
dispose() {
|
||||||
model.clearData();
|
model.clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll to a specific conversation, this conversation must be existed in conversation list.
|
||||||
|
/// If not exist, invoking `loadData` recursively, until find the target conversation.
|
||||||
|
scrollToConversation(String conversationID){
|
||||||
|
model.scrollToConversation = conversationID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
@ -354,7 +353,7 @@ class _TIMUIKitHistoryMessageListState
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: theme.chatBgColor ,
|
color: theme.chatBgColor,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -369,24 +368,27 @@ class _TIMUIKitHistoryMessageListState
|
||||||
// itemExtent: widget.mainHistoryListConfig?.itemExtent,
|
// itemExtent: widget.mainHistoryListConfig?.itemExtent,
|
||||||
// prototypeItem: widget.mainHistoryListConfig?.prototypeItem,
|
// prototypeItem: widget.mainHistoryListConfig?.prototypeItem,
|
||||||
cacheExtent: widget.mainHistoryListConfig?.cacheExtent ?? 1500,
|
cacheExtent: widget.mainHistoryListConfig?.cacheExtent ?? 1500,
|
||||||
semanticChildCount: widget.mainHistoryListConfig?.semanticChildCount,
|
semanticChildCount:
|
||||||
dragStartBehavior: widget.mainHistoryListConfig?.dragStartBehavior ??
|
widget.mainHistoryListConfig?.semanticChildCount,
|
||||||
DragStartBehavior.start,
|
dragStartBehavior:
|
||||||
|
widget.mainHistoryListConfig?.dragStartBehavior ??
|
||||||
|
DragStartBehavior.start,
|
||||||
keyboardDismissBehavior:
|
keyboardDismissBehavior:
|
||||||
widget.mainHistoryListConfig?.keyboardDismissBehavior ??
|
widget.mainHistoryListConfig?.keyboardDismissBehavior ??
|
||||||
ScrollViewKeyboardDismissBehavior.manual,
|
ScrollViewKeyboardDismissBehavior.manual,
|
||||||
restorationId: widget.mainHistoryListConfig?.restorationId,
|
restorationId: widget.mainHistoryListConfig?.restorationId,
|
||||||
clipBehavior:
|
clipBehavior:
|
||||||
widget.mainHistoryListConfig?.clipBehavior ?? Clip.hardEdge,
|
widget.mainHistoryListConfig?.clipBehavior ?? Clip.hardEdge,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
shrinkWrap: !shouldShowUnreadMessage,
|
shrinkWrap: !shouldShowUnreadMessage,
|
||||||
controller: _autoScrollController,
|
controller: _autoScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
|
padding:
|
||||||
|
widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
|
||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
final messageItem = unreadMessageList[index];
|
final messageItem = unreadMessageList[index];
|
||||||
return AutoScrollTag(
|
return AutoScrollTag(
|
||||||
controller: _autoScrollController,
|
controller: _autoScrollController,
|
||||||
|
|
@ -395,20 +397,23 @@ class _TIMUIKitHistoryMessageListState
|
||||||
getMessageIdentifier(messageItem, index)),
|
getMessageIdentifier(messageItem, index)),
|
||||||
highlightColor: Colors.black.withOpacity(0.1),
|
highlightColor: Colors.black.withOpacity(0.1),
|
||||||
child: KeepAliveWrapper(
|
child: KeepAliveWrapper(
|
||||||
|
keepAlive: messageItem?.elemType ==
|
||||||
|
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16),
|
horizontal: 16),
|
||||||
child: _getMessageItemBuilder(messageItem))),
|
child:
|
||||||
|
_getMessageItemBuilder(messageItem))),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount: unreadMessageList.length,
|
childCount: unreadMessageList.length,
|
||||||
findChildIndexCallback: (Key key) {
|
findChildIndexCallback: (Key key) {
|
||||||
final ValueKey<String> valueKey =
|
final ValueKey<String> valueKey =
|
||||||
key as ValueKey<String>;
|
key as ValueKey<String>;
|
||||||
final String data = valueKey.value;
|
final String data = valueKey.value;
|
||||||
final int index = unreadMessageList.indexWhere(
|
final int index = unreadMessageList.indexWhere(
|
||||||
(element) =>
|
(element) =>
|
||||||
getMessageIdentifier(element, 0) == data);
|
getMessageIdentifier(element, 0) == data);
|
||||||
return index != -1 ? index : null;
|
return index != -1 ? index : null;
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
|
|
@ -417,10 +422,11 @@ class _TIMUIKitHistoryMessageListState
|
||||||
key: centerKey,
|
key: centerKey,
|
||||||
),
|
),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
|
padding:
|
||||||
|
widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
|
||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
final messageItem = readMessageList[index];
|
final messageItem = readMessageList[index];
|
||||||
if (index == readMessageList.length - 1) {
|
if (index == readMessageList.length - 1) {
|
||||||
if (widget.model.haveMoreData) {
|
if (widget.model.haveMoreData) {
|
||||||
|
|
@ -434,15 +440,19 @@ class _TIMUIKitHistoryMessageListState
|
||||||
AutoScrollTag(
|
AutoScrollTag(
|
||||||
controller: _autoScrollController,
|
controller: _autoScrollController,
|
||||||
index: -index,
|
index: -index,
|
||||||
key: ValueKey(
|
key: ValueKey(getMessageIdentifier(
|
||||||
getMessageIdentifier(messageItem, index)),
|
messageItem, index)),
|
||||||
highlightColor: Colors.black.withOpacity(0.1),
|
highlightColor:
|
||||||
|
Colors.black.withOpacity(0.1),
|
||||||
child: KeepAliveWrapper(
|
child: KeepAliveWrapper(
|
||||||
child: Container(
|
keepAlive: messageItem?.elemType ==
|
||||||
padding: const EdgeInsets.symmetric(
|
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||||
horizontal: 16),
|
child: Container(
|
||||||
child: _getMessageItemBuilder(
|
padding: const EdgeInsets.symmetric(
|
||||||
messageItem))),
|
horizontal: 16),
|
||||||
|
child: _getMessageItemBuilder(
|
||||||
|
messageItem)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -455,24 +465,26 @@ class _TIMUIKitHistoryMessageListState
|
||||||
getMessageIdentifier(messageItem, index)),
|
getMessageIdentifier(messageItem, index)),
|
||||||
highlightColor: Colors.black.withOpacity(0.1),
|
highlightColor: Colors.black.withOpacity(0.1),
|
||||||
child: KeepAliveWrapper(
|
child: KeepAliveWrapper(
|
||||||
|
keepAlive: messageItem?.elemType ==
|
||||||
|
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16),
|
horizontal: 16),
|
||||||
child: _getMessageItemBuilder(messageItem))),
|
child:
|
||||||
|
_getMessageItemBuilder(messageItem))),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
addAutomaticKeepAlives: true,
|
||||||
childCount: readMessageList.length,
|
childCount: readMessageList.length,
|
||||||
findChildIndexCallback: (Key key) {
|
findChildIndexCallback: (Key key) {
|
||||||
final ValueKey<String> valueKey =
|
final ValueKey<String> valueKey =
|
||||||
key as ValueKey<String>;
|
key as ValueKey<String>;
|
||||||
final String data = valueKey.value;
|
final String data = valueKey.value;
|
||||||
final int index = readMessageList.indexWhere(
|
final int index = readMessageList.indexWhere(
|
||||||
(element) =>
|
(element) =>
|
||||||
getMessageIdentifier(element, 0) == data);
|
getMessageIdentifier(element, 0) == data);
|
||||||
return index > -1 ? index : null;
|
return index > -1 ? index : null;
|
||||||
}
|
})),
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -500,9 +512,11 @@ class _TIMUIKitHistoryMessageListState
|
||||||
class TIMUIKitHistoryMessageListSelector extends TIMUIKitStatelessWidget {
|
class TIMUIKitHistoryMessageListSelector extends TIMUIKitStatelessWidget {
|
||||||
final Widget Function(BuildContext, List<V2TimMessage?>, Widget?) builder;
|
final Widget Function(BuildContext, List<V2TimMessage?>, Widget?) builder;
|
||||||
final String conversationID;
|
final String conversationID;
|
||||||
|
|
||||||
TIMUIKitHistoryMessageListSelector(
|
TIMUIKitHistoryMessageListSelector(
|
||||||
{Key? key, required this.builder, required this.conversationID})
|
{Key? key, required this.builder, required this.conversationID})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||||
return Selector<TUIChatGlobalModel, List<V2TimMessage?>>(
|
return Selector<TUIChatGlobalModel, List<V2TimMessage?>>(
|
||||||
|
|
|
||||||
|
|
@ -419,12 +419,6 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
||||||
? AlignmentDirectional.topEnd
|
? AlignmentDirectional.topEnd
|
||||||
: AlignmentDirectional.topStart,
|
: AlignmentDirectional.topStart,
|
||||||
children: [
|
children: [
|
||||||
// AspectRatio(
|
|
||||||
// aspectRatio: networkImagePositionRadio ?? positionRadio,
|
|
||||||
// child: Container(
|
|
||||||
// decoration: const BoxDecoration(color: Colors.white),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
getImage(
|
getImage(
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||||
import 'package:flutter_slidable_for_tencent_im/flutter_slidable.dart';
|
import 'package:flutter_slidable_for_tencent_im/flutter_slidable.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
|
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart';
|
||||||
|
|
@ -9,6 +10,7 @@ import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversa
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
|
||||||
|
|
||||||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||||
|
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/ui/controller/tim_uikit_conversation_controller.dart';
|
import 'package:tencent_cloud_chat_uikit/ui/controller/tim_uikit_conversation_controller.dart';
|
||||||
|
|
||||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||||
|
|
@ -140,6 +142,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||||
final TUIFriendShipViewModel friendShipViewModel =
|
final TUIFriendShipViewModel friendShipViewModel =
|
||||||
serviceLocator<TUIFriendShipViewModel>();
|
serviceLocator<TUIFriendShipViewModel>();
|
||||||
|
late AutoScrollController _autoScrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -147,7 +150,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
final controller = getController();
|
final controller = getController();
|
||||||
_timuiKitConversationController = controller;
|
_timuiKitConversationController = controller;
|
||||||
_timuiKitConversationController.model = model;
|
_timuiKitConversationController.model = model;
|
||||||
// _timuiKitConversationController.loadData();
|
_autoScrollController = AutoScrollController();
|
||||||
}
|
}
|
||||||
|
|
||||||
TIMUIKitConversationController getController() {
|
TIMUIKitConversationController getController() {
|
||||||
|
|
@ -219,12 +222,46 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
return widget.itemSlidableBuilder ?? _defaultSlidableBuilder;
|
return widget.itemSlidableBuilder ?? _defaultSlidableBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onScrollToConversation(String conversationID) {
|
||||||
|
final msgList = getFilteredConversation();
|
||||||
|
bool isFound = false;
|
||||||
|
int targetIndex = 1;
|
||||||
|
for (int i = msgList.length - 1; i >= 0; i--) {
|
||||||
|
final currentConversation = msgList[i];
|
||||||
|
if (currentConversation?.conversationID == conversationID) {
|
||||||
|
isFound = true;
|
||||||
|
targetIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isFound) {
|
||||||
|
_autoScrollController.scrollToIndex(
|
||||||
|
targetIndex,
|
||||||
|
preferPosition: AutoScrollPosition.begin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
// model.dispose();
|
// model.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<V2TimConversation?> getFilteredConversation(){
|
||||||
|
List<V2TimConversation?> filteredConversationList = model
|
||||||
|
.conversationList
|
||||||
|
.where((element) =>
|
||||||
|
(element?.groupID != null || element?.userID != null))
|
||||||
|
.toList();
|
||||||
|
if (widget.conversationCollector != null) {
|
||||||
|
filteredConversationList = filteredConversationList
|
||||||
|
.where(widget.conversationCollector!)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return filteredConversationList;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||||
final theme = value.theme;
|
final theme = value.theme;
|
||||||
|
|
@ -235,19 +272,16 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
],
|
],
|
||||||
builder: (BuildContext context, Widget? w) {
|
builder: (BuildContext context, Widget? w) {
|
||||||
final _model = Provider.of<TUIConversationViewModel>(context);
|
final _model = Provider.of<TUIConversationViewModel>(context);
|
||||||
|
bool haveMoreData = _model.haveMoreData;
|
||||||
final _friendShipViewModel =
|
final _friendShipViewModel =
|
||||||
Provider.of<TUIFriendShipViewModel>(context);
|
Provider.of<TUIFriendShipViewModel>(context);
|
||||||
_model.lifeCycle = widget.lifeCycle;
|
_model.lifeCycle = widget.lifeCycle;
|
||||||
List<V2TimConversation?> filteredConversationList = _model
|
|
||||||
.conversationList
|
List<V2TimConversation?> filteredConversationList = getFilteredConversation();
|
||||||
.where((element) =>
|
|
||||||
(element?.groupID != null || element?.userID != null))
|
if(TencentUtils.checkString(_model.scrollToConversation) != null){
|
||||||
.toList();
|
_onScrollToConversation(_model.scrollToConversation!);
|
||||||
bool haveMoreData = _model.haveMoreData;
|
_model.clearScrollToConversation();
|
||||||
if (widget.conversationCollector != null) {
|
|
||||||
filteredConversationList = filteredConversationList
|
|
||||||
.where(widget.conversationCollector!)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SlidableAutoCloseBehavior(
|
return SlidableAutoCloseBehavior(
|
||||||
|
|
@ -258,6 +292,7 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
},
|
},
|
||||||
child: filteredConversationList.isNotEmpty
|
child: filteredConversationList.isNotEmpty
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
|
controller: _autoScrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: filteredConversationList.length,
|
itemCount: filteredConversationList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
@ -283,37 +318,42 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
|
||||||
|
|
||||||
final slidableChildren =
|
final slidableChildren =
|
||||||
_getSlidableBuilder()(conversationItem!);
|
_getSlidableBuilder()(conversationItem!);
|
||||||
return Slidable(
|
return AutoScrollTag(
|
||||||
groupTag: 'conversation-list',
|
key: ValueKey(conversationItem.conversationID),
|
||||||
child: InkWell(
|
controller: _autoScrollController,
|
||||||
child: TIMUIKitConversationItem(
|
index: index,
|
||||||
isShowDraft: widget.isShowDraft,
|
child: Slidable(
|
||||||
lastMessageBuilder: widget.lastMessageBuilder,
|
groupTag: 'conversation-list',
|
||||||
faceUrl: conversationItem.faceUrl ?? "",
|
child: InkWell(
|
||||||
nickName: conversationItem.showName ?? "",
|
child: TIMUIKitConversationItem(
|
||||||
isDisturb: conversationItem.recvOpt != 0,
|
isShowDraft: widget.isShowDraft,
|
||||||
lastMsg: conversationItem.lastMessage,
|
lastMessageBuilder: widget.lastMessageBuilder,
|
||||||
isPined: conversationItem.isPinned ?? false,
|
faceUrl: conversationItem.faceUrl ?? "",
|
||||||
groupAtInfoList:
|
nickName: conversationItem.showName ?? "",
|
||||||
conversationItem.groupAtInfoList ?? [],
|
isDisturb: conversationItem.recvOpt != 0,
|
||||||
unreadCount:
|
lastMsg: conversationItem.lastMessage,
|
||||||
conversationItem.unreadCount ?? 0,
|
isPined: conversationItem.isPinned ?? false,
|
||||||
draftText: conversationItem.draftText,
|
groupAtInfoList:
|
||||||
onlineStatus: (widget.isShowOnlineStatus &&
|
conversationItem.groupAtInfoList ?? [],
|
||||||
conversationItem.userID != null &&
|
unreadCount:
|
||||||
conversationItem.userID!.isNotEmpty)
|
conversationItem.unreadCount ?? 0,
|
||||||
? onlineStatus
|
draftText: conversationItem.draftText,
|
||||||
: null,
|
onlineStatus: (widget.isShowOnlineStatus &&
|
||||||
draftTimestamp:
|
conversationItem.userID != null &&
|
||||||
conversationItem.draftTimestamp,
|
conversationItem.userID!.isNotEmpty)
|
||||||
convType: conversationItem.type),
|
? onlineStatus
|
||||||
onTap: () => onTapConvItem(conversationItem),
|
: null,
|
||||||
),
|
draftTimestamp:
|
||||||
endActionPane: ActionPane(
|
conversationItem.draftTimestamp,
|
||||||
extentRatio:
|
convType: conversationItem.type),
|
||||||
slidableChildren.length > 2 ? 0.77 : 0.5,
|
onTap: () => onTapConvItem(conversationItem),
|
||||||
motion: const DrawerMotion(),
|
),
|
||||||
children: slidableChildren));
|
endActionPane: ActionPane(
|
||||||
|
extentRatio:
|
||||||
|
slidableChildren.length > 2 ? 0.77 : 0.5,
|
||||||
|
motion: const DrawerMotion(),
|
||||||
|
children: slidableChildren)),
|
||||||
|
);
|
||||||
})
|
})
|
||||||
: (widget.emptyBuilder != null
|
: (widget.emptyBuilder != null
|
||||||
? widget.emptyBuilder!()
|
? widget.emptyBuilder!()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class LinkPreviewEntry {
|
||||||
/// If you provide `onUpdateMessage(String linkInfoJson)`, it can save the link info to local custom data than call updating the message on UI automatically.
|
/// If you provide `onUpdateMessage(String linkInfoJson)`, it can save the link info to local custom data than call updating the message on UI automatically.
|
||||||
static Future<LinkPreviewContent?> getFirstLinkPreviewContent(
|
static Future<LinkPreviewContent?> getFirstLinkPreviewContent(
|
||||||
{required V2TimMessage message, VoidCallback? onUpdateMessage}) async {
|
{required V2TimMessage message, VoidCallback? onUpdateMessage}) async {
|
||||||
final String? messageText = message.textElem!.text;
|
final String? messageText = message.textElem?.text;
|
||||||
if (messageText == null) {
|
if (messageText == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ class LinkPreviewEntry {
|
||||||
/// get the [LinkPreviewContent] with preview widget and website information for all the links
|
/// get the [LinkPreviewContent] with preview widget and website information for all the links
|
||||||
static Future<List<LinkPreviewContent?>?> getAllLinkPreviewContent(
|
static Future<List<LinkPreviewContent?>?> getAllLinkPreviewContent(
|
||||||
V2TimMessage message) async {
|
V2TimMessage message) async {
|
||||||
final String? messageText = message.textElem!.text;
|
final String? messageText = message.textElem?.text;
|
||||||
if (messageText == null) {
|
if (messageText == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
pubspec.lock
34
pubspec.lock
|
|
@ -149,10 +149,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_android
|
name: camera_android
|
||||||
sha256: "4cef01e8e78fe27c809a429bf74352ab94ab76b0c980e3ec708f1414614e3d9f"
|
sha256: e491c836147f60dd8a54cf3895fd2960e13b21b78a9d15b563a1b6c70894f142
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.3"
|
version: "0.10.4"
|
||||||
camera_avfoundation:
|
camera_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -165,10 +165,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_platform_interface
|
name: camera_platform_interface
|
||||||
sha256: "0eedd642d905ca24f1c483fe9ea0d0e7287b86a402845c28d24df28cc7b0ee6e"
|
sha256: b632be28e61d00a233f67d98ea90fd7041956f27a1c65500188ee459be60e15f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.4"
|
version: "2.4.0"
|
||||||
camera_web:
|
camera_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -245,10 +245,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: f71079978789bc2fe78d79227f1f8cfe195b31bbd8db2399b0d15a4b96fb843b
|
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+2"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -596,10 +596,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "39c013200046d14c58b71dc4fa3d00e425fc9c699d589136cd3ca018727c0493"
|
sha256: "8ffb14b43713d7c43fb21299cc18181cc5b39bd3ea1cc427a085c6400fe5aa52"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.6+6"
|
version: "0.8.6+7"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1193,18 +1193,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: tencent_extended_text
|
name: tencent_extended_text
|
||||||
sha256: "2904d064eeb9d3395f7d31bdc9f168ee75e7783f20161b1a6eb4647677a56721"
|
sha256: "27a2f7ee58ada480e295102471f1733a7402178a239d0c80a7aa33a134c641ef"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.2"
|
||||||
tencent_extended_text_field:
|
tencent_extended_text_field:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: tencent_extended_text_field
|
name: tencent_extended_text_field
|
||||||
sha256: "4bb5bb3863792b7cee48d76cd100b0084906baa2bf4e1a917283f5de62076b0b"
|
sha256: d311c240983dbf78e31b58f91e425920a40d6564942813e692a3419bf5c9deb0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
tencent_extended_text_library:
|
tencent_extended_text_library:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1329,10 +1329,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809"
|
sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.8"
|
version: "6.1.9"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1345,10 +1345,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3
|
sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.18"
|
version: "6.1.0"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1551,4 +1551,4 @@ packages:
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.0 <4.0.0"
|
dart: ">=2.19.0 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.7.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: tencent_cloud_chat_uikit
|
name: tencent_cloud_chat_uikit
|
||||||
description: Chat UI components library and basic chat business logic for Tencent Cloud Chat, helping you build In-APP Chat module easily.
|
description: Chat UI components library and basic chat business logic for Tencent Cloud Chat, helping you build In-APP Chat module easily.
|
||||||
version: 1.5.0+1
|
version: 1.6.0
|
||||||
homepage: https://www.tencentcloud.com/products/im?from=pub
|
homepage: https://www.tencentcloud.com/products/im?from=pub
|
||||||
repository: https://github.com/TencentCloud/chat-uikit-flutter
|
repository: https://github.com/TencentCloud/chat-uikit-flutter
|
||||||
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
|
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue