feat: Upgrade UIKit to 2.2.0

This commit is contained in:
anonymous 2023-08-18 16:40:21 +08:00
parent 2fefab6196
commit d0b60c68a8
40 changed files with 1393 additions and 691 deletions

View File

@ -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

View File

@ -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:

View File

@ -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<String>? onChangeInputField,
{String? groupID}) async {
{String? groupID,
List<V2TimGroupMemberFullInfo?>? 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<bool> 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<V2TimMessage> 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<void> loadSelfMemberInfo({required String groupID}) async {
V2TimValueCallback<List<V2TimGroupMemberFullInfo>> 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<void> 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;

View File

@ -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";

View File

@ -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<String, String> 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<bool> 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<V2TimMessage> msgList =
await _lifeCycle?.didGetHistoricalMessageList(newList) ?? newList;
setMessageList(convID, msgList);
} else {
final List<V2TimMessage> 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<void> 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;
}

View File

@ -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<TUISelfInfoViewModel>();
serviceLocator<TUISelfInfoViewModel>();
final TUISettingModel settingModel = serviceLocator<TUISettingModel>();
selfInfoViewModel.globalConfig = config;
settingModel.init();
@ -75,23 +76,28 @@ class CoreServicesImpl implements CoreServices {
}
@override
Future<bool?> init({
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
required int sdkAppID,
required LogLevelEnum loglevel,
required V2TimSDKListener listener,
LanguageEnum? language,
String? extraLanguage,
TIMUIKitConfig? config,
Future<bool?> init(
{
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? 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<TUIFriendShipViewModel>();
serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>();
serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>();
serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel =
serviceLocator<TUIGroupListenerModel>();
serviceLocator<TUIGroupListenerModel>();
tuiFriendShipViewModel.addFriendListener();
tuiConversationViewModel.setConversationListener();
@ -183,13 +189,13 @@ class CoreServicesImpl implements CoreServices {
void removeListener() {
final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>();
serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>();
serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>();
serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel =
serviceLocator<TUIGroupListenerModel>();
serviceLocator<TUIGroupListenerModel>();
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<TUIFriendShipViewModel>();
serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>();
serviceLocator<TUIConversationViewModel>();
tuiFriendShipViewModel.initFriendShipModel();
tuiConversationViewModel.initConversation();
@ -221,11 +226,11 @@ class CoreServicesImpl implements CoreServices {
clearData() {
final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>();
serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>();
serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>();
serviceLocator<TUIChatGlobalModel>();
tuiFriendShipViewModel.clearData();
tuiConversationViewModel.clearData();
@ -235,18 +240,18 @@ class CoreServicesImpl implements CoreServices {
updateUserStatusList(List<V2TimUserStatus> newUserStatusList) {
try {
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
serviceLocator<TUISelfInfoViewModel>();
if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) {
return;
}
final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>();
serviceLocator<TUIFriendShipViewModel>();
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<String> 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<TUISelfInfoViewModel>();
serviceLocator<TUISelfInfoViewModel>();
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

View File

@ -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<bool> 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

View File

@ -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 {

109
lib/ui/utils/logger.dart Normal file
View File

@ -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<void> createDirectoryIfNotExists(String path) async {
final directory = Directory(p.dirname(path));
if (await directory.exists() == false) {
await directory.create(recursive: true);
}
}
Future<void> 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<String> 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);
}
}
}

View File

@ -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;
}

View File

@ -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 >

View File

@ -279,7 +279,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
/// Whether to use the default emoji
final bool isUseDefaultEmoji;
final List customEmojiStickerList;
final List<CustomEmojiFaceData> 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,

View File

@ -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<TIMUIKitMessageTooltip> {
final TUIChatGlobalModel globalModal = serviceLocator<TUIChatGlobalModel>();
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
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 {

View File

@ -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<CustomEmojiFaceData> 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,

View File

@ -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<TIMUIKitImageElem> {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
double? networkImagePositionRadio; //
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
final MessageService _messageService = serviceLocator<MessageService>();
Widget? imageItem;
@ -373,178 +373,145 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
}
}
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<double> calculateAspectRatio(ImageProvider imageProvider) async {
Completer<double> completer = Completer<double>();
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<TIMUIKitImageElem> {
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<TIMUIKitImageElem> {
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<TIMUIKitImageElem> {
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);

View File

@ -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<CustomEmojiFaceData> customEmojiStickerList;
const TIMUIKitReplyElem({
Key? key,
@ -72,7 +74,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
: "{}"));
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<TIMUIKitReplyElem> {
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<TIMUIKitReplyElem> {
}
} catch (e) {
// ignore: avoid_print
print(e.toString());
outputLogger.i(e.toString());
}
}
if (message != null) {
@ -132,8 +134,8 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
_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<TIMUIKitReplyElem> {
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<TIMUIKitReplyElem> {
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<TIMUIKitReplyElem> {
final defaultStyle = isFromSelf
? (theme.chatMessageItemFromSelfBgColor ??
theme.lightPrimaryMaterialColor.shade50)
theme.lightPrimaryMaterialColor.shade50)
: (theme.chatMessageItemFromOthersBgColor);
final backgroundColor = isShowJumpState
@ -353,23 +354,29 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
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<TIMUIKitReplyElem> {
borderRadius: widget.borderRadius ?? borderRadius,
),
constraints:
BoxConstraints(maxWidth: MediaQuery
.of(context)
.size
.width * 0.6),
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6),
child: GestureDetector(
onTap: _jumpToRawMsg,
child: Column(
@ -421,22 +425,34 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
// 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 ==

View File

@ -25,7 +25,7 @@ class TIMUIKitTextElem extends StatefulWidget {
final TUIChatSeparateViewModel chatModel;
final bool? isShowMessageReaction;
final bool isUseDefaultEmoji;
final List customEmojiStickerList;
final List<CustomEmojiFaceData> customEmojiStickerList;
const TIMUIKitTextElem(
{Key? key,
@ -167,7 +167,13 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
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<TIMUIKitTextElem> {
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,
)),

View File

@ -23,7 +23,7 @@ class TIMUIKitTextTranslationElem extends StatefulWidget {
final TUIChatSeparateViewModel chatModel;
final bool? isShowMessageReaction;
final bool isUseDefaultEmoji;
final List customEmojiStickerList;
final List<CustomEmojiFaceData> 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,
)),

View File

@ -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';

View File

@ -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<CustomEmojiFaceData> 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,

View File

@ -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<CustomEmojiFaceData> 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<CustomEmojiFaceData> customEmojiStickerList;
// Load the default emojis into a Map
Map<String, String> loadDefaultEmojis() {
Map<String, String> 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<String, String>, List<String>) loadCustomEmojis() {
Map<String, String> customEmojiMap = {};
List<String> 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<String, String> _emojiMap = <String, String>{};
// A getter method for _emojiMap
Map<String, String> get emojiMap => _emojiMap;
// A Map instance variable to store the emojis and their paths
final Map<String, List> _emojiKeyCategoryMap = <String, List>{};
// A getter method for _emojiMap
Map<String, List> 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<CustomEmojiFaceData> customEmojiStickerList = const []}) {
return _instance ??= EmojiUtil._internal(
isUseQQPackage: isUseQQPackage,
customEmojiStickerList: customEmojiStickerList,
isUseTencentCloudChatPackage: isUseTencentCloudChatPackage);
}
}

View File

@ -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(

View File

@ -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<MorePanel> {
_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<MorePanel> {
}
}
} catch (err) {
print("err: $err");
outputLogger.i("err: $err");
}
}
@ -462,18 +466,41 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
)) {
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<MorePanel> {
// Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context);
}
} catch (error) {
print("err: $error");
outputLogger.i("err: $error");
}
}
@ -524,7 +551,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
convType: convType),
context);
} catch (e) {
print("_sendFileErr: ${e.toString()}");
outputLogger.i("_sendFileErr: ${e.toString()}");
}
}
@ -558,7 +585,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
convType: convType),
context);
} catch (e) {
print("_sendFileErr: ${e.toString()}");
outputLogger.i("_sendFileErr: ${e.toString()}");
}
}
@ -589,7 +616,8 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
}
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<MorePanel> {
throw TypeError();
}
} catch (e) {
print("_sendFileErr: ${e.toString()}");
outputLogger.i("_sendFileErr: ${e.toString()}");
}
}
@ -709,35 +737,35 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
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(),
),
),

