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
|
||||
|
||||
### Improvements
|
||||
|
|
|
|||
|
|
@ -1139,7 +1139,7 @@ packages:
|
|||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "2.1.0+2"
|
||||
version: "2.1.2"
|
||||
tencent_cloud_uikit_core:
|
||||
dependency: transitive
|
||||
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_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';
|
||||
|
|
@ -104,6 +107,54 @@ class TUIChatModelTools {
|
|||
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(
|
||||
{required String msgID,
|
||||
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/ui/constants/history_message_constant.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';
|
||||
|
||||
enum LoadDirection { previous, latest }
|
||||
|
|
@ -44,7 +43,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
bool haveMoreData = false;
|
||||
bool haveMoreLatestData = false;
|
||||
String _currentPlayedMsgId = "";
|
||||
String _editRevokedMsg = "";
|
||||
GroupReceiptAllowType? _groupType;
|
||||
List<V2TimMessage> _multiSelectedMessageList = [];
|
||||
V2TimMessage? _repliedMessage;
|
||||
|
|
@ -108,13 +106,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
String get editRevokedMsg => _editRevokedMsg;
|
||||
|
||||
set editRevokedMsg(String value) {
|
||||
_editRevokedMsg = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
GroupReceiptAllowType? get groupType => _groupType;
|
||||
|
||||
set groupType(GroupReceiptAllowType? value) {
|
||||
|
|
@ -810,54 +801,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
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({
|
||||
required String text,
|
||||
required String convID,
|
||||
|
|
@ -888,7 +831,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
final cloudCustomData = {
|
||||
"messageReply": {
|
||||
"messageID": _repliedMessage!.msgID,
|
||||
"messageAbstract": _getMessageAbstract(_repliedMessage!),
|
||||
"messageAbstract": tools.getMessageAbstract(
|
||||
_repliedMessage!, abstractMessageBuilder),
|
||||
"messageSender": hasNickName
|
||||
? _repliedMessage!.nickName
|
||||
: _repliedMessage?.sender,
|
||||
|
|
@ -1439,11 +1383,27 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
globalModel.setMessageList(conversationID, []);
|
||||
}
|
||||
|
||||
Future<V2TimCallback> revokeMsg(String msgID,
|
||||
Future<Object?> revokeMsg(String msgID, bool isAdmin,
|
||||
[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(
|
||||
msgID: msgID, webMessageInstance: webMessageInstance);
|
||||
|
||||
if (res.code == 0) {
|
||||
globalModel.onMessageRevoked(msgID, conversationID);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -823,6 +823,81 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
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(
|
||||
String msgID, String localCustomData, String conversationID) async {
|
||||
final res = await _messageService.setLocalCustomData(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
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/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/view_models/tui_chat_global_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
|
||||
|
|
|
|||
|
|
@ -179,6 +179,100 @@ class TIMUIKitChatController {
|
|||
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;
|
||||
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
|
||||
sendForwardMessage({
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class TUIKitScreenUtils {
|
|||
|
||||
final diagonalInInches =
|
||||
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
|
||||
print("diagonalInInches $diagonalInInches");
|
||||
|
||||
deviceType = diagonalInInches < 11.0 ? DeviceType.Mobile : DeviceType.Desktop;
|
||||
return deviceType ?? DeviceType.Mobile;
|
||||
|
|
|
|||
|
|
@ -413,8 +413,6 @@ class _TIMUIKitHistoryMessageListState
|
|||
keepAlive: messageItem?.elemType ==
|
||||
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16),
|
||||
child:
|
||||
_getMessageItemBuilder(messageItem))),
|
||||
);
|
||||
|
|
@ -449,7 +447,6 @@ class _TIMUIKitHistoryMessageListState
|
|||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
final messageItem = readMessageList[index];
|
||||
final isSelf = messageItem?.isSelf ?? true;
|
||||
if (index == readMessageList.length - 1) {
|
||||
if (haveMoreData) {
|
||||
throttleFunction(
|
||||
|
|
@ -475,9 +472,6 @@ class _TIMUIKitHistoryMessageListState
|
|||
MessageElemType
|
||||
.V2TIM_ELEM_TYPE_SOUND,
|
||||
child: Container(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
horizontal: 16),
|
||||
child: _getMessageItemBuilder(
|
||||
messageItem))),
|
||||
),
|
||||
|
|
@ -500,9 +494,6 @@ class _TIMUIKitHistoryMessageListState
|
|||
keepAlive: messageItem?.elemType ==
|
||||
MessageElemType.V2TIM_ELEM_TYPE_SOUND,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: isSelf ? 0 : 16,
|
||||
right: isSelf ? 16 : 0),
|
||||
child: _getMessageItemBuilder(
|
||||
messageItem))),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
|||
import '../TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_select_emoji.dart';
|
||||
|
||||
typedef MessageRowBuilder = Widget? Function(
|
||||
|
||||
/// current message
|
||||
V2TimMessage message,
|
||||
|
||||
|
|
@ -141,7 +140,8 @@ class MessageToolTipItem {
|
|||
final String iconImageAsset;
|
||||
final VoidCallback onClick;
|
||||
|
||||
MessageToolTipItem({required this.label,
|
||||
MessageToolTipItem(
|
||||
{required this.label,
|
||||
required this.id,
|
||||
required this.iconImageAsset,
|
||||
required this.onClick});
|
||||
|
|
@ -177,7 +177,8 @@ class ToolTipsConfig {
|
|||
List<MessageToolTipItem> Function(
|
||||
V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips;
|
||||
|
||||
ToolTipsConfig({this.showDeleteMessage = true,
|
||||
ToolTipsConfig(
|
||||
{this.showDeleteMessage = true,
|
||||
this.showMultipleChoiceMessage = true,
|
||||
this.showRecallMessage = true,
|
||||
this.showReplyMessage = true,
|
||||
|
|
@ -186,7 +187,8 @@ class ToolTipsConfig {
|
|||
this.showForwardMessage = true,
|
||||
this.additionalMessageToolTips,
|
||||
@Deprecated(
|
||||
"Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.") this.additionalItemBuilder});
|
||||
"Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.")
|
||||
this.additionalItemBuilder});
|
||||
}
|
||||
|
||||
class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
||||
|
|
@ -279,10 +281,20 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
|||
|
||||
final List customEmojiStickerList;
|
||||
|
||||
const TIMUIKitHistoryMessageListItem({Key? key,
|
||||
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 TIMUIKitHistoryMessageListItem(
|
||||
{Key? key,
|
||||
required this.message,
|
||||
@Deprecated(
|
||||
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") this.showNickName = false,
|
||||
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
|
||||
this.showNickName = false,
|
||||
this.onScrollToIndex,
|
||||
this.onScrollToIndexBegin,
|
||||
this.onTapForOthersPortrait,
|
||||
|
|
@ -307,7 +319,9 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
|
|||
this.isUseDefaultEmoji = false,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.textFieldController,
|
||||
this.onSecondaryTapForOthersPortrait})
|
||||
this.onSecondaryTapForOthersPortrait,
|
||||
this.groupMemberInfo,
|
||||
this.customMessageHoverBarOnDesktop})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -391,8 +405,25 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
return false;
|
||||
}
|
||||
|
||||
Widget _messageItemBuilder(V2TimMessage messageItem,
|
||||
TUIChatSeparateViewModel model) {
|
||||
(bool isRevoke, bool isRevokeByAdmin) isRevokeMessage(
|
||||
V2TimMessage message, TUIChatSeparateViewModel model) {
|
||||
if (message.status == 6) {
|
||||
return (true, false);
|
||||
} else if (model.chatConfig.isGroupAdminRecallEnabled) {
|
||||
try {
|
||||
final customData = jsonDecode(message.cloudCustomData ?? "{}");
|
||||
final isRevoke = customData["isRevoke"] ?? false;
|
||||
final revokeByAdmin = customData["revokeByAdmin"] ?? false;
|
||||
return (isRevoke, revokeByAdmin);
|
||||
} catch (e) {
|
||||
return (false, false);
|
||||
}
|
||||
}
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
Widget _messageItemBuilder(
|
||||
V2TimMessage messageItem, TUIChatSeparateViewModel model) {
|
||||
final msgType = messageItem.elemType;
|
||||
final isShowJump = (model.jumpMsgID == messageItem.msgID) &&
|
||||
(messageItem.msgID?.isNotEmpty ?? false);
|
||||
|
|
@ -620,7 +651,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
groupMemberList: model.groupMemberList ?? []));
|
||||
}
|
||||
|
||||
Widget _selfRevokeEditMessageBuilder(theme, model) {
|
||||
Widget _selfRevokeEditMessageBuilder(theme, TUIChatSeparateViewModel model) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
alignment: Alignment.center,
|
||||
|
|
@ -633,7 +664,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
text: TIM_t("重新编辑"),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
model.editRevokedMsg = widget.message.textElem?.text ?? "";
|
||||
widget.textFieldController
|
||||
?.setTextField(widget.message.textElem?.text ?? "");
|
||||
},
|
||||
style: TextStyle(color: theme.primaryColor),
|
||||
)
|
||||
|
|
@ -645,14 +677,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
TIM_t_para("{{option2}}撤回了一条消息", "$option2撤回了一条消息")(
|
||||
option2: option2),
|
||||
TIM_t_para("{{option2}}撤回了一条消息", "$option2撤回了一条消息")(option2: option2),
|
||||
style: TextStyle(color: theme.weakTextColor, fontSize: 12),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _timeDividerBuilder(theme, int timeStamp,
|
||||
TUIChatSeparateViewModel model) {
|
||||
Widget _timeDividerBuilder(
|
||||
theme, int timeStamp, TUIChatSeparateViewModel model) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
|
|
@ -719,58 +750,43 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
}
|
||||
|
||||
bool isRevocable(int timestamp) =>
|
||||
(DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch / 1000).ceil() - timestamp < 120;
|
||||
(DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp < 120;
|
||||
|
||||
_onOpenToolTip(c,
|
||||
_onOpenToolTip(
|
||||
c,
|
||||
V2TimMessage message,
|
||||
TUIChatSeparateViewModel model,
|
||||
TUITheme theme,
|
||||
TapDownDetails? details,
|
||||
bool? isFromWideTooltip,
|
||||
bool? isShowMoreSticker,) {
|
||||
bool? isShowMoreSticker,
|
||||
) {
|
||||
if (tooltip != null && tooltip!.isOpen) {
|
||||
tooltip!.close();
|
||||
return;
|
||||
}
|
||||
tooltip = null;
|
||||
|
||||
final screenHeight = MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height;
|
||||
final screenWidth = MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width;
|
||||
final isLongMessage =
|
||||
context.size!.height + 350 > screenHeight && PlatformUtils().isMobile;
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final isLongMessage =
|
||||
context.size!.height + 350 > screenHeight && !(isDesktopScreen);
|
||||
final tapDetails =
|
||||
(isDesktopScreen || isLongMessage) ? (details ?? _tapDetails) : details;
|
||||
final isSelf = message.isSelf ?? true;
|
||||
|
||||
final targetWidth =
|
||||
min(MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width * 0.84, 350).toDouble();
|
||||
min(MediaQuery.of(context).size.width * 0.84, 350).toDouble();
|
||||
final double dx = !isSelf
|
||||
? min(tapDetails?.globalPosition.dx ?? targetWidth,
|
||||
screenWidth - targetWidth)
|
||||
: max(tapDetails?.globalPosition.dx ?? targetWidth, targetWidth)
|
||||
.toDouble();
|
||||
final double dy = min(
|
||||
tapDetails?.globalPosition.dy ?? MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height,
|
||||
MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height - 320)
|
||||
tapDetails?.globalPosition.dy ?? MediaQuery.of(context).size.height,
|
||||
MediaQuery.of(context).size.height - 320)
|
||||
.toDouble();
|
||||
final finalTapDetail = tapDetails != null
|
||||
? TapDownDetails(
|
||||
|
|
@ -804,7 +820,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
});
|
||||
}
|
||||
|
||||
initTools({BuildContext? context,
|
||||
initTools(
|
||||
{BuildContext? context,
|
||||
bool isLongMessage = false,
|
||||
required TUIChatSeparateViewModel model,
|
||||
TUITheme? theme,
|
||||
|
|
@ -829,10 +846,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
if (context != null) {
|
||||
RenderBox? box = _key.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (details != null && box != null) {
|
||||
double screenWidth = MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width;
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
final mousePosition = details.globalPosition;
|
||||
hasArrow = isDesktopScreen ? false : true;
|
||||
arrowTipDistance = 0;
|
||||
|
|
@ -846,14 +860,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
}
|
||||
} else {
|
||||
if (box != null) {
|
||||
double screenWidth = MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width;
|
||||
double viewInsetsBottom = MediaQuery
|
||||
.of(context)
|
||||
.viewInsets
|
||||
.bottom;
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
double viewInsetsBottom = MediaQuery.of(context).viewInsets.bottom;
|
||||
Offset offset = box.localToGlobal(Offset.zero);
|
||||
double boxWidth = box.size.width;
|
||||
if (isSelf) {
|
||||
|
|
@ -893,7 +901,10 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
showCloseButton: ShowCloseButton.none,
|
||||
touchThroughAreaShape: ClipAreaShape.rectangle,
|
||||
content: TIMUIKitMessageTooltip(
|
||||
iSUseDefaultHoverBar: model.chatConfig.isUseMessageHoverBarOnDesktop &&
|
||||
widget.customMessageHoverBarOnDesktop == null,
|
||||
model: model,
|
||||
groupMemberInfo: widget.groupMemberInfo,
|
||||
isShowMoreSticker: isShowMoreSticker ?? false,
|
||||
toolTipsConfig: widget.toolTipsConfig,
|
||||
isUseMessageReaction: isUseMessageReaction,
|
||||
|
|
@ -986,13 +997,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
),
|
||||
onClick: (details) {
|
||||
_onOpenToolTip(
|
||||
context,
|
||||
widget.message,
|
||||
model,
|
||||
theme,
|
||||
details,
|
||||
true,
|
||||
true);
|
||||
context, widget.message, model, theme, details, true, true);
|
||||
},
|
||||
),
|
||||
if (widget.toolTipsConfig?.showReplyMessage ?? true)
|
||||
|
|
@ -1035,19 +1040,12 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
context: context,
|
||||
title: TIM_t("转发"),
|
||||
submitWidget: Text(TIM_t("发送")),
|
||||
width: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width * 0.5,
|
||||
height: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height * 0.8,
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
onSubmit: () {
|
||||
forwardMessageScreenKey.currentState?.handleForwardMessage();
|
||||
},
|
||||
child: (onClose) =>
|
||||
Container(
|
||||
child: (onClose) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ForwardMessageScreen(
|
||||
conversationType: ConvType.c2c,
|
||||
|
|
@ -1068,13 +1066,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
),
|
||||
onClick: (details) {
|
||||
_onOpenToolTip(
|
||||
context,
|
||||
widget.message,
|
||||
model,
|
||||
theme,
|
||||
details,
|
||||
true,
|
||||
false);
|
||||
context, widget.message, model, theme, details, true, false);
|
||||
},
|
||||
),
|
||||
...?model.chatConfig.additionalDesktopMessageHoverBarItem
|
||||
|
|
@ -1092,7 +1084,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
context);
|
||||
}
|
||||
|
||||
Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model,
|
||||
Widget renderHoverTipAndReadStatus(
|
||||
TUIChatSeparateViewModel model,
|
||||
bool isSelf,
|
||||
V2TimMessage message,
|
||||
bool isPeerRead,
|
||||
|
|
@ -1100,7 +1093,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
bool isDownloadWaiting) {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final wideHoverTipList = model.chatConfig.isUseMessageHoverBarOnDesktop
|
||||
final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop &&
|
||||
widget.customMessageHoverBarOnDesktop == null)
|
||||
? getMessageHoverControlBar(model, theme)
|
||||
: [];
|
||||
final lastItemName =
|
||||
|
|
@ -1120,8 +1114,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(
|
||||
children: wideHoverTipList
|
||||
.map((e) =>
|
||||
Tooltip(
|
||||
.map((e) => Tooltip(
|
||||
message: e.name,
|
||||
preferBelow: false,
|
||||
textStyle: TextStyle(fontSize: 12, color: theme.white),
|
||||
|
|
@ -1149,8 +1142,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
.toList(),
|
||||
),
|
||||
),
|
||||
if (isDesktopScreen &&
|
||||
isShowWideToolTip &&
|
||||
widget.customMessageHoverBarOnDesktop != null)
|
||||
widget.customMessageHoverBarOnDesktop!(message),
|
||||
if (!isDesktopScreen ||
|
||||
(model.chatConfig.isUseMessageHoverBarOnDesktop &&
|
||||
widget.customMessageHoverBarOnDesktop == null &&
|
||||
!isShowWideToolTip))
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
|
|
@ -1209,10 +1207,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
final message = widget.message;
|
||||
final msgType = message.elemType;
|
||||
final isSelf = message.isSelf ?? true;
|
||||
final msgStatus = message.status;
|
||||
final isGroupTipsMsg =
|
||||
msgType == MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS;
|
||||
final isRevokedMsg = msgStatus == 6;
|
||||
|
||||
final revokeStatus = isRevokeMessage(message, model);
|
||||
final isRevokedMsg = revokeStatus.$1;
|
||||
final isAdminRevoke = revokeStatus.$2;
|
||||
|
||||
final isTimeDivider = msgType == 11;
|
||||
final isLatestDivider = msgType == 101;
|
||||
final isPeerRead = message.isPeerRead ?? false;
|
||||
|
|
@ -1251,8 +1252,13 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
}
|
||||
|
||||
if (isRevokedMsg) {
|
||||
final displayName =
|
||||
isSelf ? TIM_t("您") : message.nickName ?? message.sender;
|
||||
final displayName = isAdminRevoke
|
||||
? TIM_t("管理员")
|
||||
: (isSelf
|
||||
? TIM_t("您")
|
||||
: TencentUtils.checkString(message.nickName) ??
|
||||
TencentUtils.checkString(message.sender) ??
|
||||
message.userID);
|
||||
return isSelf && isRevokeEditable && isRevocable(message.timestamp!)
|
||||
? _selfRevokeEditMessageBuilder(theme, model)
|
||||
: _revokedMessageBuilder(theme, displayName ?? "");
|
||||
|
|
@ -1274,8 +1280,8 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
Container(
|
||||
builder: (context, constraints) => Container(
|
||||
padding: EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0),
|
||||
margin: widget.padding ?? const EdgeInsets.only(bottom: 20),
|
||||
child: Row(
|
||||
key: _key,
|
||||
|
|
@ -1287,8 +1293,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
margin:
|
||||
EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0),
|
||||
child: CheckBoxButton(
|
||||
isChecked: model.multiSelectedMessageList.contains(
|
||||
message),
|
||||
isChecked: model.multiSelectedMessageList.contains(message),
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
model.addToMultiSelectedMessageList(message);
|
||||
|
|
@ -1343,8 +1348,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
onLongPress: () {
|
||||
if (widget.onLongPressForOthersHeadPortrait !=
|
||||
null) {}
|
||||
if (model.chatConfig
|
||||
.isAllowLongPressAvatarToAt) {
|
||||
if (model.chatConfig.isAllowLongPressAvatarToAt) {
|
||||
widget.onLongPressForOthersHeadPortrait!(
|
||||
message.sender, message.nickName);
|
||||
}
|
||||
|
|
@ -1434,8 +1438,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
margin: const EdgeInsets.only(bottom: 4),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery
|
||||
.of(context)
|
||||
maxWidth: MediaQuery.of(context)
|
||||
.size
|
||||
.width /
|
||||
1.7),
|
||||
|
|
@ -1479,8 +1482,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
message.status,
|
||||
model)),
|
||||
onSecondaryTapDown: (details) {
|
||||
if (widget.onLongPress !=
|
||||
null) {
|
||||
if (widget.onLongPress != null) {
|
||||
widget.onLongPress!(
|
||||
context, message);
|
||||
return;
|
||||
|
|
@ -1499,8 +1501,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (widget.onLongPress !=
|
||||
null) {
|
||||
if (widget.onLongPress != null) {
|
||||
widget.onLongPress!(
|
||||
context, message);
|
||||
return;
|
||||
|
|
@ -1527,8 +1528,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
widget.isUseDefaultEmoji,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
isFromSelf: message.isSelf ??
|
||||
true,
|
||||
isFromSelf: message.isSelf ?? true,
|
||||
isShowJump: false,
|
||||
clearJump: () {},
|
||||
chatModel: model)
|
||||
|
|
@ -1538,8 +1538,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
),
|
||||
if (!isSelf &&
|
||||
message.elemType ==
|
||||
MessageElemType
|
||||
.V2TIM_ELEM_TYPE_SOUND &&
|
||||
MessageElemType.V2TIM_ELEM_TYPE_SOUND &&
|
||||
message.localCustomInt != null &&
|
||||
message.localCustomInt !=
|
||||
HistoryMessageDartConstant.read)
|
||||
|
|
@ -1547,8 +1546,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
padding: const EdgeInsets.only(
|
||||
left: 5, bottom: 12),
|
||||
child: Icon(Icons.circle,
|
||||
color: theme.cautionColor,
|
||||
size: 10)),
|
||||
color: theme.cautionColor, size: 10)),
|
||||
if (!isSelf)
|
||||
renderHoverTipAndReadStatus(
|
||||
model,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ class TIMUIKitMessageTooltip extends StatefulWidget {
|
|||
|
||||
final bool isShowMoreSticker;
|
||||
|
||||
final V2TimGroupMemberFullInfo? groupMemberInfo;
|
||||
|
||||
final bool iSUseDefaultHoverBar;
|
||||
|
||||
const TIMUIKitMessageTooltip(
|
||||
{Key? key,
|
||||
this.toolTipsConfig,
|
||||
|
|
@ -63,7 +67,9 @@ class TIMUIKitMessageTooltip extends StatefulWidget {
|
|||
required this.selectEmojiPanelPosition,
|
||||
required this.onCloseTooltip,
|
||||
required this.onSelectSticker,
|
||||
this.isShowMoreSticker = false})
|
||||
this.isShowMoreSticker = false,
|
||||
this.groupMemberInfo,
|
||||
required this.iSUseDefaultHoverBar})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -141,8 +147,9 @@ class TIMUIKitMessageTooltipState
|
|||
}
|
||||
|
||||
bool isRevocable(int timestamp, int upperTimeLimit) =>
|
||||
(DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp <
|
||||
upperTimeLimit;
|
||||
((DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp <
|
||||
upperTimeLimit) &&
|
||||
(widget.message.isSelf ?? true);
|
||||
|
||||
Widget ItemInkWell({
|
||||
Widget? child,
|
||||
|
|
@ -181,14 +188,24 @@ class TIMUIKitMessageTooltipState
|
|||
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(
|
||||
TUITheme theme, TUIChatSeparateViewModel model, V2TimMessage message) {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final isCanRevoke = isRevocable(
|
||||
final isCanRevokeSelf = isRevocable(
|
||||
widget.message.timestamp!, model.chatConfig.upperRecallTime);
|
||||
final shouldShowRevokeAction = isCanRevoke &&
|
||||
(widget.message.isSelf ?? true) &&
|
||||
final shouldShowRevokeAction = (isCanRevokeSelf || isAdminCanRecall()) &&
|
||||
widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL;
|
||||
final shouldShowReplyAction = !(widget.message.customElem?.data != null &&
|
||||
MessageUtils.isCallingData(widget.message.customElem!.data!));
|
||||
|
|
@ -258,13 +275,11 @@ class TIMUIKitMessageTooltipState
|
|||
}
|
||||
if (type == "forwardMessage") {
|
||||
return tooltipsConfig.showForwardMessage &&
|
||||
!(isDesktopScreen &&
|
||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
||||
!(isDesktopScreen && widget.iSUseDefaultHoverBar);
|
||||
}
|
||||
if (type == "replyMessage") {
|
||||
return tooltipsConfig.showReplyMessage &&
|
||||
!(isDesktopScreen &&
|
||||
widget.model.chatConfig.isUseMessageHoverBarOnDesktop);
|
||||
!(isDesktopScreen && widget.iSUseDefaultHoverBar);
|
||||
}
|
||||
if (type == "delete") {
|
||||
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
|
||||
|
|
@ -435,7 +450,11 @@ class TIMUIKitMessageTooltipState
|
|||
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
|
||||
break;
|
||||
case "revoke":
|
||||
model.revokeMsg(msgID, messageItem.messageFromWeb);
|
||||
model.revokeMsg(
|
||||
msgID,
|
||||
!isRevocable(
|
||||
widget.message.timestamp!, model.chatConfig.upperRecallTime),
|
||||
messageItem.messageFromWeb);
|
||||
break;
|
||||
case 'translate':
|
||||
model.translateText(widget.message);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
|||
|
||||
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({
|
||||
Key? key,
|
||||
this.itemBuilder,
|
||||
|
|
@ -101,6 +109,8 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
|
|||
this.textFieldController,
|
||||
required this.conversation,
|
||||
this.onSecondaryTapAvatar,
|
||||
this.groupMemberInfo,
|
||||
this.customMessageHoverBarOnDesktop,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -162,6 +172,9 @@ class _TIMUIKitHistoryMessageListContainerState
|
|||
mainHistoryListConfig: widget.mainHistoryListConfig,
|
||||
itemBuilder: (context, message) {
|
||||
return TIMUIKitHistoryMessageListItem(
|
||||
customMessageHoverBarOnDesktop:
|
||||
widget.customMessageHoverBarOnDesktop,
|
||||
groupMemberInfo: widget.groupMemberInfo,
|
||||
textFieldController: widget.textFieldController,
|
||||
userAvatarBuilder: widget.userAvatarBuilder,
|
||||
customEmojiStickerList: widget.customEmojiStickerList,
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class TIMUIKitReplyElem extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
||||
MessageRepliedData? repliedMessage = null;
|
||||
MessageRepliedData? repliedMessage;
|
||||
V2TimMessage? rawMessage;
|
||||
bool isShowJumpState = false;
|
||||
bool isShining = false;
|
||||
|
|
@ -105,6 +105,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
|||
msgID: repliedMessageAbstract.msgID);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
if (repliedMessage == null) {
|
||||
return const SizedBox(width: 0, height: 12);
|
||||
|
|
@ -154,6 +170,17 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
|||
}
|
||||
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 isSelf = message.isSelf ?? true;
|
||||
final customAbstractMessage =
|
||||
|
|
@ -350,7 +377,10 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
|
|||
borderRadius: widget.borderRadius ?? borderRadius,
|
||||
),
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6),
|
||||
BoxConstraints(maxWidth: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width * 0.6),
|
||||
child: GestureDetector(
|
||||
onTap: _jumpToRawMsg,
|
||||
child: Column(
|
||||
|
|
|
|||
|
|
@ -282,13 +282,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
package: 'tencent_cloud_chat_uikit',
|
||||
height: 64)),
|
||||
),
|
||||
if (widget.message.videoElem?.duration != null &&
|
||||
widget.message.videoElem!.duration! > 0)
|
||||
Positioned(
|
||||
right: 10,
|
||||
bottom: 10,
|
||||
child: Text(
|
||||
MessageUtils.formatVideoTime(
|
||||
widget.message.videoElem?.duration ??
|
||||
0)
|
||||
MessageUtils.formatVideoTime(widget
|
||||
.message.videoElem!.duration!)
|
||||
.toString(),
|
||||
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() {
|
||||
final selection = textEditingController.selection;
|
||||
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) {
|
||||
focusNode.requestFocus();
|
||||
} else {
|
||||
|
|
@ -682,7 +670,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
if (widget.controller != null) {
|
||||
widget.controller?.addListener(controllerHandler);
|
||||
}
|
||||
widget.model.addListener(onModelChanged);
|
||||
final AppLocale appLocale = I18nUtils.findDeviceLocale(null);
|
||||
languageType =
|
||||
(appLocale == AppLocale.zhHans || appLocale == AppLocale.zhHant)
|
||||
|
|
@ -696,7 +683,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
controllerHandler() {
|
||||
final actionType = widget.controller?.actionType;
|
||||
if (actionType == ActionType.longPressToAt) {
|
||||
_longPressToAt(
|
||||
mentionMemberInMessage(
|
||||
widget.controller?.atUserID, widget.controller?.atUserName);
|
||||
} else if (actionType == ActionType.setTextField) {
|
||||
final newText = widget.controller?.inputText ?? "";
|
||||
|
|
@ -704,6 +691,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
textEditingController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: textEditingController.text.length));
|
||||
lastText = textEditingController.text;
|
||||
focusNode.requestFocus();
|
||||
return;
|
||||
} else if (actionType == ActionType.requestFocus) {
|
||||
focusNode.requestFocus();
|
||||
|
|
@ -736,7 +724,6 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
@override
|
||||
void dispose() {
|
||||
handleSetDraftText();
|
||||
widget.model.removeListener(onModelChanged);
|
||||
if (widget.controller != null) {
|
||||
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() {
|
||||
actionType = ActionType.hideAllPanel;
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ class _TIMUIKitTextFieldLayoutNarrowState
|
|||
addText: (int unicode) {
|
||||
final newText = String.fromCharCode(unicode);
|
||||
widget.addStickerToText(newText);
|
||||
setSendButton();
|
||||
// handleSetDraftText();
|
||||
},
|
||||
addCustomEmojiText: ((String singleEmojiName) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
|
@ -227,6 +226,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print(e);
|
||||
}
|
||||
generateDefaultControlBarItems();
|
||||
|
|
@ -239,6 +239,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
sendFileUseJs(imageFile);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print("Paste image failed: ${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1033,7 +1034,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
decoration: InputDecoration(
|
||||
hoverColor: hexToColor("fafafa"),
|
||||
hoverColor: Colors.transparent,
|
||||
border: InputBorder.none,
|
||||
hintStyle: const TextStyle(
|
||||
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/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_self_info_view_model.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/constants/history_message_constant.dart';
|
||||
|
|
@ -144,6 +145,12 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
/// Custom emoji panel.
|
||||
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
|
||||
final Widget Function(BuildContext context)? textFieldBuilder;
|
||||
|
||||
|
|
@ -181,7 +188,8 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
this.textFieldBuilder,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.customAppBar,
|
||||
this.onSecondaryTapAvatar})
|
||||
this.onSecondaryTapAvatar,
|
||||
this.customMessageHoverBarOnDesktop})
|
||||
: super(key: key) {
|
||||
startTime = DateTime
|
||||
.now()
|
||||
|
|
@ -194,10 +202,12 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
|
||||
class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||
TUIChatSeparateViewModel model = TUIChatSeparateViewModel();
|
||||
final TUISelfInfoViewModel selfInfoViewModel =
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||
final TUIConversationViewModel conversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final TIMUIKitInputTextFieldController textFieldController =
|
||||
TIMUIKitInputTextFieldController textFieldController =
|
||||
TIMUIKitInputTextFieldController();
|
||||
bool isInit = false;
|
||||
final TUIChatGlobalModel chatGlobalModel =
|
||||
|
|
@ -279,6 +289,9 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
if (oldWidget.textFieldBuilder != null && widget.textFieldBuilder == null) {
|
||||
textFieldController = TIMUIKitInputTextFieldController();
|
||||
}
|
||||
}
|
||||
|
||||
updateDraft() async {
|
||||
|
|
@ -388,6 +401,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
}).toList();
|
||||
}
|
||||
|
||||
final selfUserID = selfInfoViewModel.loginInfo?.userID;
|
||||
final TUIGroupListenerModel groupListenerModel =
|
||||
Provider.of<TUIGroupListenerModel>(context, listen: true);
|
||||
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
|
||||
|
|
@ -464,7 +478,14 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
child: Listener(
|
||||
onPointerMove: closePanel,
|
||||
child: TIMUIKitHistoryMessageListContainer(
|
||||
customMessageHoverBarOnDesktop: widget
|
||||
.customMessageHoverBarOnDesktop,
|
||||
conversation: widget.conversation,
|
||||
groupMemberInfo: model.groupMemberList
|
||||
?.firstWhere(
|
||||
(element) =>
|
||||
element?.userID == selfUserID,
|
||||
orElse: () => null),
|
||||
textFieldController: textFieldController,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
|
|
@ -532,14 +553,17 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
conversationID: _getConvID(),
|
||||
conversationType: _getConvType(),
|
||||
initText: TencentUtils.checkString(
|
||||
widget.draftText) ?? (PlatformUtils().isWeb
|
||||
widget.draftText) ??
|
||||
(PlatformUtils().isWeb
|
||||
? TencentUtils.checkString(
|
||||
conversationViewModel.getWebDraft(
|
||||
conversationID: widget.conversation
|
||||
conversationViewModel
|
||||
.getWebDraft(
|
||||
conversationID: widget
|
||||
.conversation
|
||||
.conversationID))
|
||||
:
|
||||
TencentUtils.checkString(widget
|
||||
.conversation.draftText)),
|
||||
: TencentUtils.checkString(
|
||||
widget.conversation
|
||||
.draftText)),
|
||||
hintText: widget.textFieldHintText,
|
||||
showMorePanel: widget.config
|
||||
?.isAllowShowMorePanel ??
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class TIMUIKitChatConfig {
|
|||
/// Customize the time divider among the two messages.
|
||||
final TimeDividerConfig? timeDividerConfig;
|
||||
|
||||
/// control if allowed to show reading status.
|
||||
/// Control if allowed to show reading status.
|
||||
/// [Default]: true.
|
||||
final bool isShowReadingStatus;
|
||||
|
||||
|
|
@ -191,9 +191,18 @@ class TIMUIKitChatConfig {
|
|||
/// [Default]: true.
|
||||
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(
|
||||
{this.onTapLink,
|
||||
this.timeDividerConfig,
|
||||
this.isGroupAdminRecallEnabled = false,
|
||||
this.isAutoReportRead = true,
|
||||
this.faceURIPrefix,
|
||||
this.faceURISuffix,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// ignore_for_file: unrelated_type_equality_checks
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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 {
|
||||
final isRevokedMessage = widget.lastMsg!.status == 6;
|
||||
final revokeStatus = isRevokeMessage(widget.lastMsg);
|
||||
final isRevokedMessage = revokeStatus.$1;
|
||||
final isAdminRevoke = revokeStatus.$2;
|
||||
if (isRevokedMessage) {
|
||||
final isSelf = widget.lastMsg!.isSelf ?? true;
|
||||
final option1 = isSelf
|
||||
final option1 = isAdminRevoke
|
||||
? TIM_t("管理员")
|
||||
: (isSelf
|
||||
? TIM_t("您")
|
||||
: widget.lastMsg!.nickName ?? widget.lastMsg?.sender;
|
||||
: widget.lastMsg!.nickName ?? widget.lastMsg?.sender);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
groupTipsAbstractText = TIM_t_para(
|
||||
|
|
|
|||
|
|
@ -86,15 +86,20 @@ class TIMUIKitProfile extends StatefulWidget {
|
|||
const TIMUIKitProfile(
|
||||
{Key? key,
|
||||
required this.userID,
|
||||
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||
@Deprecated(
|
||||
"[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||
this.operationListBuilder,
|
||||
@Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||
@Deprecated(
|
||||
"[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead")
|
||||
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 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.handleProfileDetailCardTap,
|
||||
@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 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")
|
||||
@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.profileWidgetBuilder,
|
||||
|
|
@ -130,7 +135,8 @@ class _TIMUIKitProfileState extends TIMUIKitState<TIMUIKitProfile> {
|
|||
@override
|
||||
void didUpdateWidget(covariant TIMUIKitProfile oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.userID != widget.userID) {
|
||||
if (oldWidget.userID != widget.userID ||
|
||||
_model.userProfile?.friendInfo?.userID != widget.userID) {
|
||||
_model.userProfile = null;
|
||||
_model.loadData(
|
||||
userID: widget.userID, isNeedConversation: !widget.isSelf);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
|
||||
|
|
@ -57,12 +59,34 @@ class TIMUIKitSearchMsgDetailState
|
|||
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) {
|
||||
final msgType = message.elemType;
|
||||
final isRevokedMessage = message.status == 6;
|
||||
final revokeStatus = isRevokeMessage(message);
|
||||
final isRevokedMessage = revokeStatus.$1;
|
||||
final isAdminRevoke = revokeStatus.$2;
|
||||
if (isRevokedMessage) {
|
||||
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撤回了一条消息")(
|
||||
option2: option2);
|
||||
}
|
||||
|
|
@ -173,7 +197,8 @@ class TIMUIKitSearchMsgDetailState
|
|||
final currentText = _controller.text;
|
||||
if (currentMsgListForConversation.isEmpty &&
|
||||
widget.initMessageList != null &&
|
||||
widget.initMessageList!.isNotEmpty && currentText.isEmpty) {
|
||||
widget.initMessageList!.isNotEmpty &&
|
||||
currentText.isEmpty) {
|
||||
currentMsgListForConversation = widget.initMessageList!;
|
||||
}
|
||||
|
||||
|
|
@ -237,8 +262,8 @@ class TIMUIKitSearchMsgDetailState
|
|||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
..._renderListMessage(
|
||||
currentMsgListForConversation, context, isDesktopScreen),
|
||||
..._renderListMessage(currentMsgListForConversation,
|
||||
context, isDesktopScreen),
|
||||
_renderShowALl(keywordState.isNotEmpty &&
|
||||
totalMsgInConversationCount >
|
||||
currentMsgListForConversation.length)
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ class TUIKitWidePopup {
|
|||
builder: (context) {
|
||||
return WillPopScope(
|
||||
child: AlertDialog(
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
backgroundColor: Colors.transparent,
|
||||
titlePadding: 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(
|
||||
backgroundColor: isDarkBackground ? const Color(0x7F000000) : null,
|
||||
closeFun: () {
|
||||
isShow = false;
|
||||
if (entry != null) {
|
||||
entry?.remove();
|
||||
entry = null;
|
||||
}
|
||||
},
|
||||
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),
|
||||
initOffset: offset,
|
||||
child: contentWidget),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -764,7 +764,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.0.3"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: markdown
|
||||
sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
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.
|
||||
version: 2.1.1
|
||||
version: 2.1.2
|
||||
homepage: https://www.tencentcloud.com/products/im?from=pub
|
||||
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
|
||||
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
|
||||
|
|
@ -74,6 +74,7 @@ dependencies:
|
|||
cross_file: ^0.3.3+4
|
||||
csslib: 0.17.2
|
||||
diff_match_patch: ^0.4.1
|
||||
markdown: ^7.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^1.0.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue