diff --git a/example/pubspec.lock b/example/pubspec.lock index 72dea3f..41d9098 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1139,7 +1139,7 @@ packages: path: ".." relative: true source: path - version: "2.1.0+1" + version: "2.1.0+2" tencent_cloud_uikit_core: dependency: transitive description: diff --git a/images/svg/send_face.png b/images/svg/send_face.png new file mode 100644 index 0000000..11b1dbb Binary files /dev/null and b/images/svg/send_face.png differ diff --git a/images/svg/send_face.svg b/images/svg/send_face.svg index 4f0d3c5..cc96225 100644 --- a/images/svg/send_face.svg +++ b/images/svg/send_face.svg @@ -1,5 +1,7 @@ - - - + + + + + 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 a0984ec..bedb4af 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,6 +20,7 @@ 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 } @@ -53,7 +54,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { bool showC2cMessageEditStatus = true; TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig(); ValueChanged? setInputField; - String Function(V2TimMessage message)? abstractMessageBuilder; + String? Function(V2TimMessage message)? abstractMessageBuilder; Function(String userID, TapDownDetails tapDetails)? onTapAvatar; V2TimGroupMemberFullInfo? _currentChatUserInfo; V2TimGroupInfo? _groupInfo; @@ -228,7 +229,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { globalModel.setChatConfig(chatConfig); globalModel.clearRecivedNewMessageCount(); _isInit = true; - Future.delayed(const Duration(milliseconds: 300), (){ + Future.delayed(const Duration(milliseconds: 300), () { markMessageAsRead(); }); } @@ -809,7 +810,25 @@ class TUIChatSeparateViewModel extends ChangeNotifier { return null; } - String _getAbstractMessage(V2TimMessage message) { + 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: @@ -831,7 +850,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { case MessageElemType.V2TIM_ELEM_TYPE_SOUND: return "[语音消息]"; case MessageElemType.V2TIM_ELEM_TYPE_TEXT: - return "[文本消息]"; + return message.textElem?.text ?? "[文本消息]"; case MessageElemType.V2TIM_ELEM_TYPE_VIDEO: return "[视频消息]"; default: @@ -869,7 +888,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { final cloudCustomData = { "messageReply": { "messageID": _repliedMessage!.msgID, - "messageAbstract": _getAbstractMessage(_repliedMessage!), + "messageAbstract": _getMessageAbstract(_repliedMessage!), "messageSender": hasNickName ? _repliedMessage!.nickName : _repliedMessage?.sender, @@ -893,9 +912,10 @@ class TUIChatSeparateViewModel extends ChangeNotifier { ]; globalModel.setMessageList(conversationID, currentHistoryMsgList); - final sendMsgRes = await _messageService.sendReplyMessage( + _repliedMessage = null; + final sendMsgRes = await _messageService.sendMessage( + cloudCustomData: json.encode(cloudCustomData), id: textMessageInfo.id as String, - replyMessage: _repliedMessage!, offlinePushInfo: tools.buildMessagePushInfo( messageInfoWithSender, convID, convType), needReadReceipt: chatConfig.isShowGroupReadingStatus && @@ -908,7 +928,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier { .contains(oldGroupType))), groupID: groupID, receiver: receiver); - _repliedMessage = null; notifyListeners(); globalModel.updateMessage(sendMsgRes, convID, messageInfoWithSender.id ?? "", convType, groupType, setInputField); 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 6d8985f..40b7175 100644 --- a/lib/business_logic/view_models/tui_conversation_view_model.dart +++ b/lib/business_logic/view_models/tui_conversation_view_model.dart @@ -2,6 +2,7 @@ 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'; @@ -42,6 +43,7 @@ class TUIConversationViewModel extends ChangeNotifier { late V2TimConversationListener _conversationListener; List _conversationList = []; static V2TimConversation? _selectedConversation; + Map webDraftMap = {}; bool _haveMoreData = true; int _totalUnReadCount = 0; @@ -65,9 +67,8 @@ class TUIConversationViewModel extends ChangeNotifier { .toList(); _conversationList.removeWhere((element) => element?.isPinned == true); _conversationList = [...pinnedConversation, ..._conversationList]; - // ignore: empty_catches - } catch (e) { - } + // ignore: empty_catches + } catch (e) {} } else { _conversationList.sort((a, b) => b!.orderkey!.compareTo(a!.orderkey!)); } @@ -259,10 +260,46 @@ class TUIConversationViewModel extends ChangeNotifier { listener: _conversationListener); } - Future setConversationDraft( - {required String conversationID, String? draftText}) async { - return _conversationService.setConversationDraft( - conversationID: conversationID, draftText: draftText); + Future setConversationDraft({ + required String conversationID, + String? draftText, + bool isTopic = false, + String? groupID, + bool isAllowWeb = true, + }) async { + assert(!isTopic || (groupID != null && groupID.isNotEmpty), + "When 'isTopic' is true, 'groupID' must not be null or empty."); + if (PlatformUtils().isWeb && isAllowWeb) { + webDraftMap[conversationID] = draftText ?? ""; + return V2TimCallback(code: 0, desc: ""); + } else { + if (isTopic) { + final topicInfoList = await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .getTopicInfoList(groupID: groupID!, topicIDList: [conversationID]); + final topicInfo = topicInfoList.data?.first.topicInfo; + topicInfo?.draftText = draftText; + final res = await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .setTopicInfo(groupID: groupID, topicInfo: topicInfo!); + return res; + } else { + return _conversationService.setConversationDraft( + conversationID: conversationID, draftText: draftText); + } + } + } + + clearWebDraft({ + required String conversationID, + }) { + webDraftMap[conversationID] = ""; + } + + String? getWebDraft({ + required String conversationID, + }) { + return TencentUtils.checkString(webDraftMap[conversationID]); } clearData() { diff --git a/lib/data_services/core/core_services.dart b/lib/data_services/core/core_services.dart index 5c68558..1faa4ab 100644 --- a/lib/data_services/core/core_services.dart +++ b/lib/data_services/core/core_services.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dart'; @@ -92,4 +93,6 @@ abstract class CoreServices { setDarkTheme(); setLightTheme(); + + setDeviceType(DeviceType deviceType); } diff --git a/lib/data_services/core/core_services_implements.dart b/lib/data_services/core/core_services_implements.dart index 47fc81a..97fc281 100644 --- a/lib/data_services/core/core_services_implements.dart +++ b/lib/data_services/core/core_services_implements.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_setting_model.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; @@ -74,22 +75,28 @@ class CoreServicesImpl implements CoreServices { @override Future init( { - /// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error. ValueChanged? onTUIKitCallbackListener, required int sdkAppID, required LogLevelEnum loglevel, required V2TimSDKListener listener, LanguageEnum? language, - String? extraLanguage, + String? extraLanguage, TIMUIKitConfig? config, + + /// Specify the current device platform, mobile or desktop, based on your needs. + /// TUIKit will automatically determine the platform if no specification is provided. DeviceType? platform, + DeviceType? platform, VoidCallback? onWebLoginSuccess}) async { + if (platform != null) { + TUIKitScreenUtils.deviceType = platform; + } addIdentifier(); - if(extraLanguage != null){ + if (extraLanguage != null) { Future.delayed(const Duration(milliseconds: 1), () { I18nUtils(null, extraLanguage); }); - }else if (language != null) { + } else if (language != null) { Future.delayed(const Duration(milliseconds: 1), () { I18nUtils(null, languageEnumToString[language]); }); @@ -139,11 +146,11 @@ class CoreServicesImpl implements CoreServices { required String userId, }) async { _userID = userId; - if(extraLanguage != null){ + if (extraLanguage != null) { Future.delayed(const Duration(milliseconds: 1), () { I18nUtils(null, extraLanguage); }); - }else if (language != null) { + } else if (language != null) { Future.delayed(const Duration(milliseconds: 1), () { I18nUtils(null, languageEnumToString[language]); }); @@ -246,9 +253,8 @@ class CoreServicesImpl implements CoreServices { } tuiFriendShipViewModel.userStatusList = currentUserStatusList; - // ignore: empty_catches - } catch (e) { - } + // ignore: empty_catches + } catch (e) {} } @override @@ -398,4 +404,9 @@ class CoreServicesImpl implements CoreServices { .doBackground(unreadCount: totalCount ?? 0); } } + + @override + setDeviceType(DeviceType deviceType) { + TUIKitScreenUtils.deviceType = deviceType; + } } diff --git a/lib/ui/controller/tim_uikit_chat_controller.dart b/lib/ui/controller/tim_uikit_chat_controller.dart index 054ca99..43cd351 100644 --- a/lib/ui/controller/tim_uikit_chat_controller.dart +++ b/lib/ui/controller/tim_uikit_chat_controller.dart @@ -138,8 +138,8 @@ class TIMUIKitChatController { assert((groupID == null) != (userID == null)); assert(groupID != null || convType != ConvType.group); assert(userID != null || convType != ConvType.c2c); - if (isNavigateToMessageListBottom) { - scrollController?.animateTo( + if (isNavigateToMessageListBottom && scrollController != null) { + scrollController!.animateTo( scrollController!.position.minScrollExtent, duration: const Duration(milliseconds: 200), curve: Curves.ease, @@ -159,7 +159,7 @@ class TIMUIKitChatController { offlinePushInfo: offlinePushInfo); } else if (model != null) { /// Sends a message to the current conversation specified on `TIMUIKitChat`. 发送到 `TIMUIKitChat` 中指定的当前对话。 - if (isNavigateToMessageListBottom) { + if (isNavigateToMessageListBottom && scrollController != null) { scrollController?.animateTo( scrollController!.position.minScrollExtent, duration: const Duration(milliseconds: 200), diff --git a/lib/ui/utils/screen_utils.dart b/lib/ui/utils/screen_utils.dart index 5416c25..b137c46 100644 --- a/lib/ui/utils/screen_utils.dart +++ b/lib/ui/utils/screen_utils.dart @@ -28,8 +28,9 @@ class TUIKitScreenUtils { final diagonalInInches = sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0; + print("diagonalInInches $diagonalInInches"); - deviceType = diagonalInInches < 8.0 ? DeviceType.Mobile : DeviceType.Desktop; + deviceType = diagonalInInches < 11.0 ? DeviceType.Mobile : DeviceType.Desktop; return deviceType ?? DeviceType.Mobile; }else{ if(context != null){ 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 2410199..880c882 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,33 +38,34 @@ 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, - /// the message widget for current message, build by your custom builder or our default builder - Widget messageWidget, + /// current message + V2TimMessage message, - /// scroll to the specific message, it will shows in the screen center, and call isNeedShowJumpStatus if necessary - Function onScrollToIndex, + /// the message widget for current message, build by your custom builder or our default builder + Widget messageWidget, - /// if current message been called to jumped by other message - bool isNeedShowJumpStatus, + /// scroll to the specific message, it will shows in the screen center, and call isNeedShowJumpStatus if necessary + Function onScrollToIndex, - /// clear the been jumped status, recommend to execute after get 'isNeedShowJumpStatus' - VoidCallback clearJumpStatus, + /// if current message been called to jumped by other message + bool isNeedShowJumpStatus, - /// scroll to specific message, it will shows on the screen top, without the call isNeedShowJumpStatus - Function onScrollToIndexBegin, -); + /// 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, + ); 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; @@ -140,11 +141,10 @@ 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,18 +177,16 @@ 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 { @@ -197,11 +195,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; @@ -211,7 +209,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; @@ -260,18 +258,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; @@ -281,37 +279,35 @@ 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}) + 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}) : super(key: key); @override @@ -361,7 +357,7 @@ class _TIMUIKItHistoryMessageListItemState // ignore: unused_field final MessageService _messageService = serviceLocator(); final TUISelfInfoViewModel selfInfoModel = - serviceLocator(); + serviceLocator(); final TUIThemeViewModel themeModel = serviceLocator(); // bool isChecked = false; @@ -395,8 +391,8 @@ class _TIMUIKItHistoryMessageListItemState return false; } - Widget _messageItemBuilder( - V2TimMessage messageItem, TUIChatSeparateViewModel model) { + Widget _messageItemBuilder(V2TimMessage messageItem, + TUIChatSeparateViewModel model) { final msgType = messageItem.elemType; final isShowJump = (model.jumpMsgID == messageItem.msgID) && (messageItem.msgID?.isNotEmpty ?? false); @@ -411,13 +407,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, @@ -432,10 +428,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( @@ -456,13 +452,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, @@ -481,10 +477,10 @@ class _TIMUIKItHistoryMessageListItemState } final customWidget = messageItemBuilder?.textMessageItemBuilder != null ? messageItemBuilder!.textMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) : null; return customWidget ?? TIMUIKitTextElem( @@ -504,10 +500,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( @@ -521,10 +517,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( @@ -539,21 +535,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( @@ -567,10 +563,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( @@ -582,23 +578,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, @@ -649,13 +645,14 @@ 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), @@ -686,11 +683,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( @@ -709,11 +706,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), + ]), + )), ), ), ], @@ -722,48 +719,63 @@ 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 screenHeight = MediaQuery + .of(context) + .size + .height; + final screenWidth = MediaQuery + .of(context) + .size + .width; final isLongMessage = context.size!.height + 350 > screenHeight && PlatformUtils().isMobile; final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; 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( @@ -792,14 +804,13 @@ 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; @@ -818,7 +829,10 @@ 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; @@ -832,8 +846,14 @@ 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) { @@ -880,7 +900,7 @@ class _TIMUIKItHistoryMessageListItemState message: widget.message, allowAtUserWhenReply: widget.allowAtUserWhenReply, onLongPressForOthersHeadPortrait: - widget.onLongPressForOthersHeadPortrait, + widget.onLongPressForOthersHeadPortrait, selectEmojiPanelPosition: selectEmojiPanelPosition, onCloseTooltip: () => tooltip?.close(), onSelectSticker: (int value) { @@ -966,7 +986,13 @@ 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) @@ -1009,12 +1035,19 @@ 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, @@ -1035,7 +1068,13 @@ class _TIMUIKItHistoryMessageListItemState ), onClick: (details) { _onOpenToolTip( - context, widget.message, model, theme, details, true, false); + context, + widget.message, + model, + theme, + details, + true, + false); }, ), ...?model.chatConfig.additionalDesktopMessageHoverBarItem @@ -1053,8 +1092,7 @@ class _TIMUIKItHistoryMessageListItemState context); } - Widget renderHoverTipAndReadStatus( - TUIChatSeparateViewModel model, + Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model, bool isSelf, V2TimMessage message, bool isPeerRead, @@ -1066,7 +1104,7 @@ class _TIMUIKItHistoryMessageListItemState ? getMessageHoverControlBar(model, theme) : []; final lastItemName = - wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : ""; + wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : ""; return Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1082,35 +1120,38 @@ 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) + if (!isDesktopScreen || + (model.chatConfig.isUseMessageHoverBarOnDesktop && + !isShowWideToolTip)) const SizedBox( height: 24, ), @@ -1161,9 +1202,9 @@ 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; @@ -1199,7 +1240,7 @@ class _TIMUIKItHistoryMessageListItemState if (isGroupTipsMsg) { if (widget.messageItemBuilder?.groupTipsMessageItemBuilder != null) { final groupTipsMessage = - widget.messageItemBuilder!.groupTipsMessageItemBuilder!( + widget.messageItemBuilder!.groupTipsMessageItemBuilder!( message, (model.jumpMsgID == message.msgID), clearJump, @@ -1211,7 +1252,7 @@ class _TIMUIKItHistoryMessageListItemState if (isRevokedMsg) { final displayName = - isSelf ? TIM_t("您") : message.nickName ?? message.sender; + isSelf ? TIM_t("您") : message.nickName ?? message.sender; return isSelf && isRevokeEditable && isRevocable(message.timestamp!) ? _selfRevokeEditMessageBuilder(theme, model) : _revokedMessageBuilder(theme, displayName ?? ""); @@ -1233,321 +1274,334 @@ 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( + 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) { - setState(() { - isShowWideToolTip = true; - }); - } - }, - onExit: (_) { - if (isDesktopScreen) { - setState(() { - isShowWideToolTip = false; - }); - } - }, - child: GestureDetector( - behavior: - model.isMultiSelect ? HitTestBehavior.translucent : null, - onTap: () { - if (model.isMultiSelect) { - final checked = - model.multiSelectedMessageList.contains(message); - if (checked) { - model.removeFromMultiSelectedMessageList(message); - } else { - model.addToMultiSelectedMessageList(message); + 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; + }); } - } - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: isSelf - ? MainAxisAlignment.end - : MainAxisAlignment.start, - children: [ - if (!isSelf && widget.showAvatar) - InkWell( - 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); - } + }, + onExit: (_) { + if (isDesktopScreen && + model.chatConfig.isUseMessageHoverBarOnDesktop) { + setState(() { + isShowWideToolTip = false; + }); + } + }, + child: GestureDetector( + behavior: + model.isMultiSelect ? HitTestBehavior.translucent : null, + 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); } - : 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); - } + }, + onTapDown: isDesktopScreen + ? (details) { + if (widget.onTapForOthersPortrait != null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + 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), - ), + } + : 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 + ), + ), + 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( + : 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 / + maxWidth: MediaQuery + .of(context) + .size + .width / 1.7), child: Text( MessageUtils.getDisplayName(message), overflow: TextOverflow.ellipsis, style: widget.themeData - ?.nickNameTextStyle ?? + ?.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: + 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 && - PlatformUtils().isMobile) { - _onOpenToolTip( - context, - message, - model, - theme, - null, - false, - false); - } - }, - onTapDown: (details) { - _tapDetails = details; - }, - ), - TIMUIKitTextTranslationElem( - message: message, - isUseDefaultEmoji: + 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: + customEmojiStickerList: widget.customEmojiStickerList, - isFromSelf: message.isSelf ?? true, - isShowJump: false, - clearJump: () {}, - chatModel: model) - ], - ); - }), + 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 (!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 (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)), - ), + 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 48f0744..a21d8da 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 @@ -351,7 +351,9 @@ class TIMUIKitMessageTooltipState children: [ Image.asset( item.iconImageAsset, - package: 'tencent_cloud_chat_uikit', + package: defaultTipsIds.contains(item.id) + ? 'tencent_cloud_chat_uikit' + : null, width: 20, height: 20, ), 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 e8e10b2..329069c 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 @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_model_tools.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; @@ -57,7 +58,7 @@ class TIMUIKitReplyElem extends StatefulWidget { } class _TIMUIKitReplyElemState extends TIMUIKitState { - MessageRepliedData? repliedMessage; + MessageRepliedData? repliedMessage = null; V2TimMessage? rawMessage; bool isShowJumpState = false; bool isShining = false; @@ -81,10 +82,32 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { } _getMessageByMessageID() async { - final cloudCustomData = _getRepliedMessage(); + final MessageRepliedData? cloudCustomData = _getRepliedMessage(); if (cloudCustomData != null) { + if (mounted) { + setState(() { + repliedMessage = cloudCustomData; + }); + } + final messageID = cloudCustomData.messageID; - final message = await widget.chatModel.findMessage(messageID); + V2TimMessage? message = await widget.chatModel.findMessage(messageID); + if (message == null) { + try { + final RepliedMessageAbstract repliedMessageAbstract = + RepliedMessageAbstract.fromJson( + jsonDecode(cloudCustomData.messageAbstract)); + if (repliedMessageAbstract.isNotEmpty) { + message = V2TimMessage( + elemType: 0, + seq: repliedMessageAbstract.seq, + timestamp: repliedMessageAbstract.timestamp, + msgID: repliedMessageAbstract.msgID); + } + } catch (e) { + print(e.toString()); + } + } if (message != null) { if (mounted) { setState(() { @@ -93,36 +116,53 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { } } } - if (mounted) { - setState(() { - repliedMessage = cloudCustomData; - }); - } } Widget _defaultRawMessageText(String text, TUITheme? theme) { return Text(text, + maxLines: 2, + overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, color: theme?.weakTextColor, fontWeight: FontWeight.w400)); } + _renderMessageSummary(TUITheme? theme) { + try { + final RepliedMessageAbstract repliedMessageAbstract = + RepliedMessageAbstract.fromJson( + jsonDecode(repliedMessage?.messageAbstract ?? "")); + if (TencentUtils.checkString(repliedMessageAbstract.summary) != null) { + return _defaultRawMessageText(repliedMessageAbstract.summary!, theme); + } + return _defaultRawMessageText( + repliedMessage?.messageAbstract ?? TIM_t("[未知消息]"), theme); + } catch (e) { + return _defaultRawMessageText( + repliedMessage?.messageAbstract ?? TIM_t("[未知消息]"), theme); + } + } + _rawMessageBuilder(V2TimMessage? message, TUITheme? theme) { if (repliedMessage == null) { return const SizedBox(width: 0, height: 12); } if (message == null) { if (repliedMessage?.messageAbstract != null) { - return _defaultRawMessageText(repliedMessage!.messageAbstract, theme); + _renderMessageSummary(theme); } return const SizedBox(width: 0, height: 12); } final messageType = message.elemType; final isSelf = message.isSelf ?? true; - if (widget.chatModel.abstractMessageBuilder != null) { + final customAbstractMessage = + widget.chatModel.abstractMessageBuilder != null + ? widget.chatModel.abstractMessageBuilder!(message) + : null; + if (customAbstractMessage != null) { return _defaultRawMessageText( - widget.chatModel.abstractMessageBuilder!(message), + customAbstractMessage, theme, ); } @@ -173,7 +213,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { messageID: message.msgID ?? "", isSelf: isSelf); default: - return _defaultRawMessageText(TIM_t("[未知消息]"), theme); + return _renderMessageSummary(theme); } } 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 b4bbc45..608c923 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart @@ -93,6 +93,8 @@ class TIMUIKitInputTextField extends StatefulWidget { final String? groupType; + final String? groupID; + const TIMUIKitInputTextField( {Key? key, required this.conversationID, @@ -113,7 +115,8 @@ class TIMUIKitInputTextField extends StatefulWidget { required this.model, required this.currentConversation, this.groupType, - this.atMemberPanelScroll}) + this.atMemberPanelScroll, + this.groupID}) : super(key: key); @override @@ -166,21 +169,22 @@ class _InputTextFieldState extends TIMUIKitState { return text.replaceAll(RegExp(r'\ufeff'), ""); } - handleSetDraftText([String? id, ConvType? convType]) async { + Future handleSetDraftText([String? id, ConvType? convType]) async { + String text = textEditingController.text; String convID = id ?? widget.conversationID; - String conversationID = convID.contains("@TOPIC#") + final isTopic = convID.contains("@TOPIC#"); + String conversationID = isTopic ? convID : ((convType ?? widget.conversationType) == ConvType.c2c ? "c2c_$convID" : "group_$convID"); - String text = textEditingController.text; - String? draftText = _filterU200b(text); - - if (draftText.isEmpty) { - draftText = ""; - } - await conversationModel.setConversationDraft( - conversationID: conversationID, draftText: draftText); + String draftText = _filterU200b(text); + return await conversationModel.setConversationDraft( + groupID: widget.groupID, + isTopic: isTopic, + isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb, + conversationID: conversationID, + draftText: draftText); } backSpaceText() { @@ -189,11 +193,9 @@ class _InputTextFieldState extends TIMUIKitState { if (originalText == zeroWidthSpace) { _handleSoftKeyBoardDelete(); - // _addDeleteTag(); } else { text = originalText.characters.skipLast(1); textEditingController.text = text; - // handleSetDraftText(); } } @@ -202,6 +204,7 @@ class _InputTextFieldState extends TIMUIKitState { lastText = ""; final text = textEditingController.text.trim(); final convType = widget.conversationType; + conversationModel.clearWebDraft(conversationID: widget.conversationID); if (text.isNotEmpty && text != zeroWidthSpace) { if (widget.model.repliedMessage != null) { MessageUtils.handleMessageError( @@ -259,6 +262,7 @@ class _InputTextFieldState extends TIMUIKitState { } onSubmitted() async { + conversationModel.clearWebDraft(conversationID: widget.conversationID); lastText = ""; final text = textEditingController.text.trim(); final convType = widget.conversationType; @@ -417,6 +421,22 @@ class _InputTextFieldState extends TIMUIKitState { mentionedMembersMap = map; } + updateMentionedMap() { + Map map = {}; + Iterable matches = atTextReg.allMatches(textEditingController.text); + List parseAtList = []; + for (final item in matches) { + final str = item.group(0); + parseAtList.add(str); + } + for (String? key in parseAtList) { + if (key != null && mentionedMembersMap[key] != null) { + map[key] = mentionedMembersMap[key]!; + } + } + mentionedMembersMap = map; + } + _handleAtText(String text, TUIChatSeparateViewModel model) async { final text = textEditingController.text; String? groupID = widget.conversationType == ConvType.group @@ -449,22 +469,11 @@ class _InputTextFieldState extends TIMUIKitState { textEditingController.selection = TextSelection.collapsed(offset: atIndex); lastText = newText; - Map map = {}; - Iterable matches = atTextReg.allMatches(text); - List parseAtList = []; - for (final item in matches) { - final str = item.group(0); - parseAtList.add(str); - } - for (String? key in parseAtList) { - if (key != null && mentionedMembersMap[key] != null) { - map[key] = mentionedMembersMap[key]!; - } - } - mentionedMembersMap = map; + updateMentionedMap(); return; } } + updateMentionedMap(); } final int selfRole = widget.model.selfMemberInfo?.role ?? 0; @@ -709,8 +718,8 @@ class _InputTextFieldState extends TIMUIKitState { void didUpdateWidget(TIMUIKitInputTextField oldWidget) { super.didUpdateWidget(oldWidget); if (widget.conversationID != oldWidget.conversationID) { - handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType); mentionedMembersMap.clear(); + handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType); if (oldWidget.initText != widget.initText) { textEditingController.text = widget.initText ?? ""; } else { 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 3a427c1..8e95045 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 @@ -309,12 +309,23 @@ class _TIMUIKitTextFieldLayoutNarrowState }); }; } + String getAbstractMessage(V2TimMessage message) { + final String? customAbstractMessage = widget + .model.abstractMessageBuilder != null ? widget.model + .abstractMessageBuilder!(widget.model.repliedMessage!) : null; + return customAbstractMessage ?? MessageUtils + .getAbstractMessageAsync( + widget.model.repliedMessage!, widget.model.groupMemberList ?? []); + } _buildRepliedMessage(V2TimMessage? repliedMessage) { final haveRepliedMessage = repliedMessage != null; if (haveRepliedMessage) { final text = - "${MessageUtils.getDisplayName(widget.model.repliedMessage!)}:${widget.model.abstractMessageBuilder != null ? widget.model.abstractMessageBuilder!(widget.model.repliedMessage!) : MessageUtils.getAbstractMessageAsync(widget.model.repliedMessage!, widget.model.groupMemberList ?? [])}"; + "${MessageUtils.getDisplayName( + widget.model.repliedMessage!)}:${getAbstractMessage( + repliedMessage + )}"; return Container( color: widget.backgroundColor ?? hexToColor("f5f5f6"), alignment: Alignment.centerLeft, 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 c390f5b..4501ba9 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,6 +1,7 @@ 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'; @@ -32,6 +33,7 @@ import 'package:universal_html/html.dart' as html; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; +import 'package:flutter_svg/flutter_svg.dart'; // ignore: unnecessary_import import 'dart:typed_data'; @@ -262,36 +264,62 @@ class _TIMUIKitTextFieldLayoutWideState }; } + String getAbstractMessage(V2TimMessage message) { + final String? customAbstractMessage = + widget.model.abstractMessageBuilder != null + ? widget.model.abstractMessageBuilder!(widget.model.repliedMessage!) + : null; + return customAbstractMessage ?? + MessageUtils.getAbstractMessageAsync( + widget.model.repliedMessage!, widget.model.groupMemberList ?? []); + } + _buildRepliedMessage(V2TimMessage? repliedMessage) { final haveRepliedMessage = repliedMessage != null; if (haveRepliedMessage) { - final text = - "${MessageUtils.getDisplayName(widget.model.repliedMessage!)}:${widget.model.abstractMessageBuilder != null ? widget.model.abstractMessageBuilder!(widget.model.repliedMessage!) : MessageUtils.getAbstractMessageAsync(widget.model.repliedMessage!, widget.model.groupMemberList ?? [])}"; return Container( color: widget.backgroundColor ?? hexToColor("f5f5f6"), alignment: Alignment.centerLeft, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.start, children: [ + Text( + TIM_t("回复 "), + style: TextStyle( + color: hexToColor("8f959e"), fontSize: 14), + ), + Text( + MessageUtils.getDisplayName( + widget.model.repliedMessage!), + softWrap: true, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: hexToColor("8f959e"), + fontSize: 14, + fontWeight: FontWeight.bold), + ), Expanded( child: Text( - text, - softWrap: true, - maxLines: 3, + ": ${getAbstractMessage(repliedMessage)}", + maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(color: hexToColor("8f959e"), fontSize: 14), + style: TextStyle( + fontSize: 14, + color: hexToColor("8f959e"), + ), ), ), const SizedBox( - width: 16, + width: 8, ), InkWell( onTap: () { widget.model.repliedMessage = null; }, - child: Icon(Icons.clear, color: hexToColor("8f959e"), size: 18), + child: Icon(Icons.cancel, color: hexToColor("8f959e"), size: 18), ) ], ), @@ -314,8 +342,9 @@ class _TIMUIKitTextFieldLayoutWideState entry = null; } }, - initOffset: offset ?? - Offset(MediaQuery.of(context).size.height * 0.5 + 20, + initOffset: offset != null + ? Offset(offset.dx, max(offset.dy, 16)) + : Offset(MediaQuery.of(context).size.height * 0.5 + 20, MediaQuery.of(context).size.height * 0.5 - 100), child: Container( decoration: BoxDecoration( @@ -793,7 +822,7 @@ class _TIMUIKitTextFieldLayoutWideState onClick: (offset) { _sendEmoji(offset, widget.theme); }, - svgPath: "images/svg/send_face.svg"), + imgPath: "images/svg/send_face.png"), if (config.showScreenshotButton && PlatformUtils().isDesktop) DesktopControlBarItem( item: "screenShot", @@ -950,6 +979,7 @@ class _TIMUIKitTextFieldLayoutWideState color: widget.backgroundColor ?? theme.desktopChatMessageInputBgColor, child: Column( children: [ + _buildRepliedMessage(widget.repliedMessage), SizedBox( height: 1, child: Container( @@ -963,7 +993,6 @@ class _TIMUIKitTextFieldLayoutWideState children: generateControlBar(widget.model, theme), ), ), - _buildRepliedMessage(widget.repliedMessage), Container( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 6), constraints: const BoxConstraints(minHeight: 50), diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart index 1e2c089..32f3f41 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart @@ -11,12 +11,14 @@ import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/chat_life_cyc import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; +import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_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'; import 'package:tencent_cloud_chat_uikit/ui/controller/tim_uikit_chat_controller.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/frame.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/optimize_utils.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/at_member_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_multi_select_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_send_file.dart'; @@ -50,7 +52,7 @@ class TIMUIKitChat extends StatefulWidget { /// use for customize avatar final Widget Function(BuildContext context, V2TimMessage message)? - userAvatarBuilder; + userAvatarBuilder; /// Use for show conversation name. /// This field is not necessary to be provided, when `conversation` is provided, unless you want to cover this field manually. @@ -61,7 +63,7 @@ class TIMUIKitChat extends StatefulWidget { /// Avatar and name in message reaction secondary tap callback. final void Function(String userID, TapDownDetails tapDetails)? - onSecondaryTapAvatar; + onSecondaryTapAvatar; @Deprecated( "Nickname will not shows in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") @@ -108,18 +110,23 @@ class TIMUIKitChat extends StatefulWidget { final TongueItemBuilder? tongueItemBuilder; /// The `groupAtInfoList` from `V2TimConversation`. - /// This field is not necessary to be provided, when `conversation` is provided, unless you want to cover this field manually. + /// This field is not necessary to be provided, when `conversation` is provided, + /// unless you want to cover this field manually. final List? groupAtInfoList; /// The configuration for the whole `TIMUIKitChat` widget. final TIMUIKitChatConfig? config; - /// The callback for jumping to the page for `TIMUIKitGroupApplicationList` or other pages to deal with enter group application for group administrator manually, in the case of [public group]. + /// The callback for jumping to the page for `TIMUIKitGroupApplicationList` + /// or other pages to deal with enter group application for group administrator manually, + /// in the case of [public group]. /// The parameter here is `String groupID` final ValueChanged? onDealWithGroupApplication; - /// The builder for abstract messages, normally used in replied message and forward message. - final String Function(V2TimMessage message)? abstractMessageBuilder; + /// The generator for the abstract summary preview of a message, + /// typically used in replied and forwarded messages. + /// Returns `null` to use the default message summary. + final String? Function(V2TimMessage message)? abstractMessageBuilder; /// The configuration for tool tips panel, long press messages will show this panel. final ToolTipsConfig? toolTipsConfig; @@ -140,45 +147,45 @@ class TIMUIKitChat extends StatefulWidget { /// Custom text field final Widget Function(BuildContext context)? textFieldBuilder; - TIMUIKitChat( - {Key? key, - this.groupID, - required this.conversation, - this.conversationID, - this.conversationType, - this.conversationShowName, - this.abstractMessageBuilder, - this.onTapAvatar, - @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.showTotalUnReadCount = false, - this.messageItemBuilder, - @Deprecated("Please use [extraTipsActionItemBuilder] instead") - this.exteraTipsActionItemBuilder, - this.extraTipsActionItemBuilder, - this.draftText, - this.textFieldHintText, - this.initFindingMsg, - this.userAvatarBuilder, - this.appBarConfig, - this.controller, - this.morePanelConfig, - this.customStickerPanel, - this.config = const TIMUIKitChatConfig(), - this.tongueItemBuilder, - this.groupAtInfoList, - this.mainHistoryListConfig, - this.onDealWithGroupApplication, - this.toolTipsConfig, - this.lifeCycle, - this.topFixWidget = const SizedBox(), - this.textFieldBuilder, - this.customEmojiStickerList = const [], - this.customAppBar, - this.onSecondaryTapAvatar}) + TIMUIKitChat({Key? key, + this.groupID, + required this.conversation, + this.conversationID, + this.conversationType, + this.conversationShowName, + this.abstractMessageBuilder, + this.onTapAvatar, + @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.showTotalUnReadCount = false, + this.messageItemBuilder, + @Deprecated( + "Please use [extraTipsActionItemBuilder] instead") this.exteraTipsActionItemBuilder, + this.extraTipsActionItemBuilder, + this.draftText, + this.textFieldHintText, + this.initFindingMsg, + this.userAvatarBuilder, + this.appBarConfig, + this.controller, + this.morePanelConfig, + this.customStickerPanel, + this.config = const TIMUIKitChatConfig(), + this.tongueItemBuilder, + this.groupAtInfoList, + this.mainHistoryListConfig, + this.onDealWithGroupApplication, + this.toolTipsConfig, + this.lifeCycle, + this.topFixWidget = const SizedBox(), + this.textFieldBuilder, + this.customEmojiStickerList = const [], + this.customAppBar, + this.onSecondaryTapAvatar}) : super(key: key) { - startTime = DateTime.now().millisecondsSinceEpoch; + startTime = DateTime + .now() + .millisecondsSinceEpoch; } @override @@ -188,11 +195,13 @@ class TIMUIKitChat extends StatefulWidget { class _TUIChatState extends TIMUIKitState { TUIChatSeparateViewModel model = TUIChatSeparateViewModel(); final TUIThemeViewModel themeViewModel = serviceLocator(); + final TUIConversationViewModel conversationViewModel = + serviceLocator(); final TIMUIKitInputTextFieldController textFieldController = - TIMUIKitInputTextFieldController(); + TIMUIKitInputTextFieldController(); bool isInit = false; final TUIChatGlobalModel chatGlobalModel = - serviceLocator(); + serviceLocator(); bool _dragging = false; final GlobalKey alignKey = GlobalKey(); @@ -200,13 +209,19 @@ class _TUIChatState extends TIMUIKitState { late AutoScrollController autoController = AutoScrollController( viewportBoundaryGetter: () => - Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), + Rect.fromLTRB(0, 0, 0, MediaQuery + .of(context) + .padding + .bottom), axis: Axis.vertical, ); late AutoScrollController atMemberPanelScroll = AutoScrollController( viewportBoundaryGetter: () => - Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), + Rect.fromLTRB(0, 0, 0, MediaQuery + .of(context) + .padding + .bottom), axis: Axis.vertical, ); @@ -220,11 +235,16 @@ class _TUIChatState extends TIMUIKitState { model.onTapAvatar = widget.onTapAvatar; WidgetsBinding.instance.addPostFrameCallback((_) async { if (kProfileMode) { - widget.endTime = DateTime.now().millisecondsSinceEpoch; + widget.endTime = DateTime + .now() + .millisecondsSinceEpoch; int timeSpend = widget.endTime - widget.startTime; print("Page render time:$timeSpend ms"); } }); + Future.delayed(const Duration(milliseconds: 500), () { + updateDraft(); + }); } @override @@ -245,8 +265,8 @@ class _TUIChatState extends TIMUIKitState { model = TUIChatSeparateViewModel(); model.abstractMessageBuilder = widget.abstractMessageBuilder; model.onTapAvatar = widget.onTapAvatar; - textFieldController.requestFocus(); Future.delayed(const Duration(milliseconds: 50), () { + updateDraft(); textFieldController.requestFocus(); try { autoController.jumpTo( @@ -261,6 +281,22 @@ class _TUIChatState extends TIMUIKitState { } } + updateDraft() async { + final isTopic = widget.conversation.conversationID.contains("@TOPIC#"); + if (isTopic) { + final topicInfoList = await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .getTopicInfoList( + groupID: widget.groupID!, + topicIDList: [widget.conversation.conversationID]); + final topicInfo = topicInfoList.data?.first.topicInfo; + final draftText = topicInfo?.draftText; + if (TencentUtils.checkString(draftText) != null) { + textFieldController.setTextField(draftText!); + } + } + } + Widget _renderJoinGroupApplication(int amount, TUITheme theme) { String option1 = amount.toString(); return Container( @@ -318,7 +354,7 @@ class _TUIChatState extends TIMUIKitState { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final TUITheme theme = value.theme; final closePanel = - OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60); + OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60); final isBuild = isInit; isInit = true; @@ -337,7 +373,7 @@ class _TUIChatState extends TIMUIKitState { ], builder: (context, model, w) { final TUIChatGlobalModel chatGlobalModel = - Provider.of(context, listen: true); + Provider.of(context, listen: true); widget.controller?.model = model; widget.controller?.textFieldController = textFieldController; @@ -347,13 +383,13 @@ class _TUIChatState extends TIMUIKitState { widget.onDealWithGroupApplication != null) { filteredApplicationList = chatGlobalModel.groupApplicationList.where((item) { - return (item.groupID == widget.conversationID) && - item.handleStatus == 0; - }).toList(); + return (item.groupID == widget.conversationID) && + item.handleStatus == 0; + }).toList(); } final TUIGroupListenerModel groupListenerModel = - Provider.of(context, listen: true); + Provider.of(context, listen: true); final NeedUpdate? needUpdate = groupListenerModel.needUpdate; if (needUpdate != null && needUpdate.groupID == widget.conversationID) { @@ -379,13 +415,13 @@ class _TUIChatState extends TIMUIKitState { resizeToAvoidBottomInset: false, appBar: (widget.customAppBar == null) ? TIMUIKitAppBar( - showTotalUnReadCount: widget.showTotalUnReadCount, - config: widget.appBarConfig, - conversationShowName: _getTitle(), - conversationID: _getConvID(), - showC2cMessageEditStatus: - widget.config?.showC2cMessageEditStatus ?? true, - ) + showTotalUnReadCount: widget.showTotalUnReadCount, + config: widget.appBarConfig, + conversationShowName: _getTitle(), + conversationID: _getConvID(), + showC2cMessageEditStatus: + widget.config?.showC2cMessageEditStatus ?? true, + ) : null, body: DropTarget( onDragDone: (detail) { @@ -421,90 +457,100 @@ class _TUIChatState extends TIMUIKitState { if (widget.topFixWidget != null) widget.topFixWidget!, Expanded( child: Container( - color: theme.chatBgColor, - child: Align( - key: alignKey, - alignment: Alignment.topCenter, - child: Listener( - onPointerMove: closePanel, - child: TIMUIKitHistoryMessageListContainer( - conversation: widget.conversation, - textFieldController: textFieldController, - customEmojiStickerList: + color: theme.chatBgColor, + child: Align( + key: alignKey, + alignment: Alignment.topCenter, + child: Listener( + onPointerMove: closePanel, + child: TIMUIKitHistoryMessageListContainer( + conversation: widget.conversation, + textFieldController: textFieldController, + customEmojiStickerList: widget.customEmojiStickerList, - isUseDefaultEmoji: + isUseDefaultEmoji: widget.config!.isUseDefaultEmoji, - key: listContainerKey, - isAllowScroll: true, - userAvatarBuilder: widget.userAvatarBuilder, - toolTipsConfig: widget.toolTipsConfig, - groupAtInfoList: widget.groupAtInfoList, - tongueItemBuilder: widget.tongueItemBuilder, - onLongPressForOthersHeadPortrait: - (String? userId, String? nickName) { - textFieldController.longPressToAt( - nickName, userId); - }, - mainHistoryListConfig: + key: listContainerKey, + isAllowScroll: true, + userAvatarBuilder: widget + .userAvatarBuilder, + toolTipsConfig: widget.toolTipsConfig, + groupAtInfoList: widget.groupAtInfoList, + tongueItemBuilder: widget + .tongueItemBuilder, + onLongPressForOthersHeadPortrait: + (String? userId, String? nickName) { + textFieldController.longPressToAt( + nickName, userId); + }, + mainHistoryListConfig: widget.mainHistoryListConfig, - initFindingMsg: widget.initFindingMsg, - extraTipsActionItemBuilder: + initFindingMsg: widget.initFindingMsg, + extraTipsActionItemBuilder: widget.extraTipsActionItemBuilder ?? widget.exteraTipsActionItemBuilder, - conversationType: _getConvType(), - scrollController: autoController, - onSecondaryTapAvatar: + conversationType: _getConvType(), + scrollController: autoController, + onSecondaryTapAvatar: widget.onSecondaryTapAvatar, - onTapAvatar: widget.onTapAvatar, - // ignore: deprecated_member_use_from_same_package - showNickName: widget.showNickName, - messageItemBuilder: + onTapAvatar: widget.onTapAvatar, + // ignore: deprecated_member_use_from_same_package + showNickName: widget.showNickName, + messageItemBuilder: widget.messageItemBuilder, - conversationID: _getConvID(), - ), - )), - )), + conversationID: _getConvID(), + ), + )), + )), Selector( builder: (context, value, child) { return value ? MultiSelectPanel( - conversationType: _getConvType(), - ) + conversationType: _getConvType(), + ) : (widget.textFieldBuilder != null - ? widget.textFieldBuilder!(context) - : TIMUIKitInputTextField( - atMemberPanelScroll: - atMemberPanelScroll, - groupType: - widget.conversation.groupType, - currentConversation: - widget.conversation, - model: model, - controller: textFieldController, - customEmojiStickerList: - widget.customEmojiStickerList, - isUseDefaultEmoji: - widget.config!.isUseDefaultEmoji, - customStickerPanel: - widget.customStickerPanel, - morePanelConfig: - widget.morePanelConfig, - scrollController: autoController, - conversationID: _getConvID(), - conversationType: _getConvType(), - initText: widget.draftText ?? - widget.conversation.draftText, - hintText: widget.textFieldHintText, - showMorePanel: widget.config - ?.isAllowShowMorePanel ?? - true, - showSendAudio: widget.config - ?.isAllowSoundMessage ?? - true, - showSendEmoji: widget - .config?.isAllowEmojiPanel ?? - true, - )); + ? widget.textFieldBuilder!(context) + : TIMUIKitInputTextField( + groupID: widget.groupID, + atMemberPanelScroll: + atMemberPanelScroll, + groupType: + widget.conversation.groupType, + currentConversation: + widget.conversation, + model: model, + controller: textFieldController, + customEmojiStickerList: + widget.customEmojiStickerList, + isUseDefaultEmoji: + widget.config!.isUseDefaultEmoji, + customStickerPanel: + widget.customStickerPanel, + morePanelConfig: + widget.morePanelConfig, + scrollController: autoController, + conversationID: _getConvID(), + conversationType: _getConvType(), + initText: TencentUtils.checkString( + widget.draftText) ?? (PlatformUtils().isWeb + ? TencentUtils.checkString( + conversationViewModel.getWebDraft( + conversationID: widget.conversation + .conversationID)) + : + TencentUtils.checkString(widget + .conversation.draftText)), + hintText: widget.textFieldHintText, + showMorePanel: widget.config + ?.isAllowShowMorePanel ?? + true, + showSendAudio: widget.config + ?.isAllowSoundMessage ?? + true, + showSendEmoji: widget + .config?.isAllowEmojiPanel ?? + true, + )); }, selector: (c, model) { return model.isMultiSelect; @@ -533,13 +579,13 @@ class TIMUIKitChatProviderScope extends StatelessWidget { final TUIChatGlobalModel globalModel = serviceLocator(); TUIChatSeparateViewModel? model; final TUIGroupListenerModel groupListenerModel = - serviceLocator(); + serviceLocator(); final TUIThemeViewModel themeViewModel = serviceLocator(); final Widget? child; /// You could get the model from here, and transfer it to other widget from TUIKit. final Widget Function(BuildContext, TUIChatSeparateViewModel, Widget?) - builder; + builder; final List? providers; /// `TIMUIKitChatController` needs to be provided if you use it outside. @@ -566,21 +612,20 @@ class TIMUIKitChatProviderScope extends StatelessWidget { final AutoScrollController? scrollController; - TIMUIKitChatProviderScope( - {Key? key, - this.child, - this.providers, - this.textFieldController, - required this.builder, - this.model, - this.groupID, - this.isBuild, - required this.conversationID, - required this.conversationType, - this.controller, - this.config, - this.lifeCycle, - this.scrollController}) + TIMUIKitChatProviderScope({Key? key, + this.child, + this.providers, + this.textFieldController, + required this.builder, + this.model, + this.groupID, + this.isBuild, + required this.conversationID, + required this.conversationType, + this.controller, + this.config, + this.lifeCycle, + this.scrollController}) : super(key: key) { if (isBuild ?? false) { return; @@ -596,7 +641,7 @@ class TIMUIKitChatProviderScope extends StatelessWidget { model?.initForEachConversation( conversationType, conversationID, - (String value) { + (String value) { textFieldController?.textEditingController?.text = value; }, groupID: groupID, diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart index eddebef..1b01cb7 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart @@ -185,6 +185,12 @@ class TIMUIKitChatConfig { /// Define the lines in the text message input field on Desktop. final int desktopMessageInputFieldLines; + /// Specifies whether to use the draft feature on the Web, as the Chat SDK does not support this functionality. + /// If enabled, draft data will be stored in TUIKit's memory. + /// Note that the draft text will be lost upon refreshing the website. + /// [Default]: true. + final bool isUseDraftOnWeb; + const TIMUIKitChatConfig( {this.onTapLink, this.timeDividerConfig, @@ -203,7 +209,7 @@ class TIMUIKitChatConfig { this.isShowSelfNameInGroup = false, this.offlinePushInfo, @Deprecated("Please use [isShowGroupReadingStatus] instead") - this.isShowGroupMessageReadReceipt = true, + this.isShowGroupMessageReadReceipt = true, this.upperRecallTime = 120, this.isShowOthersNameInGroup = true, this.urlPreviewType = UrlPreviewType.onlyHyperlink, @@ -213,13 +219,14 @@ class TIMUIKitChatConfig { this.notificationIOSSound = "", this.isAllowSoundMessage = true, @Deprecated("Please use [groupReadReceiptPermissionList] instead") - this.groupReadReceiptPermisionList, + this.groupReadReceiptPermisionList, this.groupReadReceiptPermissionList, this.isAllowEmojiPanel = true, this.isAllowShowMorePanel = true, this.isShowReadingStatus = true, this.desktopControlBarConfig, this.isAllowLongPressMessage = true, + this.isUseDraftOnWeb = true, this.isAllowClickAvatar = true, this.isEnableTextSelection, this.additionalDesktopMessageHoverBarItem, diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart index b21315e..f63c1c9 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart @@ -1,3 +1,5 @@ +import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; + class MessageRepliedData { late String messageAbstract; late String messageSender; @@ -10,6 +12,42 @@ class MessageRepliedData { } } +class RepliedMessageAbstract { + final int? elemType; + final String? msgID; + final int? timestamp; + final String? seq; + final String? summary; + + RepliedMessageAbstract( + {this.elemType, this.msgID, this.timestamp, this.seq, this.summary}); + + // fromJson constructor + RepliedMessageAbstract.fromJson(Map json) + : elemType = json['elemType'], + msgID = json['msgID'], + timestamp = json['timestamp'], + seq = json['seq'], + summary = json['summary']; + + // toJson function + Map toJson() { + return { + 'summary': summary, + 'elemType': elemType, + 'msgID': msgID, + 'timestamp': timestamp, + 'seq': seq, + }; + } + + // isNotEmpty method + bool get isNotEmpty => + TencentUtils.checkString(msgID) != null && + TencentUtils.checkString(timestamp.toString()) != null && + TencentUtils.checkString(seq) != null; +} + class CloudCustomData { Map? messageReply; Map? messageReaction = {}; diff --git a/lib/ui/widgets/link_preview/link_preview_entry.dart b/lib/ui/widgets/link_preview/link_preview_entry.dart index ba0de41..b51a0ed 100644 --- a/lib/ui/widgets/link_preview/link_preview_entry.dart +++ b/lib/ui/widgets/link_preview/link_preview_entry.dart @@ -17,7 +17,8 @@ class LinkPreviewEntry { return isMarkdown ? LinkTextMarkdown( isEnableTextSelection: isEnableTextSelection, - messageText: replaceSingleNewlineWithTwo(messageText), + messageText: addSpaceAfterLeftBracket( + addSpaceBeforeHttp(replaceSingleNewlineWithTwo(messageText))), style: style, onLinkTap: onLinkTap) : LinkText( @@ -30,10 +31,27 @@ class LinkPreviewEntry { }; } + static String addSpaceAfterLeftBracket(String inputText) { + return inputText.splitMapJoin( + RegExp(r'<\w+[^<>]*>'), + onMatch: (match) { + return match.group(0)!.replaceFirst('<', '< '); + }, + onNonMatch: (text) => text, + ); + } + static String replaceSingleNewlineWithTwo(String inputText) { - return inputText.replaceAllMapped( - RegExp(r'(? '\n\n', + return inputText.split('\n').join('\n\n'); + } + + static String addSpaceBeforeHttp(String inputText) { + return inputText.splitMapJoin( + RegExp(r'http'), + onMatch: (match) { + return ' http'; + }, + onNonMatch: (text) => text, ); } diff --git a/lib/ui/widgets/link_preview/widgets/link_text.dart b/lib/ui/widgets/link_preview/widgets/link_text.dart index 3460a3d..fc12d6f 100644 --- a/lib/ui/widgets/link_preview/widgets/link_text.dart +++ b/lib/ui/widgets/link_preview/widgets/link_text.dart @@ -8,6 +8,7 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:tencent_im_base/base_widgets/tim_stateless_widget.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/common/utils.dart'; +import 'package:markdown/markdown.dart' as md; class LinkTextMarkdown extends TIMStatelessWidget { /// Callback for when link is tapped @@ -40,6 +41,7 @@ class LinkTextMarkdown extends TIMStatelessWidget { .copyWith( a: TextStyle(color: LinkUtils.hexToColor("015fff")), ), + extensionSet: md.ExtensionSet.gitHubWeb, onTapLink: ( String link, String? href, @@ -155,3 +157,20 @@ class LinkText extends TIMStatelessWidget { )); } } + +class TextBuilder extends MarkdownElementBuilder { + @override + Widget? visitText(md.Text text, TextStyle? preferredStyle) { + return Text(text.textContent); + } +} + +class RawHtmlSyntax extends md.InlineSyntax { + RawHtmlSyntax() : super(r'<.+?>'); + + @override + bool onMatch(md.InlineParser parser, Match match) { + parser.addNode(md.Text(match[0]!)); + return true; + } +} diff --git a/lib/ui/widgets/wide_popup.dart b/lib/ui/widgets/wide_popup.dart index 5b023ee..12628a8 100644 --- a/lib/ui/widgets/wide_popup.dart +++ b/lib/ui/widgets/wide_popup.dart @@ -7,6 +7,7 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart'; class TUIKitWidePopup { static OverlayEntry? entry; + static bool isShow = false; static showSecondaryConfirmDialog({ required TUIKitWideModalOperationKey operationKey, @@ -17,7 +18,7 @@ class TUIKitWidePopup { VoidCallback? onCancel, }) { return TUIKitWidePopup.showPopupWindow( - operationKey: operationKey, + operationKey: operationKey, context: context, isDarkBackground: false, onCancel: onCancel, @@ -56,37 +57,205 @@ class TUIKitWidePopup { VoidCallback? onConfirm, VoidCallback? onCancel, }) async { + if (isShow) { + return; + } + isShow = true; final TUISelfInfoViewModel selfInfoViewModel = - serviceLocator(); + serviceLocator(); - if(selfInfoViewModel.globalConfig?.showDesktopModalFunc != null){ + if (selfInfoViewModel.globalConfig?.showDesktopModalFunc != null) { final res = await selfInfoViewModel.globalConfig!.showDesktopModalFunc!( - operationKey, - context, - child, - theme, - width, - height, - offset, - initText, - borderRadius, - isDarkBackground, - title, - onSubmit, - submitWidget, - onConfirm, - onCancel - ); + operationKey, + context, + child, + theme, + width, + height, + offset, + initText, + borderRadius, + isDarkBackground, + title, + onSubmit, + submitWidget, + onConfirm, + onCancel); - if(res == true){ + if (res == true) { return; } } + final isUseMaterialAlert = (offset == null); + + final Widget contentWidget = Container( + width: width, + height: height, + decoration: BoxDecoration( + borderRadius: + borderRadius ?? const BorderRadius.all(Radius.circular(16)), + color: theme?.wideBackgroundColor ?? const Color(0xFFffffff), + border: isDarkBackground + ? Border.all( + width: 2, + color: theme?.weakBackgroundColor ?? const Color(0xFFbebebe), + ) + : null, + boxShadow: (isDarkBackground || isUseMaterialAlert) + ? null + : const [ + BoxShadow( + color: Color(0xFFbebebe), + offset: Offset(3, 3), + blurRadius: 10, + spreadRadius: 1, + ), + ], + ), + child: Column( + children: [ + if (title != null) + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: hexToColor("f5f6f7"), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + color: theme?.darkTextColor ?? const Color(0xFF444444)), + ), + InkWell( + onTap: () { + if (onSubmit != null) { + onSubmit(); + } + isShow = false; + if (offset == null) { + Navigator.pop(context); + } else { + entry?.remove(); + entry = null; + } + }, + child: onSubmit != null + ? (submitWidget ?? const Icon(Icons.check)) + : const Icon(Icons.close), + ) + ], + ), + ), + if (title != null) + SizedBox( + height: 1, + child: Container( + color: theme?.weakDividerColor ?? const Color(0xFFE5E6E9), + ), + ), + if (height != null && width != null) + Expanded(child: child(() { + isShow = false; + if (isUseMaterialAlert) { + Navigator.pop(context); + } else { + entry?.remove(); + entry = null; + } + })), + if (height == null || width == null) + child(() { + isShow = false; + if (isUseMaterialAlert) { + Navigator.pop(context); + } else { + entry?.remove(); + entry = null; + } + }), + if (onCancel != null || onConfirm != null) + Container( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (onCancel != null) + Container( + margin: const EdgeInsets.only(right: 16), + child: OutlinedButton( + onPressed: () { + isShow = false; + if (isUseMaterialAlert) { + Navigator.pop(context); + } else { + entry?.remove(); + entry = null; + } + onCancel(); + }, + child: Text( + TIM_t("取消"), + style: TextStyle( + color: theme?.weakTextColor ?? Colors.black), + )), + ), + if (onConfirm != null) + Container( + margin: const EdgeInsets.only(right: 16), + child: ElevatedButton( + onPressed: () { + isShow = false; + if (isUseMaterialAlert) { + Navigator.pop(context); + } else { + entry?.remove(); + entry = null; + } + onConfirm(); + }, + child: Text( + TIM_t("确定"), + style: TextStyle(color: theme?.primaryColor), + )), + ), + ], + ), + ) + ], + ), + ); + + if (isUseMaterialAlert) { + return showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return WillPopScope( + child: AlertDialog( + 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), + content: contentWidget, + ), + onWillPop: () { + return Future.value(false); + }); + }); + } + if (entry != null) { return; } + entry = OverlayEntry(builder: (BuildContext context) { return Material( color: Colors.transparent, @@ -100,133 +269,13 @@ class TUIKitWidePopup { }, 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) + ? Offset( + MediaQuery.of(context).size.width * 0.5 - width / 2, + MediaQuery.of(context).size.height * 0.5 - height / 2) : null), - child: Container( - width: width, - height: height, - decoration: BoxDecoration( - borderRadius: - borderRadius ?? const BorderRadius.all(Radius.circular(16)), - color: theme?.wideBackgroundColor ?? const Color(0xFFffffff), - border: isDarkBackground - ? Border.all( - width: 2, - color: - theme?.weakBackgroundColor ?? const Color(0xFFbebebe), - ) - : null, - boxShadow: isDarkBackground - ? null - : const [ - BoxShadow( - color: Color(0xFFbebebe), - offset: Offset(3, 3), - blurRadius: 10, - spreadRadius: 1, - ), - ], - ), - child: Column( - children: [ - if (title != null) - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: hexToColor("f5f6f7"), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - title, - style: TextStyle( - fontSize: 18, - color: theme?.darkTextColor ?? - const Color(0xFF444444)), - ), - InkWell( - onTap: () { - if (onSubmit != null) { - onSubmit(); - } - entry?.remove(); - entry = null; - }, - child: onSubmit != null - ? (submitWidget ?? const Icon(Icons.check)) - : const Icon(Icons.close), - ) - ], - ), - ), - if (title != null) - SizedBox( - height: 1, - child: Container( - color: theme?.weakDividerColor ?? const Color(0xFFE5E6E9), - ), - ), - if (height != null && width != null) - Expanded(child: child(() { - entry?.remove(); - entry = null; - })), - if (height == null || width == null) - child(() { - entry?.remove(); - entry = null; - }), - if (onCancel != null || onConfirm != null) - Container( - padding: const EdgeInsets.only(bottom: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (onCancel != null) - Container( - margin: const EdgeInsets.only(right: 16), - child: OutlinedButton( - onPressed: () { - entry?.remove(); - entry = null; - onCancel(); - }, - child: Text( - TIM_t("取消"), - style: TextStyle( - color: - theme?.weakTextColor ?? Colors.black), - )), - ), - if (onConfirm != null) - Container( - margin: const EdgeInsets.only(right: 16), - child: ElevatedButton( - onPressed: () { - entry?.remove(); - entry = null; - onConfirm(); - }, - child: Text( - TIM_t("确定"), - style: TextStyle(color: theme?.primaryColor), - )), - ), - ], - ), - ) - ], - ), - )), + child: contentWidget), ); }); Overlay.of(context).insert(entry!); - } } diff --git a/pubspec.yaml b/pubspec.yaml index 92a3e2b..337966e 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.0+2 +version: 2.1.1 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