View File

@ -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<SendSoundMessage> {
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 =

File diff suppressed because one or more lines are too long

View File

@ -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<CustomEmojiFaceData> customEmojiStickerList;
final List<CustomStickerPackage> 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)],
),
),
],

View File

@ -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<CustomEmojiFaceData> customEmojiStickerList;
/// Conversation need search
final V2TimConversation currentConversation;
final List<CustomStickerPackage> 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,

View File

@ -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<CustomEmojiFaceData> 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<V2TimGroupMemberFullInfo?>? 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<TIMUIKitChat> {
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<TIMUIKitChat> {
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<TIMUIKitChat> {
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<TIMUIKitChat> {
}
}
List<CustomEmojiFaceData> 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<TIMUIKitChat> {
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<V2TimGroupMemberFullInfo?>? 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

View File

@ -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<int> unicodeEmojiList;
/// A list of CustomStickerPackage instances, where each instance represents a sticker package.
/// Default value: an empty list.
final List<CustomStickerPackage> 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,

View File

@ -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,

View File

@ -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<TIMUIKitLastMsg> {
});
}
} 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<TIMUIKitLastMsg> {
}
}
Future<String> _getLastMsgShowText(
Future<String?> _getLastMsgShowText(
V2TimMessage? message, BuildContext context) async {
final msgType = message!.elemType;
switch (msgType) {
@ -120,7 +121,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return TIM_t("[聊天记录]");
default:
return TIM_t("未知消息");
return null;
}
}
@ -162,7 +163,7 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
Text(_getAtMessage(),
style: TextStyle(
color: theme.cautionColor, fontSize: widget.fontSize)),
Expanded(
if(TencentUtils.checkString(groupTipsAbstractText) != null)Expanded(
child: Text(
groupTipsAbstractText,
softWrap: true,

View File

@ -143,6 +143,8 @@ class _ContactListState extends TIMUIKitState<ContactList> {
onChanged: (isChecked) {
if (isChecked) {
if (selectedMemberIsOverFlow()) {
selectedMember = [item];
setState(() {});
return;
}
selectedMember.add(item);
@ -285,6 +287,8 @@ class _ContactListState extends TIMUIKitState<ContactList> {
selectedMember.remove(memberInfo);
} else {
if (selectedMemberIsOverFlow()) {
selectedMember = [memberInfo];
setState(() {});
return;
}
selectedMember.add(memberInfo);

View File

@ -212,7 +212,7 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
}
_doubleClickAnimationListener = () {
//print(_animation.value);
//outputLogger.i(_animation.value);
state.handleDoubleTap(
scale: _doubleClickAnimation!.value,
doubleTapPosition: pointerDownPosition);

View File

@ -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<CustomEmojiFaceData> 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;
}

View File

@ -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<CustomEmojiFaceData> 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);
};
}

View File

@ -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<String> parts = src.split('#');
if (parts.isEmpty) {
return const SizedBox();
}
final String path = parts.first;
double? width;
double? height;
if (parts.length == 2) {
final List<String> 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);
}
}

View File

@ -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<CustomEmojiFaceData> 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<CustomEmojiFaceData> 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;
}
}

View File

@ -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(

View File

@ -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:

View File

@ -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