Upgrade TUIKit to 2.1.2
This commit is contained in:
parent
2f001240cd
commit
455c6d0800
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -1,3 +1,29 @@
|
||||||
|
## 2.1.2
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
* Introduced a new message recall mode, which enables group administrators to recall any message from any group member. To enable this feature, set `isGroupAdminRecallEnabled` in `TIMUIKitChatConfig` to `true`.
|
||||||
|
* Added support for draft text functionality on the Web. Activate this feature by setting `isUseDraftOnWeb` in `TIMUIKitChatConfig` to `true`. Since the Chat SDK doesn't support this functionality, draft data will be stored in TUIKit's memory. Be aware that draft text will be lost upon refreshing the website.
|
||||||
|
* Enabled using the default message abstract text when `abstractMessageBuilder` returns `null`.
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* The duration for video messages sent from the Web will no longer be displayed, as this type of video message does not contain an accurate video duration.
|
||||||
|
* Removed the hover color on the message input area on Desktop.
|
||||||
|
* Added auto-focus support for the message input area on Desktop.
|
||||||
|
* Enhanced the rendering of text messages in markdown mode, particularly for clickable link extraction and HTML tag handling.
|
||||||
|
* Limited the number of lines displayed for replied messages to a maximum of 2 lines to avoid occupying excessive space.
|
||||||
|
* Optimized the message replying process, ensuring that a message referencing another message can still display the replied message, even when it is too old.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fixed an issue that could cause the profile page to display no data.
|
||||||
|
* Fixed an issue that could prevent the message sending button from being displayed after selecting an emoji on mobile Web.
|
||||||
|
* Fixed an issue that could prevent the message long-press menu from showing on mobile Web.
|
||||||
|
* Fixed an issue where editing a message would carry over to another conversation when switching between conversations.
|
||||||
|
* Fixed an issue that could prevent displaying the `Modal` on Desktop.
|
||||||
|
* Fixed an issue that caused the `iconImageAsset` from the `MessageToolTipItem` class to not work properly.
|
||||||
|
|
||||||
## 2.1.0+2
|
## 2.1.0+2
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
|
||||||
|
|
@ -1139,7 +1139,7 @@ packages:
|
||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "2.1.0+2"
|
version: "2.1.2"
|
||||||
tencent_cloud_uikit_core:
|
tencent_cloud_uikit_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart';
|
||||||
import 'package:tencent_im_base/tencent_im_base.dart';
|
import 'package:tencent_im_base/tencent_im_base.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
|
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
|
||||||
|
|
@ -16,7 +19,7 @@ class TUIChatModelTools {
|
||||||
if (globalModel.chatConfig.offlinePushInfo != null) {
|
if (globalModel.chatConfig.offlinePushInfo != null) {
|
||||||
final customData =
|
final customData =
|
||||||
globalModel.chatConfig.offlinePushInfo!(message, convID, convType);
|
globalModel.chatConfig.offlinePushInfo!(message, convID, convType);
|
||||||
if(customData != null){
|
if (customData != null) {
|
||||||
return customData;
|
return customData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +107,54 @@ class TUIChatModelTools {
|
||||||
return messageInfo;
|
return messageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getMessageSummary(V2TimMessage message,
|
||||||
|
String? Function(V2TimMessage message)? abstractMessageBuilder) {
|
||||||
|
final String? customAbstractMessage =
|
||||||
|
abstractMessageBuilder != null ? abstractMessageBuilder(message) : null;
|
||||||
|
if (customAbstractMessage != null) {
|
||||||
|
return customAbstractMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
final elemType = message.elemType;
|
||||||
|
switch (elemType) {
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
|
||||||
|
return "[表情消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
|
||||||
|
return "[自定义消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
|
||||||
|
return "[文件消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
|
||||||
|
return "[群消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
|
||||||
|
return "[图片消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
|
||||||
|
return "[位置消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
|
||||||
|
return "[合并消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_NONE:
|
||||||
|
return "[没有元素]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
|
||||||
|
return "[语音消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
|
||||||
|
return message.textElem?.text ?? "[文本消息]";
|
||||||
|
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
|
||||||
|
return "[视频消息]";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMessageAbstract(V2TimMessage message,
|
||||||
|
String? Function(V2TimMessage message)? abstractMessageBuilder) {
|
||||||
|
final messageAbstract = RepliedMessageAbstract(
|
||||||
|
summary: TIM_t(getMessageSummary(message, abstractMessageBuilder)),
|
||||||
|
elemType: message.elemType,
|
||||||
|
msgID: message.msgID,
|
||||||
|
timestamp: message.timestamp,
|
||||||
|
seq: message.seq);
|
||||||
|
return jsonEncode(messageAbstract.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
Future<V2TimMessage?> getExistingMessageByID(
|
Future<V2TimMessage?> getExistingMessageByID(
|
||||||
{required String msgID,
|
{required String msgID,
|
||||||
required String conversationID,
|
required String conversationID,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.
|
||||||
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/ui/constants/history_message_constant.dart';
|
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
enum LoadDirection { previous, latest }
|
enum LoadDirection { previous, latest }
|
||||||
|
|
@ -44,7 +43,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
bool haveMoreData = false;
|
bool haveMoreData = false;
|
||||||
bool haveMoreLatestData = false;
|
bool haveMoreLatestData = false;
|
||||||
String _currentPlayedMsgId = "";
|
String _currentPlayedMsgId = "";
|
||||||
String _editRevokedMsg = "";
|
|
||||||
GroupReceiptAllowType? _groupType;
|
GroupReceiptAllowType? _groupType;
|
||||||
List<V2TimMessage> _multiSelectedMessageList = [];
|
List<V2TimMessage> _multiSelectedMessageList = [];
|
||||||
V2TimMessage? _repliedMessage;
|
V2TimMessage? _repliedMessage;
|
||||||
|
|
@ -108,13 +106,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get editRevokedMsg => _editRevokedMsg;
|
|
||||||
|
|
||||||
set editRevokedMsg(String value) {
|
|
||||||
_editRevokedMsg = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupReceiptAllowType? get groupType => _groupType;
|
GroupReceiptAllowType? get groupType => _groupType;
|
||||||
|
|
||||||
set groupType(GroupReceiptAllowType? value) {
|
set groupType(GroupReceiptAllowType? value) {
|
||||||
|
|
@ -810,54 +801,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getMessageAbstract(V2TimMessage message){
|
|
||||||
final messageAbstract = RepliedMessageAbstract(
|
|
||||||
summary: TIM_t(_getMessageSummary(message)),
|
|
||||||
elemType: message.elemType,
|
|
||||||
msgID: message.msgID,
|
|
||||||
timestamp: message.timestamp,
|
|
||||||
seq: message.seq
|
|
||||||
);
|
|
||||||
return jsonEncode(messageAbstract.toJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getMessageSummary(V2TimMessage message) {
|
|
||||||
final String? customAbstractMessage = abstractMessageBuilder != null
|
|
||||||
? abstractMessageBuilder!(message)
|
|
||||||
: null;
|
|
||||||
if (customAbstractMessage != null) {
|
|
||||||
return customAbstractMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
final elemType = message.elemType;
|
|
||||||
switch (elemType) {
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
|
|
||||||
return "[表情消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
|
|
||||||
return "[自定义消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
|
|
||||||
return "[文件消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
|
|
||||||
return "[群消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
|
|
||||||
return "[图片消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
|
|
||||||
return "[位置消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
|
|
||||||
return "[合并消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_NONE:
|
|
||||||
return "[没有元素]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
|
|
||||||
return "[语音消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
|
|
||||||
return message.textElem?.text ?? "[文本消息]";
|
|
||||||
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
|
|
||||||
return "[视频消息]";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<V2TimValueCallback<V2TimMessage>?> sendReplyMessage({
|
Future<V2TimValueCallback<V2TimMessage>?> sendReplyMessage({
|
||||||
required String text,
|
required String text,
|
||||||
required String convID,
|
required String convID,
|
||||||
|
|
@ -888,7 +831,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
final cloudCustomData = {
|
final cloudCustomData = {
|
||||||
"messageReply": {
|
"messageReply": {
|
||||||
"messageID": _repliedMessage!.msgID,
|
"messageID": _repliedMessage!.msgID,
|
||||||
"messageAbstract": _getMessageAbstract(_repliedMessage!),
|
"messageAbstract": tools.getMessageAbstract(
|
||||||
|
_repliedMessage!, abstractMessageBuilder),
|
||||||
"messageSender": hasNickName
|
"messageSender": hasNickName
|
||||||
? _repliedMessage!.nickName
|
? _repliedMessage!.nickName
|
||||||
: _repliedMessage?.sender,
|
: _repliedMessage?.sender,
|
||||||
|
|
@ -914,7 +858,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
|
|
||||||
_repliedMessage = null;
|
_repliedMessage = null;
|
||||||
final sendMsgRes = await _messageService.sendMessage(
|
final sendMsgRes = await _messageService.sendMessage(
|
||||||
cloudCustomData: json.encode(cloudCustomData),
|
cloudCustomData: json.encode(cloudCustomData),
|
||||||
id: textMessageInfo.id as String,
|
id: textMessageInfo.id as String,
|
||||||
offlinePushInfo: tools.buildMessagePushInfo(
|
offlinePushInfo: tools.buildMessagePushInfo(
|
||||||
messageInfoWithSender, convID, convType),
|
messageInfoWithSender, convID, convType),
|
||||||
|
|
@ -1439,11 +1383,27 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
||||||
globalModel.setMessageList(conversationID, []);
|
globalModel.setMessageList(conversationID, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<V2TimCallback> revokeMsg(String msgID,
|
Future<Object?> revokeMsg(String msgID, bool isAdmin,
|
||||||
[Object? webMessageInstance]) async {
|
[Object? webMessageInstance]) async {
|
||||||
|
if (chatConfig.isGroupAdminRecallEnabled) {
|
||||||
|
final V2TimMessage? message = globalModel.messageListMap[conversationID]
|
||||||
|
?.firstWhere((element) => element.msgID == msgID);
|
||||||
|
if (message != null) {
|
||||||
|
if (PlatformUtils().isWeb) {
|
||||||
|
final decodedMessage = jsonDecode(message.messageFromWeb!);
|
||||||
|
decodedMessage["cloudCustomData"] =
|
||||||
|
jsonEncode({"isRevoke": true, "revokeByAdmin": isAdmin});
|
||||||
|
message.messageFromWeb = jsonEncode(decodedMessage);
|
||||||
|
} else {
|
||||||
|
message.cloudCustomData =
|
||||||
|
jsonEncode({"isRevoke": true, "revokeByAdmin": isAdmin});
|
||||||
|
}
|
||||||
|
return await modifyMessage(message: message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final res = await _messageService.revokeMessage(
|
final res = await _messageService.revokeMessage(
|
||||||
msgID: msgID, webMessageInstance: webMessageInstance);
|
msgID: msgID, webMessageInstance: webMessageInstance);
|
||||||
|
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
globalModel.onMessageRevoked(msgID, conversationID);
|
globalModel.onMessageRevoked(msgID, conversationID);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -823,6 +823,81 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<V2TimValueCallback<V2TimMessage>?> sendReplyMessageFromController({
|
||||||
|
required String text,
|
||||||
|
required V2TimMessage messageBeenReplied,
|
||||||
|
required String convID,
|
||||||
|
required ConvType convType,
|
||||||
|
ValueChanged<String>? setInputField,
|
||||||
|
OfflinePushInfo? offlinePushInfo,
|
||||||
|
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||||
|
bool? onlineUserOnly,
|
||||||
|
bool? isExcludedFromUnreadCount,
|
||||||
|
bool? needReadReceipt,
|
||||||
|
String? localCustomData,
|
||||||
|
}) async {
|
||||||
|
if (text.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
|
||||||
|
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
|
||||||
|
V2TimMsgCreateInfoResult? textMessageInfo =
|
||||||
|
await _messageService.createTextMessage(text: text);
|
||||||
|
|
||||||
|
textMessageInfo = await _messageService.createTextAtMessage(
|
||||||
|
text: text +
|
||||||
|
" @${TencentUtils.checkString(messageBeenReplied.nickName) ?? TencentUtils.checkString(messageBeenReplied.sender) ?? TencentUtils.checkString(messageBeenReplied.userID)}",
|
||||||
|
atUserList: [
|
||||||
|
TencentUtils.checkString(messageBeenReplied.sender) ??
|
||||||
|
TencentUtils.checkString(messageBeenReplied.userID) ??
|
||||||
|
""
|
||||||
|
]);
|
||||||
|
|
||||||
|
final V2TimMessage? messageInfo = textMessageInfo!.messageInfo;
|
||||||
|
|
||||||
|
if (messageInfo != null) {
|
||||||
|
final messageInfoWithSender = messageInfo.sender == null
|
||||||
|
? tools.setUserInfoForMessage(messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
|
||||||
|
: messageInfo;
|
||||||
|
|
||||||
|
final hasNickName = messageBeenReplied.nickName != null &&
|
||||||
|
messageBeenReplied.nickName != "";
|
||||||
|
final cloudCustomData = {
|
||||||
|
"messageReply": {
|
||||||
|
"messageID": messageBeenReplied.msgID,
|
||||||
|
"messageAbstract": tools.getMessageAbstract(
|
||||||
|
messageBeenReplied, abstractMessageBuilder),
|
||||||
|
"messageSender": hasNickName
|
||||||
|
? messageBeenReplied.nickName
|
||||||
|
: messageBeenReplied.sender,
|
||||||
|
"messageType": messageBeenReplied.elemType,
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
messageInfoWithSender.cloudCustomData = json.encode(cloudCustomData);
|
||||||
|
|
||||||
|
currentHistoryMsgList = [messageInfoWithSender, ...currentHistoryMsgList];
|
||||||
|
setMessageList(convID, currentHistoryMsgList);
|
||||||
|
|
||||||
|
return _sendMessage(
|
||||||
|
cloudCustomData: json.encode(cloudCustomData),
|
||||||
|
id: textMessageInfo.id as String,
|
||||||
|
offlinePushInfo: offlinePushInfo ??
|
||||||
|
tools.buildMessagePushInfo(
|
||||||
|
messageInfo, convID, ConvType.values[convType.index]),
|
||||||
|
priority: priority,
|
||||||
|
onlineUserOnly: onlineUserOnly,
|
||||||
|
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||||
|
needReadReceipt: needReadReceipt,
|
||||||
|
localCustomData: localCustomData,
|
||||||
|
convID: convID,
|
||||||
|
setInputField: setInputField,
|
||||||
|
convType: ConvType.values[convType.index],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> setLocalCustomData(
|
Future<bool> setLocalCustomData(
|
||||||
String msgID, String localCustomData, String conversationID) async {
|
String msgID, String localCustomData, String conversationID) async {
|
||||||
final res = await _messageService.setLocalCustomData(
|
final res = await _messageService.setLocalCustomData(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||||
import 'package:tencent_im_base/tencent_im_base.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';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
|
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,100 @@ class TIMUIKitChatController {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a message, replying to another message, to the specified conversation, or to the current conversation specified on `TIMUIKitChat`.
|
||||||
|
/// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters.
|
||||||
|
Future<V2TimValueCallback<V2TimMessage>?>? sendReplyMessage({
|
||||||
|
required String messageText,
|
||||||
|
required V2TimMessage messageBeenReplied,
|
||||||
|
|
||||||
|
/// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`.
|
||||||
|
ConvType? convType,
|
||||||
|
|
||||||
|
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
|
||||||
|
String? userID,
|
||||||
|
|
||||||
|
/// The target group ID. Required if convType is ConvType.group.
|
||||||
|
String? groupID,
|
||||||
|
|
||||||
|
/// A callback function to update the input field when message sending fails.
|
||||||
|
ValueChanged<String>? setInputField,
|
||||||
|
|
||||||
|
/// Offline push information.
|
||||||
|
OfflinePushInfo? offlinePushInfo,
|
||||||
|
|
||||||
|
/// Whether automatically scrolling to the bottom of the message list after sending a message.
|
||||||
|
/// This field solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
|
||||||
|
bool isNavigateToMessageListBottom = true,
|
||||||
|
|
||||||
|
/// Message priorities. This field is valid only for group chat messages.
|
||||||
|
/// You are advised to set higher priorities for important messages (such as red packet and gift messages)
|
||||||
|
/// and set lower priorities for frequent but unimportant messages (such as like messages).
|
||||||
|
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
|
||||||
|
|
||||||
|
/// Whether the message can be received only by online users.
|
||||||
|
/// If this field is set to true, the message cannot be pulled in recipient historical message pulling.
|
||||||
|
/// This field is often used to implement weak notification features such as "The other party is typing" or unimportant notifications in the group. This field is not supported by audio-video groups (AVChatRoom).
|
||||||
|
bool? onlineUserOnly,
|
||||||
|
|
||||||
|
/// Whether the message is excluded from the conversation unread message count.
|
||||||
|
bool? isExcludedFromUnreadCount,
|
||||||
|
|
||||||
|
/// Whether a read receipt is required.
|
||||||
|
bool? needReadReceipt,
|
||||||
|
|
||||||
|
/// Local custom message data (saved locally, will not be sent to the peer end,
|
||||||
|
/// and will become invalid after the app is uninstalled and reinstalled).
|
||||||
|
String? localCustomData,
|
||||||
|
}) {
|
||||||
|
if (convType != null) {
|
||||||
|
/// Sends a message to the specified conversation.
|
||||||
|
assert((groupID == null) != (userID == null));
|
||||||
|
assert(groupID != null || convType != ConvType.group);
|
||||||
|
assert(userID != null || convType != ConvType.c2c);
|
||||||
|
if (isNavigateToMessageListBottom && scrollController != null) {
|
||||||
|
scrollController!.animateTo(
|
||||||
|
scrollController!.position.minScrollExtent,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalChatModel.sendReplyMessageFromController(
|
||||||
|
text: messageText,
|
||||||
|
messageBeenReplied: messageBeenReplied,
|
||||||
|
priority: priority,
|
||||||
|
onlineUserOnly: onlineUserOnly,
|
||||||
|
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||||
|
needReadReceipt: needReadReceipt,
|
||||||
|
localCustomData: localCustomData,
|
||||||
|
convType: convType,
|
||||||
|
convID: (convType == ConvType.group ? groupID : userID) ?? "",
|
||||||
|
setInputField: setInputField,
|
||||||
|
offlinePushInfo: offlinePushInfo);
|
||||||
|
} else if (model != null) {
|
||||||
|
/// Sends a message to the current conversation specified on `TIMUIKitChat`. 发送到 `TIMUIKitChat` 中指定的当前对话。
|
||||||
|
if (isNavigateToMessageListBottom && scrollController != null) {
|
||||||
|
scrollController?.animateTo(
|
||||||
|
scrollController!.position.minScrollExtent,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalChatModel.sendReplyMessageFromController(
|
||||||
|
text: messageText,
|
||||||
|
messageBeenReplied: messageBeenReplied,
|
||||||
|
priority: priority,
|
||||||
|
onlineUserOnly: onlineUserOnly,
|
||||||
|
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
||||||
|
needReadReceipt: needReadReceipt,
|
||||||
|
localCustomData: localCustomData,
|
||||||
|
convType: model!.conversationType ?? ConvType.group,
|
||||||
|
convID: model!.conversationID,
|
||||||
|
setInputField: setInputField,
|
||||||
|
offlinePushInfo: offlinePushInfo);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Send forward message;
|
/// Send forward message;
|
||||||
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
|
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
|
||||||
sendForwardMessage({
|
sendForwardMessage({
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ class TUIKitScreenUtils {
|
||||||
|
|
||||||
final diagonalInInches =
|
final diagonalInInches =
|
||||||
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
|
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
|
||||||
print("diagonalInInches $diagonalInInches");
|
|
||||||
|
|
||||||
deviceType = diagonalInInches < 11.0 ? DeviceType.Mobile : DeviceType.Desktop;
|
deviceType = diagonalInInches < 11.0 ? DeviceType.Mobile : DeviceType.Desktop;
|
||||||
return deviceType ?? DeviceType.Mobile;
|
return deviceType ?? DeviceType.Mobile;
|
||||||
|
|
|
||||||
|
|
@ -413,8 +413,6 @@ class _TIMUIKitHistoryMessageListState
|
||||||
keepAlive: messageItem?.elemType ==
|
keepAlive: messageItem?.elemType ==
|
||||||
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16),
|
|
||||||
child:
|
child:
|
||||||
_getMessageItemBuilder(messageItem))),
|
_getMessageItemBuilder(messageItem))),
|
||||||
);
|
);
|
||||||
|
|
@ -449,7 +447,6 @@ class _TIMUIKitHistoryMessageListState
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
final messageItem = readMessageList[index];
|
final messageItem = readMessageList[index];
|
||||||
final isSelf = messageItem?.isSelf ?? true;
|
|
||||||
if (index == readMessageList.length - 1) {
|
if (index == readMessageList.length - 1) {
|
||||||
if (haveMoreData) {
|
if (haveMoreData) {
|
||||||
throttleFunction(
|
throttleFunction(
|
||||||
|
|
@ -475,9 +472,6 @@ class _TIMUIKitHistoryMessageListState
|
||||||
MessageElemType
|
MessageElemType
|
||||||
.V2TIM_ELEM_TYPE_SOUND,
|
.V2TIM_ELEM_TYPE_SOUND,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets
|
|
||||||
.symmetric(
|
|
||||||
horizontal: 16),
|
|
||||||
child: _getMessageItemBuilder(
|
child: _getMessageItemBuilder(
|
||||||
messageItem))),
|
messageItem))),
|
||||||
),
|
),
|
||||||
|
|
@ -500,9 +494,6 @@ class _TIMUIKitHistoryMessageListState
|
||||||
keepAlive: messageItem?.elemType ==
|
keepAlive: messageItem?.elemType ==
|
||||||
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: isSelf ? 0 : 16,
|
|
||||||
right: isSelf ? 16 : 0),
|
|
||||||
child: _getMessageItemBuilder(
|
child: _getMessageItemBuilder(
|
||||||
messageItem))),
|
messageItem))),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -52,6 +52,10 @@ class TIMUIKitMessageTooltip extends StatefulWidget {
|
||||||
|
|
||||||
final bool isShowMoreSticker;
|
final bool isShowMoreSticker;
|
||||||
|
|
||||||
|
final V2TimGroupMemberFullInfo? groupMemberInfo;
|
||||||
|
|
||||||
|
final bool iSUseDefaultHoverBar;
|
||||||
|
|
||||||
const TIMUIKitMessageTooltip(
|
const TIMUIKitMessageTooltip(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
this.toolTipsConfig,
|
this.toolTipsConfig,
|
||||||
|
|
@ -63,7 +67,9 @@ class TIMUIKitMessageTooltip extends StatefulWidget {
|
||||||
required this.selectEmojiPanelPosition,
|
required this.selectEmojiPanelPosition,
|
||||||
required this.onCloseTooltip,
|
required this.onCloseTooltip,
|
||||||
required this.onSelectSticker,
|
required this.onSelectSticker,
|
||||||
this.isShowMoreSticker = false})
|
this.isShowMoreSticker = false,
|
||||||
|
this.groupMemberInfo,
|
||||||
|
required this.iSUseDefaultHoverBar})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -141,8 +147,9 @@ class TIMUIKitMessageTooltipState
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isRevocable(int timestamp, int upperTimeLimit) =>
|
bool isRevocable(int timestamp, int upperTimeLimit) =>
|
||||||
(DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp <
|
((DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp <
|
||||||
upperTimeLimit;
|
upperTimeLimit) &&
|
||||||
|
(widget.message.isSelf ?? true);
|
||||||
|
|
||||||
Widget ItemInkWell({
|
Widget ItemInkWell({
|
||||||
Widget? child,
|
Widget? child,
|
||||||
|
|
@ -181,14 +188,24 @@ class TIMUIKitMessageTooltipState
|
||||||
return isvote;
|
return isvote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAdminCanRecall() {
|
||||||
|
if (widget.groupMemberInfo != null &&
|
||||||
|
widget.model.chatConfig.isGroupAdminRecallEnabled) {
|
||||||
|
final selfRole = widget.groupMemberInfo!.role;
|
||||||
|
return selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN ||
|
||||||
|
selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_buildLongPressTipItem(
|
_buildLongPressTipItem(
|
||||||
TUITheme theme, TUIChatSeparateViewModel model, V2TimMessage message) {
|
TUITheme theme, TUIChatSeparateViewModel model, V2TimMessage message) {
|
||||||
final isDesktopScreen =
|
final isDesktopScreen =
|
||||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||||
final isCanRevoke = isRevocable(
|
final isCanRevokeSelf = isRevocable(
|
||||||
widget.message.timestamp!, model.chatConfig.upperRecallTime);
|
widget.message.timestamp!, model.chatConfig.upperRecallTime);
|
||||||
final shouldShowRevokeAction = isCanRevoke &&
|
final shouldShowRevokeAction = (isCanRevokeSelf || isAdminCanRecall()) &&
|
||||||
(widget.message.isSelf ?? true) &&
|
|
||||||
widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL;
|
widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL;
|
||||||
final shouldShowReplyAction = !(widget.message.customElem?.data != null &&
|
final shouldShowReplyAction = !(widget.message.customElem?.data != null &&
|
||||||
MessageUtils.isCallingData(widget.message.customElem!.data!));
|
MessageUtils.isCallingData(widget.message.customElem!.data!));
|
||||||
|
|
@ -258,13 +275,11 @@ class TIMUIKitMessageTooltipState
|
||||||
}
|
}
|
||||||
if (type == "forwardMessage") {
|
if (type == "forwardMessage") {
|
||||||
return tooltipsConfig.showForwardMessage &&
|
return tooltipsConfig.showForwardMessage &&
|
||||||
!(isDesktopScreen &&
|
!(isDesktopScreen && widget.iSUseDefaultHoverBar);
|
||||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
|
||||||
}
|
}
|
||||||
if (type == "replyMessage") {
|
if (type == "replyMessage") {
|
||||||
return tooltipsConfig.showReplyMessage &&
|
return tooltipsConfig.showReplyMessage &&
|
||||||
!(isDesktopScreen &&
|
!(isDesktopScreen && widget.iSUseDefaultHoverBar);
|
||||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
|
||||||
}
|
}
|
||||||
if (type == "delete") {
|
if (type == "delete") {
|
||||||
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
|
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
|
||||||
|
|
@ -435,7 +450,11 @@ class TIMUIKitMessageTooltipState
|
||||||
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
|
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
|
||||||
break;
|
break;
|
||||||
case "revoke":
|
case "revoke":
|
||||||
model.revokeMsg(msgID, messageItem.messageFromWeb);
|
model.revokeMsg(
|
||||||
|
msgID,
|
||||||
|
!isRevocable(
|
||||||
|
widget.message.timestamp!, model.chatConfig.upperRecallTime),
|
||||||
|
messageItem.messageFromWeb);
|
||||||
break;
|
break;
|
||||||
case 'translate':
|
case 'translate':
|
||||||
model.translateText(widget.message);
|
model.translateText(widget.message);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,14 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
||||||
|
|
||||||
final V2TimConversation conversation;
|
final V2TimConversation conversation;
|
||||||
|
|
||||||
|
final V2TimGroupMemberFullInfo? groupMemberInfo;
|
||||||
|
|
||||||
|
/// This parameter accepts a custom widget to be displayed when the mouse hovers over a message,
|
||||||
|
/// replacing the default message hover action bar.
|
||||||
|
/// Applicable only on desktop platforms.
|
||||||
|
/// If provided, the default message action functionality will appear in the right-click context menu instead.
|
||||||
|
final Widget Function(V2TimMessage message)? customMessageHoverBarOnDesktop;
|
||||||
|
|
||||||
const TIMUIKitHistoryMessageListContainer({
|
const TIMUIKitHistoryMessageListContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
|
|
@ -101,6 +109,8 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
||||||
this.textFieldController,
|
this.textFieldController,
|
||||||
required this.conversation,
|
required this.conversation,
|
||||||
this.onSecondaryTapAvatar,
|
this.onSecondaryTapAvatar,
|
||||||
|
this.groupMemberInfo,
|
||||||
|
this.customMessageHoverBarOnDesktop,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -162,6 +172,9 @@ class _TIMUIKitHistoryMessageListContainerState
|
||||||
mainHistoryListConfig: widget.mainHistoryListConfig,
|
mainHistoryListConfig: widget.mainHistoryListConfig,
|
||||||
itemBuilder: (context, message) {
|
itemBuilder: (context, message) {
|
||||||
return TIMUIKitHistoryMessageListItem(
|
return TIMUIKitHistoryMessageListItem(
|
||||||
|
customMessageHoverBarOnDesktop:
|
||||||
|
widget.customMessageHoverBarOnDesktop,
|
||||||
|
groupMemberInfo: widget.groupMemberInfo,
|
||||||
textFieldController: widget.textFieldController,
|
textFieldController: widget.textFieldController,
|
||||||
userAvatarBuilder: widget.userAvatarBuilder,
|
userAvatarBuilder: widget.userAvatarBuilder,
|
||||||
customEmojiStickerList: widget.customEmojiStickerList,
|
customEmojiStickerList: widget.customEmojiStickerList,
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class TIMUIKitReplyElem extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
MessageRepliedData? repliedMessage = null;
|
MessageRepliedData? repliedMessage;
|
||||||
V2TimMessage? rawMessage;
|
V2TimMessage? rawMessage;
|
||||||
bool isShowJumpState = false;
|
bool isShowJumpState = false;
|
||||||
bool isShining = false;
|
bool isShining = false;
|
||||||
|
|
@ -72,7 +72,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
: "{}"));
|
: "{}"));
|
||||||
if (messageCloudCustomData.messageReply != null) {
|
if (messageCloudCustomData.messageReply != null) {
|
||||||
final MessageRepliedData repliedMessage =
|
final MessageRepliedData repliedMessage =
|
||||||
MessageRepliedData.fromJson(messageCloudCustomData.messageReply!);
|
MessageRepliedData.fromJson(messageCloudCustomData.messageReply!);
|
||||||
return repliedMessage;
|
return repliedMessage;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -95,8 +95,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
try {
|
try {
|
||||||
final RepliedMessageAbstract repliedMessageAbstract =
|
final RepliedMessageAbstract repliedMessageAbstract =
|
||||||
RepliedMessageAbstract.fromJson(
|
RepliedMessageAbstract.fromJson(
|
||||||
jsonDecode(cloudCustomData.messageAbstract));
|
jsonDecode(cloudCustomData.messageAbstract));
|
||||||
if (repliedMessageAbstract.isNotEmpty) {
|
if (repliedMessageAbstract.isNotEmpty) {
|
||||||
message = V2TimMessage(
|
message = V2TimMessage(
|
||||||
elemType: 0,
|
elemType: 0,
|
||||||
|
|
@ -105,6 +105,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
msgID: repliedMessageAbstract.msgID);
|
msgID: repliedMessageAbstract.msgID);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// ignore: avoid_print
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,8 +132,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
_renderMessageSummary(TUITheme? theme) {
|
_renderMessageSummary(TUITheme? theme) {
|
||||||
try {
|
try {
|
||||||
final RepliedMessageAbstract repliedMessageAbstract =
|
final RepliedMessageAbstract repliedMessageAbstract =
|
||||||
RepliedMessageAbstract.fromJson(
|
RepliedMessageAbstract.fromJson(
|
||||||
jsonDecode(repliedMessage?.messageAbstract ?? ""));
|
jsonDecode(repliedMessage?.messageAbstract ?? ""));
|
||||||
if (TencentUtils.checkString(repliedMessageAbstract.summary) != null) {
|
if (TencentUtils.checkString(repliedMessageAbstract.summary) != null) {
|
||||||
return _defaultRawMessageText(repliedMessageAbstract.summary!, theme);
|
return _defaultRawMessageText(repliedMessageAbstract.summary!, theme);
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +145,21 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(bool isRevoke, bool isRevokeByAdmin) isRevokeMessage(V2TimMessage message) {
|
||||||
|
if (message.status == 6) {
|
||||||
|
return (true, false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
final customData = jsonDecode(message.cloudCustomData ?? "{}");
|
||||||
|
final isRevoke = customData["isRevoke"] ?? false;
|
||||||
|
final revokeByAdmin = customData["revokeByAdmin"] ?? false;
|
||||||
|
return (isRevoke, revokeByAdmin);
|
||||||
|
} catch (e) {
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_rawMessageBuilder(V2TimMessage? message, TUITheme? theme) {
|
_rawMessageBuilder(V2TimMessage? message, TUITheme? theme) {
|
||||||
if (repliedMessage == null) {
|
if (repliedMessage == null) {
|
||||||
return const SizedBox(width: 0, height: 12);
|
return const SizedBox(width: 0, height: 12);
|
||||||
|
|
@ -154,12 +170,23 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
}
|
}
|
||||||
return const SizedBox(width: 0, height: 12);
|
return const SizedBox(width: 0, height: 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final revokeStatus = isRevokeMessage(message);
|
||||||
|
final isRevokedMsg = revokeStatus.$1;
|
||||||
|
final isAdminRevoke = revokeStatus.$2;
|
||||||
|
|
||||||
|
if (isRevokedMsg) {
|
||||||
|
return _defaultRawMessageText(
|
||||||
|
isAdminRevoke ? TIM_t("[消息被管理员撤回]") : TIM_t("[消息被撤回]"),
|
||||||
|
theme);
|
||||||
|
}
|
||||||
|
|
||||||
final messageType = message.elemType;
|
final messageType = message.elemType;
|
||||||
final isSelf = message.isSelf ?? true;
|
final isSelf = message.isSelf ?? true;
|
||||||
final customAbstractMessage =
|
final customAbstractMessage =
|
||||||
widget.chatModel.abstractMessageBuilder != null
|
widget.chatModel.abstractMessageBuilder != null
|
||||||
? widget.chatModel.abstractMessageBuilder!(message)
|
? widget.chatModel.abstractMessageBuilder!(message)
|
||||||
: null;
|
: null;
|
||||||
if (customAbstractMessage != null) {
|
if (customAbstractMessage != null) {
|
||||||
return _defaultRawMessageText(
|
return _defaultRawMessageText(
|
||||||
customAbstractMessage,
|
customAbstractMessage,
|
||||||
|
|
@ -275,14 +302,14 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
try {
|
try {
|
||||||
final String localJSON = widget.message.localCustomData!;
|
final String localJSON = widget.message.localCustomData!;
|
||||||
final LocalCustomDataModel? localPreviewInfo =
|
final LocalCustomDataModel? localPreviewInfo =
|
||||||
LocalCustomDataModel.fromMap(json.decode(localJSON));
|
LocalCustomDataModel.fromMap(json.decode(localJSON));
|
||||||
if (localPreviewInfo != null &&
|
if (localPreviewInfo != null &&
|
||||||
!localPreviewInfo.isLinkPreviewEmpty()) {
|
!localPreviewInfo.isLinkPreviewEmpty()) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 8),
|
margin: const EdgeInsets.only(top: 8),
|
||||||
child:
|
child:
|
||||||
// You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget.
|
// You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget.
|
||||||
LinkPreviewWidget(linkPreview: localPreviewInfo),
|
LinkPreviewWidget(linkPreview: localPreviewInfo),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -317,7 +344,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
|
|
||||||
final defaultStyle = isFromSelf
|
final defaultStyle = isFromSelf
|
||||||
? (theme.chatMessageItemFromSelfBgColor ??
|
? (theme.chatMessageItemFromSelfBgColor ??
|
||||||
theme.lightPrimaryMaterialColor.shade50)
|
theme.lightPrimaryMaterialColor.shade50)
|
||||||
: (theme.chatMessageItemFromOthersBgColor);
|
: (theme.chatMessageItemFromOthersBgColor);
|
||||||
|
|
||||||
final backgroundColor = isShowJumpState
|
final backgroundColor = isShowJumpState
|
||||||
|
|
@ -326,15 +353,15 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
|
|
||||||
final borderRadius = isFromSelf
|
final borderRadius = isFromSelf
|
||||||
? const BorderRadius.only(
|
? const BorderRadius.only(
|
||||||
topLeft: Radius.circular(10),
|
topLeft: Radius.circular(10),
|
||||||
topRight: Radius.circular(2),
|
topRight: Radius.circular(2),
|
||||||
bottomLeft: Radius.circular(10),
|
bottomLeft: Radius.circular(10),
|
||||||
bottomRight: Radius.circular(10))
|
bottomRight: Radius.circular(10))
|
||||||
: const BorderRadius.only(
|
: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(2),
|
topLeft: Radius.circular(2),
|
||||||
topRight: Radius.circular(10),
|
topRight: Radius.circular(10),
|
||||||
bottomLeft: Radius.circular(10),
|
bottomLeft: Radius.circular(10),
|
||||||
bottomRight: Radius.circular(10));
|
bottomRight: Radius.circular(10));
|
||||||
final textWithLink = LinkPreviewEntry.getHyperlinksText(
|
final textWithLink = LinkPreviewEntry.getHyperlinksText(
|
||||||
widget.message.textElem?.text ?? "",
|
widget.message.textElem?.text ?? "",
|
||||||
widget.chatModel.chatConfig.isSupportMarkdownForTextMessage,
|
widget.chatModel.chatConfig.isSupportMarkdownForTextMessage,
|
||||||
|
|
@ -342,7 +369,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
||||||
customEmojiStickerList: widget.customEmojiStickerList,
|
customEmojiStickerList: widget.customEmojiStickerList,
|
||||||
isEnableTextSelection:
|
isEnableTextSelection:
|
||||||
widget.chatModel.chatConfig.isEnableTextSelection ?? false);
|
widget.chatModel.chatConfig.isEnableTextSelection ?? false);
|
||||||
return Container(
|
return Container(
|
||||||
padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10),
|
padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -350,7 +377,10 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
borderRadius: widget.borderRadius ?? borderRadius,
|
borderRadius: widget.borderRadius ?? borderRadius,
|
||||||
),
|
),
|
||||||
constraints:
|
constraints:
|
||||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6),
|
BoxConstraints(maxWidth: MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size
|
||||||
|
.width * 0.6),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _jumpToRawMsg,
|
onTap: _jumpToRawMsg,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -391,22 +421,22 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||||
// You can render the widget from extension directly, with a [TextStyle] optionally.
|
// You can render the widget from extension directly, with a [TextStyle] optionally.
|
||||||
widget.chatModel.chatConfig.urlPreviewType != UrlPreviewType.none
|
widget.chatModel.chatConfig.urlPreviewType != UrlPreviewType.none
|
||||||
? textWithLink!(
|
? textWithLink!(
|
||||||
style: widget.fontStyle ??
|
style: widget.fontStyle ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: isDesktopScreen ? 14 : 16,
|
fontSize: isDesktopScreen ? 14 : 16,
|
||||||
textBaseline: TextBaseline.ideographic,
|
textBaseline: TextBaseline.ideographic,
|
||||||
height: widget.chatModel.chatConfig.textHeight))
|
height: widget.chatModel.chatConfig.textHeight))
|
||||||
: ExtendedText(widget.message.textElem?.text ?? "",
|
: ExtendedText(widget.message.textElem?.text ?? "",
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: widget.fontStyle ??
|
style: widget.fontStyle ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: isDesktopScreen ? 14 : 16,
|
fontSize: isDesktopScreen ? 14 : 16,
|
||||||
height: widget.chatModel.chatConfig.textHeight),
|
height: widget.chatModel.chatConfig.textHeight),
|
||||||
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
|
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
|
||||||
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
isUseDefaultEmoji: widget.isUseDefaultEmoji,
|
||||||
customEmojiStickerList: widget.customEmojiStickerList,
|
customEmojiStickerList: widget.customEmojiStickerList,
|
||||||
showAtBackground: true,
|
showAtBackground: true,
|
||||||
)),
|
)),
|
||||||
// If the link preview info is available, render the preview card.
|
// If the link preview info is available, render the preview card.
|
||||||
if (_renderPreviewWidget() != null &&
|
if (_renderPreviewWidget() != null &&
|
||||||
widget.chatModel.chatConfig.urlPreviewType ==
|
widget.chatModel.chatConfig.urlPreviewType ==
|
||||||
|
|
|
||||||
|
|
@ -282,16 +282,17 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
||||||
package: 'tencent_cloud_chat_uikit',
|
package: 'tencent_cloud_chat_uikit',
|
||||||
height: 64)),
|
height: 64)),
|
||||||
),
|
),
|
||||||
Positioned(
|
if (widget.message.videoElem?.duration != null &&
|
||||||
right: 10,
|
widget.message.videoElem!.duration! > 0)
|
||||||
bottom: 10,
|
Positioned(
|
||||||
child: Text(
|
right: 10,
|
||||||
MessageUtils.formatVideoTime(
|
bottom: 10,
|
||||||
widget.message.videoElem?.duration ??
|
child: Text(
|
||||||
0)
|
MessageUtils.formatVideoTime(widget
|
||||||
.toString(),
|
.message.videoElem!.duration!)
|
||||||
style: const TextStyle(
|
.toString(),
|
||||||
color: Colors.white, fontSize: 12))),
|
style: const TextStyle(
|
||||||
|
color: Colors.white, fontSize: 12))),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -316,18 +316,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onModelChanged() {
|
|
||||||
if (widget.model.editRevokedMsg != "") {
|
|
||||||
narrowTextFieldKey.currentState?.showKeyboard = true;
|
|
||||||
focusNode.requestFocus();
|
|
||||||
textEditingController.text = widget.model.editRevokedMsg;
|
|
||||||
textEditingController.selection = TextSelection.fromPosition(TextPosition(
|
|
||||||
affinity: TextAffinity.downstream,
|
|
||||||
offset: widget.model.editRevokedMsg.length));
|
|
||||||
widget.model.editRevokedMsg = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onCursorChange() {
|
_onCursorChange() {
|
||||||
final selection = textEditingController.selection;
|
final selection = textEditingController.selection;
|
||||||
currentCursor = selection.baseOffset;
|
currentCursor = selection.baseOffset;
|
||||||
|
|
@ -346,7 +334,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
"";
|
"";
|
||||||
}
|
}
|
||||||
|
|
||||||
_longPressToAt(String? userID, String? nickName) {
|
mentionMemberInMessage(String? userID, String? nickName) {
|
||||||
if (TencentUtils.checkString(userID) == null) {
|
if (TencentUtils.checkString(userID) == null) {
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -682,7 +670,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
if (widget.controller != null) {
|
if (widget.controller != null) {
|
||||||
widget.controller?.addListener(controllerHandler);
|
widget.controller?.addListener(controllerHandler);
|
||||||
}
|
}
|
||||||
widget.model.addListener(onModelChanged);
|
|
||||||
final AppLocale appLocale = I18nUtils.findDeviceLocale(null);
|
final AppLocale appLocale = I18nUtils.findDeviceLocale(null);
|
||||||
languageType =
|
languageType =
|
||||||
(appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant)
|
(appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant)
|
||||||
|
|
@ -696,7 +683,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
controllerHandler() {
|
controllerHandler() {
|
||||||
final actionType = widget.controller?.actionType;
|
final actionType = widget.controller?.actionType;
|
||||||
if (actionType == ActionType.longPressToAt) {
|
if (actionType == ActionType.longPressToAt) {
|
||||||
_longPressToAt(
|
mentionMemberInMessage(
|
||||||
widget.controller?.atUserID, widget.controller?.atUserName);
|
widget.controller?.atUserID, widget.controller?.atUserName);
|
||||||
} else if (actionType == ActionType.setTextField) {
|
} else if (actionType == ActionType.setTextField) {
|
||||||
final newText = widget.controller?.inputText ?? "";
|
final newText = widget.controller?.inputText ?? "";
|
||||||
|
|
@ -704,6 +691,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
textEditingController.selection = TextSelection.fromPosition(
|
textEditingController.selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: textEditingController.text.length));
|
TextPosition(offset: textEditingController.text.length));
|
||||||
lastText = textEditingController.text;
|
lastText = textEditingController.text;
|
||||||
|
focusNode.requestFocus();
|
||||||
return;
|
return;
|
||||||
} else if (actionType == ActionType.requestFocus) {
|
} else if (actionType == ActionType.requestFocus) {
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
|
|
@ -736,7 +724,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
handleSetDraftText();
|
handleSetDraftText();
|
||||||
widget.model.removeListener(onModelChanged);
|
|
||||||
if (widget.controller != null) {
|
if (widget.controller != null) {
|
||||||
widget.controller?.removeListener(controllerHandler);
|
widget.controller?.removeListener(controllerHandler);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class TIMUIKitInputTextFieldController extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// text field unfocus and hide all panel
|
/// text field unfocused and hide all panel
|
||||||
hideAllPanel() {
|
hideAllPanel() {
|
||||||
actionType = ActionType.hideAllPanel;
|
actionType = ActionType.hideAllPanel;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ class _TIMUIKitTextFieldLayoutNarrowState
|
||||||
addText: (int unicode) {
|
addText: (int unicode) {
|
||||||
final newText = String.fromCharCode(unicode);
|
final newText = String.fromCharCode(unicode);
|
||||||
widget.addStickerToText(newText);
|
widget.addStickerToText(newText);
|
||||||
|
setSendButton();
|
||||||
// handleSetDraftText();
|
// handleSetDraftText();
|
||||||
},
|
},
|
||||||
addCustomEmojiText: ((String singleEmojiName) {
|
addCustomEmojiText: ((String singleEmojiName) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
|
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
@ -227,6 +226,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// ignore: avoid_print
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
generateDefaultControlBarItems();
|
generateDefaultControlBarItems();
|
||||||
|
|
@ -239,6 +239,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
||||||
sendFileUseJs(imageFile);
|
sendFileUseJs(imageFile);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// ignore: avoid_print
|
||||||
print("Paste image failed: ${e.toString()}");
|
print("Paste image failed: ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1033,7 +1034,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
||||||
textAlignVertical: TextAlignVertical.top,
|
textAlignVertical: TextAlignVertical.top,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hoverColor: hexToColor("fafafa"),
|
hoverColor: Colors.transparent,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintStyle: const TextStyle(
|
hintStyle: const TextStyle(
|
||||||
color: Color(0xffAEA4A3),
|
color: Color(0xffAEA4A3),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
|
||||||
|
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_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/tencent_cloud_chat_uikit.dart';
|
||||||
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
|
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
|
||||||
|
|
@ -144,6 +145,12 @@ class TIMUIKitChat extends StatefulWidget {
|
||||||
/// Custom emoji panel.
|
/// Custom emoji panel.
|
||||||
final CustomStickerPanel? customStickerPanel;
|
final CustomStickerPanel? customStickerPanel;
|
||||||
|
|
||||||
|
/// This parameter accepts a custom widget to be displayed when the mouse hovers over a message,
|
||||||
|
/// replacing the default message hover action bar.
|
||||||
|
/// Applicable only on desktop platforms.
|
||||||
|
/// If provided, the default message action functionality will appear in the right-click context menu instead.
|
||||||
|
final Widget Function(V2TimMessage message)? customMessageHoverBarOnDesktop;
|
||||||
|
|
||||||
/// Custom text field
|
/// Custom text field
|
||||||
final Widget Function(BuildContext context)? textFieldBuilder;
|
final Widget Function(BuildContext context)? textFieldBuilder;
|
||||||
|
|
||||||
|
|
@ -181,7 +188,8 @@ class TIMUIKitChat extends StatefulWidget {
|
||||||
this.textFieldBuilder,
|
this.textFieldBuilder,
|
||||||
this.customEmojiStickerList = const [],
|
this.customEmojiStickerList = const [],
|
||||||
this.customAppBar,
|
this.customAppBar,
|
||||||
this.onSecondaryTapAvatar})
|
this.onSecondaryTapAvatar,
|
||||||
|
this.customMessageHoverBarOnDesktop})
|
||||||
: super(key: key) {
|
: super(key: key) {
|
||||||
startTime = DateTime
|
startTime = DateTime
|
||||||
.now()
|
.now()
|
||||||
|
|
@ -194,10 +202,12 @@ class TIMUIKitChat extends StatefulWidget {
|
||||||
|
|
||||||
class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||||
TUIChatSeparateViewModel model = TUIChatSeparateViewModel();
|
TUIChatSeparateViewModel model = TUIChatSeparateViewModel();
|
||||||
|
final TUISelfInfoViewModel selfInfoViewModel =
|
||||||
|
serviceLocator<TUISelfInfoViewModel>();
|
||||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||||
final TUIConversationViewModel conversationViewModel =
|
final TUIConversationViewModel conversationViewModel =
|
||||||
serviceLocator<TUIConversationViewModel>();
|
serviceLocator<TUIConversationViewModel>();
|
||||||
final TIMUIKitInputTextFieldController textFieldController =
|
TIMUIKitInputTextFieldController textFieldController =
|
||||||
TIMUIKitInputTextFieldController();
|
TIMUIKitInputTextFieldController();
|
||||||
bool isInit = false;
|
bool isInit = false;
|
||||||
final TUIChatGlobalModel chatGlobalModel =
|
final TUIChatGlobalModel chatGlobalModel =
|
||||||
|
|
@ -279,6 +289,9 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (oldWidget.textFieldBuilder != null && widget.textFieldBuilder == null) {
|
||||||
|
textFieldController = TIMUIKitInputTextFieldController();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDraft() async {
|
updateDraft() async {
|
||||||
|
|
@ -388,6 +401,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final selfUserID = selfInfoViewModel.loginInfo?.userID;
|
||||||
final TUIGroupListenerModel groupListenerModel =
|
final TUIGroupListenerModel groupListenerModel =
|
||||||
Provider.of<TUIGroupListenerModel>(context, listen: true);
|
Provider.of<TUIGroupListenerModel>(context, listen: true);
|
||||||
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
|
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
|
||||||
|
|
@ -464,7 +478,14 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||||
child: Listener(
|
child: Listener(
|
||||||
onPointerMove: closePanel,
|
onPointerMove: closePanel,
|
||||||
child: TIMUIKitHistoryMessageListContainer(
|
child: TIMUIKitHistoryMessageListContainer(
|
||||||
|
customMessageHoverBarOnDesktop: widget
|
||||||
|
.customMessageHoverBarOnDesktop,
|
||||||
conversation: widget.conversation,
|
conversation: widget.conversation,
|
||||||
|
groupMemberInfo: model.groupMemberList
|
||||||
|
?.firstWhere(
|
||||||
|
(element) =>
|
||||||
|
element?.userID == selfUserID,
|
||||||
|
orElse: () => null),
|
||||||
textFieldController: textFieldController,
|
textFieldController: textFieldController,
|
||||||
customEmojiStickerList:
|
customEmojiStickerList:
|
||||||
widget.customEmojiStickerList,
|
widget.customEmojiStickerList,
|
||||||
|
|
@ -532,14 +553,17 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||||
conversationID: _getConvID(),
|
conversationID: _getConvID(),
|
||||||
conversationType: _getConvType(),
|
conversationType: _getConvType(),
|
||||||
initText: TencentUtils.checkString(
|
initText: TencentUtils.checkString(
|
||||||
widget.draftText) ?? (PlatformUtils().isWeb
|
widget.draftText) ??
|
||||||
? TencentUtils.checkString(
|
(PlatformUtils().isWeb
|
||||||
conversationViewModel.getWebDraft(
|
? TencentUtils.checkString(
|
||||||
conversationID: widget.conversation
|
conversationViewModel
|
||||||
.conversationID))
|
.getWebDraft(
|
||||||
:
|
conversationID: widget
|
||||||
TencentUtils.checkString(widget
|
.conversation
|
||||||
.conversation.draftText)),
|
.conversationID))
|
||||||
|
: TencentUtils.checkString(
|
||||||
|
widget.conversation
|
||||||
|
.draftText)),
|
||||||
hintText: widget.textFieldHintText,
|
hintText: widget.textFieldHintText,
|
||||||
showMorePanel: widget.config
|
showMorePanel: widget.config
|
||||||
?.isAllowShowMorePanel ??
|
?.isAllowShowMorePanel ??
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class TIMUIKitChatConfig {
|
||||||
/// Customize the time divider among the two messages.
|
/// Customize the time divider among the two messages.
|
||||||
final TimeDividerConfig? timeDividerConfig;
|
final TimeDividerConfig? timeDividerConfig;
|
||||||
|
|
||||||
/// control if allowed to show reading status.
|
/// Control if allowed to show reading status.
|
||||||
/// [Default]: true.
|
/// [Default]: true.
|
||||||
final bool isShowReadingStatus;
|
final bool isShowReadingStatus;
|
||||||
|
|
||||||
|
|
@ -191,9 +191,18 @@ class TIMUIKitChatConfig {
|
||||||
/// [Default]: true.
|
/// [Default]: true.
|
||||||
final bool isUseDraftOnWeb;
|
final bool isUseDraftOnWeb;
|
||||||
|
|
||||||
|
/// Determines whether a group administrator is allowed to recall any
|
||||||
|
/// message from any group member. If this capability is enabled,
|
||||||
|
/// recalled messages will not be interoperable with Native clients
|
||||||
|
/// and will only take effect on other Flutter clients.
|
||||||
|
///
|
||||||
|
/// [Default]: false
|
||||||
|
final bool isGroupAdminRecallEnabled;
|
||||||
|
|
||||||
const TIMUIKitChatConfig(
|
const TIMUIKitChatConfig(
|
||||||
{this.onTapLink,
|
{this.onTapLink,
|
||||||
this.timeDividerConfig,
|
this.timeDividerConfig,
|
||||||
|
this.isGroupAdminRecallEnabled = false,
|
||||||
this.isAutoReportRead = true,
|
this.isAutoReportRead = true,
|
||||||
this.faceURIPrefix,
|
this.faceURIPrefix,
|
||||||
this.faceURISuffix,
|
this.faceURISuffix,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// ignore_for_file: unrelated_type_equality_checks
|
// ignore_for_file: unrelated_type_equality_checks
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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';
|
||||||
|
|
@ -45,13 +47,35 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(bool isRevoke, bool isRevokeByAdmin) isRevokeMessage(V2TimMessage? message) {
|
||||||
|
if (message == null) {
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
if (message.status == 6) {
|
||||||
|
return (true, false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
final customData = jsonDecode(message.cloudCustomData ?? "{}");
|
||||||
|
final isRevoke = customData["isRevoke"] ?? false;
|
||||||
|
final revokeByAdmin = customData["revokeByAdmin"] ?? false;
|
||||||
|
return (isRevoke, revokeByAdmin);
|
||||||
|
} catch (e) {
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _getMsgElem() async {
|
void _getMsgElem() async {
|
||||||
final isRevokedMessage = widget.lastMsg!.status == 6;
|
final revokeStatus = isRevokeMessage(widget.lastMsg);
|
||||||
|
final isRevokedMessage = revokeStatus.$1;
|
||||||
|
final isAdminRevoke = revokeStatus.$2;
|
||||||
if (isRevokedMessage) {
|
if (isRevokedMessage) {
|
||||||
final isSelf = widget.lastMsg!.isSelf ?? true;
|
final isSelf = widget.lastMsg!.isSelf ?? true;
|
||||||
final option1 = isSelf
|
final option1 = isAdminRevoke
|
||||||
? TIM_t("您")
|
? TIM_t("管理员")
|
||||||
: widget.lastMsg!.nickName ?? widget.lastMsg?.sender;
|
: (isSelf
|
||||||
|
? TIM_t("您")
|
||||||
|
: widget.lastMsg!.nickName ?? widget.lastMsg?.sender);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
groupTipsAbstractText = TIM_t_para(
|
groupTipsAbstractText = TIM_t_para(
|
||||||
|
|
|
||||||
|
|
@ -86,16 +86,21 @@ class TIMUIKitProfile extends StatefulWidget {
|
||||||
const TIMUIKitProfile(
|
const TIMUIKitProfile(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.userID,
|
required this.userID,
|
||||||
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
@Deprecated(
|
||||||
this.operationListBuilder,
|
"[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||||
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
this.operationListBuilder,
|
||||||
this.bottomOperationBuilder,
|
@Deprecated(
|
||||||
@Deprecated("This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
"[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||||
this.handleProfileDetailCardTap,
|
this.bottomOperationBuilder,
|
||||||
@Deprecated("This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
@Deprecated(
|
||||||
this.canJumpToPersonalProfile = false,
|
"This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
||||||
@Deprecated("This widget will no longer shows the personal info card and will not support to change self avatar, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
this.handleProfileDetailCardTap,
|
||||||
this.onSelfAvatarTap,
|
@Deprecated(
|
||||||
|
"This widget will no longer shows the personal info card and can not jump to personal info page automatically, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
||||||
|
this.canJumpToPersonalProfile = false,
|
||||||
|
@Deprecated(
|
||||||
|
"This widget will no longer shows the personal info card and will not support to change self avatar, please navigate to your custom personal info page manually and directly, you may refer to our demo")
|
||||||
|
this.onSelfAvatarTap,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.profileWidgetBuilder,
|
this.profileWidgetBuilder,
|
||||||
this.profileWidgetsOrder,
|
this.profileWidgetsOrder,
|
||||||
|
|
@ -130,7 +135,8 @@ class _TIMUIKitProfileState extends TIMUIKitState<TIMUIKitProfile> {
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant TIMUIKitProfile oldWidget) {
|
void didUpdateWidget(covariant TIMUIKitProfile oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.userID != widget.userID) {
|
if (oldWidget.userID != widget.userID ||
|
||||||
|
_model.userProfile?.friendInfo?.userID != widget.userID) {
|
||||||
_model.userProfile = null;
|
_model.userProfile = null;
|
||||||
_model.loadData(
|
_model.loadData(
|
||||||
userID: widget.userID, isNeedConversation: !widget.isSelf);
|
userID: widget.userID, isNeedConversation: !widget.isSelf);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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';
|
||||||
|
|
@ -57,12 +59,34 @@ class TIMUIKitSearchMsgDetailState
|
||||||
updateMsgResult(widget.keyword, true);
|
updateMsgResult(widget.keyword, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(bool isRevoke, bool isRevokeByAdmin) isRevokeMessage(V2TimMessage? message) {
|
||||||
|
if (message == null) {
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
if (message.status == 6) {
|
||||||
|
return (true, false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
final customData = jsonDecode(message.cloudCustomData ?? "{}");
|
||||||
|
final isRevoke = customData["isRevoke"] ?? false;
|
||||||
|
final revokeByAdmin = customData["revokeByAdmin"] ?? false;
|
||||||
|
return (isRevoke, revokeByAdmin);
|
||||||
|
} catch (e) {
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _getMsgElem(V2TimMessage message) {
|
String _getMsgElem(V2TimMessage message) {
|
||||||
final msgType = message.elemType;
|
final msgType = message.elemType;
|
||||||
final isRevokedMessage = message.status == 6;
|
final revokeStatus = isRevokeMessage(message);
|
||||||
|
final isRevokedMessage = revokeStatus.$1;
|
||||||
|
final isAdminRevoke = revokeStatus.$2;
|
||||||
if (isRevokedMessage) {
|
if (isRevokedMessage) {
|
||||||
final isSelf = message.isSelf ?? true;
|
final isSelf = message.isSelf ?? true;
|
||||||
final option2 = isSelf ? TIM_t("您") : message.nickName ?? message.sender;
|
final option2 = isAdminRevoke
|
||||||
|
? TIM_t("管理员")
|
||||||
|
: (isSelf ? TIM_t("您") : message.nickName ?? message.sender);
|
||||||
return TIM_t_para("{{option2}}撤回了一条消息", "$option2撤回了一条消息")(
|
return TIM_t_para("{{option2}}撤回了一条消息", "$option2撤回了一条消息")(
|
||||||
option2: option2);
|
option2: option2);
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +197,8 @@ class TIMUIKitSearchMsgDetailState
|
||||||
final currentText = _controller.text;
|
final currentText = _controller.text;
|
||||||
if (currentMsgListForConversation.isEmpty &&
|
if (currentMsgListForConversation.isEmpty &&
|
||||||
widget.initMessageList != null &&
|
widget.initMessageList != null &&
|
||||||
widget.initMessageList!.isNotEmpty && currentText.isEmpty) {
|
widget.initMessageList!.isNotEmpty &&
|
||||||
|
currentText.isEmpty) {
|
||||||
currentMsgListForConversation = widget.initMessageList!;
|
currentMsgListForConversation = widget.initMessageList!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,18 +258,18 @@ class TIMUIKitSearchMsgDetailState
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
children: [
|
children: [
|
||||||
..._renderListMessage(
|
..._renderListMessage(currentMsgListForConversation,
|
||||||
currentMsgListForConversation, context, isDesktopScreen),
|
context, isDesktopScreen),
|
||||||
_renderShowALl(keywordState.isNotEmpty &&
|
_renderShowALl(keywordState.isNotEmpty &&
|
||||||
totalMsgInConversationCount >
|
totalMsgInConversationCount >
|
||||||
currentMsgListForConversation.length)
|
currentMsgListForConversation.length)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,8 @@ class TUIKitWidePopup {
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
titlePadding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
|
titlePadding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
|
contentPadding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
|
||||||
|
|
@ -262,17 +264,13 @@ class TUIKitWidePopup {
|
||||||
child: TUIKitDragArea(
|
child: TUIKitDragArea(
|
||||||
backgroundColor: isDarkBackground ? const Color(0x7F000000) : null,
|
backgroundColor: isDarkBackground ? const Color(0x7F000000) : null,
|
||||||
closeFun: () {
|
closeFun: () {
|
||||||
|
isShow = false;
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
entry?.remove();
|
entry?.remove();
|
||||||
entry = null;
|
entry = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initOffset: offset ??
|
initOffset: offset,
|
||||||
(width != null && height != null
|
|
||||||
? Offset(
|
|
||||||
MediaQuery.of(context).size.width * 0.5 - width / 2,
|
|
||||||
MediaQuery.of(context).size.height * 0.5 - height / 2)
|
|
||||||
: null),
|
|
||||||
child: contentWidget),
|
child: contentWidget),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -764,7 +764,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.3"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: markdown
|
name: markdown
|
||||||
sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e"
|
sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: tencent_cloud_chat_uikit
|
name: tencent_cloud_chat_uikit
|
||||||
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
|
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
|
||||||
version: 2.1.1
|
version: 2.1.2
|
||||||
homepage: https://www.tencentcloud.com/products/im?from=pub
|
homepage: https://www.tencentcloud.com/products/im?from=pub
|
||||||
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
|
repository: https://github.com/TencentCloud/tc-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
|
||||||
|
|
@ -74,6 +74,7 @@ dependencies:
|
||||||
cross_file: ^0.3.3+4
|
cross_file: ^0.3.3+4
|
||||||
csslib: 0.17.2
|
csslib: 0.17.2
|
||||||
diff_match_patch: ^0.4.1
|
diff_match_patch: ^0.4.1
|
||||||
|
markdown: ^7.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue