diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbd2c5..737c29b 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/example/pubspec.lock b/example/pubspec.lock index 41d9098..fcb29e1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -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: diff --git a/lib/business_logic/separate_models/tui_chat_model_tools.dart b/lib/business_logic/separate_models/tui_chat_model_tools.dart index 521923e..624b8ab 100644 --- a/lib/business_logic/separate_models/tui_chat_model_tools.dart +++ b/lib/business_logic/separate_models/tui_chat_model_tools.dart @@ -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'; @@ -16,7 +19,7 @@ class TUIChatModelTools { if (globalModel.chatConfig.offlinePushInfo != null) { final customData = globalModel.chatConfig.offlinePushInfo!(message, convID, convType); - if(customData != null){ + if (customData != null) { return customData; } } @@ -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 getExistingMessageByID( {required String msgID, required String conversationID, diff --git a/lib/business_logic/separate_models/tui_chat_separate_view_model.dart b/lib/business_logic/separate_models/tui_chat_separate_view_model.dart index bedb4af..6cfeeae 100644 --- a/lib/business_logic/separate_models/tui_chat_separate_view_model.dart +++ b/lib/business_logic/separate_models/tui_chat_separate_view_model.dart @@ -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 _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?> 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, @@ -914,7 +858,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { _repliedMessage = null; final sendMsgRes = await _messageService.sendMessage( - cloudCustomData: json.encode(cloudCustomData), + cloudCustomData: json.encode(cloudCustomData), id: textMessageInfo.id as String, offlinePushInfo: tools.buildMessagePushInfo( messageInfoWithSender, convID, convType), @@ -1439,11 +1383,27 @@ class TUIChatSeparateViewModel extends ChangeNotifier { globalModel.setMessageList(conversationID, []); } - Future revokeMsg(String msgID, + Future 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); } diff --git a/lib/business_logic/view_models/tui_chat_global_model.dart b/lib/business_logic/view_models/tui_chat_global_model.dart index 4442a02..006a67c 100644 --- a/lib/business_logic/view_models/tui_chat_global_model.dart +++ b/lib/business_logic/view_models/tui_chat_global_model.dart @@ -823,6 +823,81 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { return null; } + Future?> sendReplyMessageFromController({ + required String text, + required V2TimMessage messageBeenReplied, + required String convID, + required ConvType convType, + ValueChanged? 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(); + List 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 setLocalCustomData( String msgID, String localCustomData, String conversationID) async { final res = await _messageService.setLocalCustomData( diff --git a/lib/business_logic/view_models/tui_conversation_view_model.dart b/lib/business_logic/view_models/tui_conversation_view_model.dart index 40b7175..e7add7f 100644 --- a/lib/business_logic/view_models/tui_conversation_view_model.dart +++ b/lib/business_logic/view_models/tui_conversation_view_model.dart @@ -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'; diff --git a/lib/ui/controller/tim_uikit_chat_controller.dart b/lib/ui/controller/tim_uikit_chat_controller.dart index 43cd351..df19da4 100644 --- a/lib/ui/controller/tim_uikit_chat_controller.dart +++ b/lib/ui/controller/tim_uikit_chat_controller.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?>? 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? 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({ diff --git a/lib/ui/utils/screen_utils.dart b/lib/ui/utils/screen_utils.dart index b137c46..a61e51f 100644 --- a/lib/ui/utils/screen_utils.dart +++ b/lib/ui/utils/screen_utils.dart @@ -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; diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart index b9239b5..afe222a 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart @@ -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))), ); diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart index 880c882..e50f201 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart @@ -38,34 +38,33 @@ 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, - /// current message - V2TimMessage message, + /// the message widget for current message, build by your custom builder or our default builder + Widget messageWidget, - /// the message widget for current message, build by your custom builder or our default builder - Widget messageWidget, + /// scroll to the specific message, it will shows in the screen center, and call isNeedShowJumpStatus if necessary + Function onScrollToIndex, - /// scroll to the specific message, it will shows in the screen center, and call isNeedShowJumpStatus if necessary - Function onScrollToIndex, + /// if current message been called to jumped by other message + bool isNeedShowJumpStatus, - /// if current message been called to jumped by other message - bool isNeedShowJumpStatus, + /// clear the been jumped status, recommend to execute after get 'isNeedShowJumpStatus' + VoidCallback clearJumpStatus, - /// clear the been jumped status, recommend to execute after get 'isNeedShowJumpStatus' - VoidCallback clearJumpStatus, - - /// scroll to specific message, it will shows on the screen top, without the call isNeedShowJumpStatus - Function onScrollToIndexBegin, - ); + /// scroll to specific message, it will shows on the screen top, without the call isNeedShowJumpStatus + Function onScrollToIndexBegin, +); typedef MessageNickNameBuilder = Widget Function( BuildContext context, V2TimMessage message, TUIChatSeparateViewModel model); typedef MessageItemContent = Widget? Function( - V2TimMessage message, - bool isShowJump, - VoidCallback clearJump, - ); + V2TimMessage message, + bool isShowJump, + VoidCallback clearJump, +); class MessageHoverControlItem { String name; @@ -141,10 +140,11 @@ class MessageToolTipItem { final String iconImageAsset; final VoidCallback onClick; - MessageToolTipItem({required this.label, - required this.id, - required this.iconImageAsset, - required this.onClick}); + MessageToolTipItem( + {required this.label, + required this.id, + required this.iconImageAsset, + required this.onClick}); } class ToolTipsConfig { @@ -177,16 +177,18 @@ class ToolTipsConfig { List Function( V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips; - ToolTipsConfig({this.showDeleteMessage = true, - this.showMultipleChoiceMessage = true, - this.showRecallMessage = true, - this.showReplyMessage = true, - this.showTranslation = true, - this.showCopyMessage = true, - 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}); + ToolTipsConfig( + {this.showDeleteMessage = true, + this.showMultipleChoiceMessage = true, + this.showRecallMessage = true, + this.showReplyMessage = true, + this.showTranslation = true, + this.showCopyMessage = true, + 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}); } class TIMUIKitHistoryMessageListItem extends StatefulWidget { @@ -195,11 +197,11 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// tap remote user avatar callback function final void Function(String userID, TapDownDetails tapDetails)? - onTapForOthersPortrait; + onTapForOthersPortrait; /// secondary tap remote user avatar callback function final void Function(String userID, TapDownDetails tapDetails)? - onSecondaryTapForOthersPortrait; + onSecondaryTapForOthersPortrait; /// the function use for reply message, when click replied message can scroll to it. final Function? onScrollToIndex; @@ -209,7 +211,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// the callback for long press event, except myself avatar final Function(String? userId, String? nickName)? - onLongPressForOthersHeadPortrait; + onLongPressForOthersHeadPortrait; /// message item builder, works for customize all message types and row layout. final MessageItemBuilder? messageItemBuilder; @@ -258,18 +260,18 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// avatar builder final Widget Function(BuildContext context, V2TimMessage message)? - userAvatarBuilder; + userAvatarBuilder; /// theme info for message and avatar final MessageThemeData? themeData; /// builder for nick name row final Widget Function(BuildContext context, V2TimMessage message)? - topRowBuilder; + topRowBuilder; /// builder for bottom raw which under message content final Widget Function(BuildContext context, V2TimMessage message)? - bottomRowBuilder; + bottomRowBuilder; // open MessageReaction final bool? isUseMessageReaction; @@ -279,35 +281,47 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { final List customEmojiStickerList; - 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, - this.onScrollToIndex, - this.onScrollToIndexBegin, - this.onTapForOthersPortrait, - this.messageItemBuilder, - this.onLongPressForOthersHeadPortrait, - this.showAvatar = true, - this.showMessageSending = true, - this.showMessageReadRecipt = true, - this.allowLongPress = true, - this.toolTipsConfig, - this.onLongPress, - this.showGroupMessageReadRecipt = false, - this.allowAtUserWhenReply = true, - this.allowAvatarTap = true, - this.userAvatarBuilder, - this.themeData, - this.padding, - this.textPadding, - this.topRowBuilder, - this.isUseMessageReaction, - this.bottomRowBuilder, - this.isUseDefaultEmoji = false, - this.customEmojiStickerList = const [], - this.textFieldController, - this.onSecondaryTapForOthersPortrait}) + 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, + this.onScrollToIndex, + this.onScrollToIndexBegin, + this.onTapForOthersPortrait, + this.messageItemBuilder, + this.onLongPressForOthersHeadPortrait, + this.showAvatar = true, + this.showMessageSending = true, + this.showMessageReadRecipt = true, + this.allowLongPress = true, + this.toolTipsConfig, + this.onLongPress, + this.showGroupMessageReadRecipt = false, + this.allowAtUserWhenReply = true, + this.allowAvatarTap = true, + this.userAvatarBuilder, + this.themeData, + this.padding, + this.textPadding, + this.topRowBuilder, + this.isUseMessageReaction, + this.bottomRowBuilder, + this.isUseDefaultEmoji = false, + this.customEmojiStickerList = const [], + this.textFieldController, + this.onSecondaryTapForOthersPortrait, + this.groupMemberInfo, + this.customMessageHoverBarOnDesktop}) : super(key: key); @override @@ -357,7 +371,7 @@ class _TIMUIKItHistoryMessageListItemState // ignore: unused_field final MessageService _messageService = serviceLocator(); final TUISelfInfoViewModel selfInfoModel = - serviceLocator(); + serviceLocator(); final TUIThemeViewModel themeModel = serviceLocator(); // bool isChecked = false; @@ -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); @@ -407,13 +438,13 @@ class _TIMUIKItHistoryMessageListItemState switch (msgType) { case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM: final customWidget = - messageItemBuilder?.customMessageItemBuilder != null - ? messageItemBuilder!.customMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + messageItemBuilder?.customMessageItemBuilder != null + ? messageItemBuilder!.customMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitCustomElem( message: messageItem, @@ -428,10 +459,10 @@ class _TIMUIKItHistoryMessageListItemState case MessageElemType.V2TIM_ELEM_TYPE_SOUND: final customWidget = messageItemBuilder?.soundMessageItemBuilder != null ? messageItemBuilder!.soundMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitSoundElem( @@ -452,13 +483,13 @@ class _TIMUIKItHistoryMessageListItemState case MessageElemType.V2TIM_ELEM_TYPE_TEXT: if (isReplyMessage(messageItem)) { final customWidget = - messageItemBuilder?.textReplyMessageItemBuilder != null - ? messageItemBuilder!.textReplyMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + messageItemBuilder?.textReplyMessageItemBuilder != null + ? messageItemBuilder!.textReplyMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitReplyElem( message: messageItem, @@ -477,10 +508,10 @@ class _TIMUIKItHistoryMessageListItemState } final customWidget = messageItemBuilder?.textMessageItemBuilder != null ? messageItemBuilder!.textMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitTextElem( @@ -500,10 +531,10 @@ class _TIMUIKItHistoryMessageListItemState case MessageElemType.V2TIM_ELEM_TYPE_FACE: final customWidget = messageItemBuilder?.faceMessageItemBuilder != null ? messageItemBuilder!.faceMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitFaceElem( @@ -517,10 +548,10 @@ class _TIMUIKItHistoryMessageListItemState case MessageElemType.V2TIM_ELEM_TYPE_FILE: final customWidget = messageItemBuilder?.fileMessageItemBuilder != null ? messageItemBuilder!.fileMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitFileElem( @@ -535,21 +566,21 @@ class _TIMUIKItHistoryMessageListItemState ); case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS: final customWidget = - messageItemBuilder?.groupTipsMessageItemBuilder != null - ? messageItemBuilder!.groupTipsMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + messageItemBuilder?.groupTipsMessageItemBuilder != null + ? messageItemBuilder!.groupTipsMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? Text(TIM_t("[群系统消息]")); case MessageElemType.V2TIM_ELEM_TYPE_IMAGE: final customWidget = messageItemBuilder?.imageMessageItemBuilder != null ? messageItemBuilder!.imageMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitImageElem( @@ -563,10 +594,10 @@ class _TIMUIKItHistoryMessageListItemState case MessageElemType.V2TIM_ELEM_TYPE_VIDEO: final customWidget = messageItemBuilder?.videoMessageItemBuilder != null ? messageItemBuilder!.videoMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitVideoElem( @@ -578,23 +609,23 @@ class _TIMUIKItHistoryMessageListItemState ); case MessageElemType.V2TIM_ELEM_TYPE_LOCATION: final customWidget = - messageItemBuilder?.locationMessageItemBuilder != null - ? messageItemBuilder!.locationMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + messageItemBuilder?.locationMessageItemBuilder != null + ? messageItemBuilder!.locationMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? Text(TIM_t("[位置]")); case MessageElemType.V2TIM_ELEM_TYPE_MERGER: final customWidget = - messageItemBuilder?.mergerMessageItemBuilder != null - ? messageItemBuilder!.mergerMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + messageItemBuilder?.mergerMessageItemBuilder != null + ? messageItemBuilder!.mergerMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitMergerElem( messageItemBuilder: messageItemBuilder, @@ -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), @@ -683,11 +714,11 @@ class _TIMUIKItHistoryMessageListItemState width: 100, child: Container( decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - const Color(0x00C0E1FF), - theme.primaryColor ?? CommonColor.lightPrimaryColor - ]), - )), + gradient: LinearGradient(colors: [ + const Color(0x00C0E1FF), + theme.primaryColor ?? CommonColor.lightPrimaryColor + ]), + )), ), ), Text( @@ -706,11 +737,11 @@ class _TIMUIKItHistoryMessageListItemState width: 100, child: Container( decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - theme.primaryColor ?? CommonColor.primaryColor, - const Color(0x00C0E1FF), - ]), - )), + gradient: LinearGradient(colors: [ + theme.primaryColor ?? CommonColor.primaryColor, + const Color(0x00C0E1FF), + ]), + )), ), ), ], @@ -719,63 +750,48 @@ class _TIMUIKItHistoryMessageListItemState } bool isRevocable(int timestamp) => - (DateTime - .now() - .millisecondsSinceEpoch / 1000).ceil() - timestamp < 120; + (DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp < 120; - _onOpenToolTip(c, - V2TimMessage message, - TUIChatSeparateViewModel model, - TUITheme theme, - TapDownDetails? details, - bool? isFromWideTooltip, - bool? isShowMoreSticker,) { + _onOpenToolTip( + c, + V2TimMessage message, + TUIChatSeparateViewModel model, + TUITheme theme, + TapDownDetails? details, + bool? isFromWideTooltip, + 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; + (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) + screenWidth - targetWidth) : max(tapDetails?.globalPosition.dx ?? targetWidth, targetWidth) - .toDouble(); + .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( - globalPosition: Offset(dx, dy), - ) + globalPosition: Offset(dx, dy), + ) : null; initTools( @@ -804,13 +820,14 @@ class _TIMUIKItHistoryMessageListItemState }); } - initTools({BuildContext? context, - bool isLongMessage = false, - required TUIChatSeparateViewModel model, - TUITheme? theme, - bool? isShowMoreSticker, - TapDownDetails? details, - bool? isFromWideToolTip}) { + initTools( + {BuildContext? context, + bool isLongMessage = false, + required TUIChatSeparateViewModel model, + TUITheme? theme, + bool? isShowMoreSticker, + TapDownDetails? details, + bool? isFromWideToolTip}) { final isUseMessageReaction = widget.message.elemType == 2 ? false : model.chatConfig.isUseMessageReaction; @@ -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,14 +901,17 @@ 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, message: widget.message, allowAtUserWhenReply: widget.allowAtUserWhenReply, onLongPressForOthersHeadPortrait: - widget.onLongPressForOthersHeadPortrait, + widget.onLongPressForOthersHeadPortrait, selectEmojiPanelPosition: selectEmojiPanelPosition, onCloseTooltip: () => tooltip?.close(), onSelectSticker: (int value) { @@ -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,11 +1093,12 @@ 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 = - wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : ""; + wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : ""; return Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1120,37 +1114,41 @@ class _TIMUIKItHistoryMessageListItemState margin: const EdgeInsets.symmetric(horizontal: 4), child: Row( children: wideHoverTipList - .map((e) => - Tooltip( - message: e.name, - preferBelow: false, - textStyle: TextStyle(fontSize: 12, color: theme.white), - child: Row( - children: [ - InkWell( - onTapDown: e.onClick, - child: SizedBox( - width: 22, - height: 22, - child: e.icon, - ), - ), - if (lastItemName != e.name) - SizedBox( - width: 1, - height: 22, - child: Container( - color: theme.weakDividerColor, + .map((e) => Tooltip( + message: e.name, + preferBelow: false, + textStyle: TextStyle(fontSize: 12, color: theme.white), + child: Row( + children: [ + InkWell( + onTapDown: e.onClick, + child: SizedBox( + width: 22, + height: 22, + child: e.icon, + ), ), - ) - ], - ), - )) + if (lastItemName != e.name) + SizedBox( + width: 1, + height: 22, + child: Container( + color: theme.weakDividerColor, + ), + ) + ], + ), + )) .toList(), ), ), + if (isDesktopScreen && + isShowWideToolTip && + widget.customMessageHoverBarOnDesktop != null) + widget.customMessageHoverBarOnDesktop!(message), if (!isDesktopScreen || (model.chatConfig.isUseMessageHoverBarOnDesktop && + widget.customMessageHoverBarOnDesktop == null && !isShowWideToolTip)) const SizedBox( height: 24, @@ -1202,17 +1200,20 @@ class _TIMUIKItHistoryMessageListItemState @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final TUIChatSeparateViewModel model = - Provider.of(context); + Provider.of(context); final isDownloadWaiting = context.select( - (value) => value.isWaiting(widget.message.msgID ?? "")); + (value) => value.isWaiting(widget.message.msgID ?? "")); final TUITheme theme = value.theme; 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; @@ -1240,7 +1241,7 @@ class _TIMUIKItHistoryMessageListItemState if (isGroupTipsMsg) { if (widget.messageItemBuilder?.groupTipsMessageItemBuilder != null) { final groupTipsMessage = - widget.messageItemBuilder!.groupTipsMessageItemBuilder!( + widget.messageItemBuilder!.groupTipsMessageItemBuilder!( message, (model.jumpMsgID == message.msgID), clearJump, @@ -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,334 +1280,326 @@ class _TIMUIKItHistoryMessageListItemState } return LayoutBuilder( - builder: (context, constraints) => - Container( - margin: widget.padding ?? const EdgeInsets.only(bottom: 20), - child: Row( - key: _key, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (model.isMultiSelect) - Container( - margin: + 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, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (model.isMultiSelect) + Container( + margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0), - child: CheckBoxButton( - isChecked: model.multiSelectedMessageList.contains( - message), - onChanged: (value) { - if (value) { - model.addToMultiSelectedMessageList(message); - } else { - model.removeFromMultiSelectedMessageList(message); - } - }, - ), - ), - Expanded( - child: MouseRegion( - onEnter: (_) { - if (isDesktopScreen && - model.chatConfig.isUseMessageHoverBarOnDesktop) { - setState(() { - isShowWideToolTip = true; - }); - } - }, - onExit: (_) { - if (isDesktopScreen && - model.chatConfig.isUseMessageHoverBarOnDesktop) { - setState(() { - isShowWideToolTip = false; - }); - } - }, - child: GestureDetector( - behavior: + child: CheckBoxButton( + isChecked: model.multiSelectedMessageList.contains(message), + onChanged: (value) { + if (value) { + model.addToMultiSelectedMessageList(message); + } else { + model.removeFromMultiSelectedMessageList(message); + } + }, + ), + ), + Expanded( + child: MouseRegion( + onEnter: (_) { + if (isDesktopScreen && + model.chatConfig.isUseMessageHoverBarOnDesktop) { + setState(() { + isShowWideToolTip = true; + }); + } + }, + onExit: (_) { + if (isDesktopScreen && + model.chatConfig.isUseMessageHoverBarOnDesktop) { + setState(() { + isShowWideToolTip = false; + }); + } + }, + child: GestureDetector( + behavior: model.isMultiSelect ? HitTestBehavior.translucent : null, - onTap: () { - if (model.isMultiSelect) { - final checked = + onTap: () { + if (model.isMultiSelect) { + final checked = model.multiSelectedMessageList.contains(message); - if (checked) { - model.removeFromMultiSelectedMessageList(message); - } else { - model.addToMultiSelectedMessageList(message); - } - } else { - return; - } - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: isSelf - ? MainAxisAlignment.end - : MainAxisAlignment.start, - children: [ - if (!isSelf && widget.showAvatar) - GestureDetector( - onLongPress: () { - if (widget.onLongPressForOthersHeadPortrait != - null) {} - if (model.chatConfig - .isAllowLongPressAvatarToAt) { - widget.onLongPressForOthersHeadPortrait!( - message.sender, message.nickName); - } - }, - onTapDown: isDesktopScreen - ? (details) { - if (widget.onTapForOthersPortrait != null && - widget.allowAvatarTap) { - widget.onTapForOthersPortrait!( - message.sender ?? "", details); - } - } - : null, - onTap: isDesktopScreen - ? null - : () { - if (widget.onTapForOthersPortrait != null && - widget.allowAvatarTap) { - widget.onTapForOthersPortrait!( - message.sender ?? "", TapDownDetails()); - } - }, - onSecondaryTap: isDesktopScreen - ? null - : () { - if (widget.onSecondaryTapForOthersPortrait != - null && - widget.allowAvatarTap) { - widget.onSecondaryTapForOthersPortrait!( - message.sender ?? "", TapDownDetails()); - } - }, - onSecondaryTapDown: isDesktopScreen - ? (details) { - if (widget.onSecondaryTapForOthersPortrait != - null && - widget.allowAvatarTap) { - widget.onSecondaryTapForOthersPortrait!( - message.sender ?? "", details); - } - } - : null, - child: widget.userAvatarBuilder != null - ? widget.userAvatarBuilder!(context, message) - : Container( - margin: (isSelf && isShowNickNameForSelf) || - (!isSelf && isShowNickNameForOthers) - ? const EdgeInsets.only(top: 2) - : null, - child: SizedBox( - width: 40, - height: 40, - child: Avatar( - faceUrl: message.faceUrl ?? "", - showName: - MessageUtils.getDisplayName(message), - ), - ), - ), - ), - if (isSelf && - widget.message.elemType == 6 && - isDownloadWaiting) - Container( - margin: const EdgeInsets.only(top: 2), - child: LoadingAnimationWidget.threeArchedCircle( - color: theme.weakTextColor ?? Colors.grey, - size: 20, - ), - ), - Container( - margin: widget.showAvatar - ? (isSelf - ? const EdgeInsets.only(right: 13) - : const EdgeInsets.only(left: 13)) - : null, - child: Column( - crossAxisAlignment: isSelf - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - if ((isSelf && isShowNickNameForSelf) || - (!isSelf && isShowNickNameForOthers)) - widget.topRowBuilder != null - ? widget.topRowBuilder!(context, message) - : Container( - margin: const EdgeInsets.only(bottom: 4), - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: MediaQuery - .of(context) - .size - .width / - 1.7), - child: Text( - MessageUtils.getDisplayName(message), - overflow: TextOverflow.ellipsis, - style: widget.themeData - ?.nickNameTextStyle ?? - TextStyle( - fontSize: 12, - color: theme.weakTextColor), - ), - )), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (isSelf) - renderHoverTipAndReadStatus( - model, - isSelf, - message, - isPeerRead, - theme, - isDownloadWaiting), - Container( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth * 0.77, - ), - child: Builder(builder: (context) { - return Column( - crossAxisAlignment: - (message.isSelf ?? true) - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - GestureDetector( - child: IgnorePointer( - ignoring: model.isMultiSelect, - child: _getMessageItemBuilder( - message, - message.status, - model)), - onSecondaryTapDown: (details) { - if (widget.onLongPress != - null) { - widget.onLongPress!( - context, message); - return; - } - if (!PlatformUtils().isMobile) { - if (widget.allowLongPress) { - _onOpenToolTip( - context, - message, - model, - theme, - details, - false, - false); - } - } - }, - onLongPress: () { - if (widget.onLongPress != - null) { - widget.onLongPress!( - context, message); - return; - } - if (widget.allowLongPress && - !isDesktopScreen) { - _onOpenToolTip( - context, - message, - model, - theme, - null, - false, - false); - } - }, - onTapDown: (details) { - _tapDetails = details; - }, - ), - TIMUIKitTextTranslationElem( - message: message, - isUseDefaultEmoji: - widget.isUseDefaultEmoji, - customEmojiStickerList: - widget.customEmojiStickerList, - isFromSelf: message.isSelf ?? - true, - isShowJump: false, - clearJump: () {}, - chatModel: model) - ], - ); - }), - ), - if (!isSelf && - message.elemType == - MessageElemType - .V2TIM_ELEM_TYPE_SOUND && - message.localCustomInt != null && - message.localCustomInt != - HistoryMessageDartConstant.read) - Padding( - padding: const EdgeInsets.only( - left: 5, bottom: 12), - child: Icon(Icons.circle, - color: theme.cautionColor, - size: 10)), - if (!isSelf) - renderHoverTipAndReadStatus( - model, - isSelf, - message, - isPeerRead, - theme, - isDownloadWaiting), - ], - ), - if (widget.bottomRowBuilder != null) - widget.bottomRowBuilder!(context, message) - ], - ), - ), - if (!isSelf && - widget.message.elemType == 6 && - isDownloadWaiting) - Container( - margin: const EdgeInsets.only(top: 24, left: 6), - child: LoadingAnimationWidget.threeArchedCircle( - color: theme.weakTextColor ?? Colors.grey, - size: 20, - ), - ), - if (isSelf && widget.showAvatar) - widget.userAvatarBuilder != null - ? widget.userAvatarBuilder!(context, message) - : SizedBox( - width: 40, - height: 40, - child: InkWell( - onTapDown: (details) { + if (checked) { + model.removeFromMultiSelectedMessageList(message); + } else { + model.addToMultiSelectedMessageList(message); + } + } else { + return; + } + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: isSelf + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + if (!isSelf && widget.showAvatar) + GestureDetector( + onLongPress: () { + if (widget.onLongPressForOthersHeadPortrait != + null) {} + if (model.chatConfig.isAllowLongPressAvatarToAt) { + widget.onLongPressForOthersHeadPortrait!( + message.sender, message.nickName); + } + }, + onTapDown: isDesktopScreen + ? (details) { if (widget.onTapForOthersPortrait != null && widget.allowAvatarTap) { widget.onTapForOthersPortrait!( message.sender ?? "", details); } + } + : null, + onTap: isDesktopScreen + ? null + : () { + if (widget.onTapForOthersPortrait != null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + message.sender ?? "", TapDownDetails()); + } }, - child: Avatar( - faceUrl: message.faceUrl ?? "", - showName: - MessageUtils.getDisplayName(message)), - ), + onSecondaryTap: isDesktopScreen + ? null + : () { + if (widget.onSecondaryTapForOthersPortrait != + null && + widget.allowAvatarTap) { + widget.onSecondaryTapForOthersPortrait!( + message.sender ?? "", TapDownDetails()); + } + }, + onSecondaryTapDown: isDesktopScreen + ? (details) { + if (widget.onSecondaryTapForOthersPortrait != + null && + widget.allowAvatarTap) { + widget.onSecondaryTapForOthersPortrait!( + message.sender ?? "", details); + } + } + : null, + child: widget.userAvatarBuilder != null + ? widget.userAvatarBuilder!(context, message) + : Container( + margin: (isSelf && isShowNickNameForSelf) || + (!isSelf && isShowNickNameForOthers) + ? const EdgeInsets.only(top: 2) + : null, + child: SizedBox( + width: 40, + height: 40, + child: Avatar( + faceUrl: message.faceUrl ?? "", + showName: + MessageUtils.getDisplayName(message), + ), + ), + ), + ), + if (isSelf && + widget.message.elemType == 6 && + isDownloadWaiting) + Container( + margin: const EdgeInsets.only(top: 2), + child: LoadingAnimationWidget.threeArchedCircle( + color: theme.weakTextColor ?? Colors.grey, + size: 20, + ), + ), + Container( + margin: widget.showAvatar + ? (isSelf + ? const EdgeInsets.only(right: 13) + : const EdgeInsets.only(left: 13)) + : null, + child: Column( + crossAxisAlignment: isSelf + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if ((isSelf && isShowNickNameForSelf) || + (!isSelf && isShowNickNameForOthers)) + widget.topRowBuilder != null + ? widget.topRowBuilder!(context, message) + : Container( + margin: const EdgeInsets.only(bottom: 4), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context) + .size + .width / + 1.7), + child: Text( + MessageUtils.getDisplayName(message), + overflow: TextOverflow.ellipsis, + style: widget.themeData + ?.nickNameTextStyle ?? + TextStyle( + fontSize: 12, + color: theme.weakTextColor), + ), + )), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isSelf) + renderHoverTipAndReadStatus( + model, + isSelf, + message, + isPeerRead, + theme, + isDownloadWaiting), + Container( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth * 0.77, + ), + child: Builder(builder: (context) { + return Column( + crossAxisAlignment: + (message.isSelf ?? true) + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + GestureDetector( + child: IgnorePointer( + ignoring: model.isMultiSelect, + child: _getMessageItemBuilder( + message, + message.status, + model)), + onSecondaryTapDown: (details) { + if (widget.onLongPress != null) { + widget.onLongPress!( + context, message); + return; + } + if (!PlatformUtils().isMobile) { + if (widget.allowLongPress) { + _onOpenToolTip( + context, + message, + model, + theme, + details, + false, + false); + } + } + }, + onLongPress: () { + if (widget.onLongPress != null) { + widget.onLongPress!( + context, message); + return; + } + if (widget.allowLongPress && + !isDesktopScreen) { + _onOpenToolTip( + context, + message, + model, + theme, + null, + false, + false); + } + }, + onTapDown: (details) { + _tapDetails = details; + }, + ), + TIMUIKitTextTranslationElem( + message: message, + isUseDefaultEmoji: + widget.isUseDefaultEmoji, + customEmojiStickerList: + widget.customEmojiStickerList, + isFromSelf: message.isSelf ?? true, + isShowJump: false, + clearJump: () {}, + chatModel: model) + ], + ); + }), + ), + if (!isSelf && + message.elemType == + MessageElemType.V2TIM_ELEM_TYPE_SOUND && + message.localCustomInt != null && + message.localCustomInt != + HistoryMessageDartConstant.read) + Padding( + padding: const EdgeInsets.only( + left: 5, bottom: 12), + child: Icon(Icons.circle, + color: theme.cautionColor, size: 10)), + if (!isSelf) + renderHoverTipAndReadStatus( + model, + isSelf, + message, + isPeerRead, + theme, + isDownloadWaiting), + ], ), - ], + if (widget.bottomRowBuilder != null) + widget.bottomRowBuilder!(context, message) + ], + ), ), - ), + if (!isSelf && + widget.message.elemType == 6 && + isDownloadWaiting) + Container( + margin: const EdgeInsets.only(top: 24, left: 6), + child: LoadingAnimationWidget.threeArchedCircle( + color: theme.weakTextColor ?? Colors.grey, + size: 20, + ), + ), + if (isSelf && widget.showAvatar) + widget.userAvatarBuilder != null + ? widget.userAvatarBuilder!(context, message) + : SizedBox( + width: 40, + height: 40, + child: InkWell( + onTapDown: (details) { + if (widget.onTapForOthersPortrait != null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + message.sender ?? "", details); + } + }, + child: Avatar( + faceUrl: message.faceUrl ?? "", + showName: + MessageUtils.getDisplayName(message)), + ), + ), + ], ), ), - ], + ), ), - ), + ], + ), + ), ); } } diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart index a21d8da..18d7802 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart @@ -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); diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart index 21fb73a..f7744ed 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart @@ -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, diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_reply_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_reply_elem.dart index 329069c..f93364e 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_reply_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_reply_elem.dart @@ -58,7 +58,7 @@ class TIMUIKitReplyElem extends StatefulWidget { } class _TIMUIKitReplyElemState extends TIMUIKitState { - MessageRepliedData? repliedMessage = null; + MessageRepliedData? repliedMessage; V2TimMessage? rawMessage; bool isShowJumpState = false; bool isShining = false; @@ -72,7 +72,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { : "{}")); if (messageCloudCustomData.messageReply != null) { final MessageRepliedData repliedMessage = - MessageRepliedData.fromJson(messageCloudCustomData.messageReply!); + MessageRepliedData.fromJson(messageCloudCustomData.messageReply!); return repliedMessage; } return null; @@ -95,8 +95,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { if (message == null) { try { final RepliedMessageAbstract repliedMessageAbstract = - RepliedMessageAbstract.fromJson( - jsonDecode(cloudCustomData.messageAbstract)); + RepliedMessageAbstract.fromJson( + jsonDecode(cloudCustomData.messageAbstract)); if (repliedMessageAbstract.isNotEmpty) { message = V2TimMessage( elemType: 0, @@ -105,6 +105,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { msgID: repliedMessageAbstract.msgID); } } catch (e) { + // ignore: avoid_print print(e.toString()); } } @@ -131,8 +132,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { _renderMessageSummary(TUITheme? theme) { try { final RepliedMessageAbstract repliedMessageAbstract = - RepliedMessageAbstract.fromJson( - jsonDecode(repliedMessage?.messageAbstract ?? "")); + RepliedMessageAbstract.fromJson( + jsonDecode(repliedMessage?.messageAbstract ?? "")); if (TencentUtils.checkString(repliedMessageAbstract.summary) != null) { return _defaultRawMessageText(repliedMessageAbstract.summary!, theme); } @@ -144,6 +145,21 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { } } + (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,12 +170,23 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { } 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 = - widget.chatModel.abstractMessageBuilder != null - ? widget.chatModel.abstractMessageBuilder!(message) - : null; + widget.chatModel.abstractMessageBuilder != null + ? widget.chatModel.abstractMessageBuilder!(message) + : null; if (customAbstractMessage != null) { return _defaultRawMessageText( customAbstractMessage, @@ -275,14 +302,14 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { try { final String localJSON = widget.message.localCustomData!; final LocalCustomDataModel? localPreviewInfo = - LocalCustomDataModel.fromMap(json.decode(localJSON)); + LocalCustomDataModel.fromMap(json.decode(localJSON)); if (localPreviewInfo != null && !localPreviewInfo.isLinkPreviewEmpty()) { return Container( margin: const EdgeInsets.only(top: 8), child: - // You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget. - LinkPreviewWidget(linkPreview: localPreviewInfo), + // You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget. + LinkPreviewWidget(linkPreview: localPreviewInfo), ); } else { return null; @@ -317,7 +344,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { final defaultStyle = isFromSelf ? (theme.chatMessageItemFromSelfBgColor ?? - theme.lightPrimaryMaterialColor.shade50) + theme.lightPrimaryMaterialColor.shade50) : (theme.chatMessageItemFromOthersBgColor); final backgroundColor = isShowJumpState @@ -326,15 +353,15 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { final borderRadius = isFromSelf ? const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(2), - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10)) + topLeft: Radius.circular(10), + topRight: Radius.circular(2), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)) : const BorderRadius.only( - topLeft: Radius.circular(2), - topRight: Radius.circular(10), - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10)); + topLeft: Radius.circular(2), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)); final textWithLink = LinkPreviewEntry.getHyperlinksText( widget.message.textElem?.text ?? "", widget.chatModel.chatConfig.isSupportMarkdownForTextMessage, @@ -342,7 +369,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { isUseDefaultEmoji: widget.isUseDefaultEmoji, customEmojiStickerList: widget.customEmojiStickerList, isEnableTextSelection: - widget.chatModel.chatConfig.isEnableTextSelection ?? false); + widget.chatModel.chatConfig.isEnableTextSelection ?? false); return Container( padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10), decoration: BoxDecoration( @@ -350,7 +377,10 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { 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( @@ -391,22 +421,22 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { // You can render the widget from extension directly, with a [TextStyle] optionally. widget.chatModel.chatConfig.urlPreviewType != UrlPreviewType.none ? textWithLink!( - style: widget.fontStyle ?? - TextStyle( - fontSize: isDesktopScreen ? 14 : 16, - textBaseline: TextBaseline.ideographic, - height: widget.chatModel.chatConfig.textHeight)) + style: widget.fontStyle ?? + TextStyle( + fontSize: isDesktopScreen ? 14 : 16, + textBaseline: TextBaseline.ideographic, + height: widget.chatModel.chatConfig.textHeight)) : ExtendedText(widget.message.textElem?.text ?? "", - softWrap: true, - style: widget.fontStyle ?? - TextStyle( - fontSize: isDesktopScreen ? 14 : 16, - height: widget.chatModel.chatConfig.textHeight), - specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: widget.isUseDefaultEmoji, - customEmojiStickerList: widget.customEmojiStickerList, - showAtBackground: true, - )), + softWrap: true, + style: widget.fontStyle ?? + TextStyle( + fontSize: isDesktopScreen ? 14 : 16, + height: widget.chatModel.chatConfig.textHeight), + specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( + isUseDefaultEmoji: widget.isUseDefaultEmoji, + customEmojiStickerList: widget.customEmojiStickerList, + showAtBackground: true, + )), // If the link preview info is available, render the preview card. if (_renderPreviewWidget() != null && widget.chatModel.chatConfig.urlPreviewType == diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_video_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_video_elem.dart index e92a143..7cdb019 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_video_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_video_elem.dart @@ -282,16 +282,17 @@ class _TIMUIKitVideoElemState extends TIMUIKitState { package: 'tencent_cloud_chat_uikit', height: 64)), ), - Positioned( - right: 10, - bottom: 10, - child: Text( - MessageUtils.formatVideoTime( - widget.message.videoElem?.duration ?? - 0) - .toString(), - style: const TextStyle( - color: Colors.white, fontSize: 12))), + if (widget.message.videoElem?.duration != null && + widget.message.videoElem!.duration! > 0) + Positioned( + right: 10, + bottom: 10, + child: Text( + MessageUtils.formatVideoTime(widget + .message.videoElem!.duration!) + .toString(), + style: const TextStyle( + color: Colors.white, fontSize: 12))), ], )); }), diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart index 608c923..c400393 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart @@ -316,18 +316,6 @@ class _InputTextFieldState extends TIMUIKitState { }); } - 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 { ""; } - _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 { 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 { 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 { 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 { @override void dispose() { handleSetDraftText(); - widget.model.removeListener(onModelChanged); if (widget.controller != null) { widget.controller?.removeListener(controllerHandler); } diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart index 628b5ef..b02cb47 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart @@ -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(); diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/narrow.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/narrow.dart index 8e95045..7bf4e82 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/narrow.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/narrow.dart @@ -201,6 +201,7 @@ class _TIMUIKitTextFieldLayoutNarrowState addText: (int unicode) { final newText = String.fromCharCode(unicode); widget.addStickerToText(newText); + setSendButton(); // handleSetDraftText(); }, addCustomEmojiText: ((String singleEmojiName) { diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/wide.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/wide.dart index 4501ba9..56f80c4 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/wide.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/wide.dart @@ -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), diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart index 32f3f41..4b7bd8a 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart @@ -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 { TUIChatSeparateViewModel model = TUIChatSeparateViewModel(); + final TUISelfInfoViewModel selfInfoViewModel = + serviceLocator(); final TUIThemeViewModel themeViewModel = serviceLocator(); final TUIConversationViewModel conversationViewModel = serviceLocator(); - final TIMUIKitInputTextFieldController textFieldController = + TIMUIKitInputTextFieldController textFieldController = TIMUIKitInputTextFieldController(); bool isInit = false; final TUIChatGlobalModel chatGlobalModel = @@ -279,6 +289,9 @@ class _TUIChatState extends TIMUIKitState { } catch (e) {} }); } + if (oldWidget.textFieldBuilder != null && widget.textFieldBuilder == null) { + textFieldController = TIMUIKitInputTextFieldController(); + } } updateDraft() async { @@ -388,6 +401,7 @@ class _TUIChatState extends TIMUIKitState { }).toList(); } + final selfUserID = selfInfoViewModel.loginInfo?.userID; final TUIGroupListenerModel groupListenerModel = Provider.of(context, listen: true); final NeedUpdate? needUpdate = groupListenerModel.needUpdate; @@ -464,7 +478,14 @@ class _TUIChatState extends TIMUIKitState { 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 { conversationID: _getConvID(), conversationType: _getConvType(), initText: TencentUtils.checkString( - widget.draftText) ?? (PlatformUtils().isWeb - ? TencentUtils.checkString( - conversationViewModel.getWebDraft( - conversationID: widget.conversation - .conversationID)) - : - TencentUtils.checkString(widget - .conversation.draftText)), + widget.draftText) ?? + (PlatformUtils().isWeb + ? TencentUtils.checkString( + conversationViewModel + .getWebDraft( + conversationID: widget + .conversation + .conversationID)) + : TencentUtils.checkString( + widget.conversation + .draftText)), hintText: widget.textFieldHintText, showMorePanel: widget.config ?.isAllowShowMorePanel ?? diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart index 1b01cb7..33e2166 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart @@ -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, diff --git a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart index 09a335a..b865f9b 100644 --- a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart +++ b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart @@ -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 { } } + (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 - ? TIM_t("您") - : widget.lastMsg!.nickName ?? widget.lastMsg?.sender; + final option1 = isAdminRevoke + ? TIM_t("管理员") + : (isSelf + ? TIM_t("您") + : widget.lastMsg!.nickName ?? widget.lastMsg?.sender); if (mounted) { setState(() { groupTipsAbstractText = TIM_t_para( diff --git a/lib/ui/views/TIMUIKitProfile/tim_uikit_profile.dart b/lib/ui/views/TIMUIKitProfile/tim_uikit_profile.dart index b3251ad..319e3d1 100644 --- a/lib/ui/views/TIMUIKitProfile/tim_uikit_profile.dart +++ b/lib/ui/views/TIMUIKitProfile/tim_uikit_profile.dart @@ -86,16 +86,21 @@ class TIMUIKitProfile extends StatefulWidget { const TIMUIKitProfile( {Key? key, required this.userID, - @Deprecated("[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead") - this.operationListBuilder, - @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") - 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") - 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, + @Deprecated( + "[operationListBuilder] and [bottomOperationBuilder] merged into [builder], please use it instead") + this.operationListBuilder, + @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") + 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") + 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.profileWidgetBuilder, this.profileWidgetsOrder, @@ -130,7 +135,8 @@ class _TIMUIKitProfileState extends TIMUIKitState { @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); diff --git a/lib/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart b/lib/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart index 622cf41..d60f017 100644 --- a/lib/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart +++ b/lib/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart @@ -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!; } @@ -233,18 +258,18 @@ class TIMUIKitSearchMsgDetailState ), Expanded( child: Scrollbar( - controller: _scrollController, - child: ListView( - controller: _scrollController, - children: [ - ..._renderListMessage( - currentMsgListForConversation, context, isDesktopScreen), - _renderShowALl(keywordState.isNotEmpty && - totalMsgInConversationCount > - currentMsgListForConversation.length) - ], - ), - )), + controller: _scrollController, + child: ListView( + controller: _scrollController, + children: [ + ..._renderListMessage(currentMsgListForConversation, + context, isDesktopScreen), + _renderShowALl(keywordState.isNotEmpty && + totalMsgInConversationCount > + currentMsgListForConversation.length) + ], + ), + )), ], ), ); diff --git a/lib/ui/widgets/wide_popup.dart b/lib/ui/widgets/wide_popup.dart index 12628a8..decced5 100644 --- a/lib/ui/widgets/wide_popup.dart +++ b/lib/ui/widgets/wide_popup.dart @@ -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), ); }); diff --git a/pubspec.lock b/pubspec.lock index fac9857..d119ffb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -764,7 +764,7 @@ packages: source: hosted version: "2.0.3" markdown: - dependency: transitive + dependency: "direct main" description: name: markdown sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e" diff --git a/pubspec.yaml b/pubspec.yaml index 337966e..c1fc164 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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