diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b6624..70b9dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 2.2.0 + +### New Features + +* Introduced a newly-designed set of Emoji image stickers, available for seamless integration within textual content, providing an enhanced user experience. +* Streamlined the implementation of stickers, removing the need for additional complex coding. Full functionality is enabled by default, with customization options available through the `stickerPanelConfig` configuration in `TIMUIKitChatConfig`. +* Extended support for rendering embedded image stickers within text messages when the `Markdown` parsing mode is activated, combining a rich, user-friendly experience with the ability to display formatted Markdown text. + +### Improvements + +* Enhanced group chat functionality on the Desktop, enabling mentions (`@` tag) to be inserted at any position within a composed message, rather than only at the end. Additionally, deleting `@` tags has been optimized. +* Maintained message sending permissions for the group owner and administrators during "mute all" scenarios. +* Enabled the use of a return `null` value for the `customHoverBar` to utilize the default. +* Refined the revoke button functionality for group administrators. +* Removed full-screen support for video previews on the Web and introduced an alternative "Open in New Window" button for an enlarged view. +* Implemented UIKit log recording to facilitate issue identification and troubleshooting. +* Introduced a delete button for the small PNG sticker selection panel on mobile devices, which previously was only available in the Unicode emoji selection panel. + +### Bug Fixes + +* Resolved an issue preventing photo capturing on devices running Android 12 or lower. +* Rectified display inaccuracies related to picture aspect ratios. +* Addressed several issues concerning voice and video calls. + ## 2.1.3+1 ### New Features diff --git a/example/pubspec.lock b/example/pubspec.lock index 88fe6bd..d4c67dd 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -728,6 +728,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0+4" + logger: + dependency: transitive + description: + name: logger + sha256: "66cb048220ca51cf9011da69fa581e4ee2bed4be6e82870d9e9baae75739da49" + url: "https://pub.dev" + source: hosted + version: "2.0.1" logging: dependency: transitive description: @@ -1193,41 +1201,41 @@ packages: dependency: transitive description: name: tencent_chat_i18n_tool - sha256: ac8171d2574ed18babedd0cb67e937e255bf02fcb72f55408d033f74d4b11949 + sha256: "0ee982e814bedd0aea4751b972901c6cfcfb224cfeb8e13ae02e43c0b8a58bbc" url: "https://pub.dev" source: hosted - version: "2.1.3+2" + version: "2.2.0" tencent_cloud_chat_sdk: dependency: transitive description: name: tencent_cloud_chat_sdk - sha256: f98bdb55164051e2b196cac6e2e79e60248ed8351dc5a91d25568712ccb15839 + sha256: "013f8c9d96bbeed06d5fe971b7802d8ddf830c776332d6c6de6ccb9de8956d83" url: "https://pub.dev" source: hosted - version: "5.1.7" + version: "5.1.8" tencent_cloud_chat_uikit: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.1.3-preview.16" + version: "2.2.0" tencent_cloud_uikit_core: dependency: transitive description: name: tencent_cloud_uikit_core - sha256: "0a0f43e4c4241b25d12a9e9f0ee91922ac800a42229d97e3d21d16041ace3104" + sha256: acb3bae877428457318b8c5604a6c263957b6df3454ed3e30e8b6f620c6b2cd9 url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.1.0" tencent_im_base: dependency: transitive description: name: tencent_im_base - sha256: "0db83050452486571ee4ac07280a2fb4bbc07d297a54235b5cf12e46e79267d0" + sha256: bc5eb080090038d21c879480c06d3ed7cb4b1dcc2cbe894189613eadf08cf7c5 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" tencent_im_sdk_plugin_desktop: dependency: "direct main" description: @@ -1240,10 +1248,10 @@ packages: dependency: transitive description: name: tencent_im_sdk_plugin_platform_interface - sha256: "6a1f053567246148ad40667f2ab71d82bcee0d5d0c12e587340d2796c342b87e" + sha256: "1f9814d654dc1ad0a4cb62936f0849defac058c3bdca471472efc8b64b63cc5e" url: "https://pub.dev" source: hosted - version: "0.3.21" + version: "0.3.22" tencent_im_sdk_plugin_web: dependency: "direct main" description: @@ -1296,10 +1304,10 @@ packages: dependency: transitive description: name: tim_ui_kit_sticker_plugin - sha256: c9b0c1261bb51a5dee54bb50c6a106061b1bb10731b9c815fc5175afa60175e2 + sha256: db8143aea26eda5feec5ec2efc5a31b2c56928cd807537778829698f3c4efec5 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "3.0.1+1" transparent_image: dependency: transitive description: 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 040df7c..d90e0ba 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/group/group_services.dart import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:uuid/uuid.dart'; @@ -171,7 +172,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier { void initForEachConversation(ConvType convType, String convID, ValueChanged? onChangeInputField, - {String? groupID}) async { + {String? groupID, + List? preGroupMemberList}) async { if (_isInit) { return; } @@ -188,7 +190,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier { if (conversationType == ConvType.group) { globalModel.refreshGroupApplicationList(); loadGroupInfo(groupID ?? convID); - loadGroupMemberList(groupID: groupID ?? convID); + if(preGroupMemberList != null){ + groupMemberList = preGroupMemberList; + selfMemberInfo = preGroupMemberList + .firstWhereOrNull((e) => e?.userID == selfModel.loginInfo?.userID); + }else{ + await loadSelfMemberInfo(groupID: groupID ?? convID); + loadGroupMemberList(groupID: groupID ?? convID); + } + if(selfMemberInfo == null){ + await loadSelfMemberInfo(groupID: groupID ?? convID); + } } else { notifyListeners(); } @@ -274,12 +286,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier { // 加载聊天记录 Future loadChatRecord({ - HistoryMsgGetTypeEnum? getType, // 获取聊天记录的方式 - int lastMsgSeq = -1, // 上一条消息的消息序号 - required int count, // 加载的消息数量 - String? lastMsgID, // 最后一条消息的ID - LoadDirection direction = - LoadDirection.previous, // 加载的方向,previous表示向上加载,latest表示向下加载 + HistoryMsgGetTypeEnum? getType, + int lastMsgSeq = -1, + required int count, + String? lastMsgID, + LoadDirection direction = LoadDirection.previous, }) async { try { // 根据加载方向设置是否还能继续加载更多消息 @@ -349,20 +360,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { List receivedList = await lifeCycle ?.didGetHistoricalMessageList(response.messageList) ?? response.messageList; - - // 根据加载方向拼接消息列表 - if (globalModel.loadingMessage[conversationID]?.isNotEmpty ?? false) { - if (direction == LoadDirection.previous) { - receivedList = _combineMessageList( - globalModel.messageListMap[conversationID]!, receivedList); - } else { - receivedList = receivedList.reversed.toList(); - receivedList = _combineMessageList( - receivedList, globalModel.messageListMap[conversationID]!); - } - } else { - globalModel.loadingMessage.remove(conversationID); - } + globalModel.loadingMessage.remove(conversationID); // 更新聊天记录到全局model globalModel.setMessageList( @@ -394,7 +392,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { return haveMoreData; } catch (e) { // ignore: avoid_print - print('loadChatRecord error: $e'); + outputLogger.i('loadChatRecord error: $e'); return false; } } @@ -485,6 +483,23 @@ class TUIChatSeparateViewModel extends ChangeNotifier { } } + Future loadSelfMemberInfo({required String groupID}) async { + V2TimValueCallback> getGroupMembersInfoRes = + await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .getGroupMembersInfo( + groupID: groupID, + memberList: [selfModel.loginInfo?.userID ?? ""], + ); + if (getGroupMembersInfoRes.code == 0) { + final userList = getGroupMembersInfoRes.data; + selfMemberInfo = userList + ?.firstWhereOrNull((e) => e.userID == selfModel.loginInfo?.userID); + notifyListeners(); + } + return; + } + Future loadGroupMemberList( {required String groupID, int count = 100, String? seq}) async { final String? nextSeq = await _loadGroupMemberListFunction( @@ -629,7 +644,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { globalModel.updateMessage( sendMsgRes, convID, id, convType, groupType, setInputField); } - if(lifeCycle?.messageDidSend != null){ + if (lifeCycle?.messageDidSend != null) { lifeCycle!.messageDidSend(sendMsgRes); } @@ -883,7 +898,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { notifyListeners(); globalModel.updateMessage(sendMsgRes, convID, messageInfoWithSender.id ?? "", convType, groupType, setInputField); - if(lifeCycle?.messageDidSend != null){ + if (lifeCycle?.messageDidSend != null) { lifeCycle!.messageDidSend(sendMsgRes); } return sendMsgRes; diff --git a/lib/business_logic/separate_models/tui_group_profile_model.dart b/lib/business_logic/separate_models/tui_group_profile_model.dart index 153ba22..c4378b2 100644 --- a/lib/business_logic/separate_models/tui_group_profile_model.dart +++ b/lib/business_logic/separate_models/tui_group_profile_model.dart @@ -1,6 +1,7 @@ // ignore_for_file: unnecessary_getters_setters, avoid_print import 'package:flutter/cupertino.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/group_profile_life_cycle.dart'; import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart'; @@ -110,7 +111,7 @@ class TUIGroupProfileModel extends ChangeNotifier { if (res.code == 0 && groupMemberListRes != null) { final groupMemberListTemp = groupMemberListRes.memberInfoList ?? []; // TODO - print( + outputLogger.i( "loadGroupMemberListfinish,groupMemberListTemp, ${groupMemberListRes.nextSeq}, ${groupMemberListTemp.length}"); _groupMemberList = [...?_groupMemberList, ...groupMemberListTemp]; _groupMemberListSeq = groupMemberListRes.nextSeq ?? "0"; diff --git a/lib/business_logic/view_models/tui_chat_global_model.dart b/lib/business_logic/view_models/tui_chat_global_model.dart index f0d5766..1351748 100644 --- a/lib/business_logic/view_models/tui_chat_global_model.dart +++ b/lib/business_logic/view_models/tui_chat_global_model.dart @@ -15,6 +15,7 @@ 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/utils/message.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; enum ConvType { none, c2c, group } @@ -107,7 +108,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { } void addWaitingList(String msgID) { - print("add to waiting list success"); + outputLogger.i("add to waiting list success"); bool contains = false; for (Map element in _waitingDownloadList) { String msgIDItem = element["msgID"] ?? ""; @@ -145,7 +146,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { isSnapshot: false, ); - print("start another download"); + outputLogger.i("start another download"); } int getReceived(msgID) { @@ -345,37 +346,6 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { } } - Future refreshCurrentHistoryListForConversation( - {HistoryMsgGetTypeEnum getType = - HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG, - int lastMsgSeq = -1, - required int count, - String? lastMsgID, - required String convID, - required ConvType convType}) async { - final currentHistoryMsgList = messageListMap[convID]; - final response = await _messageService.getHistoryMessageList( - count: count, - getType: getType, - userID: convType == ConvType.c2c ? convID : null, - groupID: convType == ConvType.group ? convID : null, - lastMsgID: lastMsgID, - lastMsgSeq: lastMsgSeq); - if (lastMsgID != null && currentHistoryMsgList != null) { - final newList = [...currentHistoryMsgList, ...response]; - final List msgList = - await _lifeCycle?.didGetHistoricalMessageList(newList) ?? newList; - setMessageList(convID, msgList); - } else { - final List messageList = - await _lifeCycle?.didGetHistoricalMessageList(response) ?? response; - setMessageList(convID, messageList); - // The messages in first screen - } - notifyListeners(); - return true; - } - clearData() { _messageListMap.clear(); _currentConversationList.clear(); @@ -416,10 +386,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { _preloadImageMap[msgItem.seq! + msgItem.timestamp.toString() + (msgItem.msgID ?? "")] = tempImg; - print("cacheImage ${msgItem.msgID}"); + outputLogger.i("cacheImage ${msgItem.msgID}"); })); } catch (e) { - print("cacheImage error ${msgItem.msgID}"); + outputLogger.i("cacheImage error ${msgItem.msgID}"); } } } @@ -443,7 +413,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { String msgIDItem = element["msgID"] ?? ""; if (msgIDItem.isNotEmpty) { if (msgID == msgIDItem) { - print("remove download"); + outputLogger.i("remove download"); return true; } } @@ -737,12 +707,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { } _onSendMessageProgress(V2TimMessage messagae, int progress) { - print("message progress: $progress"); + outputLogger.i("message progress: $progress"); } Future onMessageDownloadProgressCallback( V2TimMessageDownloadProgress messageProgress) async { - print(messageProgress.toJson()); + outputLogger.i(messageProgress.toJson()); final currentProgress = getMessageProgress(messageProgress.msgID); if (messageProgress.isFinish && currentProgress < 100) { @@ -811,7 +781,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { } } }catch(e){ - print("calculate error: ${messageProgress.toJson()}"); + outputLogger.i("calculate error: ${messageProgress.toJson()}"); } } @@ -1156,6 +1126,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass { } } final returnValue = listWithTimestamp.reversed.toList(); + outputLogger.i(returnValue.map((e) => e.toJson()) + .toList() + .toString()); return returnValue; } diff --git a/lib/data_services/core/core_services_implements.dart b/lib/data_services/core/core_services_implements.dart index 82a5293..7021c53 100644 --- a/lib/data_services/core/core_services_implements.dart +++ b/lib/data_services/core/core_services_implements.dart @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; 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/common_utils.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.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'; @@ -16,7 +17,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dar import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support.dart' -if (dart.library.html) 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support_implement.dart'; + if (dart.library.html) 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support_implement.dart'; typedef EmptyAvatarBuilder = Widget Function(BuildContext context); @@ -63,7 +64,7 @@ class CoreServicesImpl implements CoreServices { setGlobalConfig(TIMUIKitConfig? config) { final TUISelfInfoViewModel selfInfoViewModel = - serviceLocator(); + serviceLocator(); final TUISettingModel settingModel = serviceLocator(); selfInfoViewModel.globalConfig = config; settingModel.init(); @@ -75,23 +76,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, - TIMUIKitConfig? config, + 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, + 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 { + /// 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, + String? uikitLogPath, + VoidCallback? onWebLoginSuccess}) async { if (platform != null) { TUIKitScreenUtils.deviceType = platform; } + if (TencentUtils.checkString(uikitLogPath) != null) { + logOutputGenerator(uikitLogPath!); + } addIdentifier(); if (extraLanguage != null) { Future.delayed(const Duration(milliseconds: 1), () { @@ -167,13 +173,13 @@ class CoreServicesImpl implements CoreServices { void addInitListener() { final TUIFriendShipViewModel tuiFriendShipViewModel = - serviceLocator(); + serviceLocator(); final TUIConversationViewModel tuiConversationViewModel = - serviceLocator(); + serviceLocator(); final TUIChatGlobalModel tuiChatViewModel = - serviceLocator(); + serviceLocator(); final TUIGroupListenerModel tuiGroupListenerModel = - serviceLocator(); + serviceLocator(); tuiFriendShipViewModel.addFriendListener(); tuiConversationViewModel.setConversationListener(); @@ -183,13 +189,13 @@ class CoreServicesImpl implements CoreServices { void removeListener() { final TUIFriendShipViewModel tuiFriendShipViewModel = - serviceLocator(); + serviceLocator(); final TUIConversationViewModel tuiConversationViewModel = - serviceLocator(); + serviceLocator(); final TUIChatGlobalModel tuiChatViewModel = - serviceLocator(); + serviceLocator(); final TUIGroupListenerModel tuiGroupListenerModel = - serviceLocator(); + serviceLocator(); tuiFriendShipViewModel.removeFriendshipListener(); tuiConversationViewModel.removeConversationListener(); @@ -203,17 +209,16 @@ class CoreServicesImpl implements CoreServices { onCallback!(callbackValue); }); } else { - print( - "TUIKit Callback: ${callbackValue.type} - ${callbackValue - .stackTrace}"); + outputLogger.i( + "TUIKit Callback: ${callbackValue.type} - ${callbackValue.stackTrace}"); } } initDataModel() { final TUIFriendShipViewModel tuiFriendShipViewModel = - serviceLocator(); + serviceLocator(); final TUIConversationViewModel tuiConversationViewModel = - serviceLocator(); + serviceLocator(); tuiFriendShipViewModel.initFriendShipModel(); tuiConversationViewModel.initConversation(); @@ -221,11 +226,11 @@ class CoreServicesImpl implements CoreServices { clearData() { final TUIFriendShipViewModel tuiFriendShipViewModel = - serviceLocator(); + serviceLocator(); final TUIConversationViewModel tuiConversationViewModel = - serviceLocator(); + serviceLocator(); final TUIChatGlobalModel tuiChatViewModel = - serviceLocator(); + serviceLocator(); tuiFriendShipViewModel.clearData(); tuiConversationViewModel.clearData(); @@ -235,18 +240,18 @@ class CoreServicesImpl implements CoreServices { updateUserStatusList(List newUserStatusList) { try { final TUISelfInfoViewModel selfInfoViewModel = - serviceLocator(); + serviceLocator(); if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) { return; } final TUIFriendShipViewModel tuiFriendShipViewModel = - serviceLocator(); + serviceLocator(); final currentUserStatusList = tuiFriendShipViewModel.userStatusList; for (int i = 0; i < newUserStatusList.length; i++) { final int indexInCurrentUserList = currentUserStatusList.indexWhere( - (element) => element.userID == newUserStatusList[i].userID); + (element) => element.userID == newUserStatusList[i].userID); if (indexInCurrentUserList == -1) { currentUserStatusList.add(newUserStatusList[i]); } else { @@ -290,7 +295,7 @@ class CoreServicesImpl implements CoreServices { if (TencentUtils.checkString(_userID) == null) { V2TimValueCallback getLoginUserRes = - await TencentImSDKPlugin.v2TIMManager.getLoginUser(); + await TencentImSDKPlugin.v2TIMManager.getLoginUser(); if (getLoginUserRes.code == 0) { _userID = getLoginUserRes.data ?? ""; } @@ -319,7 +324,7 @@ class CoreServicesImpl implements CoreServices { _loginInfo = res?.data!.firstWhereOrNull((element) => element.userID == _userID); final TUISelfInfoViewModel selfInfoViewModel = - serviceLocator(); + serviceLocator(); if (_loginInfo != null) { selfInfoViewModel.setLoginInfo(_loginInfo); } @@ -374,10 +379,10 @@ class CoreServicesImpl implements CoreServices { return TencentImSDKPlugin.v2TIMManager .getOfflinePushManager() .setOfflinePushConfig( - businessID: businessID?.toDouble() ?? 0, - token: token, - isTPNSToken: isTPNSToken, - ); + businessID: businessID?.toDouble() ?? 0, + token: token, + isTPNSToken: isTPNSToken, + ); } @override diff --git a/lib/ui/controller/tim_uikit_chat_controller.dart b/lib/ui/controller/tim_uikit_chat_controller.dart index 60ada94..282941a 100644 --- a/lib/ui/controller/tim_uikit_chat_controller.dart +++ b/lib/ui/controller/tim_uikit_chat_controller.dart @@ -51,17 +51,21 @@ class TIMUIKitChatController { return model?.clearHistory(); } - /// refresh the history message list manually; - /// Please provide `convType` and `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`. + /// Refresh the history message list manually; Future refreshCurrentHistoryList( [String? convID, ConvType? convType]) async { - if (convID != null && convType != null) { - return globalChatModel.refreshCurrentHistoryListForConversation( - count: 50, convID: convID, convType: convType); - } else if (model != null) { + if(model != null){ + try{ + scrollController?.animateTo( + scrollController!.position.minScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.ease, + ); + }catch(e){} return model!.loadDataFromController(); + } else { + return false; } - return false; } /// Update single message at UI model diff --git a/lib/ui/utils/frame.dart b/lib/ui/utils/frame.dart index ebf1afa..d23d1c6 100644 --- a/lib/ui/utils/frame.dart +++ b/lib/ui/utils/frame.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_print, prefer_typing_uninitialized_variables import 'dart:ui'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class Frame { static var orginalCallback; @@ -27,7 +28,7 @@ class Frame { if (orginalCallback != null) { orginalCallback(timings); } - print("fps: $fps"); + outputLogger.i("fps: $fps"); } static double get fps { diff --git a/lib/ui/utils/logger.dart b/lib/ui/utils/logger.dart new file mode 100644 index 0000000..21e31ea --- /dev/null +++ b/lib/ui/utils/logger.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:logger/logger.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path/path.dart' as p; +import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; + +final outputLogger = Logger( + output: _outputLogger ?? logOutputGenerator(null), +); + +TUIKitOutput? _outputLogger; + +TUIKitOutput logOutputGenerator(String? path) { + _outputLogger = TUIKitOutput(path); + return _outputLogger!; +} + +class TUIKitOutput extends LogOutput { + Future createDirectoryIfNotExists(String path) async { + final directory = Directory(p.dirname(path)); + + if (await directory.exists() == false) { + await directory.create(recursive: true); + } + } + + Future deleteFilesOlderThanDays(String directoryPath, int days) async { + final directory = Directory(directoryPath); + final threshold = DateTime.now().subtract(Duration(days: days)); + + await for (var fileEntity in directory.list(followLinks: false)) { + if (fileEntity is File) { + final lastModified = await fileEntity.lastModified(); + if (lastModified.isBefore(threshold)) { + await fileEntity.delete(); + } + } + } + } + + Future getPlatformLogPath({String? path}) async { + if (TencentUtils.checkString(path) != null) { + outputLogger.i("The path to local log: $path"); + return path!; + } + + final String documentsDirectoryPath = + "${Platform.environment['USERPROFILE']}"; + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + String pkgName = packageInfo.packageName; + var timeName = + "${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}"; + final logPath = p.join(documentsDirectoryPath, "Documents", ".TencentCloudChat", + pkgName, "uikit_log", 'Flutter-TUIKit-$timeName.log'); + outputLogger.i("The path to local log: $logPath"); + + return logPath; + } + + File? logFile; + + TUIKitOutput(String? path) { + if (!PlatformUtils().isWeb) { + getPlatformLogPath(path: path).then((logFilePath) async { + await createDirectoryIfNotExists(logFilePath); + deleteFilesOlderThanDays(p.dirname(logFilePath), 7); + logFile = File(logFilePath); + if (logFile != null) { + if (!logFile!.existsSync()) { + logFile!.createSync(recursive: true); + } + } + }); + } + } + + @override + void output(OutputEvent event) { + var msg = "\n"; + for (var line in event.lines) { + msg += "$line \n"; + } + if (!PlatformUtils().isWeb) { + if (logFile != null) { + final sink = logFile!.openWrite( + mode: FileMode.append, + encoding: const SystemEncoding(), + ); + sink.write(utf8.decode(utf8.encode(msg))); + sink.close(); + } else { + Future.delayed(const Duration(seconds: 1)).then((value) { + if (logFile != null) { + final sink = logFile!.openWrite( + mode: FileMode.append, + encoding: const SystemEncoding(), + ); + sink.write(msg); + sink.close(); + } + }); + } + } else { + print(msg); + } + } +} diff --git a/lib/ui/utils/message.dart b/lib/ui/utils/message.dart index 32575ef..cb3fa6e 100644 --- a/lib/ui/utils/message.dart +++ b/lib/ui/utils/message.dart @@ -7,6 +7,7 @@ import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.d import 'package:tencent_cloud_chat_uikit/ui/constants/time.dart'; import 'package:collection/collection.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class MessageUtils { // 判断CallingData的方式和Trtc的方法一致 @@ -18,7 +19,7 @@ class MessageUtils { return true; } } catch (e) { - print("isCallingData json parse error"); + outputLogger.i("isCallingData json parse error"); return false; } return false; @@ -289,7 +290,7 @@ class MessageUtils { orElse: () => null); } } catch (e) { - print('getImageFromImgList error ${e.toString()}'); + outputLogger.i('getImageFromImgList error ${e.toString()}'); } return img; } diff --git a/lib/ui/utils/time_ago.dart b/lib/ui/utils/time_ago.dart index ad601a1..3efbfca 100644 --- a/lib/ui/utils/time_ago.dart +++ b/lib/ui/utils/time_ago.dart @@ -46,8 +46,14 @@ class TimeAgo { date; } - String getTimeStringForChat(int timeStamp) { + String? getTimeStringForChat(int timeStamp) { final DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000); + final DateTime epochLimit = DateTime.utc(1971); + + if (date.isBefore(epochLimit)) { + return null; + } + final Duration duration = DateTime.now().difference(date); final int diffDays = duration.inDays + (duration.inMinutes > 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 535f7aa..70688ee 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 @@ -279,7 +279,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// Whether to use the default emoji final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; final V2TimGroupMemberFullInfo? groupMemberInfo; @@ -287,7 +287,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// replacing the default message hover action bar. /// Applicable only on desktop platforms. /// If provided, the default message action functionality will appear in the right-click context menu instead. - final Widget Function(V2TimMessage message)? customMessageHoverBarOnDesktop; + final Widget? Function(V2TimMessage message)? customMessageHoverBarOnDesktop; const TIMUIKitHistoryMessageListItem( {Key? key, @@ -1093,8 +1093,11 @@ class _TIMUIKItHistoryMessageListItemState bool isDownloadWaiting) { final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final customHoverBar = widget.customMessageHoverBarOnDesktop != null + ? widget.customMessageHoverBarOnDesktop!(message) + : null; final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop && - widget.customMessageHoverBarOnDesktop == null) + customHoverBar == null) ? getMessageHoverControlBar(model, theme) : []; final lastItemName = @@ -1106,6 +1109,7 @@ class _TIMUIKItHistoryMessageListItemState children: [ if (isDesktopScreen && isShowWideToolTip && + customHoverBar == null && !((widget.message.elemType == 6 && isDownloadWaiting))) Container( decoration: BoxDecoration( @@ -1142,13 +1146,11 @@ class _TIMUIKItHistoryMessageListItemState .toList(), ), ), - if (isDesktopScreen && - isShowWideToolTip && - widget.customMessageHoverBarOnDesktop != null) - widget.customMessageHoverBarOnDesktop!(message), + if (isDesktopScreen && isShowWideToolTip && customHoverBar != null) + customHoverBar, if (!isDesktopScreen || (model.chatConfig.isUseMessageHoverBarOnDesktop && - widget.customMessageHoverBarOnDesktop == null && + customHoverBar == null && !isShowWideToolTip)) const SizedBox( height: 24, 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 af82383..dda6a3e 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 @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; @@ -79,6 +80,8 @@ class TIMUIKitMessageTooltip extends StatefulWidget { class TIMUIKitMessageTooltipState extends TIMUIKitState { final TUIChatGlobalModel globalModal = serviceLocator(); + final TUISelfInfoViewModel selfInfoViewModel = + serviceLocator(); bool isShowMoreSticker = false; bool isShowOpenFile = false; String filePath = ""; @@ -179,9 +182,9 @@ class TIMUIKitMessageTooltipState } bool isAdminCanRecall() { - if (widget.groupMemberInfo != null && - widget.model.chatConfig.isGroupAdminRecallEnabled) { - final selfRole = widget.groupMemberInfo!.role; + if (widget.model.chatConfig.isGroupAdminRecallEnabled) { + final selfMemberInfo = widget.groupMemberInfo ?? widget.model.selfMemberInfo; + final selfRole = selfMemberInfo?.role; return selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN || selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER; } else { diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart index f7744ed..46de4f2 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_history_message_list_container.dart @@ -16,6 +16,7 @@ import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageLi import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; enum LoadingPlace { none, @@ -70,7 +71,7 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget { /// Whether to use the default emoji final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; final bool isAllowScroll; @@ -82,7 +83,7 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget { /// replacing the default message hover action bar. /// Applicable only on desktop platforms. /// If provided, the default message action functionality will appear in the right-click context menu instead. - final Widget Function(V2TimMessage message)? customMessageHoverBarOnDesktop; + final Widget? Function(V2TimMessage message)? customMessageHoverBarOnDesktop; const TIMUIKitHistoryMessageListContainer({ Key? key, diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart index d038522..3b46644 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart @@ -33,6 +33,7 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/image_screen.dart'; import 'package:transparent_image/transparent_image.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class TIMUIKitImageElem extends StatefulWidget { final V2TimMessage message; @@ -58,7 +59,6 @@ class TIMUIKitImageElem extends StatefulWidget { class _TIMUIKitImageElem extends TIMUIKitState { final TUIChatGlobalModel globalModel = serviceLocator(); - double? networkImagePositionRadio; // 加这个字段用于异步获取被安全打击后的兜底图的比例 final TUIChatGlobalModel model = serviceLocator(); final MessageService _messageService = serviceLocator(); Widget? imageItem; @@ -373,178 +373,145 @@ class _TIMUIKitImageElem extends TIMUIKitState { } } - Widget _renderNetworkImage( - dynamic heroTag, double? positionRadio, TUITheme? theme, - {String? path, V2TimImage? originalImg, V2TimImage? smallImg}) { - try { - String originImgUrl = originalImg?.url ?? getOriginImgURL(); - if (originImgUrl.isEmpty && smallImg?.url != null) { - originImgUrl = smallImg!.url!; - } + Future calculateAspectRatio(ImageProvider imageProvider) async { + Completer completer = Completer(); - final imageWidget = Hero( - tag: heroTag, - child: PlatformUtils().isWeb - ? Image.network(path ?? smallImg?.url ?? originalImg!.url!, - fit: BoxFit.contain) - : CachedNetworkImage( - alignment: Alignment.topCenter, - imageUrl: path ?? smallImg?.url ?? originalImg!.url!, - errorWidget: (context, error, stackTrace) => errorPage(theme), - fit: BoxFit.contain, - cacheKey: smallImg?.uuid ?? originalImg!.uuid, - placeholder: (context, url) => - Image(image: MemoryImage(kTransparentImage)), - fadeInDuration: const Duration(milliseconds: 0), + final imageStream = imageProvider.resolve(const ImageConfiguration()); + imageStream.addListener( + ImageStreamListener((imageInfo, synchronousCall) { + if (imageInfo.image.width != 0 && imageInfo.image.height != 0) { + double aspectRatio = imageInfo.image.width / imageInfo.image.height; + completer.complete(aspectRatio); + } else { + // If unable to calculate aspect ratio, return default value of 0.5 + completer.complete(0.5); + } + }, onError: (Object exception, StackTrace? stackTrace) { + // If there's an error, return default value of 0.5 + completer.complete(1); + }), + ); + + return await completer.future; + } + + void onClickImage({ + required bool isNetworkImage, + dynamic heroTag, + TUITheme? theme, + String? imgUrl, + String? imgPath, + }) { + if (isNetworkImage) { + if (PlatformUtils().isWeb) { + TUIKitWidePopup.showMedia( + context: context, + mediaURL: widget.message.imageElem?.path ?? "", + onClickOrigin: () => launchUrl( + Uri.parse(widget.message.imageElem?.path ?? ""), + mode: LaunchMode.externalApplication, )); - - return Stack( - alignment: widget.message.isSelf ?? true - ? AlignmentDirectional.topEnd - : AlignmentDirectional.topStart, - children: [ - getImage( - GestureDetector( - onTap: () async { - if (PlatformUtils().isWeb) { - TUIKitWidePopup.showMedia( - aspectRatio: positionRadio, - context: context, - mediaURL: widget.message.imageElem?.path ?? "", - onClickOrigin: () => launchUrl( - Uri.parse(widget.message.imageElem?.path ?? ""), - mode: LaunchMode.externalApplication, - )); - return; - } - if (PlatformUtils().isDesktop) { - _handleOnTapPreviewImageOnDesktop( - positionRadio: positionRadio, - originImgUrl: originImgUrl, - ); - } else { - Navigator.of(context).push( - PageRouteBuilder( - opaque: false, // set to false - pageBuilder: (_, __, ___) => ImageScreen( - imageProvider: CachedNetworkImageProvider( - path ?? originImgUrl, - cacheKey: widget.message.msgID, - ), - heroTag: heroTag, - messageID: widget.message.msgID, - downloadFn: () async { - return await _saveImg(theme!); - })), - ); - } - }, - child: positionRadio != null - ? AspectRatio( - aspectRatio: positionRadio, - child: imageWidget, - ) - : imageWidget, - ), - imageElem: e) - ], - ); - } catch (e) { - return errorDisplay(context, theme); + return; + } + if (PlatformUtils().isDesktop) { + _handleOnTapPreviewImageOnDesktop( + originImgUrl: imgUrl, + ); + } else { + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + pageBuilder: (_, __, ___) => ImageScreen( + imageProvider: CachedNetworkImageProvider( + imgUrl ?? "", + cacheKey: widget.message.msgID, + ), + heroTag: heroTag, + messageID: widget.message.msgID, + downloadFn: () async { + return await _saveImg(theme!); + })), + ); + } + } else { + if (PlatformUtils().isDesktop) { + TUIKitWidePopup.showMedia( + mediaLocalPath: imgPath, + context: context, + onClickOrigin: () => launchDesktopFile(imgPath ?? "")); + } else { + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, // set to false + pageBuilder: (_, __, ___) => ImageScreen( + imageProvider: FileImage(File(imgPath ?? "")), + heroTag: heroTag, + messageID: widget.message.msgID, + downloadFn: () async { + return await _saveImg(theme!); + }), + ), + ); + } } } - Widget _renderLocalImage(String smallImage, dynamic heroTag, - double? positionRadio, TUITheme? theme, String? originImage) { - double? currentPositionRadio = positionRadio; - File imgF = File(smallImage); + Widget _renderAllImage( + {dynamic heroTag, + TUITheme? theme, + bool isNetworkImage = false, + String? webPath, + V2TimImage? originalImg, + V2TimImage? smallImg, + String? smallLocalPath, + String? originLocalPath}) { + Widget getImageWidget() { + if (isNetworkImage) { + return Hero( + tag: heroTag, + child: PlatformUtils().isWeb + ? Image.network(webPath ?? smallImg?.url ?? originalImg!.url!, + fit: BoxFit.contain) + : CachedNetworkImage( + alignment: Alignment.topCenter, + imageUrl: webPath ?? smallImg?.url ?? originalImg!.url!, + errorWidget: (context, error, stackTrace) => + errorPage(theme), + fit: BoxFit.contain, + cacheKey: smallImg?.uuid ?? originalImg!.uuid, + placeholder: (context, url) => + Image(image: MemoryImage(kTransparentImage)), + fadeInDuration: const Duration(milliseconds: 0), + )); + } else { + final imgPath = (TencentUtils.checkString(smallLocalPath) != null - bool isExist = imgF.existsSync(); - if (!isExist) { - return errorDisplay(context, theme); + ? smallLocalPath + : originLocalPath)!; + return Hero( + tag: heroTag, + child: Image.file(File(imgPath), fit: BoxFit.contain)); + } } - Image image = Image.file(imgF); + ImageProvider imageProvider; + if (isNetworkImage) { + imageProvider = CachedNetworkImageProvider( + webPath ?? smallImg?.url ?? originalImg!.url!); + } else { + imageProvider = FileImage(File(smallLocalPath ?? originLocalPath!)); + } - String showImage = (originImage != null && File(originImage).existsSync()) - ? originImage - : smallImage; - - image.image - .resolve(const ImageConfiguration()) - .addListener(ImageStreamListener((image, synchronousCall) { - if (image.image.width != 0 && image.image.height != 0) { - currentPositionRadio = image.image.width / image.image.height; - } - })); - final message = widget.message; - final preloadImage = model.preloadImageMap[ - message.seq! + message.timestamp.toString() + (message.msgID ?? "")]; - - final isDesktopScreen = - TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; - - final imageWidget = Hero( - tag: heroTag, - child: preloadImage != null - ? FittedBox( - fit: BoxFit.contain, - child: RawImage( - image: preloadImage, - fit: BoxFit.contain, - ), - ) - : FittedBox( - fit: BoxFit.contain, - child: Image.file( - File(smallImage), - fit: BoxFit.contain, - ), - ), - ); - - return Stack( - alignment: AlignmentDirectional.topStart, - children: [ - if (!isDesktopScreen && currentPositionRadio != null) - AspectRatio( - aspectRatio: currentPositionRadio!, - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - ), - ), - getImage( - GestureDetector( - onTap: () { - if (PlatformUtils().isDesktop) { - TUIKitWidePopup.showMedia( - aspectRatio: positionRadio, - mediaLocalPath: showImage, - context: context, - onClickOrigin: () => launchDesktopFile(showImage)); - } else { - Navigator.of(context).push( - PageRouteBuilder( - opaque: false, // set to false - pageBuilder: (_, __, ___) => ImageScreen( - imageProvider: FileImage(File(showImage)), - heroTag: heroTag, - messageID: widget.message.msgID, - downloadFn: () async { - return await _saveImg(theme!); - }), - ), - ); - } - }, - child: positionRadio != null - ? AspectRatio( - aspectRatio: positionRadio, - child: imageWidget, - ) - : imageWidget), - imageElem: null) - ], + return GestureDetector( + onTap: () => onClickImage( + heroTag: heroTag, + isNetworkImage: isNetworkImage, + imgUrl: webPath ?? smallImg?.url ?? originalImg?.url ?? "", + imgPath: (TencentUtils.checkString(originLocalPath) != null + ? originLocalPath + : smallLocalPath) ?? "" + ), + child: getImageWidget(), ); } @@ -587,26 +554,6 @@ class _TIMUIKitImageElem extends TIMUIKitState { isSnapshot: false); } } - // 先暂时下掉用网络图片计算尺寸比例的feature,在没有找到准确的判断图片是否被打击前 - // setOnlineImageRatio(); - } - - void setOnlineImageRatio() { - if (networkImagePositionRadio == null) { - V2TimImage? smallImg = getImageFromList(V2TimImageTypesEnum.small); - V2TimImage? originalImg = getImageFromList(V2TimImageTypesEnum.original); - Image image = Image.network(smallImg?.url ?? originalImg?.url ?? ""); - - image.image - .resolve(const ImageConfiguration()) - .addListener(ImageStreamListener((ImageInfo info, bool _) { - if (info.image.width != 0 && info.image.height != 0) { - setState(() { - networkImagePositionRadio = (info.image.width / info.image.height); - }); - } - })); - } } bool isNeedShowLocalPath() { @@ -628,10 +575,13 @@ class _TIMUIKitImageElem extends TIMUIKitState { if (PlatformUtils().isWeb && widget.message.imageElem!.path != null) { // Displaying on Web only - return _renderNetworkImage(heroTag, positionRadio, theme, + return _renderAllImage( + heroTag: heroTag, + theme: theme, + isNetworkImage: true, smallImg: smallImg, originalImg: originalImg, - path: widget.message.imageElem!.path); + webPath: widget.message.imageElem!.path); } try { @@ -639,36 +589,47 @@ class _TIMUIKitImageElem extends TIMUIKitState { widget.message.imageElem!.path != null && widget.message.imageElem!.path!.isNotEmpty && File(widget.message.imageElem!.path!).existsSync())) { - return _renderLocalImage( - widget.message.imageElem!.path!, - heroTag, - networkImagePositionRadio ?? positionRadio, - theme, - widget.message.imageElem!.path!); + return _renderAllImage( + smallLocalPath: widget.message.imageElem!.path!, + heroTag: heroTag, + theme: theme, + originLocalPath: widget.message.imageElem!.path!); } } catch (e) { // ignore: avoid_print - print(e); + outputLogger.i(e); } try { - if (smallImg?.localUrl != null && - smallImg?.localUrl != "" && - File((smallImg?.localUrl!)!).existsSync()) { - return _renderLocalImage(smallImg!.localUrl!, heroTag, positionRadio, - theme, originalImg?.localUrl); + if ((TencentUtils.checkString(smallImg?.localUrl) != null && + File((smallImg?.localUrl!)!).existsSync()) || + (TencentUtils.checkString(originalImg?.localUrl) != null && + File((originalImg?.localUrl!)!).existsSync())) { + return _renderAllImage( + smallLocalPath: smallImg?.localUrl ?? "", + heroTag: heroTag, + theme: theme, + originLocalPath: originalImg?.localUrl); } } catch (e) { // ignore: avoid_print - print(e); - return _renderNetworkImage(heroTag, positionRadio, theme, - smallImg: smallImg, originalImg: originalImg); + outputLogger.i(e); + return _renderAllImage( + heroTag: heroTag, + theme: theme, + isNetworkImage: true, + smallImg: smallImg, + originalImg: originalImg); } if ((smallImg?.url ?? originalImg?.url) != null && (smallImg?.url ?? originalImg?.url)!.isNotEmpty) { - return _renderNetworkImage(heroTag, positionRadio, theme, - smallImg: smallImg, originalImg: originalImg); + return _renderAllImage( + heroTag: heroTag, + theme: theme, + isNetworkImage: true, + smallImg: smallImg, + originalImg: originalImg); } return errorDisplay(context, theme); 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 f93364e..53e4f75 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 @@ -22,6 +22,8 @@ import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_c import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/link_preview_entry.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/models/link_preview_content.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/widgets/link_preview.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; class TIMUIKitReplyElem extends StatefulWidget { final V2TimMessage message; @@ -35,7 +37,7 @@ class TIMUIKitReplyElem extends StatefulWidget { final TUIChatSeparateViewModel chatModel; final bool? isShowMessageReaction; final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; const TIMUIKitReplyElem({ Key? key, @@ -72,7 +74,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { : "{}")); if (messageCloudCustomData.messageReply != null) { final MessageRepliedData repliedMessage = - MessageRepliedData.fromJson(messageCloudCustomData.messageReply!); + MessageRepliedData.fromJson(messageCloudCustomData.messageReply!); return repliedMessage; } return null; @@ -95,8 +97,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { if (message == null) { try { final RepliedMessageAbstract repliedMessageAbstract = - RepliedMessageAbstract.fromJson( - jsonDecode(cloudCustomData.messageAbstract)); + RepliedMessageAbstract.fromJson( + jsonDecode(cloudCustomData.messageAbstract)); if (repliedMessageAbstract.isNotEmpty) { message = V2TimMessage( elemType: 0, @@ -106,7 +108,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { } } catch (e) { // ignore: avoid_print - print(e.toString()); + outputLogger.i(e.toString()); } } if (message != null) { @@ -132,8 +134,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { _renderMessageSummary(TUITheme? theme) { try { final RepliedMessageAbstract repliedMessageAbstract = - RepliedMessageAbstract.fromJson( - jsonDecode(repliedMessage?.messageAbstract ?? "")); + RepliedMessageAbstract.fromJson( + jsonDecode(repliedMessage?.messageAbstract ?? "")); if (TencentUtils.checkString(repliedMessageAbstract.summary) != null) { return _defaultRawMessageText(repliedMessageAbstract.summary!, theme); } @@ -177,16 +179,15 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { if (isRevokedMsg) { return _defaultRawMessageText( - isAdminRevoke ? TIM_t("[消息被管理员撤回]") : TIM_t("[消息被撤回]"), - theme); + isAdminRevoke ? TIM_t("[消息被管理员撤回]") : TIM_t("[消息被撤回]"), theme); } final messageType = message.elemType; final isSelf = message.isSelf ?? true; final customAbstractMessage = - widget.chatModel.abstractMessageBuilder != null - ? widget.chatModel.abstractMessageBuilder!(message) - : null; + widget.chatModel.abstractMessageBuilder != null + ? widget.chatModel.abstractMessageBuilder!(message) + : null; if (customAbstractMessage != null) { return _defaultRawMessageText( customAbstractMessage, @@ -302,14 +303,14 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { try { final String localJSON = widget.message.localCustomData!; final LocalCustomDataModel? localPreviewInfo = - LocalCustomDataModel.fromMap(json.decode(localJSON)); + LocalCustomDataModel.fromMap(json.decode(localJSON)); if (localPreviewInfo != null && !localPreviewInfo.isLinkPreviewEmpty()) { return Container( margin: const EdgeInsets.only(top: 8), child: - // You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget. - LinkPreviewWidget(linkPreview: localPreviewInfo), + // You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget. + LinkPreviewWidget(linkPreview: localPreviewInfo), ); } else { return null; @@ -344,7 +345,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { final defaultStyle = isFromSelf ? (theme.chatMessageItemFromSelfBgColor ?? - theme.lightPrimaryMaterialColor.shade50) + theme.lightPrimaryMaterialColor.shade50) : (theme.chatMessageItemFromOthersBgColor); final backgroundColor = isShowJumpState @@ -353,23 +354,29 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { final borderRadius = isFromSelf ? const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(2), - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10)) + topLeft: Radius.circular(10), + topRight: Radius.circular(2), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)) : const BorderRadius.only( - topLeft: Radius.circular(2), - topRight: Radius.circular(10), - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10)); + topLeft: Radius.circular(2), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)); final textWithLink = LinkPreviewEntry.getHyperlinksText( widget.message.textElem?.text ?? "", widget.chatModel.chatConfig.isSupportMarkdownForTextMessage, onLinkTap: widget.chatModel.chatConfig.onTapLink, - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget.chatModel.chatConfig.stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget.chatModel.chatConfig + .stickerPanelConfig?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, isEnableTextSelection: - widget.chatModel.chatConfig.isEnableTextSelection ?? false); + widget.chatModel.chatConfig.isEnableTextSelection ?? false); return Container( padding: widget.textPadding ?? EdgeInsets.all(isDesktopScreen ? 12 : 10), decoration: BoxDecoration( @@ -377,10 +384,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { borderRadius: widget.borderRadius ?? borderRadius, ), constraints: - BoxConstraints(maxWidth: MediaQuery - .of(context) - .size - .width * 0.6), + BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6), child: GestureDetector( onTap: _jumpToRawMsg, child: Column( @@ -421,22 +425,34 @@ class _TIMUIKitReplyElemState extends TIMUIKitState { // You can render the widget from extension directly, with a [TextStyle] optionally. widget.chatModel.chatConfig.urlPreviewType != UrlPreviewType.none ? textWithLink!( - style: widget.fontStyle ?? - TextStyle( - fontSize: isDesktopScreen ? 14 : 16, - textBaseline: TextBaseline.ideographic, - height: widget.chatModel.chatConfig.textHeight)) + style: widget.fontStyle ?? + TextStyle( + fontSize: isDesktopScreen ? 14 : 16, + textBaseline: TextBaseline.ideographic, + height: widget.chatModel.chatConfig.textHeight)) : ExtendedText(widget.message.textElem?.text ?? "", - softWrap: true, - style: widget.fontStyle ?? - TextStyle( - fontSize: isDesktopScreen ? 14 : 16, - height: widget.chatModel.chatConfig.textHeight), - specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: widget.isUseDefaultEmoji, - customEmojiStickerList: widget.customEmojiStickerList, - showAtBackground: true, - )), + softWrap: true, + style: widget.fontStyle ?? + TextStyle( + fontSize: isDesktopScreen ? 14 : 16, + height: widget.chatModel.chatConfig.textHeight), + specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( + isUseQQPackage: (widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true, + customEmojiStickerList: widget.customEmojiStickerList, + showAtBackground: true, + )), // If the link preview info is available, render the preview card. if (_renderPreviewWidget() != null && widget.chatModel.chatConfig.urlPreviewType == diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_elem.dart index 2938f5d..23e6871 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_elem.dart @@ -25,7 +25,7 @@ class TIMUIKitTextElem extends StatefulWidget { final TUIChatSeparateViewModel chatModel; final bool? isShowMessageReaction; final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; const TIMUIKitTextElem( {Key? key, @@ -167,7 +167,13 @@ class _TIMUIKitTextElemState extends TIMUIKitState { widget.message.textElem?.text ?? "", widget.chatModel.chatConfig.isSupportMarkdownForTextMessage, onLinkTap: widget.chatModel.chatConfig.onTapLink, - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget.chatModel.chatConfig.stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget.chatModel.chatConfig + .stickerPanelConfig?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, isEnableTextSelection: widget.chatModel.chatConfig.isEnableTextSelection ?? false); @@ -232,7 +238,19 @@ class _TIMUIKitTextElemState extends TIMUIKitState { fontSize: isDesktopScreen ? 14 : 16, height: widget.chatModel.chatConfig.textHeight), specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, showAtBackground: true, )), diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_translate_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_translate_elem.dart index 9a4a219..d63189f 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_translate_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_translate_elem.dart @@ -23,7 +23,7 @@ class TIMUIKitTextTranslationElem extends StatefulWidget { final TUIChatSeparateViewModel chatModel; final bool? isShowMessageReaction; final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; const TIMUIKitTextTranslationElem( {Key? key, @@ -124,7 +124,13 @@ class _TIMUIKitTextTranslationElemState final textWithLink = LinkPreviewEntry.getHyperlinksText(translateText ?? "", widget.chatModel.chatConfig.isSupportMarkdownForTextMessage, onLinkTap: widget.chatModel.chatConfig.onTapLink, - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget.chatModel.chatConfig.stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget.chatModel.chatConfig + .stickerPanelConfig?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, isEnableTextSelection: widget.chatModel.chatConfig.isEnableTextSelection ?? false); @@ -160,7 +166,19 @@ class _TIMUIKitTextTranslationElemState fontSize: isDesktopScreen ? 14 : 16, height: widget.chatModel.chatConfig.textHeight), specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget + .chatModel + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, showAtBackground: true, )), diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/at_member_panel.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/at_member_panel.dart index 4eb68dc..49a4af5 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/at_member_panel.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/at_member_panel.dart @@ -4,7 +4,6 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; - import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart'; diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart index c2746a2..7277368 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart @@ -1,15 +1,16 @@ - // ignore_for_file: file_names import 'package:extended_text_field/extended_text_field.dart'; import 'package:flutter/material.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/http_text.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; import 'emoji_text.dart'; class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder { DefaultSpecialTextSpanBuilder({ - this.isUseDefaultEmoji = false, + this.isUseQQPackage = false, + this.isUseTencentCloudChatPackage = false, this.customEmojiStickerList = const [], this.showAtBackground = false, }); @@ -17,9 +18,11 @@ class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder { /// whether show background for @somebody final bool showAtBackground; - final bool isUseDefaultEmoji; + final bool isUseQQPackage; - final List customEmojiStickerList; + final bool isUseTencentCloudChatPackage; + + final List customEmojiStickerList; @override SpecialText? createSpecialText(String flag, @@ -33,8 +36,9 @@ class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder { ///index is end index of start flag, so text start index should be index-(flag.length-1) if (isStart(flag, EmojiText.flag)) { return EmojiText(textStyle, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, start: index! - (EmojiText.flag.length - 1), - isUseDefaultEmoji: isUseDefaultEmoji, + isUseQQPackage: isUseQQPackage, customEmojiStickerList: customEmojiStickerList); } else if (isStart(flag, HttpText.flag)) { return HttpText(textStyle, onTap, diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart index 2a7a564..a92a025 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart @@ -1,44 +1,54 @@ import 'package:flutter/material.dart'; import 'package:extended_text/extended_text.dart'; import 'package:tim_ui_kit_sticker_plugin/constant/emoji.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; ///emoji/image text class EmojiText extends SpecialText { EmojiText(TextStyle? textStyle, {this.start, - this.isUseDefaultEmoji = false, + this.isUseTencentCloudChatPackage = false, + this.isUseQQPackage = false, this.customEmojiStickerList = const []}) : super(EmojiText.flag, ']', textStyle); static const String flag = '['; final int? start; - final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final bool isUseQQPackage; + final bool isUseTencentCloudChatPackage; + final List customEmojiStickerList; + @override InlineSpan finishText() { final String key = toString(); + final EmojiUtil emojiUtil = EmojiUtil( + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, + isUseQQPackage: isUseQQPackage, + customEmojiStickerList: customEmojiStickerList); - if (EmojiUtil( - isUseDefaultEmoji: isUseDefaultEmoji, - customEmojiStickerList: customEmojiStickerList) - .instance - .emojiMap - .containsKey(key)) { + if (emojiUtil.emojiMap.containsKey(key)) { double size = 16; final TextStyle ts = textStyle!; if (ts.fontSize != null) { - size = ts.fontSize! * 1.15; + size = ts.fontSize! * 1.44; } - if (isUseDefaultEmoji == true) { + if (isUseQQPackage == true && + (emojiUtil.emojiKeyCategoryMap["4349"]?.contains(key) ?? false)) { return ImageSpan( - AssetImage( - EmojiUtil( - isUseDefaultEmoji: isUseDefaultEmoji, - customEmojiStickerList: customEmojiStickerList) - .instance - .emojiMap[key]!, - package: "tencent_im_base"), + AssetImage(emojiUtil.emojiMap[key]!, + package: "tim_ui_kit_sticker_plugin"), + actualText: key, + imageWidth: size, + imageHeight: size, + start: start!, + // fit: BoxFit.cover, + margin: const EdgeInsets.all(0)); + } else if (isUseTencentCloudChatPackage == true && + (emojiUtil.emojiKeyCategoryMap["tcc1"]?.contains(key) ?? false)) { + return ImageSpan( + AssetImage(emojiUtil.emojiMap[key]!, + package: "tim_ui_kit_sticker_plugin"), actualText: key, imageWidth: size, imageHeight: size, @@ -46,12 +56,7 @@ class EmojiText extends SpecialText { // fit: BoxFit.cover, margin: const EdgeInsets.all(0)); } else { - return ImageSpan( - AssetImage(EmojiUtil( - isUseDefaultEmoji: isUseDefaultEmoji, - customEmojiStickerList: customEmojiStickerList) - .instance - .emojiMap[key]!), + return ImageSpan(AssetImage(emojiUtil.emojiMap[key]!), actualText: key, imageWidth: size, imageHeight: size, @@ -66,39 +71,90 @@ class EmojiText extends SpecialText { } class EmojiUtil { - EmojiUtil( - {this.isUseDefaultEmoji = false, this.customEmojiStickerList = const []}); - EmojiUtil._(this.isUseDefaultEmoji, this.customEmojiStickerList) { - if (isUseDefaultEmoji == true) { - for (int i = 0; i < ConstData.emojiList.length; i++) { - for (int j = 0; j < ConstData.emojiList[i].list.length; j++) { - String? emojiName = ConstData.emojiList[i].list[j].split('.png')[0]; - _emojiMap['[$emojiName]'] = - '$_emojiFilePath/${ConstData.emojiList[i].name}/$emojiName.png'; - _emojiMap['[${ConstData.emojiMapList[emojiName]}]'] = - '$_emojiFilePath/${ConstData.emojiList[i].name}/$emojiName.png'; - } - } - } else { - for (int i = 0; i < customEmojiStickerList.length; i++) { - for (int j = 0; j < customEmojiStickerList[i].list.length; j++) { - String? emojiName = - customEmojiStickerList[i].list[j].split('.png')[0]; - _emojiMap['[$emojiName]'] = - '$_emojiFilePath/${customEmojiStickerList[i].name}/$emojiName.png'; + // Private constructor initializing the emoji data + EmojiUtil._internal( + {required this.isUseQQPackage, + required this.isUseTencentCloudChatPackage, + required this.customEmojiStickerList}) { + _emojiMap.addAll(loadDefaultEmojis()); + + final customEmojis = loadCustomEmojis(); + _emojiMap.addAll(customEmojis.$1); + _emojiKeyCategoryMap["custom"] = customEmojis.$2; + } + + final bool isUseQQPackage; + final bool isUseTencentCloudChatPackage; + final List customEmojiStickerList; + + // Load the default emojis into a Map + Map loadDefaultEmojis() { + Map defaultEmojiMap = {}; + for (final emojiGroup in TUIKitStickerConstData.emojiList) { + final groupName = emojiGroup.name; + final keyList = []; + if ((isUseQQPackage && groupName == "4349") || + (isUseTencentCloudChatPackage && groupName == "tcc1")) { + for (final emoji in emojiGroup.list) { + String emojiName = emoji.split('.png')[0]; + defaultEmojiMap['[$emojiName]'] = + '$_emojiFilePath/$groupName/$emojiName.png'; + keyList.add('[$emojiName]'); + + if (groupName == "4349") { + final zhKey = TUIKitStickerConstData.emojiMapList[emojiName]; + defaultEmojiMap['[$zhKey]'] = + '$_emojiFilePath/$groupName/$emojiName.png'; + keyList.add('[$zhKey]'); + } } + _emojiKeyCategoryMap[groupName] = keyList; } } + return defaultEmojiMap; } - final List customEmojiStickerList; - final bool isUseDefaultEmoji; + + // Load the custom emojis into a Map + (Map, List) loadCustomEmojis() { + Map customEmojiMap = {}; + List keyList = []; + for (final customEmojiGroup in customEmojiStickerList) { + for (final customEmoji in customEmojiGroup.list) { + String customEmojiName = customEmoji.split('.png')[0]; + customEmojiMap['[$customEmojiName]'] = + '$_emojiFilePath/${customEmojiGroup.name}/$customEmojiName.png'; + keyList.add('[$customEmojiName]'); + } + } + return (customEmojiMap, keyList); + } + + // A Map instance variable to store the emojis and their paths final Map _emojiMap = {}; + // A getter method for _emojiMap Map get emojiMap => _emojiMap; + // A Map instance variable to store the emojis and their paths + final Map _emojiKeyCategoryMap = {}; + + // A getter method for _emojiMap + Map get emojiKeyCategoryMap => _emojiKeyCategoryMap; + + // An instance variable to store the emoji file path final String _emojiFilePath = 'assets/custom_face_resource'; - // EmojiUitl? _instance; + + // Singleton pattern to avoid creating multiple instances of EmojiUtil static EmojiUtil? _instance; - EmojiUtil get instance => - _instance ??= EmojiUtil._(isUseDefaultEmoji, customEmojiStickerList); + + // Factory constructor to return the singleton instance of EmojiUtil with custom parameters + factory EmojiUtil( + {bool isUseQQPackage = false, + bool isUseTencentCloudChatPackage = false, + List customEmojiStickerList = const []}) { + return _instance ??= EmojiUtil._internal( + isUseQQPackage: isUseQQPackage, + customEmojiStickerList: customEmojiStickerList, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage); + } } diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart index a1df2dd..d5a8b27 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart @@ -4,6 +4,7 @@ import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class EmojiPanel extends TIMUIKitStatelessWidget { final void Function(int unicode) onTapEmoji; @@ -22,7 +23,7 @@ class EmojiPanel extends TIMUIKitStatelessWidget { @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { // ignore: avoid_print - print(TIM_t( + outputLogger.i(TIM_t( "暂未安装表情包插件,如需使用表情相关功能,请根据本文档安装:https://cloud.tencent.com/document/product/269/70746")); return SingleChildScrollView( child: Column( diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart index 7e4c37b..33f1749 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart @@ -29,6 +29,7 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; // ignore: unnecessary_import import 'dart:typed_data'; import 'package:universal_html/html.dart' as html; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class MorePanelConfig { final bool showGalleryPickAction; @@ -357,41 +358,44 @@ class _MorePanelState extends TIMUIKitState { _sendImageMessage(TUIChatSeparateViewModel model, TUITheme theme) async { try { - if (PlatformUtils().isMobile){ - if(PlatformUtils().isAndroid){ + if (PlatformUtils().isMobile) { + if (PlatformUtils().isAndroid) { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; if ((androidInfo.version.sdkInt) >= 33) { final videos = await Permissions.checkPermission( - context,Permission.videos.value, + context, + Permission.videos.value, theme, ); final photos = await Permissions.checkPermission( - context,Permission.photos.value, + context, + Permission.photos.value, theme, ); - if(!videos && !photos){ + if (!videos && !photos) { return; } } else { final storage = await Permissions.checkPermission( - context, Permission.storage.value, + context, + Permission.storage.value, theme, ); - if(!storage){ + if (!storage) { return; } } - }else{ + } else { final photos = await Permissions.checkPermission( context, Permission.photos.value, theme, ); - if(!photos){ + if (!photos) { return; } } - } + } final convID = widget.conversationID; final convType = widget.conversationType; @@ -446,7 +450,7 @@ class _MorePanelState extends TIMUIKitState { } } } catch (err) { - print("err: $err"); + outputLogger.i("err: $err"); } } @@ -462,18 +466,41 @@ class _MorePanelState extends TIMUIKitState { )) { return; } - if (!await Permissions.checkPermission( - context, - Permission.photos.value, - theme, - )) { - return; - } await Permissions.checkPermission( context, Permission.microphone.value, theme, ); + + if (PlatformUtils().isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + if ((androidInfo.version.sdkInt) >= 33) { + if (!await Permissions.checkPermission( + context, + Permission.photos.value, + theme, + )) { + return; + } + } else { + if (!await Permissions.checkPermission( + context, + Permission.storage.value, + theme, + )) { + return; + } + } + } else { + if (!await Permissions.checkPermission( + context, + Permission.photos.value, + theme, + )) { + return; + } + } + final convID = widget.conversationID; final convType = widget.conversationType; final pickedFile = await CameraPicker.pickFromCamera(context, @@ -498,7 +525,7 @@ class _MorePanelState extends TIMUIKitState { // Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context); } } catch (error) { - print("err: $error"); + outputLogger.i("err: $error"); } } @@ -524,7 +551,7 @@ class _MorePanelState extends TIMUIKitState { convType: convType), context); } catch (e) { - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -558,7 +585,7 @@ class _MorePanelState extends TIMUIKitState { convType: convType), context); } catch (e) { - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -589,7 +616,8 @@ class _MorePanelState extends TIMUIKitState { } String? option2 = result.files.single.path ?? ""; - print(TIM_t_para("选择成功{{option2}}", "选择成功$option2")(option2: option2)); + outputLogger + .i(TIM_t_para("选择成功{{option2}}", "选择成功$option2")(option2: option2)); File file = File(result.files.single.path!); final int size = file.lengthSync(); @@ -606,7 +634,7 @@ class _MorePanelState extends TIMUIKitState { throw TypeError(); } } catch (e) { - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -709,35 +737,35 @@ class _MorePanelState extends TIMUIKitState { runSpacing: 20, children: itemList(model, theme) .map((item) => InkWell( - onTap: () { - if (item.onTap != null) { - item.onTap!(context); - } - }, - child: widget.morePanelConfig?.actionBuilder != null - ? widget.morePanelConfig?.actionBuilder!(item) - : SizedBox( - height: 94, - width: 64, - child: Column( - children: [ - Container( - height: 64, - width: 64, - margin: const EdgeInsets.only(bottom: 4), - decoration: const BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(5))), - child: item.icon, - ), - Text( - item.title, - style: TextStyle( - fontSize: 12, color: theme.darkTextColor), - ) - ], - ), - ))) + onTap: () { + if (item.onTap != null) { + item.onTap!(context); + } + }, + child: widget.morePanelConfig?.actionBuilder != null + ? widget.morePanelConfig?.actionBuilder!(item) + : SizedBox( + height: 94, + width: 64, + child: Column( + children: [ + Container( + height: 64, + width: 64, + margin: const EdgeInsets.only(bottom: 4), + decoration: const BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(5))), + child: item.icon, + ), + Text( + item.title, + style: TextStyle( + fontSize: 12, color: theme.darkTextColor), + ) + ], + ), + ))) .toList(), ), ), diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart index 0bbf7e7..4c557da 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart @@ -15,6 +15,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/sound_record.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class SendSoundMessage extends StatefulWidget { /// conversation ID @@ -255,12 +256,12 @@ class _SendSoundMessageState extends TIMUIKitState { path: soundPath!, duration: recordDuration!.ceil(), model: model); } } else if (status == "onStart") { - print("start record"); + outputLogger.i("start record"); setState(() { isRecording = true; }); } else { - print(status); + outputLogger.i(status); } }); final amplitudesResponseSubscription = 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 6eeda06..192492c 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:diff_match_patch/diff_match_patch.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_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_layout/narrow.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -85,7 +86,7 @@ class TIMUIKitInputTextField extends StatefulWidget { /// Whether to use the default emoji final bool isUseDefaultEmoji; - final List customEmojiStickerList; + final List customEmojiStickerList; /// sticker panel customization final CustomStickerPanel? customStickerPanel; @@ -145,39 +146,115 @@ class _InputTextFieldState extends TIMUIKitState { MuteStatus muteStatus = MuteStatus.none; bool _isComposingText = false; int latestSendEditStatusTime = DateTime.now().millisecondsSinceEpoch; + List stickerPackageList = []; + + generateStickerList() { + if (widget.customStickerPanel != null) { + // Keep using original scheme. + return; + } + final stickerConfig = + widget.model.chatConfig.stickerPanelConfig ?? StickerPanelConfig(); + if (stickerConfig.useTencentCloudChatStickerPackage) { + final tccEmojiSet = TUIKitStickerConstData.emojiList + .firstWhere((element) => element.name == "tcc1"); + stickerPackageList.add(CustomStickerPackage( + name: tccEmojiSet.name, + baseUrl: "assets/custom_face_resource/${tccEmojiSet.name}", + isEmoji: tccEmojiSet.isEmoji, + isDefaultEmoji: true, + stickerList: tccEmojiSet.list + .asMap() + .keys + .map((idx) => + CustomSticker(index: idx, name: tccEmojiSet.list[idx])) + .toList(), + menuItem: CustomSticker( + index: 0, + name: tccEmojiSet.icon, + ))); + } + if (stickerConfig.useQQStickerPackage) { + final qqEmojiSet = TUIKitStickerConstData.emojiList + .firstWhere((element) => element.name == "4349"); + stickerPackageList.add(CustomStickerPackage( + name: qqEmojiSet.name, + baseUrl: "assets/custom_face_resource/${qqEmojiSet.name}", + isEmoji: qqEmojiSet.isEmoji, + isDefaultEmoji: true, + stickerList: qqEmojiSet.list + .asMap() + .keys + .map((idx) => + CustomSticker(index: idx, name: qqEmojiSet.list[idx])) + .toList(), + menuItem: CustomSticker( + index: 0, + name: qqEmojiSet.icon, + ))); + } + if (stickerConfig.unicodeEmojiList.isNotEmpty) { + final defEmojiList = + TUIKitStickerConstData.defaultUnicodeEmojiList.map((emojiItem) { + return CustomSticker( + index: 0, name: emojiItem.toString(), unicode: emojiItem); + }).toList(); + stickerPackageList.add(CustomStickerPackage( + name: "defaultEmoji", + stickerList: defEmojiList, + menuItem: defEmojiList[0])); + } + stickerPackageList.addAll(stickerConfig.customStickerPackages); + + return stickerPackageList; + } setCurrentCursor(int? value) { currentCursor = value; } + RegExp EmojiRegex() => RegExp( + r'[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)'); + + bool isEmoji(String input) { + return EmojiRegex().hasMatch(input); + } + void deleteStickerFromText() { String originalText = textEditingController.text; - String text; - final cursorPosition = - currentCursor ?? originalText.length; if (originalText == zeroWidthSpace) { _handleSoftKeyBoardDelete(); } else if (originalText.isNotEmpty) { - if (cursorPosition == originalText.length) { - text = originalText.characters.skipLast(1).toString(); - currentCursor = null; - } else if (cursorPosition > 0 && cursorPosition < originalText.length) { - final firstString = originalText.substring(0, cursorPosition - 2); - final secondString = originalText.substring(cursorPosition); - text = '$firstString$secondString'; - if(currentCursor != null){ - currentCursor = currentCursor! - 2; + String text = originalText; + final cursorPosition = currentCursor ?? originalText.length; + + if (cursorPosition > 0) { + final EmojiUtil emojiUtil = EmojiUtil(); + int removeLength = 1; + int openBracketIndex = originalText.lastIndexOf('[', cursorPosition - 1); + + if (openBracketIndex != -1 && originalText[cursorPosition - 1] == ']') { + // Small png emoji + String key = originalText.substring(openBracketIndex, cursorPosition); + + if (emojiUtil.emojiMap.containsKey(key)) { + removeLength = cursorPosition - openBracketIndex; + } + } else if (cursorPosition > 1 && isEmoji(originalText.substring(cursorPosition - 2, cursorPosition))) { + removeLength = 2; } - } else { - text = originalText.characters.skipLast(1).toString(); - currentCursor = null; + + text = originalText.substring(0, cursorPosition - removeLength) + originalText.substring(cursorPosition); + currentCursor = (currentCursor ?? removeLength) - removeLength; } + textEditingController.text = text; if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) { - textEditingController.selection = TextSelection.fromPosition(TextPosition( - offset: currentCursor ?? textEditingController.text.length)); + textEditingController.selection = TextSelection.fromPosition( + TextPosition( + offset: currentCursor ?? textEditingController.text.length)); focusNode.requestFocus(); } } @@ -261,6 +338,16 @@ class _InputTextFieldState extends TIMUIKitState { // index为emoji的index,data为baseurl+name onCustomEmojiFaceSubmitted(int index, String data) { final convType = widget.conversationType; + + // This part of the code is written to adapt to the Native side requirements. + // It extracts the substring needed to interact with Native side by splitting + // and parsing the given data value. + RegExp regex = RegExp(r'assets\/custom_face_resource\/(4350|4351|4352)'); + if (regex.hasMatch(data)) { + index += 1; + data = (data.split("/")[3]).split("@")[0]; + } + if (widget.model.repliedMessage != null) { MessageUtils.handleMessageError( widget.model.sendFaceMessage( @@ -414,7 +501,7 @@ class _InputTextFieldState extends TIMUIKitState { final dx = min(inputWidth - 180, caretPosition.dx + 16); final dy = max( 24, - 18 * widget.model.chatConfig.desktopMessageInputFieldLines - + 21 * widget.model.chatConfig.desktopMessageInputFieldLines - caretPosition.dy) .toDouble(); @@ -453,8 +540,37 @@ class _InputTextFieldState extends TIMUIKitState { mentionedMembersMap = map; } + (int, String, bool)? findChangedCharacter( + String originalString, String newString) { + if (newString.length < originalString.length) { + final originalStringLength = originalString.length; + final newStringLength = newString.length; + for (int i = 0; i < newString.length; ++i) { + if (originalString[originalStringLength - i - 1] != + newString[newStringLength - i - 1]) { + return ( + newStringLength - i, + originalString[originalStringLength - i - 1], + false + ); + } + } + return (newString.length, originalString[newString.length], false); + } else if (newString.length > originalString.length) { + for (int i = 0; i < originalString.length; ++i) { + if (originalString[i] != newString[i]) { + return (i, newString[i], true); + } + } + return (originalString.length, newString[originalString.length], true); + } else { + return null; + } + } + _handleAtText(String text, TUIChatSeparateViewModel model) async { final text = textEditingController.text; + final String originalText = lastText; String? groupID = widget.conversationType == ConvType.group ? widget.conversationID : null; @@ -468,19 +584,19 @@ class _InputTextFieldState extends TIMUIKitState { int textLength = text.length; // 删除的话 - if (lastText.length > textLength) { - final List differencesList = diff(lastText, text); + if (originalText.length > textLength) { + final List differencesList = diff(originalText, text); final diffIndex = differencesList.first.text.length - 1; - int atIndex = lastText.lastIndexOf('@', diffIndex); - int spaceIndex = lastText.indexOf(' ', diffIndex); + int atIndex = originalText.lastIndexOf('@', diffIndex); + int spaceIndex = originalText.indexOf(' ', diffIndex); if (diffIndex < 0 || atIndex < 0 || spaceIndex <= atIndex) { lastText = text; } else { - String atTag = lastText.substring(atIndex, spaceIndex); - String deletedChar = lastText[diffIndex]; + String atTag = originalText.substring(atIndex, spaceIndex); + String deletedChar = originalText[diffIndex]; if (shouldRemoveAtTag(atTag, deletedChar)) { - final newText = lastText.substring(0, atIndex) + - lastText.substring(spaceIndex + 1); + final newText = originalText.substring(0, atIndex) + + originalText.substring(spaceIndex + 1); textEditingController.text = newText; textEditingController.selection = TextSelection.collapsed(offset: atIndex); @@ -498,10 +614,30 @@ class _InputTextFieldState extends TIMUIKitState { selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER); if (isDesktopScreen) { - final atPlace = text.lastIndexOf("@"); - final keyword = text.substring(atPlace + 1); + (int, String, bool)? changedCharacterRecord = + findChangedCharacter(originalText, text); + int? changedTextPosition = changedCharacterRecord?.$1; + String? changedCharacter = changedCharacterRecord?.$2; + bool isAdded = changedCharacterRecord?.$3 ?? false; + + String? subText, keyword; + int? atPlace; + + if (changedTextPosition != null) { + subText = isAdded == true + ? text.substring(0, changedTextPosition + 1) + : text.substring(0, changedTextPosition); + atPlace = subText.lastIndexOf('@'); + if (atPlace != -1) { + keyword = text.substring( + atPlace + 1, changedTextPosition + (isAdded ? 1 : 0)); + } + } else { + atPlace = -1; + } + if (atPlace >= 0) { - if (text[textLength - 1] == "@") { + if (isAdded && changedCharacter == "@") { final atPosition = getAtPosition(text, atPlace); model.atPositionX = atPosition.dx; model.atPositionY = atPosition.dy; @@ -518,8 +654,9 @@ class _InputTextFieldState extends TIMUIKitState { TencentUtils.checkStringWithoutSpace(element?.userID) ?? "") .toLowerCase(); + keyword ??= ""; return element != null && - showName.contains(keyword.toLowerCase()) && + showName.contains(keyword!.toLowerCase()) && TencentUtils.checkString(showName) != null && element.userID != widget.model.selfMemberInfo?.userID; }) @@ -548,7 +685,8 @@ class _InputTextFieldState extends TIMUIKitState { return userAName.compareTo(userBName); }); - if (canAtAll && showAtMemberList.isNotEmpty && keyword.isEmpty) { + keyword ??= ""; + if (canAtAll && showAtMemberList.isNotEmpty && keyword!.isEmpty) { showAtMemberList = [ V2TimGroupMemberFullInfo( userID: "__kImSDK_MesssageAtALL__", nickName: TIM_t("所有人")), @@ -620,7 +758,7 @@ class _InputTextFieldState extends TIMUIKitState { KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) { final activeIndex = widget.model.activeAtIndex; final showMemberList = widget.model.showAtMemberList; - final isEneter = (event.physicalKey == PhysicalKeyboardKey.enter) || + final isPressEnter = (event.physicalKey == PhysicalKeyboardKey.enter) || (event.physicalKey == PhysicalKeyboardKey.numpadEnter); if (event.runtimeType == RawKeyDownEvent) { if (event.physicalKey == PhysicalKeyboardKey.backspace) { @@ -632,7 +770,7 @@ class _InputTextFieldState extends TIMUIKitState { event.isAltPressed || event.isControlPressed || event.isMetaPressed) && - isEneter) { + isPressEnter) { final offset = textEditingController.selection.baseOffset; textEditingController.text = '${lastText.substring(0, offset)}\n${lastText.substring(offset)}'; @@ -641,7 +779,7 @@ class _InputTextFieldState extends TIMUIKitState { lastText = textEditingController.text; return KeyEventResult.handled; - } else if (isEneter) { + } else if (isPressEnter) { if (!_isComposingText) { if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) { onSubmitted(); @@ -708,6 +846,7 @@ class _InputTextFieldState extends TIMUIKitState { textEditingController.addListener(() { _isComposingText = textEditingController.value.composing.start != -1; }); + generateStickerList(); } controllerHandler() { @@ -782,7 +921,13 @@ class _InputTextFieldState extends TIMUIKitState { if (!mounted) { return; } - if (widget.conversationType == ConvType.group) { + + final int selfRole = widget.model.selfMemberInfo?.role ?? 0; + final bool willNotBeenMuted = + (selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN || + selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER); + + if (widget.conversationType == ConvType.group && !willNotBeenMuted) { if ((model.groupInfo?.isAllMuted ?? false) && muteStatus != MuteStatus.all) { Future.delayed(const Duration(seconds: 0), () { @@ -863,6 +1008,7 @@ class _InputTextFieldState extends TIMUIKitState { return TUIKitScreenUtils.getDeviceWidget( context: context, defaultWidget: TIMUIKitTextFieldLayoutNarrow( + stickerPackageList: stickerPackageList, onEmojiSubmitted: onEmojiSubmitted, onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted, backSpaceText: deleteStickerFromText, @@ -897,6 +1043,7 @@ class _InputTextFieldState extends TIMUIKitState { showMorePanel: widget.showMorePanel, customEmojiStickerList: widget.customEmojiStickerList), desktopWidget: TIMUIKitTextFieldLayoutWide( + stickerPackageList: stickerPackageList, chatConfig: widget.chatConfig ?? widget.model.chatConfig, theme: theme, currentConversation: widget.currentConversation, 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 7bf4e82..fa8736a 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 @@ -15,7 +15,6 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/optimize_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; -import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_send_sound_message.dart'; import 'package:extended_text_field/extended_text_field.dart'; import 'package:tencent_keyboard_visibility/tencent_keyboard_visibility.dart'; @@ -91,7 +90,9 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget { final VoidCallback goDownBottom; - final List customEmojiStickerList; + final List customEmojiStickerList; + + final List stickerPackageList; const TIMUIKitTextFieldLayoutNarrow( {Key? key, @@ -125,7 +126,8 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget { required this.showMorePanel, this.hintText, required this.customEmojiStickerList, - this.controller}) + this.controller, + required this.stickerPackageList}) : super(key: key); @override @@ -185,51 +187,71 @@ class _TIMUIKitTextFieldLayoutNarrowState } } - Widget _getBottomContainer() { + Widget _getBottomContainer(TUITheme theme) { if (showEmojiPanel) { return widget.customStickerPanel != null ? widget.customStickerPanel!( - sendTextMessage: () { - widget.onEmojiSubmitted(); - setSendButton(); - }, - sendFaceMessage: widget.onCustomEmojiFaceSubmitted, - deleteText: () { - widget.backSpaceText(); - setSendButton(); - }, - addText: (int unicode) { - final newText = String.fromCharCode(unicode); - widget.addStickerToText(newText); - setSendButton(); - // handleSetDraftText(); - }, - addCustomEmojiText: ((String singleEmojiName) { - String? emojiName = singleEmojiName.split('.png')[0]; - if (widget.isUseDefaultEmoji && - widget.languageType == 'zh' && - ConstData.emojiMapList[emojiName] != null && - ConstData.emojiMapList[emojiName] != '') { - emojiName = ConstData.emojiMapList[emojiName]; - } - final newText = '[$emojiName]'; - widget.addStickerToText(newText); - setSendButton(); - }), - defaultCustomEmojiStickerList: - widget.isUseDefaultEmoji ? ConstData.emojiList : []) - : EmojiPanel(onTapEmoji: (unicode) { - final newText = String.fromCharCode(unicode); - widget.addStickerToText(newText); - setSendButton(); - // handleSetDraftText(); - }, onSubmitted: () { - widget.onEmojiSubmitted(); - setSendButton(); - }, delete: () { - widget.backSpaceText(); - setSendButton(); - }); + sendTextMessage: () { + widget.onEmojiSubmitted(); + setSendButton(); + }, + sendFaceMessage: widget.onCustomEmojiFaceSubmitted, + deleteText: () { + widget.backSpaceText(); + setSendButton(); + }, + addText: (int unicode) { + final newText = String.fromCharCode(unicode); + widget.addStickerToText(newText); + setSendButton(); + // handleSetDraftText(); + }, + addCustomEmojiText: ((String singleEmojiName) { + String? emojiName = singleEmojiName.split('.png')[0]; + if (widget.isUseDefaultEmoji && + widget.languageType == 'zh' && + TUIKitStickerConstData.emojiMapList[emojiName] != null && + TUIKitStickerConstData.emojiMapList[emojiName] != '') { + emojiName = TUIKitStickerConstData.emojiMapList[emojiName]; + } + final newText = '[$emojiName]'; + widget.addStickerToText(newText); + setSendButton(); + }), + defaultCustomEmojiStickerList: widget.isUseDefaultEmoji + ? TUIKitStickerConstData.emojiList + : []) + : StickerPanel( + isWideScreen: false, + sendTextMsg: () { + widget.onEmojiSubmitted(); + setSendButton(); + }, + sendFaceMsg: widget.onCustomEmojiFaceSubmitted, + deleteText: () { + widget.backSpaceText(); + setSendButton(); + }, + addText: (int unicode) { + final newText = String.fromCharCode(unicode); + widget.addStickerToText(newText); + setSendButton(); + // handleSetDraftText(); + }, + addCustomEmojiText: ((String singleEmojiName) { + String? emojiName = singleEmojiName.split('.png')[0]; + if (widget.isUseDefaultEmoji && + widget.languageType == 'zh' && + TUIKitStickerConstData.emojiMapList[emojiName] != null && + TUIKitStickerConstData.emojiMapList[emojiName] != '') { + emojiName = TUIKitStickerConstData.emojiMapList[emojiName]; + } + final newText = '[$emojiName]'; + widget.addStickerToText(newText); + setSendButton(); + }), + customStickerPackageList: widget.stickerPackageList, + lightPrimaryColor: theme.lightPrimaryColor); } if (showMore) { @@ -310,23 +332,22 @@ 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 ?? []); + 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!)}:${getAbstractMessage( - repliedMessage - )}"; + "${MessageUtils.getDisplayName(widget.model.repliedMessage!)}:${getAbstractMessage(repliedMessage)}"; return Container( color: widget.backgroundColor ?? hexToColor("f5f5f6"), alignment: Alignment.centerLeft, @@ -514,8 +535,19 @@ class _TIMUIKitTextFieldLayoutNarrowState .isWeb ? null : DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: + isUseQQPackage: (widget + .model + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget + .model + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, showAtBackground: true, @@ -609,9 +641,7 @@ class _TIMUIKitTextFieldLayoutNarrowState height: max(_getBottomHeight(), 0.0), child: ListView( physics: const NeverScrollableScrollPhysics(), - children: [ - _getBottomContainer() - ], + children: [_getBottomContainer(theme)], ), ), ], 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 08b94e2..3b5c198 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 @@ -25,7 +25,6 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/screen_shot.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; -import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart'; import 'package:extended_text_field/extended_text_field.dart'; import 'package:universal_html/html.dart' as html; @@ -36,6 +35,7 @@ import 'package:flutter_svg/flutter_svg.dart'; // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; class DesktopControlBarItem { final String item; @@ -147,11 +147,13 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget { final VoidCallback goDownBottom; - final List customEmojiStickerList; + final List customEmojiStickerList; /// Conversation need search final V2TimConversation currentConversation; + final List stickerPackageList; + const TIMUIKitTextFieldLayoutWide( {Key? key, this.customStickerPanel, @@ -186,7 +188,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget { this.controller, required this.currentConversation, required this.theme, - required this.chatConfig}) + required this.chatConfig, + required this.stickerPackageList}) : super(key: key); @override @@ -230,7 +233,7 @@ class _TIMUIKitTextFieldLayoutWideState } } catch (e) { // ignore: avoid_print - print(e); + outputLogger.i(e); } generateDefaultControlBarItems(); } @@ -243,7 +246,7 @@ class _TIMUIKitTextFieldLayoutWideState } } catch (e) { // ignore: avoid_print - print("Paste image failed: ${e.toString()}"); + outputLogger.i("Paste image failed: ${e.toString()}"); } } @@ -387,25 +390,60 @@ class _TIMUIKitTextFieldLayoutWideState String? emojiName = singleEmojiName.split('.png')[0]; if (widget.isUseDefaultEmoji && widget.languageType == 'zh' && - ConstData.emojiMapList[emojiName] != null && - ConstData.emojiMapList[emojiName] != '') { - emojiName = ConstData.emojiMapList[emojiName]; + TUIKitStickerConstData.emojiMapList[emojiName] != + null && + TUIKitStickerConstData.emojiMapList[emojiName] != + '') { + emojiName = + TUIKitStickerConstData.emojiMapList[emojiName]; } final newText = '[$emojiName]'; widget.addStickerToText(newText); entry?.remove(); entry = null; }), - defaultCustomEmojiStickerList: - widget.isUseDefaultEmoji ? ConstData.emojiList : []) - : EmojiPanel(onTapEmoji: (unicode) { - final newText = String.fromCharCode(unicode); - widget.addStickerToText(newText); - }, onSubmitted: () { - widget.onEmojiSubmitted(); - }, delete: () { - widget.backSpaceText(); - }), + defaultCustomEmojiStickerList: widget.isUseDefaultEmoji + ? TUIKitStickerConstData.emojiList + : []) + : StickerPanel( + isWideScreen: true, + height: widget.chatConfig.desktopStickerPanelHeight, + width: 350, + sendTextMsg: null, + sendFaceMsg: (_, __){ + widget.onCustomEmojiFaceSubmitted(_, __); + entry?.remove(); + entry = null; + }, + deleteText: () { + widget.backSpaceText(); + }, + addText: (int unicode) { + final newText = String.fromCharCode(unicode); + widget.addStickerToText(newText); + entry?.remove(); + entry = null; + }, + addCustomEmojiText: ((String singleEmojiName) { + String? emojiName = singleEmojiName.split('.png')[0]; + if (widget.isUseDefaultEmoji && + widget.languageType == 'zh' && + TUIKitStickerConstData.emojiMapList[emojiName] != + null && + TUIKitStickerConstData.emojiMapList[emojiName] != + '') { + emojiName = + TUIKitStickerConstData.emojiMapList[emojiName]; + } + final newText = '[$emojiName]'; + widget.addStickerToText(newText); + entry?.remove(); + entry = null; + }), + customStickerPackageList: widget.stickerPackageList, + bottomColor: theme.weakBackgroundColor, + backgroundColor: theme.wideBackgroundColor, + lightPrimaryColor: theme.lightPrimaryColor), ), )); }); @@ -477,7 +515,7 @@ class _TIMUIKitTextFieldLayoutWideState } } catch (e) { // ignore: avoid_print - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -568,7 +606,7 @@ class _TIMUIKitTextFieldLayoutWideState context); } catch (e) { // ignore: avoid_print - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -603,7 +641,7 @@ class _TIMUIKitTextFieldLayoutWideState context); } catch (e) { // ignore: avoid_print - print("_sendFileErr: ${e.toString()}"); + outputLogger.i("_sendFileErr: ${e.toString()}"); } } @@ -730,7 +768,7 @@ class _TIMUIKitTextFieldLayoutWideState } } catch (err) { // ignore: avoid_print - print("send media err: $err"); + outputLogger.i("send media err: $err"); onTIMCallback(TIMCallback( type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频文件异常"), @@ -1051,7 +1089,19 @@ class _TIMUIKitTextFieldLayoutWideState specialTextSpanBuilder: PlatformUtils().isWeb ? null : DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: widget.isUseDefaultEmoji, + isUseQQPackage: (widget + .model + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true) || + widget.isUseDefaultEmoji, + isUseTencentCloudChatPackage: widget + .model + .chatConfig + .stickerPanelConfig + ?.useTencentCloudChatStickerPackage ?? + true, customEmojiStickerList: widget.customEmojiStickerList, showAtBackground: true, diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart index fb31585..c5eb4b2 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat.dart @@ -18,6 +18,7 @@ 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/logger.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'; @@ -138,7 +139,8 @@ class TIMUIKitChat extends StatefulWidget { /// The top fixed widget. final Widget? topFixWidget; - final List customEmojiStickerList; + /// Specify the custom small png emoji packages. + final List customEmojiStickerList; final Widget? customAppBar; @@ -151,17 +153,26 @@ class TIMUIKitChat extends StatefulWidget { /// replacing the default message hover action bar. /// Applicable only on desktop platforms. /// If provided, the default message action functionality will appear in the right-click context menu instead. - final Widget Function(V2TimMessage message)? customMessageHoverBarOnDesktop; + /// Returns `null` to use default hover bar. + final Widget? Function(V2TimMessage message)? customMessageHoverBarOnDesktop; /// Custom text field final Widget Function(BuildContext context)? textFieldBuilder; + /// An optional parameter `groupMemberList` can be provided. + /// `groupMemberList` accepts a list of nullable `V2TimGroupMemberFullInfo` objects. + /// The purpose of this parameter is to allow the client to supply a pre-fetched list + /// of group member information. If this list is provided, it will not make + /// additional network requests to fetch the group member information internally. + List? groupMemberList; + TIMUIKitChat( {Key? key, this.groupID, required this.conversation, this.conversationID, this.conversationType, + this.groupMemberList, this.conversationShowName, this.abstractMessageBuilder, this.onTapAvatar, @@ -241,11 +252,9 @@ class _TUIChatState extends TIMUIKitState { model.abstractMessageBuilder = widget.abstractMessageBuilder; model.onTapAvatar = widget.onTapAvatar; WidgetsBinding.instance.addPostFrameCallback((_) async { - if (kProfileMode) { - widget.endTime = DateTime.now().millisecondsSinceEpoch; - int timeSpend = widget.endTime - widget.startTime; - print("Page render time:$timeSpend ms"); - } + widget.endTime = DateTime.now().millisecondsSinceEpoch; + int timeSpend = widget.endTime - widget.startTime; + outputLogger.i("Page render time:$timeSpend ms"); }); Future.delayed(const Duration(milliseconds: 500), () { updateDraft(); @@ -372,6 +381,7 @@ class _TUIChatState extends TIMUIKitState { scrollController: autoController, textFieldController: textFieldController, conversationID: _getConvID(), + groupMemberList: widget.groupMemberList, conversationType: _getConvType(), lifeCycle: widget.lifeCycle, config: widget.config, @@ -408,7 +418,9 @@ class _TUIChatState extends TIMUIKitState { model.loadGroupInfo(_getConvID()); break; case UpdateType.memberList: - model.loadGroupMemberList(groupID: _getConvID()); + if(widget.groupMemberList == null){ + model.loadGroupMemberList(groupID: _getConvID()); + } model.loadGroupInfo(_getConvID()); break; default: @@ -416,6 +428,27 @@ class _TUIChatState extends TIMUIKitState { } } + List customImageSmallPngEmojiPackages = []; + if (widget.config?.stickerPanelConfig?.customStickerPackages != + null && + widget.config!.stickerPanelConfig!.customStickerPackages + .isNotEmpty) { + customImageSmallPngEmojiPackages = widget + .config!.stickerPanelConfig!.customStickerPackages + .where((element) => element.isEmoji == true) + .map((e) { + return CustomEmojiFaceData( + name: e.name, + isEmoji: true, + icon: e.menuItem.url ?? "", + list: e.stickerList.map((e) => e.url ?? "").toList()); + }).toList(); + } + if (customImageSmallPngEmojiPackages.isEmpty) { + customImageSmallPngEmojiPackages + .addAll(widget.customEmojiStickerList); + } + return GestureDetector( onTap: () { textFieldController.hideAllPanel(); @@ -538,7 +571,7 @@ class _TUIChatState extends TIMUIKitState { model: model, controller: textFieldController, customEmojiStickerList: - widget.customEmojiStickerList, + customImageSmallPngEmojiPackages, isUseDefaultEmoji: widget.config!.isUseDefaultEmoji, customStickerPanel: @@ -632,10 +665,18 @@ class TIMUIKitChatProviderScope extends StatelessWidget { final AutoScrollController? scrollController; + /// An optional parameter `groupMemberList` can be provided. + /// `groupMemberList` accepts a list of nullable `V2TimGroupMemberFullInfo` objects. + /// The purpose of this parameter is to allow the client to supply a pre-fetched list + /// of group member information. If this list is provided, it will not make + /// additional network requests to fetch the group member information internally. + List? groupMemberList; + TIMUIKitChatProviderScope( {Key? key, this.child, this.providers, + this.groupMemberList, this.textFieldController, required this.builder, this.model, @@ -665,6 +706,7 @@ class TIMUIKitChatProviderScope extends StatelessWidget { (String value) { textFieldController?.textEditingController?.text = value; }, + preGroupMemberList: groupMemberList, groupID: groupID, ); model?.showC2cMessageEditStatus = (conversationType == ConvType.c2c diff --git a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart index f4a23e1..a73eed1 100644 --- a/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart +++ b/lib/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart @@ -22,7 +22,41 @@ class TimeDividerConfig { TimeDividerConfig({this.timeInterval, this.timestampParser}); } +/// StickerPanelConfig is a configuration class for the sticker panel component. +/// It allows customization of specific features such as display options for the +/// message area, sticker packages, unicode emoji lists, and custom sticker packages. +class StickerPanelConfig { + /// Determines whether to use the QQ Sticker Package. + /// Default value: true + final bool useQQStickerPackage; + + /// Determines whether to use the Tencent Cloud Chat Sticker Package. + /// Default value: true + final bool useTencentCloudChatStickerPackage; + + /// A list of unicode emoji, represented as integers. + /// Default value: a list of common Unicode Emojis. + /// To exclude Unicode Emoji from the display, pass an empty list. + final List unicodeEmojiList; + + /// A list of CustomStickerPackage instances, where each instance represents a sticker package. + /// Default value: an empty list. + final List customStickerPackages; + + StickerPanelConfig({ + this.useQQStickerPackage = true, + this.useTencentCloudChatStickerPackage = true, + this.unicodeEmojiList = TUIKitStickerConstData.defaultUnicodeEmojiList, + this.customStickerPackages = const [], + }); +} + class TIMUIKitChatConfig { + /// A StickerPanelConfig instance to configure the sticker panel's behavior and appearance. + /// This includes options such as display settings, usage of specific sticker packages, + /// unicode emoji lists, and custom sticker packages. + final StickerPanelConfig? stickerPanelConfig; + /// Customize the time divider among the two messages. final TimeDividerConfig? timeDividerConfig; @@ -135,7 +169,6 @@ class TIMUIKitChatConfig { final String Function(String data)? faceURISuffix; /// Controls whether text and replied messages can be displayed with Markdown formatting. - /// When enabled, small image stickers, including QQ stickers, will not work in message items. /// Also, when enabled, `isEnableTextSelection` will not works. /// [Default]: false. final bool isSupportMarkdownForTextMessage; @@ -210,6 +243,7 @@ class TIMUIKitChatConfig { {this.onTapLink, this.timeDividerConfig, this.desktopStickerPanelHeight = 400, + this.stickerPanelConfig, this.isGroupAdminRecallEnabled = false, this.isAutoReportRead = true, this.faceURIPrefix, diff --git a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_item.dart b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_item.dart index 0a1c692..1e0af71 100644 --- a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_item.dart +++ b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_item.dart @@ -88,13 +88,13 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget { Widget _getTimeStringForChatWidget(BuildContext context, TUITheme theme) { try { if (draftTimestamp != null && draftTimestamp != 0) { - return Text(TimeAgo().getTimeStringForChat(draftTimestamp as int), + return Text(TimeAgo().getTimeStringForChat(draftTimestamp as int) ?? "", style: TextStyle( fontSize: 12, color: theme.conversationItemTitmeTextColor, )); } else if (lastMsg != null) { - return Text(TimeAgo().getTimeStringForChat(lastMsg!.timestamp as int), + return Text(TimeAgo().getTimeStringForChat(lastMsg!.timestamp as int) ?? "", style: TextStyle( fontSize: 11, color: theme.conversationItemTitmeTextColor, diff --git a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart index b865f9b..0ae5faf 100644 --- a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart +++ b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation_last_msg.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; +import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; @@ -83,7 +84,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState { }); } } else { - final newText = await _getLastMsgShowText(widget.lastMsg, widget.context); + final newText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? ""; if (mounted) { setState(() { groupTipsAbstractText = newText; @@ -92,7 +93,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState { } } - Future _getLastMsgShowText( + Future _getLastMsgShowText( V2TimMessage? message, BuildContext context) async { final msgType = message!.elemType; switch (msgType) { @@ -120,7 +121,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState { case MessageElemType.V2TIM_ELEM_TYPE_MERGER: return TIM_t("[聊天记录]"); default: - return TIM_t("未知消息"); + return null; } } @@ -162,7 +163,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState { Text(_getAtMessage(), style: TextStyle( color: theme.cautionColor, fontSize: widget.fontSize)), - Expanded( + if(TencentUtils.checkString(groupTipsAbstractText) != null)Expanded( child: Text( groupTipsAbstractText, softWrap: true, diff --git a/lib/ui/widgets/contact_list.dart b/lib/ui/widgets/contact_list.dart index a5a9285..e076c97 100644 --- a/lib/ui/widgets/contact_list.dart +++ b/lib/ui/widgets/contact_list.dart @@ -143,6 +143,8 @@ class _ContactListState extends TIMUIKitState { onChanged: (isChecked) { if (isChecked) { if (selectedMemberIsOverFlow()) { + selectedMember = [item]; + setState(() {}); return; } selectedMember.add(item); @@ -285,6 +287,8 @@ class _ContactListState extends TIMUIKitState { selectedMember.remove(memberInfo); } else { if (selectedMemberIsOverFlow()) { + selectedMember = [memberInfo]; + setState(() {}); return; } selectedMember.add(memberInfo); diff --git a/lib/ui/widgets/image_screen.dart b/lib/ui/widgets/image_screen.dart index 832e141..9ad377a 100644 --- a/lib/ui/widgets/image_screen.dart +++ b/lib/ui/widgets/image_screen.dart @@ -212,7 +212,7 @@ class _ImageScreenState extends TIMUIKitState } _doubleClickAnimationListener = () { - //print(_animation.value); + //outputLogger.i(_animation.value); state.handleDoubleTap( scale: _doubleClickAnimation!.value, doubleTapPosition: pointerDownPosition); diff --git a/lib/ui/widgets/link_preview/compiler/md_text.dart b/lib/ui/widgets/link_preview/compiler/md_text.dart new file mode 100644 index 0000000..daf869f --- /dev/null +++ b/lib/ui/widgets/link_preview/compiler/md_text.dart @@ -0,0 +1,29 @@ +import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; + +RegExp emojiExp = RegExp(r'\[([\u4e00-\u9fa5A-Za-z0-9]+)\]'); + +String mdTextCompiler(String originalText, { + bool isUseQQPackage = false, + bool isUseTencentCloudChatPackage = false, + List customEmojiStickerList = const [], +}) { + String text = originalText; + final EmojiUtil emojiUtil = EmojiUtil( + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, + isUseQQPackage: isUseQQPackage, + customEmojiStickerList: customEmojiStickerList); + + text = text.replaceAllMapped(emojiExp, (match) { + String key = match.group(0)!; + + // Check if the emoji exists in the emoji map + if (emojiUtil.emojiMap.containsKey(key)) { + String assetPath = emojiUtil.emojiMap[key]!; + return '![sticker](resource:$assetPath#22x22)'; + } + return key; + }); + + return text; +} \ No newline at end of file diff --git a/lib/ui/widgets/link_preview/link_preview_entry.dart b/lib/ui/widgets/link_preview/link_preview_entry.dart index b51a0ed..fc257d7 100644 --- a/lib/ui/widgets/link_preview/link_preview_entry.dart +++ b/lib/ui/widgets/link_preview/link_preview_entry.dart @@ -3,6 +3,7 @@ import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/common/utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/widgets/link_preview.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/widgets/link_text.dart'; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; import 'models/link_preview_content.dart'; @@ -11,11 +12,15 @@ class LinkPreviewEntry { static LinkPreviewText? getHyperlinksText(String messageText, bool isMarkdown, {Function(String)? onLinkTap, bool isEnableTextSelection = false, - bool isUseDefaultEmoji = false, - List customEmojiStickerList = const []}) { + bool isUseQQPackage = false, + bool isUseTencentCloudChatPackage = false, + List customEmojiStickerList = const []}) { return ({TextStyle? style}) { return isMarkdown ? LinkTextMarkdown( + isUseQQPackage: isUseQQPackage, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, + customEmojiStickerList: customEmojiStickerList, isEnableTextSelection: isEnableTextSelection, messageText: addSpaceAfterLeftBracket( addSpaceBeforeHttp(replaceSingleNewlineWithTwo(messageText))), @@ -26,7 +31,8 @@ class LinkPreviewEntry { messageText: messageText, style: style, onLinkTap: onLinkTap, - isUseDefaultEmoji: isUseDefaultEmoji, + isUseQQPackage: isUseQQPackage, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, customEmojiStickerList: customEmojiStickerList); }; } diff --git a/lib/ui/widgets/link_preview/renderer/md_image.dart b/lib/ui/widgets/link_preview/renderer/md_image.dart new file mode 100644 index 0000000..f3b74f3 --- /dev/null +++ b/lib/ui/widgets/link_preview/renderer/md_image.dart @@ -0,0 +1,91 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; + +/// Type for a function that creates image widgets. +typedef ImageBuilder = Widget Function( + Uri uri, String? imageDirectory, double? width, double? height); + +Widget _handleDataSchemeUri( + Uri uri, final double? width, final double? height) { + final String mimeType = uri.data!.mimeType; + if (mimeType.startsWith('image/')) { + return Image.memory( + uri.data!.contentAsBytes(), + width: width, + height: height, + ); + } else if (mimeType.startsWith('text/')) { + return Text(uri.data!.contentAsString()); + } + return const SizedBox(); +} + +class MDImageRenderer extends StatelessWidget{ + final String src; + final String? title; + final String? alt; + MDImageRenderer({super.key, required this.src, this.title, this.alt}); + + /// A default image builder handling http/https, resource, and file URLs. +// ignore: prefer_function_declarations_over_variables + final ImageBuilder kDefaultImageBuilder = ( + Uri uri, + String? imageDirectory, + double? width, + double? height, + ) { + if (uri.scheme == 'http' || uri.scheme == 'https') { + return Image.network(uri.toString(), width: width, height: height); + } else if (uri.scheme == 'data') { + return _handleDataSchemeUri(uri, width, height); + } else if (uri.scheme == 'resource') { + return Image.asset(uri.path, width: width, height: height); + } else { + final Uri fileUri = imageDirectory != null + ? Uri.parse(imageDirectory + uri.toString()) + : uri; + if (fileUri.scheme == 'http' || fileUri.scheme == 'https') { + return Image.network(fileUri.toString(), width: width, height: height); + } else { + return Image.file(File.fromUri(fileUri), width: width, height: height); + } + } + }; + + Widget _buildImage(String src, String? title, String? alt) { + final List parts = src.split('#'); + if (parts.isEmpty) { + return const SizedBox(); + } + + final String path = parts.first; + double? width; + double? height; + if (parts.length == 2) { + final List dimensions = parts.last.split('x'); + if (dimensions.length == 2) { + width = double.parse(dimensions[0]); + height = double.parse(dimensions[1]); + } + } + + final Uri uri = Uri.parse(path); + Widget child; + if (false) { + // child = imageBuilder!(uri, title, alt); + } else { + child = kDefaultImageBuilder(uri, "", width, height); + } + + return GestureDetector(onTap:(){ + + }, child: child); + } + + @override + Widget build(BuildContext context) { + throw _buildImage(src, title, alt); + } + +} \ No newline at end of file diff --git a/lib/ui/widgets/link_preview/widgets/link_text.dart b/lib/ui/widgets/link_preview/widgets/link_text.dart index fc12d6f..64094e1 100644 --- a/lib/ui/widgets/link_preview/widgets/link_text.dart +++ b/lib/ui/widgets/link_preview/widgets/link_text.dart @@ -5,10 +5,17 @@ import 'package:extended_text/extended_text.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/emoji_text.dart'; +import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/compiler/md_text.dart'; +import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/renderer/md_image.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; +import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart'; + +typedef ImageBuilder = Widget Function( + Uri uri, String? imageDirectory, double? width, double? height); class LinkTextMarkdown extends TIMStatelessWidget { /// Callback for when link is tapped @@ -22,9 +29,18 @@ class LinkTextMarkdown extends TIMStatelessWidget { final bool? isEnableTextSelection; + final bool isUseQQPackage; + + final bool isUseTencentCloudChatPackage; + + final List customEmojiStickerList; + const LinkTextMarkdown( {Key? key, required this.messageText, + this.isUseQQPackage = false, + this.isUseTencentCloudChatPackage = false, + this.customEmojiStickerList = const [], this.isEnableTextSelection, this.onLinkTap, this.style}) @@ -33,7 +49,10 @@ class LinkTextMarkdown extends TIMStatelessWidget { @override Widget timBuild(BuildContext context) { return MarkdownBody( - data: messageText, + data: mdTextCompiler(messageText, + isUseQQPackage: isUseQQPackage, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, + customEmojiStickerList: customEmojiStickerList), selectable: isEnableTextSelection ?? false, styleSheet: MarkdownStyleSheet.fromTheme(ThemeData( textTheme: TextTheme( @@ -67,9 +86,11 @@ class LinkText extends TIMStatelessWidget { /// text style for default words final TextStyle? style; - final bool isUseDefaultEmoji; + final bool isUseQQPackage; - final List customEmojiStickerList; + final bool isUseTencentCloudChatPackage; + + final List customEmojiStickerList; final bool? isEnableTextSelection; @@ -79,7 +100,8 @@ class LinkText extends TIMStatelessWidget { this.onLinkTap, this.isEnableTextSelection, this.style, - this.isUseDefaultEmoji = false, + this.isUseQQPackage = false, + this.isUseTencentCloudChatPackage = false, this.customEmojiStickerList = const []}) : super(key: key); @@ -151,26 +173,10 @@ class LinkText extends TIMStatelessWidget { }, style: style ?? const TextStyle(fontSize: 16.0), specialTextSpanBuilder: DefaultSpecialTextSpanBuilder( - isUseDefaultEmoji: isUseDefaultEmoji, + isUseQQPackage: isUseQQPackage, + isUseTencentCloudChatPackage: isUseTencentCloudChatPackage, customEmojiStickerList: customEmojiStickerList, showAtBackground: true, )); } } - -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 9b5d04f..fe85ba8 100644 --- a/lib/ui/widgets/wide_popup.dart +++ b/lib/ui/widgets/wide_popup.dart @@ -324,13 +324,15 @@ class TUIKitWidePopup { if (isLocalResource) { videoController = VideoPlayerController.file(File(mediaPath)); } else { - videoController = VideoPlayerController.networkUrl(Uri.parse(mediaPath)); + videoController = + VideoPlayerController.networkUrl(Uri.parse(mediaPath)); } await videoController.initialize(); aspectRatioFinal = videoController.value.aspectRatio; chewieController = ChewieController( + allowFullScreen: false, videoPlayerController: videoController, aspectRatio: aspectRatioFinal, autoPlay: true, @@ -340,12 +342,9 @@ class TUIKitWidePopup { mediaWidget = Chewie(controller: chewieController); } else { - mediaWidget = FittedBox( - fit: BoxFit.contain, - child: isLocalResource - ? Image.file(File(mediaPath), fit: BoxFit.contain) - : Image.network(mediaPath, fit: BoxFit.contain), - ); + mediaWidget = isLocalResource + ? Image.file(File(mediaPath), fit: BoxFit.contain) + : Image.network(mediaPath, fit: BoxFit.contain); } showDialog( diff --git a/pubspec.lock b/pubspec.lock index 98e50b0..6a64ae4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -442,7 +442,7 @@ packages: source: hosted version: "11.0.2" fast_i18n: - dependency: "direct dev" + dependency: transitive description: name: fast_i18n sha256: f0039a3c1f5f3b7deafefdbb5222d7eb1ee9c2c2fe1222b648b285711b2c7570 @@ -811,6 +811,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0+4" + logger: + dependency: "direct main" + description: + name: logger + sha256: "66cb048220ca51cf9011da69fa581e4ee2bed4be6e82870d9e9baae75739da49" + url: "https://pub.dev" + source: hosted + version: "2.0.1" logging: dependency: transitive description: @@ -1276,42 +1284,42 @@ packages: dependency: transitive description: name: tencent_chat_i18n_tool - sha256: ac8171d2574ed18babedd0cb67e937e255bf02fcb72f55408d033f74d4b11949 + sha256: "0ee982e814bedd0aea4751b972901c6cfcfb224cfeb8e13ae02e43c0b8a58bbc" url: "https://pub.dev" source: hosted - version: "2.1.3+2" + version: "2.2.0" tencent_cloud_chat_sdk: dependency: transitive description: name: tencent_cloud_chat_sdk - sha256: f98bdb55164051e2b196cac6e2e79e60248ed8351dc5a91d25568712ccb15839 + sha256: "013f8c9d96bbeed06d5fe971b7802d8ddf830c776332d6c6de6ccb9de8956d83" url: "https://pub.dev" source: hosted - version: "5.1.7" + version: "5.1.8" tencent_cloud_uikit_core: dependency: "direct main" description: name: tencent_cloud_uikit_core - sha256: "0a0f43e4c4241b25d12a9e9f0ee91922ac800a42229d97e3d21d16041ace3104" + sha256: acb3bae877428457318b8c5604a6c263957b6df3454ed3e30e8b6f620c6b2cd9 url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.1.0" tencent_im_base: dependency: "direct main" description: name: tencent_im_base - sha256: "0db83050452486571ee4ac07280a2fb4bbc07d297a54235b5cf12e46e79267d0" + sha256: bc5eb080090038d21c879480c06d3ed7cb4b1dcc2cbe894189613eadf08cf7c5 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" tencent_im_sdk_plugin_platform_interface: dependency: transitive description: name: tencent_im_sdk_plugin_platform_interface - sha256: "6a1f053567246148ad40667f2ab71d82bcee0d5d0c12e587340d2796c342b87e" + sha256: "1f9814d654dc1ad0a4cb62936f0849defac058c3bdca471472efc8b64b63cc5e" url: "https://pub.dev" source: hosted - version: "0.3.21" + version: "0.3.22" tencent_keyboard_visibility: dependency: "direct main" description: @@ -1356,10 +1364,10 @@ packages: dependency: "direct main" description: name: tim_ui_kit_sticker_plugin - sha256: c9b0c1261bb51a5dee54bb50c6a106061b1bb10731b9c815fc5175afa60175e2 + sha256: db8143aea26eda5feec5ec2efc5a31b2c56928cd807537778829698f3c4efec5 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "3.0.1+1" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1180f5d..6b2a259 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.3+1 +version: 2.2.0 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 @@ -62,12 +62,12 @@ dependencies: uuid: ^3.0.6 tencent_open_file: ^4.0.10 tencent_keyboard_visibility: ^1.0.1 - tim_ui_kit_sticker_plugin: ^2.2.1 - tencent_im_base: ^2.0.1 + tim_ui_kit_sticker_plugin: ^3.0.1+1 + tencent_im_base: ^3.0.1 fc_native_video_thumbnail: any audioplayers: ^3.0.1 path: ^1.8.1 - tencent_cloud_uikit_core: ^1.0.7 + tencent_cloud_uikit_core: ^1.1.0 pasteboard: ^0.2.0 desktop_drop: ^0.4.1 device_info_plus: any @@ -75,11 +75,11 @@ dependencies: csslib: 0.17.2 diff_match_patch: ^0.4.1 markdown: ^7.1.0 + logger: ^2.0.1 dev_dependencies: flutter_lints: ^1.0.0 build_runner: any - fast_i18n: ^5.12.2 lints: ^1.0.1 # For information on the generic Dart part of this file, see the