Feat: Upgrade UIKit to 2.2.1

This commit is contained in:
anonymous 2023-08-29 19:25:38 +08:00
parent d0b60c68a8
commit 8310e521f8
21 changed files with 271 additions and 327 deletions

View File

@ -1,3 +1,17 @@
## 2.2.1
### New Features
* Introduced a new `groupMemberList` configuration in `TUIKitChat`; when specified, TUIKit will not load it automatically, optimizing network traffic usage.
* Added support for image copying on desktop platforms.
### Bug Fixes
* Fixed an issue preventing the removal of image loading status.
* Resolved a problem that prevented images from being saved to the device gallery.
* Addressed a potential issue causing the `mentionOtherMemberInGroup` function in `TIMUIKitChatController` to fail.
* Corrected an issue that could lead to improper image rendering.
## 2.2.0
### New Features

View File

@ -9,6 +9,7 @@
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <image_clipboard/image_clipboard_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@ -22,6 +23,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) image_clipboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ImageClipboardPlugin");
image_clipboard_plugin_register_with_registrar(image_clipboard_registrar);
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);

View File

@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
desktop_drop
file_selector_linux
image_clipboard
pasteboard
url_launcher_linux
)

View File

@ -10,6 +10,7 @@ import desktop_drop
import device_info_plus
import fc_native_video_thumbnail
import file_selector_macos
import image_clipboard
import package_info_plus
import pasteboard
import path_provider_foundation
@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
ImageClipboardPlugin.register(with: registry.registrar(forPlugin: "ImageClipboardPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@ -608,6 +608,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image_clipboard:
dependency: transitive
description:
name: image_clipboard
sha256: "1939365dc3b65acd3b1199176a330180075f4e803a6512f5622d050b62e7eff4"
url: "https://pub.dev"
source: hosted
version: "1.0.0+2"
image_gallery_saver:
dependency: transitive
description:
@ -1219,7 +1227,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.2.0"
version: "2.2.1-preview.0"
tencent_cloud_uikit_core:
dependency: transitive
description:

View File

@ -10,6 +10,7 @@
#include <desktop_drop/desktop_drop_plugin.h>
#include <fc_native_video_thumbnail/fc_native_video_thumbnail_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <image_clipboard/image_clipboard_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -23,6 +24,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FcNativeVideoThumbnailPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
ImageClipboardPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ImageClipboardPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
fc_native_video_thumbnail
file_selector_windows
image_clipboard
pasteboard
permission_handler_windows
url_launcher_windows

View File

@ -31,9 +31,8 @@ class ChatLifeCycle {
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String conversationID) shouldClearHistoricalMessageList;
/// Before rendering a message to message list.
bool Function(V2TimMessage msgID) messageShouldMount;
bool Function(V2TimMessage msg) messageShouldMount;
ChatLifeCycle({
this.shouldClearHistoricalMessageList =

View File

@ -184,7 +184,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
_groupType = null;
isGroupExist = true;
_groupInfo = null;
groupMemberList?.clear();
groupMemberList = null;
selfMemberInfo = null;
if (conversationType == ConvType.group) {

View File

@ -16,6 +16,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/utils/message.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
enum ConvType { none, c2c, group }
@ -49,7 +50,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
ChatLifeCycle? _lifeCycle;
bool _isDownloading = false;
final List<Map<String, String>> _waitingDownloadList =
List.empty(growable: true); // example {"savePath":"","url":"",msgId:""}
List.empty(growable: true); // example {"savePath":"","url":"",msgId:""}
int _totalUnreadCount = 0;
String localKeyPrefix = "TUIKit_conversation_stored_";
String localMsgIDListKey = "TUIKit_conversation_list";
@ -63,7 +64,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder;
final Map<String, int> _c2cMessageEditStatusMap =
Map.from({}); // 0 normal 1 sending
Map.from({}); // 0 normal 1 sending
final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({});
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
bool _showC2cMessageEditStatus = true;
@ -196,17 +197,15 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
Map<String, V2TimMessageReceipt> get messageReadReceiptMap =>
_messageReadReceiptMap;
String get currentSelectedConv =>
_currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
String get currentSelectedConv => _currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
.conversationID
: "";
: "";
ConvType? get currentSelectedConvType =>
_currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
ConvType? get currentSelectedConvType => _currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
.conversationType
: null;
: null;
setCurrentConversation(CurrentConversation value) {
_currentConversationList.add(value);
@ -242,13 +241,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
_c2cMessageStatusShowTimer[userID] =
Timer.periodic(const Duration(seconds: 5), (timer) {
_c2cMessageEditStatusMap[userID] = 0;
Timer? t = _c2cMessageStatusShowTimer[userID];
if (t != null && t.isActive) {
//
t.cancel();
}
});
_c2cMessageEditStatusMap[userID] = 0;
Timer? t = _c2cMessageStatusShowTimer[userID];
if (t != null && t.isActive) {
//
t.cancel();
}
});
}
notifyListeners();
}
@ -333,9 +332,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
prefs.remove(localMsgIDListKey);
}
Future<void> updateMessageFromController({required String msgID,
required String conversationID,
required ConvType conversationType}) async {
Future<void> updateMessageFromController(
{required String msgID,
required String conversationID,
required ConvType conversationType}) async {
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
V2TimMessage? newMessage = await tools.getExistingMessageByID(
msgID: msgID,
@ -364,7 +364,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_preLoadImage(List<V2TimMessage> msgList) {
List<V2TimMessage> needPreViewList =
msgList.sublist(0, max(0, min(5, msgList.length - 1)));
msgList.sublist(0, max(0, min(5, msgList.length - 1)));
for (var msgItem in needPreViewList) {
V2TimImage? getImageFromList(V2TimImageTypesEnum imgType) {
V2TimImage? img = MessageUtils.getImageFromImgList(
@ -382,12 +382,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
image.resolve(configuration).addListener(
ImageStreamListener((ImageInfo image, bool synchronousCall) {
final tempImg = image.image;
_preloadImageMap[msgItem.seq! +
msgItem.timestamp.toString() +
(msgItem.msgID ?? "")] = tempImg;
outputLogger.i("cacheImage ${msgItem.msgID}");
}));
final tempImg = image.image;
_preloadImageMap[msgItem.seq! +
msgItem.timestamp.toString() +
(msgItem.msgID ?? "")] = tempImg;
outputLogger.i("cacheImage ${msgItem.msgID}");
}));
} catch (e) {
outputLogger.i("cacheImage error ${msgItem.msgID}");
}
@ -488,13 +488,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
_c2cMessageActiveTimer[msg.sender ?? ""] =
Timer.periodic(const Duration(seconds: 30), (timer) {
_c2cMessageFromUserActiveMap[msg.sender ?? ""] = false;
Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""];
if (t != null && t.isActive) {
//
t.cancel();
}
});
_c2cMessageFromUserActiveMap[msg.sender ?? ""] = false;
Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""];
if (t != null && t.isActive) {
//
t.cancel();
}
});
}
}
} catch (err) {
@ -512,14 +512,14 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
V2TimMsgCreateInfoResult? res = await _messageService.createCustomMessage(
data: json.encode({
"businessID": "user_typing_status",
"typingStatus": isEditing == true ? 1 : 0,
"userAction": 14,
"version": 0,
"actionParam": isEditing == true
? "EIMAMSG_InputStatus_Ing"
: "EIMAMSG_InputStatus_End"
}));
"businessID": "user_typing_status",
"typingStatus": isEditing == true ? 1 : 0,
"userAction": 14,
"version": 0,
"actionParam": isEditing == true
? "EIMAMSG_InputStatus_Ing"
: "EIMAMSG_InputStatus_End"
}));
if (res != null) {
_sendMessage(
id: res.id!,
@ -534,9 +534,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
void refreshGroupApplicationList() async {
final res = await _groupServices.getGroupApplicationList();
_groupApplicationList = res.data?.groupApplicationList?.map((item) {
final V2TimGroupApplication applicationItem = item!;
return applicationItem;
}).toList() ??
final V2TimGroupApplication applicationItem = item!;
return applicationItem;
}).toList() ??
[];
notifyListeners();
}
@ -632,17 +632,14 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
if (activeMessageList != null) {
final findeIndex =
activeMessageList.indexWhere((element) => element.msgID == msgID);
activeMessageList.indexWhere((element) => element.msgID == msgID);
if (findeIndex != -1) {
final findeIndex =
activeMessageList.indexWhere((element) => element.msgID == msgID);
activeMessageList.indexWhere((element) => element.msgID == msgID);
if (findeIndex != -1) {
final targetItem = activeMessageList[findeIndex];
targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
targetItem.id = DateTime
.now()
.millisecondsSinceEpoch
.toString();
targetItem.id = DateTime.now().millisecondsSinceEpoch.toString();
activeMessageList[findeIndex] = targetItem;
}
}
@ -652,10 +649,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
onMessageModified(V2TimMessage modifiedMessage, [String? convID]) async {
modifiedMessage.id = DateTime
.now()
.millisecondsSinceEpoch
.toString();
modifiedMessage.id = DateTime.now().millisecondsSinceEpoch.toString();
final String? exactId = TencentUtils.checkString(modifiedMessage.userID) ??
TencentUtils.checkString(modifiedMessage.groupID);
final activeMessageList = _messageListMap[convID ?? exactId];
@ -716,8 +710,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
final currentProgress = getMessageProgress(messageProgress.msgID);
if (messageProgress.isFinish && currentProgress < 100) {
V2TimMessage? message = await _findAndRetrieveMessage(
messageProgress.msgID);
V2TimMessage? message =
await _findAndRetrieveMessage(messageProgress.msgID);
_handleFinishedDownload(messageProgress, message);
return;
}
@ -726,23 +720,25 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
Future<V2TimMessage?> _findAndRetrieveMessage(String messageId) async {
final messages = await _messageService.findMessages(
messageIDList: [messageId]);
final messages =
await _messageService.findMessages(messageIDList: [messageId]);
return messages?.first;
}
void _handleFinishedDownload(V2TimMessageDownloadProgress messageProgress,
V2TimMessage? message) {
void _handleFinishedDownload(
V2TimMessageDownloadProgress messageProgress, V2TimMessage? message) {
if (message != null) {
bool isImageType = message.elemType ==
MessageElemType.V2TIM_ELEM_TYPE_IMAGE;
bool isVideoType = message.elemType ==
MessageElemType.V2TIM_ELEM_TYPE_VIDEO;
bool isImageType =
message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE;
bool isVideoType =
message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO;
final originalImageType = PlatformUtils().isIOS ? 1 : 0;
if (!isImageType && !isVideoType) {
_updateMessageLocationAndDownloadFile(messageProgress);
} else if ((isImageType && messageProgress.type == 0) || isVideoType) {
Future.delayed(const Duration(seconds: 1), () =>
_updateMessageAndDownloadFile(message, messageProgress));
} else if ((isImageType && messageProgress.type == originalImageType) ||
isVideoType) {
Future.delayed(const Duration(seconds: 1),
() => _updateMessageAndDownloadFile(message, messageProgress));
} else {
return;
}
@ -751,8 +747,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
}
void _updateMessageAndDownloadFile(V2TimMessage message,
V2TimMessageDownloadProgress messageProgress) {
void _updateMessageAndDownloadFile(
V2TimMessage message, V2TimMessageDownloadProgress messageProgress) {
updateAsyncMessage(
message,
TencentUtils.checkString(message.userID) ??
@ -769,18 +765,19 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
downloadFile();
}
void _updateProgressIfNeeded(V2TimMessageDownloadProgress messageProgress,
int currentProgress) {
try{
void _updateProgressIfNeeded(
V2TimMessageDownloadProgress messageProgress, int currentProgress) {
try {
if (messageProgress.totalSize != -1 && !messageProgress.isFinish) {
int progress = min(99,
int progress = min(
99,
(messageProgress.currentSize / messageProgress.totalSize * 100)
.floor());
if (progress > 1 && progress > currentProgress) {
setMessageProgress(messageProgress.msgID, progress);
}
}
}catch(e){
} catch (e) {
outputLogger.i("calculate error: ${messageProgress.toJson()}");
}
}
@ -868,13 +865,11 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
V2TimMsgCreateInfoResult? textMessageInfo =
await _messageService.createTextMessage(text: text);
await _messageService.createTextMessage(text: text);
textMessageInfo = await _messageService.createTextAtMessage(
text: text +
"\n@${TencentUtils.checkString(messageBeenReplied.nickName) ??
TencentUtils.checkString(messageBeenReplied.sender) ??
TencentUtils.checkString(messageBeenReplied.userID)}",
"\n@${TencentUtils.checkString(messageBeenReplied.nickName) ?? TencentUtils.checkString(messageBeenReplied.sender) ?? TencentUtils.checkString(messageBeenReplied.userID)}",
atUserList: [
TencentUtils.checkString(messageBeenReplied.sender) ??
TencentUtils.checkString(messageBeenReplied.userID) ??
@ -886,7 +881,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (messageInfo != null) {
final messageInfoWithSender = messageInfo.sender == null
? tools.setUserInfoForMessage(
messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
: messageInfo;
final hasNickName = messageBeenReplied.nickName != null &&
@ -927,8 +922,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return null;
}
Future<bool> setLocalCustomData(String msgID, String localCustomData,
String conversationID) async {
Future<bool> setLocalCustomData(
String msgID, String localCustomData, String conversationID) async {
final res = await _messageService.setLocalCustomData(
msgID: msgID, localCustomData: localCustomData);
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
@ -947,8 +942,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return false;
}
Future<bool> setLocalCustomInt(String msgID, int localCustomInt,
String conversationID) async {
Future<bool> setLocalCustomInt(
String msgID, int localCustomInt, String conversationID) async {
final res = await _messageService.setLocalCustomInt(
msgID: msgID, localCustomInt: localCustomInt);
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
@ -985,7 +980,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : '';
final oldGroupType =
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
final sendMsgRes = await _messageService.sendMessage(
id: id,
receiver: receiver,
@ -993,8 +988,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
@ -1014,7 +1009,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (isEditStatusMessage == false) {
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
}
if(_lifeCycle?.messageDidSend != null){
if (_lifeCycle?.messageDidSend != null) {
_lifeCycle!.messageDidSend(sendMsgRes);
}
@ -1030,7 +1025,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners();
}
updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes,
updateMessage(
V2TimValueCallback<V2TimMessage> sendMsgRes,
String convID,
String id,
ConvType convType,
@ -1045,10 +1041,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
final findIdIndex =
currentHistoryMsgList.indexWhere((element) => element.id == id);
currentHistoryMsgList.indexWhere((element) => element.id == id);
final targetIndex = findIdIndex == -1
? currentHistoryMsgList
.indexWhere((element) => element.msgID == sendMsgResData.msgID)
.indexWhere((element) => element.msgID == sendMsgResData.msgID)
: findIdIndex;
if (targetIndex != -1) {
currentHistoryMsgList[targetIndex] = sendMsgResData;
@ -1059,13 +1055,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
loadingMessage[convID]!.removeWhere((element) => element.id == id);
}
final oldGroupType =
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
if (chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
sendMsgRes.data?.msgID != null &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType)))) {
@ -1076,12 +1072,11 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners();
}
void updateAsyncMessage(V2TimMessage message,
String convID,) {
message.id = DateTime
.now()
.millisecondsSinceEpoch
.toString();
void updateAsyncMessage(
V2TimMessage message,
String convID,
) {
message.id = DateTime.now().millisecondsSinceEpoch.toString();
final activeMessageList = _messageListMap[convID];
if (activeMessageList == null || activeMessageList.isEmpty) {
@ -1108,11 +1103,11 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
{
if (listWithTimestamp.isEmpty ||
(listWithTimestamp[listWithTimestamp.length - 1].timestamp !=
null &&
null &&
item.timestamp != null &&
(item.timestamp! -
listWithTimestamp[listWithTimestamp.length - 1]
.timestamp! >
listWithTimestamp[listWithTimestamp.length - 1]
.timestamp! >
interval))) {
listWithTimestamp.add(V2TimMessage(
userID: '',
@ -1126,15 +1121,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
}
final returnValue = listWithTimestamp.reversed.toList();
outputLogger.i(returnValue.map((e) => e.toJson())
.toList()
.toString());
outputLogger.i(returnValue.map((e) => e.toJson()).toList().toString());
return returnValue;
}
HistoryMessagePosition getMessageListPosition(String? conversationID) {
final HistoryMessagePosition? position =
_historyMessagePositionMap[conversationID];
_historyMessagePositionMap[conversationID];
if (position == null) {
_historyMessagePositionMap[conversationID ?? currentSelectedConv] =
HistoryMessagePosition.bottom;
@ -1144,8 +1137,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
}
void setMessageListPosition(String conversationID,
HistoryMessagePosition position) {
void setMessageListPosition(
String conversationID, HistoryMessagePosition position) {
_historyMessagePositionMap[conversationID] = position;
notifyListeners();
}

View File

@ -102,8 +102,6 @@ class TUIKitOutput extends LogOutput {
}
});
}
} else {
print(msg);
}
}
}

View File

@ -5,6 +5,7 @@ import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_clipboard/image_clipboard.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';
@ -83,7 +84,7 @@ class TIMUIKitMessageTooltipState
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
bool isShowMoreSticker = false;
bool isShowOpenFile = false;
bool fileBeenDownloaded = false;
String filePath = "";
@override
@ -98,11 +99,11 @@ class TIMUIKitMessageTooltipState
(widget.message.fileElem == null &&
widget.message.imageElem == null &&
widget.message.videoElem == null)) {
isShowOpenFile = false;
fileBeenDownloaded = false;
return;
}
if (PlatformUtils().isWeb) {
isShowOpenFile = true;
fileBeenDownloaded = true;
return;
}
if (PlatformUtils().isDesktop) {
@ -115,7 +116,7 @@ class TIMUIKitMessageTooltipState
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
isShowOpenFile = true;
fileBeenDownloaded = true;
return;
}
} else if (widget.message.imageElem != null) {
@ -124,19 +125,19 @@ class TIMUIKitMessageTooltipState
null &&
File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
isShowOpenFile = true;
fileBeenDownloaded = true;
return;
}
} else if (widget.message.videoElem != null) {
if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) !=
null &&
File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
isShowOpenFile = true;
fileBeenDownloaded = true;
return;
}
}
}
isShowOpenFile = false;
fileBeenDownloaded = false;
}
bool isRevocable(int timestamp, int upperTimeLimit) =>
@ -183,7 +184,8 @@ class TIMUIKitMessageTooltipState
bool isAdminCanRecall() {
if (widget.model.chatConfig.isGroupAdminRecallEnabled) {
final selfMemberInfo = widget.groupMemberInfo ?? widget.model.selfMemberInfo;
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;
@ -205,24 +207,31 @@ class TIMUIKitMessageTooltipState
final shouldShowForwardAction = !(widget.message.customElem?.data != null &&
MessageUtils.isCallingData(widget.message.customElem!.data!));
final tooltipsConfig = widget.toolTipsConfig;
final messageCanCopy = widget.message.elemType ==
MessageElemType.V2TIM_ELEM_TYPE_TEXT ||
(isDesktopScreen &&
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE &&
fileBeenDownloaded);
final List<MessageToolTipItem> defaultTipsList = [
if (isShowOpenFile)
if (fileBeenDownloaded)
MessageToolTipItem(
label: TIM_t("打开"),
id: "open",
iconImageAsset: "images/open_in_new.png",
onClick: () => _onTap("open", model)),
if (isShowOpenFile && PlatformUtils().isDesktop)
if (fileBeenDownloaded && PlatformUtils().isDesktop)
MessageToolTipItem(
label: PlatformUtils().isMacOS ? TIM_t("在访达中打开") : TIM_t("查看文件夹"),
id: "finder",
iconImageAsset: "images/folder_open.png",
onClick: () => _onTap("finder", model)),
MessageToolTipItem(
label: TIM_t("复制"),
id: "copyMessage",
iconImageAsset: "images/copy_message.png",
onClick: () => _onTap("copyMessage", model)),
if (messageCanCopy)
MessageToolTipItem(
label: TIM_t("复制"),
id: "copyMessage",
iconImageAsset: "images/copy_message.png",
onClick: () => _onTap("copyMessage", model)),
if (shouldShowForwardAction && !isVoteMessage(widget.message))
MessageToolTipItem(
label: TIM_t("转发"),
@ -263,8 +272,7 @@ class TIMUIKitMessageTooltipState
defaultFormattedTipsList = defaultTipsList.where((element) {
final type = element.id;
if (type == "copyMessage") {
return tooltipsConfig.showCopyMessage &&
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
return tooltipsConfig.showCopyMessage;
}
if (type == "forwardMessage") {
return tooltipsConfig.showForwardMessage &&
@ -402,6 +410,12 @@ class TIMUIKitMessageTooltipState
} catch (e) {}
}
Future<void> copyImageToClipboard(String imagePath) async {
ImageClipboard().copyImage(imagePath);
// final DesktopClipboard desktopClipboard = DesktopClipboard();
// desktopClipboard.copyImage(imagePath);
}
_onTap(String operation, TUIChatSeparateViewModel model) async {
final messageItem = widget.message;
final msgID = messageItem.msgID as String;
@ -485,6 +499,13 @@ class TIMUIKitMessageTooltipState
infoCode: 6660408));
// ignore: empty_catches
} catch (e) {}
} else if (widget.message.elemType ==
MessageElemType.V2TIM_ELEM_TYPE_IMAGE) {
final savePath = (TencentUtils.checkString(
widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
"");
copyImageToClipboard(savePath);
}
break;
case "replyMessage":

View File

@ -114,7 +114,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
Future<void> _saveImageToLocal(
context,
String imageUrl, {
bool isAsset = true,
bool isLocalResource = true,
TUITheme? theme,
}) async {
if (PlatformUtils().isWeb) {
@ -163,8 +163,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
}
}
//
if (!isAsset) {
if (!isLocalResource) {
if (widget.message.msgID == null || widget.message.msgID!.isEmpty) {
return;
}
@ -216,9 +215,6 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
}
return;
}
// model.setMessageProgress(widget.message.msgID!, 0);
// var result =
// await ImageGallerySaver.saveImage(Uint8List.fromList(response));
var result = await ImageGallerySaver.saveFile(imageUrl);
@ -251,44 +247,44 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
}
Future<void> _saveImg(TUITheme theme) async {
String? path = widget.message.imageElem!.path;
if (path != null && PlatformUtils().isWeb
? true
: File(path!).existsSync()) {
return await _saveImageToLocal(context, path,
isAsset: true, theme: theme);
} else {
String imgUrl = getOriginImgURL();
if (widget.message.imageElem!.imageList![0]!.localUrl != '' &&
widget.message.imageElem!.imageList![0]!.localUrl != null) {
File f = File(widget.message.imageElem!.imageList![0]!.localUrl!);
if (f.existsSync()) {
return await _saveImageToLocal(
context,
widget.message.imageElem!.imageList![0]!.localUrl!,
isAsset: true,
theme: theme,
);
try {
String? imageUrl;
bool isAssetBool = false;
final imageElem = widget.message.imageElem;
if (imageElem != null) {
final originUrl = getOriginImgURL();
final localUrl = imageElem.imageList?.firstOrNull?.localUrl;
final filePath = imageElem.path;
final isWeb = PlatformUtils().isWeb;
if (!isWeb && filePath != null && File(filePath).existsSync()) {
imageUrl = filePath;
isAssetBool = true;
} else if (localUrl != null &&
(!isWeb && File(localUrl).existsSync())) {
imageUrl = localUrl;
isAssetBool = true;
} else {
imageUrl = originUrl;
isAssetBool = false;
}
}
if (widget.message.imageElem!.path != '' &&
widget.message.imageElem!.path != null) {
File f = File(widget.message.imageElem!.path!);
if (f.existsSync()) {
return await _saveImageToLocal(
context,
widget.message.imageElem!.path!,
isAsset: true,
theme: theme,
);
}
if (imageUrl != null) {
return await _saveImageToLocal(
context,
imageUrl,
isLocalResource: isAssetBool,
theme: theme,
);
}
return await _saveImageToLocal(
context,
imgUrl,
isAsset: false,
theme: theme,
);
} catch (e) {
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO));
return;
}
}
@ -398,7 +394,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
void onClickImage({
required bool isNetworkImage,
dynamic heroTag,
TUITheme? theme,
required TUITheme theme,
String? imgUrl,
String? imgPath,
}) {
@ -429,7 +425,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
heroTag: heroTag,
messageID: widget.message.msgID,
downloadFn: () async {
return await _saveImg(theme!);
return await _saveImg(theme);
})),
);
}
@ -448,7 +444,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
heroTag: heroTag,
messageID: widget.message.msgID,
downloadFn: () async {
return await _saveImg(theme!);
return await _saveImg(theme);
}),
),
);
@ -458,7 +454,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
Widget _renderAllImage(
{dynamic heroTag,
TUITheme? theme,
required TUITheme theme,
bool isNetworkImage = false,
String? webPath,
V2TimImage? originalImg,
@ -485,7 +481,6 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
));
} else {
final imgPath = (TencentUtils.checkString(smallLocalPath) != null
? smallLocalPath
: originLocalPath)!;
return Hero(
@ -504,24 +499,34 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
return GestureDetector(
onTap: () => onClickImage(
theme: theme,
heroTag: heroTag,
isNetworkImage: isNetworkImage,
imgUrl: webPath ?? smallImg?.url ?? originalImg?.url ?? "",
imgPath: (TencentUtils.checkString(originLocalPath) != null
? originLocalPath
: smallLocalPath) ?? ""
),
? originLocalPath
: smallLocalPath) ??
""),
child: getImageWidget(),
);
}
@override
void initState() {
super.initState();
void initImages() async {
if (!PlatformUtils().isWeb &&
TencentUtils.checkString(widget.message.msgID) != null) {
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![0]!.localUrl) ==
if (widget.message.imageElem?.imageList == null ||
widget.message.imageElem!.imageList!.isEmpty) {
final response = await _messageService.getMessageOnlineUrl(
msgID: widget.message.msgID!);
final elem = response.data;
if (elem != null && elem.imageElem != null) {
widget.message.imageElem = elem.imageElem;
}
}
if (widget.message.imageElem?.imageList == null ||
widget.message.imageElem!.imageList!.isEmpty ||
TencentUtils.checkString(
widget.message.imageElem?.imageList?[0]?.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
@ -531,9 +536,11 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
imageType: 0,
isSnapshot: false);
}
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![1]!.localUrl) ==
null ||
if (widget.message.imageElem?.imageList == null ||
widget.message.imageElem!.imageList!.length < 2 &&
TencentUtils.checkString(
widget.message.imageElem?.imageList?[1]?.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![1]!.localUrl!)
.existsSync()) {
_messageService.downloadMessage(
@ -542,8 +549,10 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
imageType: 1,
isSnapshot: false);
}
if (TencentUtils.checkString(
widget.message.imageElem!.imageList![2]!.localUrl) ==
if (widget.message.imageElem?.imageList != null ||
widget.message.imageElem!.imageList!.length < 3 ||
TencentUtils.checkString(
widget.message.imageElem?.imageList?[2]?.localUrl) ==
null ||
!File(widget.message.imageElem!.imageList![2]!.localUrl!)
.existsSync()) {
@ -556,6 +565,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
}
}
@override
void initState() {
super.initState();
initImages();
}
bool isNeedShowLocalPath() {
final current = (DateTime.now().millisecondsSinceEpoch / 1000).ceil();
final timeStamp = widget.message.timestamp ?? current;
@ -563,7 +578,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
(isSent || current - timeStamp < 300);
}
Widget? _renderImage(dynamic heroTag, TUITheme? theme,
Widget? _renderImage(dynamic heroTag, TUITheme theme,
{V2TimImage? originalImg, V2TimImage? smallImg}) {
double? positionRadio;
if (smallImg?.width != null &&

File diff suppressed because one or more lines are too long

View File

@ -296,6 +296,9 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
if (oldWidget.textFieldBuilder != null && widget.textFieldBuilder == null) {
textFieldController = TIMUIKitInputTextFieldController();
}
if (oldWidget.groupMemberList != widget.groupMemberList) {
model.groupMemberList = widget.groupMemberList;
}
}
updateDraft() async {

View File

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class CenterLoading extends TIMUIKitStatelessWidget {
CenterLoading({Key? key, this.messageID}) : super(key: key);
final String? messageID;
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: serviceLocator<TUIChatGlobalModel>()),
],
builder: (context, w) {
final progress = Provider.of<TUIChatGlobalModel>(context)
.getMessageProgress(messageID);
return (progress == 0 || progress == 100)
? Container()
: Center(
child: CircularProgressIndicator(
value: progress / 100,
backgroundColor: Colors.white,
valueColor: AlwaysStoppedAnimation(theme.primaryColor)));
});
}
}

View File

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/center_loading.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/gestured_image.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/image_hero.dart';
@ -152,7 +151,8 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(
color: Colors.white)));
color: Colors.white))
);
case LoadState.completed:
final screenHeight =
MediaQuery.of(context).size.height;
@ -266,7 +266,6 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
},
),
),
CenterLoading(messageID: widget.messageID),
if (isLoading)
Container(
child: LoadingAnimationWidget.staggeredDotsWave(

View File

@ -1,91 +0,0 @@
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,9 +5,7 @@ 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';

View File

@ -683,6 +683,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image_clipboard:
dependency: "direct main"
description:
name: image_clipboard
sha256: "1939365dc3b65acd3b1199176a330180075f4e803a6512f5622d050b62e7eff4"
url: "https://pub.dev"
source: hosted
version: "1.0.0+2"
image_gallery_saver:
dependency: "direct main"
description:

View File

@ -1,7 +1,7 @@
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.2.0
homepage: https://www.tencentcloud.com/products/im?from=pub
version: 2.2.1
homepage: https://trtc.io/products/chat?utm_source=gfs&utm_medium=link&utm_campaign=%E6%B8%A0%E9%81%93&_channel_track_key=k6WgfCKn
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -76,6 +76,7 @@ dependencies:
diff_match_patch: ^0.4.1
markdown: ^7.1.0
logger: ^2.0.1
image_clipboard: ^1.0.0+2
dev_dependencies:
flutter_lints: ^1.0.0