Upgrade UIKit to 2.1.3+1

This commit is contained in:
anonymous 2023-07-19 18:43:28 +08:00
parent 085be1c0b4
commit 2fefab6196
45 changed files with 2010 additions and 1328 deletions

View File

@ -1,3 +1,28 @@
## 2.1.3+1
### New Features
* Introduced [a new custom internationalization language scheme](https://www.tencentcloud.com/document/product/1047/52154?from=pub) that supports adding language packs, adding or modifying entries, and makes customizing i18n more accessible. This feature helps your app achieve a more convenient globalization process and easier customer acquisition worldwide.
* Provided a seamless experience for previewing large images and playing videos within desktop environments (applications and web) by avoiding frequent page transitions. Enhanced the user experience for image previews and video playback. Please note that video playback is currently supported only on the web and not in desktop applications.
* Supported to integrate with the new online customer service plugin (tencent_cloud_chat_customer_service_plugin).
* Added two new life cycle hooks, `messageDidSend` and `messageShouldMount` to `ChatLifeCycle`.
### Improvements
* Optimized the usage, interface, and interaction of the sticker panel.
* Enhanced mobile video playback interaction and UI.
* Refined the error prompt when sending a 0 KB file fails.
* Enabled users to close modals on desktop by clicking the bottom gray overlay area.
* Improved the UI and interaction of image and video messages in the message list.
* Added the ability to open self-sent file messages without downloading.
* Optimized the download status animation of file messages on the web.
### Bug Fixes
* Fixed an issue preventing mobile image previews from being dragged after zooming.
* Resolved an issue that might cause the message selection status not to be removed after canceling a message forward action.
* Addressed an issue that might cause the microphone usage not to end after sending a voice message, which means the microphone was not released.
## 2.1.2
### New Features

View File

@ -1,4 +1,41 @@
<style>
.button-9 {
appearance: button;
backface-visibility: hidden;
background-color: #1d52d9;
border-radius: 6px;
border-width: 0;
box-shadow: rgba(50, 50, 93, .1) 0 0 0 1px inset,rgba(50, 50, 93, .1) 0 2px 5px 0,rgba(0, 0, 0, .07) 0 1px 1px 0;
box-sizing: border-box;
color: #fff;
cursor: pointer;
font-family: -apple-system,system-ui,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif;
font-size: 100%;
height: 44px;
line-height: 1.15;
margin: 12px 0 0;
outline: none;
overflow: hidden;
padding: 0 20px;
position: relative;
text-align: center;
text-transform: none;
transform: translateZ(0);
transition: all .2s,box-shadow .08s ease-in;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.button-9:disabled {
cursor: default;
}
.button-9:focus {
box-shadow: rgba(50, 50, 93, .1) 0 0 0 1px inset, rgba(50, 50, 93, .2) 0 6px 15px 0, rgba(0, 0, 0, .1) 0 2px 2px 0, rgba(50, 151, 211, .3) 0 0 0 4px;
}
</style>
<br>

View File

@ -8,6 +8,7 @@
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
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) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);

View File

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

View File

@ -9,6 +9,7 @@ import audioplayers_darwin
import desktop_drop
import device_info_plus
import fc_native_video_thumbnail
import file_selector_macos
import package_info_plus
import pasteboard
import path_provider_foundation
@ -22,6 +23,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#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 <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FcNativeVideoThumbnailPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FcNativeVideoThumbnailPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

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

View File

@ -4,6 +4,8 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_class.dart';
typedef MessageFunction = Future<V2TimMessage?> Function(V2TimMessage message);
typedef MessageFunctionNullCallback = Function(V2TimValueCallback<V2TimMessage> res);
typedef MessageFunctionOptional = Future<V2TimMessage?> Function(
V2TimMessage message);
@ -46,10 +48,17 @@ abstract class DefaultLifeCycle {
return list;
}
static Future<bool> defaultBooleanSolution(dynamic) async {
static Future<bool> defaultAsyncBooleanSolution(dynamic) async {
return true;
}
static bool defaultBooleanSolution(dynamic) {
return true;
}
static defaultNullCallbackSolution(dynamic) {
}
static Future<bool> defaultAddFriend(
String userID, String? remark, String? friendGroup, String? addWording,
[BuildContext? context]) async {

View File

@ -7,6 +7,6 @@ class BlockListLifeCycle {
FutureBool Function(List<String> userIDList) shouldDeleteFromBlockList;
BlockListLifeCycle({
this.shouldDeleteFromBlockList = DefaultLifeCycle.defaultBooleanSolution,
this.shouldDeleteFromBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}

View File

@ -1,4 +1,5 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class ChatLifeCycle {
/// Before a new message will be added to historical message list from long connection.
@ -12,6 +13,9 @@ class ChatLifeCycle {
/// Returns null can block the message from sending.
MessageFunction messageWillSend;
/// After a new message been sent.
MessageFunctionNullCallback messageDidSend;
/// After getting the latest message list from API,
/// and before historical message list will be rendered.
/// You may add or delete some messages here.
@ -27,14 +31,20 @@ 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;
ChatLifeCycle({
this.shouldClearHistoricalMessageList =
DefaultLifeCycle.defaultBooleanSolution,
this.shouldDeleteMessage = DefaultLifeCycle.defaultBooleanSolution,
DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteMessage = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.messageDidSend = DefaultLifeCycle.defaultNullCallbackSolution,
this.didGetHistoricalMessageList =
DefaultLifeCycle.defaultMessageListSolution,
this.messageWillSend = DefaultLifeCycle.defaultMessageSolution,
this.modifiedMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.newMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.messageShouldMount = DefaultLifeCycle.defaultBooleanSolution,
});
}

View File

@ -19,7 +19,7 @@ class ConversationLifeCycle {
this.conversationListWillMount =
DefaultLifeCycle.defaultConversationListSolution,
this.shouldClearHistoricalMessageForConversation =
DefaultLifeCycle.defaultBooleanSolution,
this.shouldDeleteConversation = DefaultLifeCycle.defaultBooleanSolution,
DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteConversation = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}

View File

@ -13,8 +13,8 @@ class NewContactLifeCycle {
NewContactLifeCycle({
this.shouldAcceptContactApplication =
DefaultLifeCycle.defaultBooleanSolution,
DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldRefuseContactApplication =
DefaultLifeCycle.defaultBooleanSolution,
DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}

View File

@ -24,10 +24,10 @@ class ProfileLifeCycle {
FutureBool Function(String userID) didRemarkUpdated;
ProfileLifeCycle({
this.didRemarkUpdated = DefaultLifeCycle.defaultBooleanSolution,
this.didRemarkUpdated = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.didGetFriendInfo = DefaultLifeCycle.defaultFriendInfoSolution,
this.shouldAddToBlockList = DefaultLifeCycle.defaultBooleanSolution,
this.shouldAddFriend = DefaultLifeCycle.defaultBooleanSolution,
this.shouldDeleteFriend = DefaultLifeCycle.defaultBooleanSolution,
this.shouldAddToBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldAddFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
@ -173,4 +174,14 @@ class TUIChatModelTools {
return null;
}
}
Future<bool> hasZeroSize(String filePath) async {
try {
final file = File(filePath);
final fileSize = await file.length();
return fileSize == 0;
} catch (e) {
return false;
}
}
}

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import
@ -177,16 +178,18 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
setInputField = onChangeInputField;
conversationType = convType;
conversationID = convID;
_groupType = null;
isGroupExist = true;
_groupInfo = null;
groupMemberList?.clear();
selfMemberInfo = null;
if (conversationType == ConvType.group) {
globalModel.refreshGroupApplicationList();
getGroupInfo(groupID ?? convID);
loadGroupInfo(groupID ?? convID);
loadGroupMemberList(groupID: groupID ?? convID);
} else {
_groupType = null;
isGroupExist = true;
_groupInfo = null;
groupMemberList?.clear();
selfMemberInfo = null;
notifyListeners();
}
if (conversationType == ConvType.c2c) {
@ -482,18 +485,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
loadGroupInfo(String groupID) async {
final groupInfo =
await _groupServices.getGroupsInfo(groupIDList: [groupID]);
if (groupInfo != null) {
final groupRes = groupInfo.first;
if (groupRes.resultCode == 0) {
_groupInfo = groupRes.groupInfo;
}
}
notifyListeners();
}
Future<void> loadGroupMemberList(
{required String groupID, int count = 100, String? seq}) async {
final String? nextSeq = await _loadGroupMemberListFunction(
@ -503,7 +494,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
groupID: groupID, count: count, seq: nextSeq);
} else {
selfMemberInfo = groupMemberList
?.firstWhere((e) => e?.userID == selfModel.loginInfo?.userID);
?.firstWhereOrNull((e) => e?.userID == selfModel.loginInfo?.userID);
notifyListeners();
}
}
@ -513,38 +504,49 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
if (seq == null || seq == "" || seq == "0") {
groupMemberList?.clear();
}
final res = await _groupServices.getGroupMemberList(
groupID: groupID,
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
count: count,
nextSeq: seq ?? groupMemberListSeq);
final groupMemberListRes = res.data;
if (res.code == 0 && groupMemberListRes != null) {
final groupMemberListTemp = groupMemberListRes.memberInfoList ?? [];
groupMemberList = [...?groupMemberList, ...groupMemberListTemp];
groupMemberListSeq = groupMemberListRes.nextSeq ?? "0";
} else if (res.code == 10010) {
isGroupExist = false;
} else if (res.code == 10007) {
isNotAMember = true;
try {
final res = await _groupServices.getGroupMemberList(
groupID: groupID,
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
count: count,
nextSeq: seq ?? groupMemberListSeq);
final groupMemberListRes = res.data;
if (res.code == 0 && groupMemberListRes != null) {
final groupMemberListTemp = groupMemberListRes.memberInfoList ?? [];
groupMemberList = [...?groupMemberList, ...groupMemberListTemp];
groupMemberListSeq = groupMemberListRes.nextSeq ?? "0";
} else if (res.code == 10010) {
isGroupExist = false;
} else if (res.code == 10007) {
isNotAMember = true;
}
return groupMemberListRes?.nextSeq;
} catch (e) {
return "";
}
return groupMemberListRes?.nextSeq;
}
getGroupInfo(String groupID) async {
final res = await _groupServices.getGroupsInfo(groupIDList: [groupID]);
if (res != null) {
final groupRes = res.first;
Future<(V2TimGroupInfo?, GroupReceiptAllowType?)> loadGroupInfo(
String groupID) async {
final groupInfoList =
await _groupServices.getGroupsInfo(groupIDList: [groupID]);
if (groupInfoList != null && groupInfoList.isNotEmpty) {
final groupRes = groupInfoList.first;
if (groupRes.resultCode == 0) {
_groupInfo = groupRes.groupInfo;
const groupTypeMap = {
"Meeting": GroupReceiptAllowType.meeting,
"Public": GroupReceiptAllowType.public,
"Work": GroupReceiptAllowType.work
};
_groupInfo = groupRes.groupInfo;
_groupType = groupTypeMap[groupRes.groupInfo?.groupType];
notifyListeners();
return (_groupInfo, _groupType);
}
}
return (null, null);
}
Future<void> updateMessageFromController(
@ -584,6 +586,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}) async {
String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : '';
if (convType == ConvType.group && _groupType == null) {
await loadGroupInfo(groupID);
}
final oldGroupType = _groupType != null
? GroupReceptAllowType.values[_groupType!.index]
: null;
@ -624,6 +629,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.updateMessage(
sendMsgRes, convID, id, convType, groupType, setInputField);
}
if(lifeCycle?.messageDidSend != null){
lifeCycle!.messageDidSend(sendMsgRes);
}
return sendMsgRes;
}
@ -875,6 +883,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
notifyListeners();
globalModel.updateMessage(sendMsgRes, convID,
messageInfoWithSender.id ?? "", convType, groupType, setInputField);
if(lifeCycle?.messageDidSend != null){
lifeCycle!.messageDidSend(sendMsgRes);
}
return sendMsgRes;
}
}
@ -1017,6 +1028,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
dynamic inputElement,
required String convID,
required ConvType convType}) async {
if (await tools.hasZeroSize(filePath ?? "")) {
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
_coreServices.callOnCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: "不支持 0KB 文件的传输",
infoCode: 6660417));
return null;
}
final fileMessageInfo = await _messageService.createFileMessage(
inputElement: inputElement,
fileName: fileName ?? filePath?.split('/').last ?? "",

View File

@ -48,7 +48,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";
@ -62,7 +62,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;
@ -102,6 +102,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
Map<String, String> get currentDownLoad => _waitingDownloadList.first;
int getWaitingListLength() {
return _waitingDownloadList.length;
}
void addWaitingList(String msgID) {
print("add to waiting list success");
bool contains = false;
@ -191,15 +195,17 @@ 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);
@ -235,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();
}
@ -326,10 +332,9 @@ 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,
@ -343,11 +348,11 @@ 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 {
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,
@ -389,7 +394,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(
@ -407,12 +412,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;
print("cacheImage ${msgItem.msgID}");
}));
final tempImg = image.image;
_preloadImageMap[msgItem.seq! +
msgItem.timestamp.toString() +
(msgItem.msgID ?? "")] = tempImg;
print("cacheImage ${msgItem.msgID}");
}));
} catch (e) {
print("cacheImage error ${msgItem.msgID}");
}
@ -513,13 +518,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) {
@ -537,14 +542,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!,
@ -559,9 +564,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();
}
@ -657,14 +662,17 @@ 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;
}
}
@ -674,7 +682,10 @@ 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];
@ -729,33 +740,78 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
print("message progress: $progress");
}
onMessageDownloadProgressCallback(
Future<void> onMessageDownloadProgressCallback(
V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.isFinish) {
setMessageProgress(messageProgress.msgID, 100);
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
downloadFile();
if (messageProgress.type == 0) {
final messages = await _messageService
.findMessages(messageIDList: [messageProgress.msgID]);
final V2TimMessage? message = messages?.first;
if (message != null) {
updateAsyncMessage(
message,
TencentUtils.checkString(message.userID) ??
TencentUtils.checkString(message.groupID) ??
"");
}
}
print(messageProgress.toJson());
final currentProgress = getMessageProgress(messageProgress.msgID);
if (messageProgress.isFinish && currentProgress < 100) {
V2TimMessage? message = await _findAndRetrieveMessage(
messageProgress.msgID);
_handleFinishedDownload(messageProgress, message);
return;
}
if (messageProgress.totalSize != -1) {
int progress =
(messageProgress.currentSize / messageProgress.totalSize * 100)
.ceil();
if (progress > 1) {
setMessageProgress(messageProgress.msgID, progress);
_updateProgressIfNeeded(messageProgress, currentProgress);
}
Future<V2TimMessage?> _findAndRetrieveMessage(String messageId) async {
final messages = await _messageService.findMessages(
messageIDList: [messageId]);
return messages?.first;
}
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;
if (!isImageType && !isVideoType) {
_updateMessageLocationAndDownloadFile(messageProgress);
} else if ((isImageType && messageProgress.type == 0) || isVideoType) {
Future.delayed(const Duration(seconds: 1), () =>
_updateMessageAndDownloadFile(message, messageProgress));
} else {
return;
}
} else {
_updateMessageLocationAndDownloadFile(messageProgress);
}
}
void _updateMessageAndDownloadFile(V2TimMessage message,
V2TimMessageDownloadProgress messageProgress) {
updateAsyncMessage(
message,
TencentUtils.checkString(message.userID) ??
TencentUtils.checkString(message.groupID) ??
"");
_updateMessageLocationAndDownloadFile(messageProgress);
}
void _updateMessageLocationAndDownloadFile(
V2TimMessageDownloadProgress messageProgress) {
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
setMessageProgress(messageProgress.msgID, 100);
downloadFile();
}
void _updateProgressIfNeeded(V2TimMessageDownloadProgress messageProgress,
int currentProgress) {
try{
if (messageProgress.totalSize != -1 && !messageProgress.isFinish) {
int progress = min(99,
(messageProgress.currentSize / messageProgress.totalSize * 100)
.floor());
if (progress > 1 && progress > currentProgress) {
setMessageProgress(messageProgress.msgID, progress);
}
}
}catch(e){
print("calculate error: ${messageProgress.toJson()}");
}
}
@ -842,11 +898,13 @@ 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 +
" @${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) ??
@ -857,7 +915,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (messageInfo != null) {
final messageInfoWithSender = messageInfo.sender == null
? tools.setUserInfoForMessage(messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
? tools.setUserInfoForMessage(
messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
: messageInfo;
final hasNickName = messageBeenReplied.nickName != null &&
@ -898,8 +957,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] ?? [];
@ -918,8 +977,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] ?? [];
@ -956,7 +1015,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,
@ -964,8 +1023,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))),
@ -985,6 +1044,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (isEditStatusMessage == false) {
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
}
if(_lifeCycle?.messageDidSend != null){
_lifeCycle!.messageDidSend(sendMsgRes);
}
return sendMsgRes;
}
@ -998,8 +1060,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners();
}
updateMessage(
V2TimValueCallback<V2TimMessage> sendMsgRes,
updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes,
String convID,
String id,
ConvType convType,
@ -1014,10 +1075,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;
@ -1028,13 +1089,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)))) {
@ -1045,11 +1106,12 @@ 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) {
@ -1068,18 +1130,19 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}
List<V2TimMessage>? getMessageList(String conversationID) {
final list = messageListMap[conversationID]?.reversed.toList() ?? [];
final list = (messageListMap[conversationID]?.reversed.toList() ?? [])
.where((element) => _lifeCycle?.messageShouldMount(element) ?? true);
final List<V2TimMessage> listWithTimestamp = [];
final interval = chatConfig.timeDividerConfig?.timeInterval ?? 300;
for (var item in list) {
{
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: '',
@ -1098,7 +1161,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
HistoryMessagePosition getMessageListPosition(String? conversationID) {
final HistoryMessagePosition? position =
_historyMessagePositionMap[conversationID];
_historyMessagePositionMap[conversationID];
if (position == null) {
_historyMessagePositionMap[conversationID ?? currentSelectedConv] =
HistoryMessagePosition.bottom;
@ -1108,8 +1171,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

@ -151,8 +151,8 @@ class TUIConversationViewModel extends ChangeNotifier {
final isRefresh = _nextSeq == "0";
final conversationResult = await _conversationService.getConversationList(
nextSeq: _nextSeq, count: count);
_nextSeq = conversationResult!.nextSeq ?? "";
final conversationList = conversationResult.conversationList;
_nextSeq = conversationResult?.nextSeq ?? "";
final conversationList = conversationResult?.conversationList;
if (conversationList != null) {
if (conversationList.isEmpty || conversationList.length < count) {
_haveMoreData = false;

View File

@ -1,7 +1,9 @@
// ignore_for_file: avoid_print
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/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';
@ -14,7 +16,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);
@ -61,7 +63,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();
@ -73,21 +75,20 @@ 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,
VoidCallback? onWebLoginSuccess}) async {
if (platform != null) {
TUIKitScreenUtils.deviceType = platform;
}
@ -166,13 +167,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();
@ -182,13 +183,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,15 +204,16 @@ class CoreServicesImpl implements CoreServices {
});
} else {
print(
"TUIKit Callback: ${callbackValue.type} - ${callbackValue.stackTrace}");
"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();
@ -219,11 +221,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();
@ -233,18 +235,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 {
@ -285,28 +287,50 @@ class CoreServicesImpl implements CoreServices {
isLoginSuccess = true;
addInitListener();
initDataModel();
if (TencentUtils.checkString(_userID) == null) {
V2TimValueCallback<String> getLoginUserRes =
await TencentImSDKPlugin.v2TIMManager.getLoginUser();
if (getLoginUserRes.code == 0) {
_userID = getLoginUserRes.data ?? "";
}
}
getUsersInfoWithRetry();
}
void getUsersInfoWithRetry() async {
V2TimValueCallback<List<V2TimUserFullInfo>>? res;
bool success = false;
while (!success) {
res = await getUsersInfo(userIDList: [_userID]);
if (res.code == 0 &&
res.data != null &&
res.data!.isNotEmpty &&
res.data!.firstWhereOrNull((element) => element.userID == _userID) !=
null) {
success = true;
} else {
await Future.delayed(const Duration(seconds: 2));
}
}
_loginInfo =
res?.data!.firstWhereOrNull((element) => element.userID == _userID);
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
getUsersInfo(userIDList: [_userID]).then((res) => {
if (res.code == 0)
{
_loginInfo = res.data![0],
selfInfoViewModel.setLoginInfo(_loginInfo!)
}
});
serviceLocator<TUISelfInfoViewModel>();
if (_loginInfo != null) {
selfInfoViewModel.setLoginInfo(_loginInfo);
}
}
// Deprecated
void didLoginOut() {
removeListener();
clearData();
getUsersInfo(userIDList: [_userID]).then((res) => {
if (res.code == 0)
{
_loginInfo = res.data![0],
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo!)
}
});
_loginInfo = null;
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo);
}
@override
@ -350,10 +374,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

@ -105,7 +105,6 @@ class GroupServicesImpl extends GroupServices {
count: count,
offset: offset);
if (res.code != 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,

File diff suppressed because one or more lines are too long

View File

@ -689,5 +689,13 @@
"k_066fxsz": "查看文件夹",
"k_0k432y2": "无法发送,包含文件夹",
"k_002wb4y": "会话",
"k_0od4qyh": "视频文件异常"
"k_0od4qyh": "视频文件异常",
"k_1bfkxg9": "不支持 0KB 文件的传输",
"k_0vvsw7g": "文件处理异常",
"k_06e224q": "[消息被管理员撤回]",
"k_1u1mjcl": "[消息被撤回]",
"k_1qcqxea": "选择多个会话",
"k_1qgmc20": "选择一个会话",
"k_1d8nx6f": "在新窗口中打开",
"k_1hz05ax": "正在下载原始资源,请稍候..."
}

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
// ignore_for_file: avoid_print
// ignore_for_file: avoid_print, empty_catches
import 'package:flutter/cupertino.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
@ -139,11 +139,13 @@ class TIMUIKitChatController {
assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) {
scrollController!.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
try{
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}catch(e){}
}
return globalChatModel.sendMessageFromController(
priority: priority,
@ -160,11 +162,13 @@ class TIMUIKitChatController {
} else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat`
if (isNavigateToMessageListBottom && scrollController != null) {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
try{
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}catch(e){}
}
return model!.sendMessageFromController(
priority: priority,
@ -230,11 +234,13 @@ class TIMUIKitChatController {
assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) {
scrollController!.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
try{
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}catch(e){}
}
return globalChatModel.sendReplyMessageFromController(
text: messageText,

View File

@ -1,7 +1,7 @@
// ignore_for_file: prefer_typing_uninitialized_variables
import 'package:intl/intl.dart';
import 'package:tencent_im_base/i18n/i18n_utils.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TimeAgo {
List<String> dayMap() {

View File

@ -1413,7 +1413,7 @@ class _TIMUIKItHistoryMessageListItemState
widget.message.elemType == 6 &&
isDownloadWaiting)
Container(
margin: const EdgeInsets.only(top: 2),
margin: const EdgeInsets.only(top: 46, right: 10),
child: LoadingAnimationWidget.threeArchedCircle(
color: theme.weakTextColor ?? Colors.grey,
size: 20,
@ -1566,7 +1566,7 @@ class _TIMUIKItHistoryMessageListItemState
widget.message.elemType == 6 &&
isDownloadWaiting)
Container(
margin: const EdgeInsets.only(top: 24, left: 6),
margin: const EdgeInsets.only(top: 46, left: 10),
child: LoadingAnimationWidget.threeArchedCircle(
color: theme.weakTextColor ?? Colors.grey,
size: 20,

View File

@ -104,24 +104,14 @@ class TIMUIKitMessageTooltipState
}
if (PlatformUtils().isDesktop) {
if (widget.message.fileElem != null) {
if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
String savePath =
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
globalModal.getFileMessageLocation(widget.message.msgID);
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
isShowOpenFile = true;
return;
}
isShowOpenFile = false;
return;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
String savePath = TencentUtils.checkString(
globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"";
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true;
return;
}
@ -399,14 +389,14 @@ class TIMUIKitMessageTooltipState
}
_onOpenDesktop(String path) {
if (PlatformUtils().isDesktop) {
OpenFile.open(path);
} else {
launchUrl(
Uri.parse(path),
mode: LaunchMode.externalApplication,
);
}
try {
if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
launchUrl(Uri.file(path));
} else {
OpenFile.open(path);
}
// ignore: empty_catches
} catch (e) {}
}
_onTap(String operation, TUIChatSeparateViewModel model) async {
@ -415,32 +405,40 @@ class TIMUIKitMessageTooltipState
switch (operation) {
case "open":
if (widget.message.fileElem != null) {
_onOpenDesktop(widget.message.fileElem!.localUrl ??
_onOpenDesktop(TencentUtils.checkString(
globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"");
} else if (widget.message.imageElem != null) {
_onOpenDesktop(widget.message.imageElem!.imageList?[0]?.localUrl ??
widget.message.imageElem?.path ??
_onOpenDesktop(TencentUtils.checkString(
widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
"");
} else if (widget.message.videoElem != null) {
_onOpenDesktop(widget.message.videoElem!.localVideoUrl ??
widget.message.videoElem?.videoPath ??
_onOpenDesktop(TencentUtils.checkString(
widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
"");
}
break;
case "finder":
String savePath = "";
if (widget.message.fileElem != null) {
savePath = (widget.message.fileElem!.localUrl ??
savePath = (TencentUtils.checkString(
globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"");
} else if (widget.message.imageElem != null) {
savePath = (widget.message.imageElem!.imageList?[0]?.localUrl ??
widget.message.imageElem?.path ??
savePath = (TencentUtils.checkString(
widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
"");
} else if (widget.message.videoElem != null) {
savePath = (widget.message.videoElem!.localVideoUrl ??
widget.message.videoElem?.videoPath ??
savePath = (TencentUtils.checkString(
widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
"");
}
final String fileDir = path.dirname(savePath);

View File

@ -10,7 +10,6 @@ import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar_title.dart';
import 'package:tuple/tuple.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_im_base/i18n/i18n_utils.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TIMUIKitAppBarTitle extends StatelessWidget {
final Widget? title;

View File

@ -5,6 +5,7 @@ import 'dart:math';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart';
import 'package:tencent_open_file/tencent_open_file.dart';
@ -49,10 +50,12 @@ class TIMUIKitFileElem extends StatefulWidget {
class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
String filePath = "";
bool isDownloading = false;
bool isWebDownloading = false;
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
int downloadProgress = 0;
late V2TimAdvancedMsgListener advancedMsgListener;
final GlobalKey containerKey = GlobalKey();
double? containerHeight;
@override
void dispose() {
@ -75,18 +78,18 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
(V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.msgID == widget.message.msgID) {
if (messageProgress.isFinish) {
if(mounted){
if (mounted) {
setState(() {
downloadProgress = 100;
});
}
} else {
if(mounted){
final currentProgress =
(messageProgress.currentSize / messageProgress.totalSize * 100)
.floor();
if (mounted && currentProgress > downloadProgress) {
setState(() {
downloadProgress = (messageProgress.currentSize /
messageProgress.totalSize *
100)
.ceil();
downloadProgress = currentProgress;
});
}
}
@ -110,30 +113,22 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
if (PlatformUtils().isWeb) {
return true;
}
if (model.getMessageProgress(widget.messageID) == 100 ||
downloadProgress == 100) {
String savePath =
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
model.getFileMessageLocation(widget.messageID);
File f = File(savePath);
if (f.existsSync() && widget.messageID != null) {
filePath = savePath;
setState(() {
downloadProgress = 100;
});
return true;
}
return false;
}
String savePath = widget.message.fileElem!.localUrl ?? '';
String savePath = TencentUtils.checkString(
model.getFileMessageLocation(widget.messageID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
'';
File f = File(savePath);
if (f.existsSync() && widget.messageID != null) {
filePath = savePath;
setState(() {
downloadProgress = 100;
});
model.setMessageProgress(widget.messageID!, 100);
if (downloadProgress != 100) {
setState(() {
downloadProgress = 100;
});
}
if (model.getMessageProgress(widget.messageID) != 100) {
model.setMessageProgress(widget.messageID!, 100);
}
return true;
}
return false;
@ -151,10 +146,12 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
}
addUrlToWaitingPath() async {
addUrlToWaitingPath(TUITheme theme) async {
if (widget.messageID != null) {
model.addWaitingList(widget.messageID!);
print("add path success");
}
if (model.getWaitingListLength() == 1) {
await downloadFile(theme);
}
}
@ -195,7 +192,25 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
await model.downloadFile();
}
Future<bool> hasZeroSize(String filePath) async {
try {
final file = File(filePath);
final fileSize = await file.length();
return fileSize == 0;
} catch (e) {
return false;
}
}
tryOpenFile(context, theme) async {
if (!PlatformUtils().isWeb &&
(await hasZeroSize(filePath) || widget.message.status == 3)) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: "不支持 0KB 文件的传输",
infoCode: 6660417));
return;
}
if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission(
@ -229,6 +244,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
}
void downloadWebFile(String fileUrl) async {
if (mounted) {
setState(() {
isWebDownloading = true;
});
}
String fileName = Uri.parse(fileUrl).pathSegments.last;
try {
http.Response response = await http.get(
@ -255,6 +275,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
..style.display = "none"
..click();
}
if (mounted) {
setState(() {
isWebDownloading = false;
});
}
}
@override
@ -280,111 +305,144 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
final String fileName = widget.fileElem!.fileName!;
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
}
return TIMUIKitMessageReactionWrapper(
chatModel: widget.chatModel,
isShowJump: widget.isShowJump,
clearJump: widget.clearJump,
isFromSelf: widget.message.isSelf ?? true,
isShowMessageReaction: widget.isShowMessageReaction ?? true,
message: widget.message,
child: GestureDetector(
onTap: () async {
if (PlatformUtils().isWeb) {
downloadWebFile(widget.fileElem?.path ?? "");
return;
}
if (await hasFile()) {
if (received == 100) {
tryOpenFile(context, theme);
} else {
//
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("正在下载中"),
infoCode: 6660411,
),
);
}
return;
}
final RenderBox? containerRenderBox =
containerKey.currentContext?.findRenderObject() as RenderBox?;
if (containerRenderBox != null) {
containerHeight = containerRenderBox.size.height;
}
if (checkIsWaiting()) {
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
infoCode: 6660413),
);
return;
} else {
await addUrlToWaitingPath();
}
await downloadFile(theme);
},
child: Container(
width: 237,
decoration: BoxDecoration(
border: Border.all(
color:
theme.weakDividerColor ?? CommonColor.weakDividerColor,
),
borderRadius: borderRadius),
child: Stack(children: [
ClipRRect(
//
borderRadius: borderRadius,
child: LinearProgressIndicator(
minHeight: 66,
value: (received == 100 ? 0 : received) / 100,
backgroundColor: received == 100
? theme.weakBackgroundColor
: Colors.white,
valueColor: AlwaysStoppedAnimation(
theme.lightPrimaryMaterialColor.shade50)),
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: Row(
mainAxisAlignment: widget.isSelf
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
constraints:
const BoxConstraints(maxWidth: 160),
child: LayoutBuilder(
builder: (buildContext, boxConstraints) {
return CustomText(
fileName,
width: boxConstraints.maxWidth,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 16,
),
);
},
),
),
if (fileSize != null)
Text(
showFileSize(fileSize),
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
style: TextStyle(
fontSize: 14, color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
return Row(
key: containerKey,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isSelf && isWebDownloading)
Container(
margin: const EdgeInsets.only(top: 2),
child: LoadingAnimationWidget.threeArchedCircle(
color: theme.weakTextColor ?? Colors.grey,
size: 20,
),
),
TIMUIKitMessageReactionWrapper(
chatModel: widget.chatModel,
isShowJump: widget.isShowJump,
clearJump: widget.clearJump,
isFromSelf: widget.message.isSelf ?? true,
isShowMessageReaction: widget.isShowMessageReaction ?? true,
message: widget.message,
child: GestureDetector(
onTap: () async {
try {
if (PlatformUtils().isWeb) {
if (!isWebDownloading) {
downloadWebFile(widget.fileElem?.path ?? "");
}
return;
}
if (await hasFile()) {
if (received == 100) {
tryOpenFile(context, theme);
} else {
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("正在下载中"),
infoCode: 6660411,
),
])),
]),
)));
);
}
return;
}
if (checkIsWaiting()) {
onTIMCallback(
TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
infoCode: 6660413),
);
return;
} else {
await addUrlToWaitingPath(theme);
}
} catch (e) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: "文件处理异常",
infoCode: 6660416));
}
},
child: Container(
width: 237,
decoration: BoxDecoration(
border: Border.all(
color: theme.weakDividerColor ??
CommonColor.weakDividerColor,
),
borderRadius: borderRadius),
child: Stack(children: [
ClipRRect(
borderRadius: borderRadius,
child: LinearProgressIndicator(
minHeight: ((containerHeight) ?? 72) - 6,
value: (received == 100 ? 0 : received) / 100,
backgroundColor: received == 100
? theme.weakBackgroundColor
: Colors.white,
valueColor: AlwaysStoppedAnimation(
theme.lightPrimaryMaterialColor.shade50)),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 12),
child: Row(
mainAxisAlignment: widget.isSelf
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
constraints:
const BoxConstraints(maxWidth: 160),
child: LayoutBuilder(
builder: (buildContext, boxConstraints) {
return CustomText(
fileName,
width: boxConstraints.maxWidth,
style: TextStyle(
color: theme.darkTextColor,
fontSize: 16,
),
);
},
),
),
if (fileSize != null)
Text(
showFileSize(fileSize),
style: TextStyle(
fontSize: 14,
color: theme.weakTextColor),
)
],
)),
TIMUIKitFileIcon(
fileFormat: fileFormat,
),
])),
]),
))),
if (!widget.isSelf && isWebDownloading)
Container(
margin: const EdgeInsets.only(top: 2),
child: LoadingAnimationWidget.threeArchedCircle(
color: theme.weakTextColor ?? Colors.grey,
size: 20,
),
),
],
);
}
}

View File

@ -8,6 +8,7 @@ import 'package:http/http.dart' as http;
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_open_file/tencent_open_file.dart';
import 'package:universal_html/html.dart' as html;
import 'dart:io';
@ -68,7 +69,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
super.didUpdateWidget(oldWidget);
}
String getBigPicUrl() {
String getOriginImgURL() {
//
V2TimImage? img = MessageUtils.getImageFromImgList(
widget.message.imageElem!.imageList,
@ -257,7 +258,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
return await _saveImageToLocal(context, path,
isAsset: true, theme: theme);
} else {
String imgUrl = getBigPicUrl();
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!);
@ -300,6 +301,14 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
return img;
}
void launchDesktopFile(String path) {
if (PlatformUtils().isWindows) {
OpenFile.open(path);
} else {
launchUrl(Uri.file(path));
}
}
Widget errorPage(theme) => Container(
height: MediaQuery.of(context).size.height,
color: theme.black,
@ -310,16 +319,85 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
child: errorDisplay(context, theme),
));
bool checkIfDownloadSuccess() {
final localUrl = TencentUtils.checkString(
model.getFileMessageLocation(widget.message.msgID)) ??
widget.message.imageElem!.imageList![0]!.localUrl;
return TencentUtils.checkString(localUrl) != null &&
File(localUrl!).existsSync();
}
_onClickOpenImageInNewWindow() {
final localUrl = TencentUtils.checkString(
model.getFileMessageLocation(widget.message.msgID)) ??
widget.message.imageElem!.imageList![0]!.localUrl;
Future.delayed(const Duration(milliseconds: 0), () async {
final isDownloaded = checkIfDownloadSuccess();
if (isDownloaded) {
launchDesktopFile(localUrl ?? "");
} else {
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载原始资源,请稍候..."),
type: TIMCallbackType.INFO));
}
});
}
_handleOnTapPreviewImageOnDesktop({
double? positionRadio,
String? originImgUrl,
}) {
final localUrl = TencentUtils.checkString(
model.getFileMessageLocation(widget.message.msgID)) ??
widget.message.imageElem!.imageList![0]!.localUrl;
if (checkIfDownloadSuccess()) {
TUIKitWidePopup.showMedia(
aspectRatio: positionRadio,
context: context,
mediaLocalPath: localUrl ?? "",
onClickOrigin: () => _onClickOpenImageInNewWindow());
} else {
if (TencentUtils.checkString(originImgUrl) != null) {
TUIKitWidePopup.showMedia(
aspectRatio: positionRadio,
context: context,
mediaURL: originImgUrl,
onClickOrigin: () => _onClickOpenImageInNewWindow());
} else {
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO));
}
}
}
Widget _renderNetworkImage(
dynamic heroTag, double? positionRadio, TUITheme? theme,
{String? path, V2TimImage? originalImg, V2TimImage? smallImg}) {
try {
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
String bigImgUrl = originalImg?.url ?? getBigPicUrl();
if (bigImgUrl.isEmpty && smallImg?.url != null) {
bigImgUrl = smallImg!.url!;
String originImgUrl = originalImg?.url ?? getOriginImgURL();
if (originImgUrl.isEmpty && smallImg?.url != null) {
originImgUrl = smallImg!.url!;
}
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),
));
return Stack(
alignment: widget.message.isSelf ?? true
? AlignmentDirectional.topEnd
@ -327,40 +405,30 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
children: [
getImage(
GestureDetector(
onTap: () {
onTap: () async {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.message.imageElem?.path ?? ""),
mode: LaunchMode.externalApplication,
);
TUIKitWidePopup.showMedia(
aspectRatio: positionRadio,
context: context,
mediaURL: widget.message.imageElem?.path ?? "",
onClickOrigin: () => launchUrl(
Uri.parse(widget.message.imageElem?.path ?? ""),
mode: LaunchMode.externalApplication,
));
return;
}
if (isDesktopScreen) {
if (TencentUtils.checkString(widget
.message.imageElem!.imageList![0]!.localUrl) !=
null &&
File(widget.message.imageElem!.imageList![0]!.localUrl!)
.existsSync()) {
if (PlatformUtils().isWindows) {
OpenFile.open(
widget.message.imageElem!.imageList![0]!.localUrl);
} else {
launchUrl(Uri.file(widget
.message.imageElem!.imageList![0]!.localUrl!));
}
}else{
onTIMCallback(TIMCallback(
infoCode: 6660414,
infoRecommendText: TIM_t("正在下载中"),
type: TIMCallbackType.INFO));
}
if (PlatformUtils().isDesktop) {
_handleOnTapPreviewImageOnDesktop(
positionRadio: positionRadio,
originImgUrl: originImgUrl,
);
} else {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false, // set to false
pageBuilder: (_, __, ___) => ImageScreen(
imageProvider: CachedNetworkImageProvider(
path ?? bigImgUrl,
path ?? originImgUrl,
cacheKey: widget.message.msgID,
),
heroTag: heroTag,
@ -371,32 +439,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
);
}
},
child: Container(
constraints:
const BoxConstraints(minWidth: 20, minHeight: 20),
child: Hero(
tag: heroTag,
child: PlatformUtils().isWeb
? Image.network(
path ?? smallImg?.url ?? originalImg!.url!,
fit: BoxFit.contain)
:
// Image.network(smallImg?.url ?? ""),
CachedNetworkImage(
// width: double.infinity,
alignment: Alignment.topCenter,
imageUrl:
path ?? smallImg?.url ?? originalImg!.url!,
// use small image in message list as priority
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),
)),
),
child: positionRadio != null
? AspectRatio(
aspectRatio: positionRadio,
child: imageWidget,
)
: imageWidget,
),
imageElem: e)
],
@ -436,6 +484,25 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
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: [
@ -450,11 +517,11 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
GestureDetector(
onTap: () {
if (PlatformUtils().isDesktop) {
if (PlatformUtils().isWindows) {
OpenFile.open(showImage);
} else {
launchUrl(Uri.file(showImage));
}
TUIKitWidePopup.showMedia(
aspectRatio: positionRadio,
mediaLocalPath: showImage,
context: context,
onClickOrigin: () => launchDesktopFile(showImage));
} else {
Navigator.of(context).push(
PageRouteBuilder(
@ -470,22 +537,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
);
}
},
child: Container(
constraints:
const BoxConstraints(minWidth: 20, minHeight: 20),
child: Hero(
tag: heroTag,
child: preloadImage != null
? RawImage(
image: preloadImage,
fit: BoxFit.contain,
)
: Image.file(
File(smallImage),
fit: BoxFit.contain,
),
),
)),
child: positionRadio != null
? AspectRatio(
aspectRatio: positionRadio,
child: imageWidget,
)
: imageWidget),
imageElem: null)
],
);
@ -623,6 +680,8 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
if (widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING) {
isSent = true;
}
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final heroTag =
"${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}";
@ -639,7 +698,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
builder: (BuildContext context, BoxConstraints constraints) {
return ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth * 0.5,
maxWidth: constraints.maxWidth * (isDesktopScreen ? 0.4 : 0.5),
minWidth: 64,
maxHeight: 256,
),

View File

@ -13,6 +13,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_wrapper.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/video_screen.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_open_file/tencent_open_file.dart';
import 'package:url_launcher/url_launcher.dart';
@ -166,6 +167,15 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
downloadMessageDetailAndSave();
}
void launchDesktopFile(String path) {
if (PlatformUtils().isWindows) {
OpenFile.open(path);
} else {
launchUrl(Uri.file(path));
}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
@ -175,10 +185,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
return GestureDetector(
onTap: () {
if (PlatformUtils().isWeb) {
launchUrl(
Uri.parse(widget.message.videoElem?.videoPath ?? ""),
mode: LaunchMode.externalApplication,
);
final url = widget.message.videoElem?.videoUrl ?? widget.message.videoElem?.videoPath ?? "";
TUIKitWidePopup.showMedia(
context: context,
mediaURL: url,
onClickOrigin: () => launchUrl(
Uri.parse(url),
mode: LaunchMode.externalApplication,
));
return;
}
if (PlatformUtils().isDesktop) {
@ -188,19 +202,20 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
TencentUtils.checkString(videoElem.localVideoUrl);
final videoPath = TencentUtils.checkString(videoElem.videoPath);
final videoUrl = videoElem.videoUrl;
if (localVideoUrl != null) {
if (PlatformUtils().isWindows) {
OpenFile.open(localVideoUrl);
} else {
launchUrl(Uri.file(localVideoUrl));
}
launchDesktopFile(localVideoUrl);
// todo
// TUIKitWidePopup.showMedia(
// context: context,
// mediaPath: localVideoUrl,
// onClickOrigin: () => launchDesktopFile(localVideoUrl));
} else if (videoPath != null) {
if (PlatformUtils().isWindows) {
OpenFile.open(videoPath);
} else {
launchUrl(Uri.file(videoPath));
}
launchDesktopFile(videoPath);
// todo
// TUIKitWidePopup.showMedia(
// context: context,
// mediaPath: videoPath,
// onClickOrigin: () => launchDesktopFile(videoPath));
} else if (TencentUtils.isTextNotEmpty(videoUrl)) {
onTIMCallback(TIMCallback(
infoCode: 6660414,

View File

@ -455,9 +455,16 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
TUITheme theme,
) async {
try {
if (!await Permissions.checkPermission(
context,
Permission.camera.value,
theme,
)) {
return;
}
if (!await Permissions.checkPermission(
context,
Permission.camera.value,
Permission.photos.value,
theme,
)) {
return;

View File

@ -42,6 +42,8 @@ class TIMUIKitInputTextField extends StatefulWidget {
/// conversation id
final String conversationID;
final TIMUIKitChatConfig? chatConfig;
/// conversation type
final ConvType conversationType;
@ -116,7 +118,8 @@ class TIMUIKitInputTextField extends StatefulWidget {
required this.currentConversation,
this.groupType,
this.atMemberPanelScroll,
this.groupID})
this.groupID,
this.chatConfig})
: super(key: key);
@override
@ -147,15 +150,51 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
currentCursor = value;
}
void deleteStickerFromText() {
String originalText = textEditingController.text;
String text;
final cursorPosition =
currentCursor ?? originalText.length;
if (originalText == zeroWidthSpace) {
_handleSoftKeyBoardDelete();
} else if (originalText.isNotEmpty) {
if (cursorPosition == originalText.length) {
text = originalText.characters.skipLast(1).toString();
currentCursor = null;
} else if (cursorPosition > 0 && cursorPosition < originalText.length) {
final firstString = originalText.substring(0, cursorPosition - 2);
final secondString = originalText.substring(cursorPosition);
text = '$firstString$secondString';
if(currentCursor != null){
currentCursor = currentCursor! - 2;
}
} else {
text = originalText.characters.skipLast(1).toString();
currentCursor = null;
}
textEditingController.text = text;
if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) {
textEditingController.selection = TextSelection.fromPosition(TextPosition(
offset: currentCursor ?? textEditingController.text.length));
focusNode.requestFocus();
}
}
}
void addStickerToText(String sticker) {
final oldText = textEditingController.text;
if (currentCursor != null && currentCursor! > -1) {
final firstString = oldText.substring(0, currentCursor);
final secondString = oldText.substring(currentCursor!);
final currentText = textEditingController.text;
if (currentCursor != null &&
currentCursor! > -1 &&
currentCursor! < currentText.length + 1) {
final firstString = currentText.substring(0, currentCursor);
final secondString = currentText.substring(currentCursor!);
currentCursor = currentCursor! + sticker.length;
textEditingController.text = "$firstString$sticker$secondString";
} else {
textEditingController.text = "$oldText$sticker";
currentCursor = null;
textEditingController.text = "$currentText$sticker";
}
if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) {
@ -169,7 +208,8 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return text.replaceAll(RegExp(r'\ufeff'), "");
}
Future handleSetDraftText([String? id, ConvType? convType]) async {
Future handleSetDraftText(
{String? id, ConvType? convType, String? groupID}) async {
String text = textEditingController.text;
String convID = id ?? widget.conversationID;
final isTopic = convID.contains("@TOPIC#");
@ -180,25 +220,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
: "group_$convID");
String draftText = _filterU200b(text);
return await conversationModel.setConversationDraft(
groupID: widget.groupID,
groupID: groupID ?? widget.groupID,
isTopic: isTopic,
isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb,
conversationID: conversationID,
draftText: draftText);
}
backSpaceText() {
String originalText = textEditingController.text;
dynamic text;
if (originalText == zeroWidthSpace) {
_handleSoftKeyBoardDelete();
} else {
text = originalText.characters.skipLast(1);
textEditingController.text = text;
}
}
// onSubmitted一样
onEmojiSubmitted() {
lastText = "";
@ -592,6 +620,8 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) {
final activeIndex = widget.model.activeAtIndex;
final showMemberList = widget.model.showAtMemberList;
final isEneter = (event.physicalKey == PhysicalKeyboardKey.enter) ||
(event.physicalKey == PhysicalKeyboardKey.numpadEnter);
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
if (textEditingController.text.isEmpty && lastText.isEmpty) {
@ -602,7 +632,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
event.isAltPressed ||
event.isControlPressed ||
event.isMetaPressed) &&
event.physicalKey == PhysicalKeyboardKey.enter) {
isEneter) {
final offset = textEditingController.selection.baseOffset;
textEditingController.text =
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
@ -611,7 +641,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
lastText = textEditingController.text;
return KeyEventResult.handled;
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
} else if (isEneter) {
if (!_isComposingText) {
if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) {
onSubmitted();
@ -707,7 +737,10 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
super.didUpdateWidget(oldWidget);
if (widget.conversationID != oldWidget.conversationID) {
mentionedMembersMap.clear();
handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType);
handleSetDraftText(
id: oldWidget.conversationID,
convType: oldWidget.conversationType,
groupID: oldWidget.groupID);
if (oldWidget.initText != widget.initText) {
textEditingController.text = widget.initText ?? "";
} else {
@ -832,7 +865,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
defaultWidget: TIMUIKitTextFieldLayoutNarrow(
onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
backSpaceText: backSpaceText,
backSpaceText: deleteStickerFromText,
addStickerToText: addStickerToText,
customStickerPanel: widget.customStickerPanel,
forbiddenText: forbiddenText,
@ -864,11 +897,12 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
showMorePanel: widget.showMorePanel,
customEmojiStickerList: widget.customEmojiStickerList),
desktopWidget: TIMUIKitTextFieldLayoutWide(
chatConfig: widget.chatConfig ?? widget.model.chatConfig,
theme: theme,
currentConversation: widget.currentConversation,
onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
backSpaceText: backSpaceText,
backSpaceText: deleteStickerFromText,
addStickerToText: addStickerToText,
customStickerPanel: widget.customStickerPanel,
forbiddenText: forbiddenText,

View File

@ -131,6 +131,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
/// show send audio icon
final bool showSendAudio;
final TIMUIKitChatConfig chatConfig;
/// on text changed
final void Function(String)? onChanged;
@ -183,7 +185,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
required this.customEmojiStickerList,
this.controller,
required this.currentConversation,
required this.theme})
required this.theme,
required this.chatConfig})
: super(key: key);
@override
@ -288,12 +291,10 @@ class _TIMUIKitTextFieldLayoutWideState
children: [
Text(
TIM_t("回复 "),
style: TextStyle(
color: hexToColor("8f959e"), fontSize: 14),
style: TextStyle(color: hexToColor("8f959e"), fontSize: 14),
),
Text(
MessageUtils.getDisplayName(
widget.model.repliedMessage!),
MessageUtils.getDisplayName(widget.model.repliedMessage!),
softWrap: true,
maxLines: 1,
overflow: TextOverflow.ellipsis,
@ -367,7 +368,7 @@ class _TIMUIKitTextFieldLayoutWideState
child: Container(
child: widget.customStickerPanel != null
? widget.customStickerPanel!(
height: 400,
height: widget.chatConfig.desktopStickerPanelHeight,
width: 350,
sendTextMessage: () {
widget.onEmojiSubmitted();
@ -495,7 +496,8 @@ class _TIMUIKitTextFieldLayoutWideState
final double? dx = (offset?.dx != null) ? offset!.dx : null;
final double? dy =
(offset?.dy != null && alignBox?.size.height != null)
? offset!.dy - 420
? offset!.dy -
(widget.chatConfig.desktopStickerPanelHeight + 20)
: null;
e.onClick((dx != null && dy != null) ? Offset(dx, dy) : null);
},
@ -813,8 +815,7 @@ class _TIMUIKitTextFieldLayoutWideState
generateDefaultControlBarItems() {
final DesktopControlBarConfig config =
widget.model.chatConfig.desktopControlBarConfig ??
DesktopControlBarConfig();
widget.chatConfig.desktopControlBarConfig ?? DesktopControlBarConfig();
final List<DesktopControlBarItem> itemsList = [
if (config.showStickerPanel)
DesktopControlBarItem(
@ -898,7 +899,7 @@ class _TIMUIKitTextFieldLayoutWideState
TUIChatSeparateViewModel model, TUITheme theme) {
final List<DesktopControlBarItem> itemsList = [
...defaultControlBarItems,
...(widget.model.chatConfig.additionalDesktopControlBarItems ?? [])
...(widget.chatConfig.additionalDesktopControlBarItems ?? [])
];
return generateBarIcons(itemsList, theme);
@ -1021,10 +1022,10 @@ class _TIMUIKitTextFieldLayoutWideState
child: ExtendedTextField(
scrollController: _scrollController,
autofocus: true,
maxLines: widget
.model.chatConfig.desktopMessageInputFieldLines,
minLines: widget
.model.chatConfig.desktopMessageInputFieldLines,
maxLines:
widget.chatConfig.desktopMessageInputFieldLines,
minLines:
widget.chatConfig.desktopMessageInputFieldLines,
focusNode: widget.focusNode,
onChanged: debounceFunc,
keyboardType: TextInputType.multiline,

View File

@ -53,7 +53,7 @@ class TIMUIKitChat extends StatefulWidget {
/// use for customize avatar
final Widget Function(BuildContext context, V2TimMessage message)?
userAvatarBuilder;
userAvatarBuilder;
/// Use for show conversation name.
/// This field is not necessary to be provided, when `conversation` is provided, unless you want to cover this field manually.
@ -64,7 +64,7 @@ class TIMUIKitChat extends StatefulWidget {
/// Avatar and name in message reaction secondary tap callback.
final void Function(String userID, TapDownDetails tapDetails)?
onSecondaryTapAvatar;
onSecondaryTapAvatar;
@Deprecated(
"Nickname will not shows in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
@ -142,6 +142,8 @@ class TIMUIKitChat extends StatefulWidget {
final Widget? customAppBar;
final Widget? inputTopBuilder;
/// Custom emoji panel.
final CustomStickerPanel? customStickerPanel;
@ -154,46 +156,47 @@ class TIMUIKitChat extends StatefulWidget {
/// Custom text field
final Widget Function(BuildContext context)? textFieldBuilder;
TIMUIKitChat({Key? key,
this.groupID,
required this.conversation,
this.conversationID,
this.conversationType,
this.conversationShowName,
this.abstractMessageBuilder,
this.onTapAvatar,
@Deprecated(
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") this.showNickName = false,
this.showTotalUnReadCount = false,
this.messageItemBuilder,
@Deprecated(
"Please use [extraTipsActionItemBuilder] instead") this.exteraTipsActionItemBuilder,
this.extraTipsActionItemBuilder,
this.draftText,
this.textFieldHintText,
this.initFindingMsg,
this.userAvatarBuilder,
this.appBarConfig,
this.controller,
this.morePanelConfig,
this.customStickerPanel,
this.config = const TIMUIKitChatConfig(),
this.tongueItemBuilder,
this.groupAtInfoList,
this.mainHistoryListConfig,
this.onDealWithGroupApplication,
this.toolTipsConfig,
this.lifeCycle,
this.topFixWidget = const SizedBox(),
this.textFieldBuilder,
this.customEmojiStickerList = const [],
this.customAppBar,
this.onSecondaryTapAvatar,
this.customMessageHoverBarOnDesktop})
TIMUIKitChat(
{Key? key,
this.groupID,
required this.conversation,
this.conversationID,
this.conversationType,
this.conversationShowName,
this.abstractMessageBuilder,
this.onTapAvatar,
@Deprecated(
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
this.showNickName = false,
this.showTotalUnReadCount = false,
this.messageItemBuilder,
@Deprecated("Please use [extraTipsActionItemBuilder] instead")
this.exteraTipsActionItemBuilder,
this.extraTipsActionItemBuilder,
this.draftText,
this.textFieldHintText,
this.initFindingMsg,
this.userAvatarBuilder,
this.appBarConfig,
this.controller,
this.morePanelConfig,
this.customStickerPanel,
this.config = const TIMUIKitChatConfig(),
this.tongueItemBuilder,
this.groupAtInfoList,
this.mainHistoryListConfig,
this.onDealWithGroupApplication,
this.toolTipsConfig,
this.lifeCycle,
this.topFixWidget = const SizedBox(),
this.textFieldBuilder,
this.customEmojiStickerList = const [],
this.customAppBar,
this.inputTopBuilder,
this.onSecondaryTapAvatar,
this.customMessageHoverBarOnDesktop})
: super(key: key) {
startTime = DateTime
.now()
.millisecondsSinceEpoch;
startTime = DateTime.now().millisecondsSinceEpoch;
}
@override
@ -203,15 +206,15 @@ class TIMUIKitChat extends StatefulWidget {
class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
TUIChatSeparateViewModel model = TUIChatSeparateViewModel();
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
serviceLocator<TUISelfInfoViewModel>();
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
final TUIConversationViewModel conversationViewModel =
serviceLocator<TUIConversationViewModel>();
serviceLocator<TUIConversationViewModel>();
TIMUIKitInputTextFieldController textFieldController =
TIMUIKitInputTextFieldController();
TIMUIKitInputTextFieldController();
bool isInit = false;
final TUIChatGlobalModel chatGlobalModel =
serviceLocator<TUIChatGlobalModel>();
serviceLocator<TUIChatGlobalModel>();
bool _dragging = false;
final GlobalKey alignKey = GlobalKey();
@ -219,19 +222,13 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
late AutoScrollController autoController = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery
.of(context)
.padding
.bottom),
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: Axis.vertical,
);
late AutoScrollController atMemberPanelScroll = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery
.of(context)
.padding
.bottom),
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: Axis.vertical,
);
@ -245,9 +242,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
model.onTapAvatar = widget.onTapAvatar;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (kProfileMode) {
widget.endTime = DateTime
.now()
.millisecondsSinceEpoch;
widget.endTime = DateTime.now().millisecondsSinceEpoch;
int timeSpend = widget.endTime - widget.startTime;
print("Page render time:$timeSpend ms");
}
@ -300,8 +295,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
final topicInfoList = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.getTopicInfoList(
groupID: widget.groupID!,
topicIDList: [widget.conversation.conversationID]);
groupID: widget.groupID!,
topicIDList: [widget.conversation.conversationID]);
final topicInfo = topicInfoList.data?.first.topicInfo;
final draftText = topicInfo?.draftText;
if (TencentUtils.checkString(draftText) != null) {
@ -367,7 +362,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final closePanel =
OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60);
OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60);
final isBuild = isInit;
isInit = true;
@ -386,7 +381,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
],
builder: (context, model, w) {
final TUIChatGlobalModel chatGlobalModel =
Provider.of<TUIChatGlobalModel>(context, listen: true);
Provider.of<TUIChatGlobalModel>(context, listen: true);
widget.controller?.model = model;
widget.controller?.textFieldController = textFieldController;
@ -396,14 +391,14 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
widget.onDealWithGroupApplication != null) {
filteredApplicationList =
chatGlobalModel.groupApplicationList.where((item) {
return (item.groupID == widget.conversationID) &&
item.handleStatus == 0;
}).toList();
return (item.groupID == widget.conversationID) &&
item.handleStatus == 0;
}).toList();
}
final selfUserID = selfInfoViewModel.loginInfo?.userID;
final TUIGroupListenerModel groupListenerModel =
Provider.of<TUIGroupListenerModel>(context, listen: true);
Provider.of<TUIGroupListenerModel>(context, listen: true);
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
if (needUpdate != null &&
needUpdate.groupID == widget.conversationID) {
@ -429,13 +424,13 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
resizeToAvoidBottomInset: false,
appBar: (widget.customAppBar == null)
? TIMUIKitAppBar(
showTotalUnReadCount: widget.showTotalUnReadCount,
config: widget.appBarConfig,
conversationShowName: _getTitle(),
conversationID: _getConvID(),
showC2cMessageEditStatus:
widget.config?.showC2cMessageEditStatus ?? true,
)
showTotalUnReadCount: widget.showTotalUnReadCount,
config: widget.appBarConfig,
conversationShowName: _getTitle(),
conversationID: _getConvID(),
showC2cMessageEditStatus:
widget.config?.showC2cMessageEditStatus ?? true,
)
: null,
body: DropTarget(
onDragDone: (detail) {
@ -463,6 +458,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.customAppBar != null) widget.customAppBar!,
if (filteredApplicationList.isNotEmpty)
@ -471,110 +467,110 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
if (widget.topFixWidget != null) widget.topFixWidget!,
Expanded(
child: Container(
color: theme.chatBgColor,
child: Align(
key: alignKey,
alignment: Alignment.topCenter,
child: Listener(
onPointerMove: closePanel,
child: TIMUIKitHistoryMessageListContainer(
customMessageHoverBarOnDesktop: widget
.customMessageHoverBarOnDesktop,
conversation: widget.conversation,
groupMemberInfo: model.groupMemberList
?.firstWhere(
(element) =>
element?.userID == selfUserID,
color: theme.chatBgColor,
child: Align(
key: alignKey,
alignment: Alignment.topCenter,
child: Listener(
onPointerMove: closePanel,
child: TIMUIKitHistoryMessageListContainer(
customMessageHoverBarOnDesktop:
widget.customMessageHoverBarOnDesktop,
conversation: widget.conversation,
groupMemberInfo: model.groupMemberList
?.firstWhere(
(element) =>
element?.userID == selfUserID,
orElse: () => null),
textFieldController: textFieldController,
customEmojiStickerList:
textFieldController: textFieldController,
customEmojiStickerList:
widget.customEmojiStickerList,
isUseDefaultEmoji:
isUseDefaultEmoji:
widget.config!.isUseDefaultEmoji,
key: listContainerKey,
isAllowScroll: true,
userAvatarBuilder: widget
.userAvatarBuilder,
toolTipsConfig: widget.toolTipsConfig,
groupAtInfoList: widget.groupAtInfoList,
tongueItemBuilder: widget
.tongueItemBuilder,
onLongPressForOthersHeadPortrait:
(String? userId, String? nickName) {
textFieldController.longPressToAt(
nickName, userId);
},
mainHistoryListConfig:
key: listContainerKey,
isAllowScroll: true,
userAvatarBuilder: widget.userAvatarBuilder,
toolTipsConfig: widget.toolTipsConfig,
groupAtInfoList: widget.groupAtInfoList,
tongueItemBuilder: widget.tongueItemBuilder,
onLongPressForOthersHeadPortrait:
(String? userId, String? nickName) {
textFieldController.longPressToAt(
nickName, userId);
},
mainHistoryListConfig:
widget.mainHistoryListConfig,
initFindingMsg: widget.initFindingMsg,
extraTipsActionItemBuilder:
initFindingMsg: widget.initFindingMsg,
extraTipsActionItemBuilder:
widget.extraTipsActionItemBuilder ??
widget.exteraTipsActionItemBuilder,
conversationType: _getConvType(),
scrollController: autoController,
onSecondaryTapAvatar:
conversationType: _getConvType(),
scrollController: autoController,
onSecondaryTapAvatar:
widget.onSecondaryTapAvatar,
onTapAvatar: widget.onTapAvatar,
// ignore: deprecated_member_use_from_same_package
showNickName: widget.showNickName,
messageItemBuilder:
onTapAvatar: widget.onTapAvatar,
// ignore: deprecated_member_use_from_same_package
showNickName: widget.showNickName,
messageItemBuilder:
widget.messageItemBuilder,
conversationID: _getConvID(),
),
)),
)),
conversationID: _getConvID(),
),
)),
)),
widget.inputTopBuilder ?? Container(),
Selector<TUIChatSeparateViewModel, bool>(
builder: (context, value, child) {
return value
? MultiSelectPanel(
conversationType: _getConvType(),
)
conversationType: _getConvType(),
)
: (widget.textFieldBuilder != null
? widget.textFieldBuilder!(context)
: TIMUIKitInputTextField(
groupID: widget.groupID,
atMemberPanelScroll:
atMemberPanelScroll,
groupType:
widget.conversation.groupType,
currentConversation:
widget.conversation,
model: model,
controller: textFieldController,
customEmojiStickerList:
widget.customEmojiStickerList,
isUseDefaultEmoji:
widget.config!.isUseDefaultEmoji,
customStickerPanel:
widget.customStickerPanel,
morePanelConfig:
widget.morePanelConfig,
scrollController: autoController,
conversationID: _getConvID(),
conversationType: _getConvType(),
initText: TencentUtils.checkString(
widget.draftText) ??
(PlatformUtils().isWeb
? TencentUtils.checkString(
conversationViewModel
.getWebDraft(
conversationID: widget
.conversation
.conversationID))
: TencentUtils.checkString(
widget.conversation
.draftText)),
hintText: widget.textFieldHintText,
showMorePanel: widget.config
?.isAllowShowMorePanel ??
true,
showSendAudio: widget.config
?.isAllowSoundMessage ??
true,
showSendEmoji: widget
.config?.isAllowEmojiPanel ??
true,
));
? widget.textFieldBuilder!(context)
: TIMUIKitInputTextField(
chatConfig: widget.config,
groupID: widget.groupID,
atMemberPanelScroll:
atMemberPanelScroll,
groupType:
widget.conversation.groupType,
currentConversation:
widget.conversation,
model: model,
controller: textFieldController,
customEmojiStickerList:
widget.customEmojiStickerList,
isUseDefaultEmoji:
widget.config!.isUseDefaultEmoji,
customStickerPanel:
widget.customStickerPanel,
morePanelConfig:
widget.morePanelConfig,
scrollController: autoController,
conversationID: _getConvID(),
conversationType: _getConvType(),
initText: TencentUtils.checkString(
widget.draftText) ??
(PlatformUtils().isWeb
? TencentUtils.checkString(
conversationViewModel
.getWebDraft(
conversationID: widget
.conversation
.conversationID))
: TencentUtils.checkString(
widget.conversation
.draftText)),
hintText: widget.textFieldHintText,
showMorePanel: widget.config
?.isAllowShowMorePanel ??
true,
showSendAudio: widget.config
?.isAllowSoundMessage ??
true,
showSendEmoji: widget
.config?.isAllowEmojiPanel ??
true,
));
},
selector: (c, model) {
return model.isMultiSelect;
@ -603,13 +599,13 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
TUIChatSeparateViewModel? model;
final TUIGroupListenerModel groupListenerModel =
serviceLocator<TUIGroupListenerModel>();
serviceLocator<TUIGroupListenerModel>();
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
final Widget? child;
/// You could get the model from here, and transfer it to other widget from TUIKit.
final Widget Function(BuildContext, TUIChatSeparateViewModel, Widget?)
builder;
builder;
final List<SingleChildWidget>? providers;
/// `TIMUIKitChatController` needs to be provided if you use it outside.
@ -636,20 +632,21 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
final AutoScrollController? scrollController;
TIMUIKitChatProviderScope({Key? key,
this.child,
this.providers,
this.textFieldController,
required this.builder,
this.model,
this.groupID,
this.isBuild,
required this.conversationID,
required this.conversationType,
this.controller,
this.config,
this.lifeCycle,
this.scrollController})
TIMUIKitChatProviderScope(
{Key? key,
this.child,
this.providers,
this.textFieldController,
required this.builder,
this.model,
this.groupID,
this.isBuild,
required this.conversationID,
required this.conversationType,
this.controller,
this.config,
this.lifeCycle,
this.scrollController})
: super(key: key) {
if (isBuild ?? false) {
return;
@ -665,7 +662,7 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
model?.initForEachConversation(
conversationType,
conversationID,
(String value) {
(String value) {
textFieldController?.textEditingController?.text = value;
},
groupID: groupID,

View File

@ -199,9 +199,17 @@ class TIMUIKitChatConfig {
/// [Default]: false
final bool isGroupAdminRecallEnabled;
/// Defines the height of the sticker panel on desktop platforms.
/// If the height of the sticker list exceeds this container height,
/// the sticker list will automatically become scrollable.
///
/// [Default]: 400
final double desktopStickerPanelHeight;
const TIMUIKitChatConfig(
{this.onTapLink,
this.timeDividerConfig,
this.desktopStickerPanelHeight = 400,
this.isGroupAdminRecallEnabled = false,
this.isAutoReportRead = true,
this.faceURIPrefix,

View File

@ -9,6 +9,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:cross_file/cross_file.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:path/path.dart' as path;
import 'package:url_launcher/url_launcher.dart';
@ -31,15 +32,27 @@ sendFileWithConfirmation(
required BuildContext context}) async {
bool isCanSend = true;
files.map((e) => e.path).any((filePath) {
final directory = Directory(filePath);
final isDirectoryExists = directory.existsSync();
if (isDirectoryExists) {
isCanSend = false;
return false;
}
return true;
});
if (!PlatformUtils().isWeb) {
files.map((e) => e.path).any((filePath) {
final directory = Directory(filePath);
final isDirectoryExists = directory.existsSync();
if (isDirectoryExists) {
isCanSend = false;
return false;
}
return true;
});
} else {
files.map((e) => e.name).any((fileName) {
String fileExtension = path.extension(fileName);
bool hasNoExtension = fileExtension.isEmpty;
if (hasNoExtension) {
isCanSend = false;
return false;
}
return true;
});
}
if (!isCanSend) {
TUIKitWidePopup.showSecondaryConfirmDialog(
@ -71,6 +84,9 @@ sendFileWithConfirmation(
child: ListView.separated(
itemBuilder: (BuildContext context, int index) {
final file = files[index];
final fileName = PlatformUtils().isWeb
? file.name
: path.basename(file.path);
return Material(
color: theme.wideBackgroundColor,
child: InkWell(
@ -84,18 +100,13 @@ sendFileWithConfirmation(
children: [
TIMUIKitFileIcon(
size: 44,
fileFormat: path
.extension(file.path)
.split(".")[path
.extension(file.path)
.split(".")
.length -
1],
fileFormat: fileName.split(
".")[fileName.split(".").length - 1],
),
const SizedBox(width: 16),
Expanded(
child: Text(
path.basename(file.path),
fileName,
style: TextStyle(
fontSize: 16,
color: theme.darkTextColor),
@ -154,9 +165,12 @@ Future<void> sendFiles(
ConvType conversationType,
BuildContext context) async {
for (final file in files) {
final fileName = file.name;
final filePath = file.path;
await MessageUtils.handleMessageError(
model.sendFileMessage(
filePath: file.path,
fileName: fileName,
filePath: filePath,
convID: _getConvID(conversation),
convType: conversationType),
context);

View File

@ -24,7 +24,8 @@ class ForwardMessageScreen extends StatefulWidget {
{Key? key,
this.isMergerForward = false,
required this.conversationType,
required this.model, this.onClose})
required this.model,
this.onClose})
: super(key: key);
@override
@ -74,19 +75,25 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
}
widget.model.updateMultiSelectStatus(false);
if(widget.onClose != null){
widget.onClose!();
}else{
if (widget.onClose != null) {
// widget.onClose!();
} else {
Navigator.pop(context);
}
}
@override
void dispose() {
super.dispose();
widget.model.updateMultiSelectStatus(false);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final TUITheme theme = value.theme;
if(isDesktopScreen){
if (isDesktopScreen) {
isMultiSelect = true;
return RecentForwardList(
isMultiSelect: isMultiSelect,
@ -101,16 +108,17 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
}
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
TIM_t("选择"),
isMultiSelect ? TIM_t("选择多个会话") : TIM_t("选择一个会话"),
style: TextStyle(
color: theme.appbarTextColor,
fontSize: 17,
),
),
shadowColor: theme.weakBackgroundColor,
backgroundColor: theme.appbarBgColor ??
theme.primaryColor,
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
leadingWidth: 80,
leading: TextButton(
onPressed: () {
if (isMultiSelect) {
@ -120,9 +128,9 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
});
} else {
widget.model.updateMultiSelectStatus(false);
if(widget.onClose != null){
if (widget.onClose != null) {
widget.onClose!();
}else{
} else {
Navigator.pop(context);
}
}
@ -135,7 +143,6 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
),
),
),
leadingWidth: 80,
actions: [
TextButton(
onPressed: () {
@ -149,9 +156,9 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
},
child: Text(
!isMultiSelect ? TIM_t("多选") : TIM_t("完成"),
style: const TextStyle(
color: Colors.white,
fontSize: 16,
style: TextStyle(
color: theme.appbarTextColor,
fontSize: 14,
),
),
)

View File

@ -122,124 +122,113 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
},
child: GestureDetector(
onTap: close,
child: ExtendedImageGesturePageView.builder(
scrollDirection: Axis.horizontal,
controller: ExtendedPageController(
initialPage: 0,
pageSpacing: 0,
shouldIgnorePointerWhenScrolling: false,
),
itemCount: 1,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return HeroWidget(
tag: widget.heroTag,
slidePagekey: slidePagekey,
child: ExtendedImage(
image: widget.imageProvider,
extendedImageGestureKey:
extendedImageGestureKey,
enableSlideOutPage: true,
// fit: BoxFit.scaleDown,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 0.8,
animationMinScale: 0.6,
maxScale: 2 * fittedScale,
animationMaxScale: 2.5 * fittedScale,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: initialScale,
initialAlignment:
InitialAlignment.topCenter,
hitTestBehavior: HitTestBehavior.opaque,
);
},
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(
color: Colors.white)));
case LoadState.completed:
final screenHeight =
MediaQuery.of(context).size.height;
final screenWidth =
MediaQuery.of(context).size.width;
final imgHeight = state.extendedImageInfo
?.image.height ??
1;
final imgWidth = state
.extendedImageInfo?.image.width ??
0;
final imgRatio = imgWidth / imgHeight;
final screenRatio =
screenWidth / screenHeight;
final fitWidthScale =
screenRatio / imgRatio;
if (screenRatio > imgRatio) {
// Long Image
// initialScale = fitWidthScale;
fittedScale = fitWidthScale;
doubleTapScales[1] = fitWidthScale;
} else {
fittedScale =
1 / fitWidthScale; // fittedHeight
doubleTapScales[1] = 1 / fitWidthScale;
}
return GesturedImage(state,
key: extendedImageGestureKey);
case LoadState.failed:
break;
}
return null;
},
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion.
final Offset? pointerDownPosition =
state.pointerDownPosition;
final double? begin =
state.gestureDetails!.totalScale;
double end;
child: HeroWidget(
tag: widget.heroTag,
slidePagekey: slidePagekey,
child: ExtendedImage(
image: widget.imageProvider,
extendedImageGestureKey:
extendedImageGestureKey,
enableSlideOutPage: true,
// fit: BoxFit.scaleDown,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 0.8,
animationMinScale: 0.6,
maxScale: 2 * fittedScale,
animationMaxScale: 2.5 * fittedScale,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: initialScale,
initialAlignment:
InitialAlignment.topCenter,
hitTestBehavior: HitTestBehavior.opaque,
);
},
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(
color: Colors.white)));
case LoadState.completed:
final screenHeight =
MediaQuery.of(context).size.height;
final screenWidth =
MediaQuery.of(context).size.width;
final imgHeight = state.extendedImageInfo
?.image.height ??
1;
final imgWidth = state
.extendedImageInfo?.image.width ??
0;
final imgRatio = imgWidth / imgHeight;
final screenRatio =
screenWidth / screenHeight;
final fitWidthScale =
screenRatio / imgRatio;
if (screenRatio > imgRatio) {
// Long Image
// initialScale = fitWidthScale;
fittedScale = fitWidthScale;
doubleTapScales[1] = fitWidthScale;
} else {
fittedScale =
1 / fitWidthScale; // fittedHeight
doubleTapScales[1] = 1 / fitWidthScale;
}
return GesturedImage(state,
key: extendedImageGestureKey);
case LoadState.failed:
break;
}
return null;
},
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion.
final Offset? pointerDownPosition =
state.pointerDownPosition;
final double? begin =
state.gestureDetails!.totalScale;
double end;
//remove old
_doubleClickAnimation?.removeListener(
_doubleClickAnimationListener);
//remove old
_doubleClickAnimation?.removeListener(
_doubleClickAnimationListener);
//stop pre
_doubleClickAnimationController.stop();
//stop pre
_doubleClickAnimationController.stop();
//reset to use
_doubleClickAnimationController.reset();
//reset to use
_doubleClickAnimationController.reset();
if (begin == doubleTapScales[0]) {
end = doubleTapScales[1];
} else {
end = doubleTapScales[0];
}
if (begin == doubleTapScales[0]) {
end = doubleTapScales[1];
} else {
end = doubleTapScales[0];
}
_doubleClickAnimationListener = () {
//print(_animation.value);
state.handleDoubleTap(
scale: _doubleClickAnimation!.value,
doubleTapPosition: pointerDownPosition);
};
_doubleClickAnimation =
_doubleClickAnimationController.drive(
Tween<double>(
begin: begin, end: end));
_doubleClickAnimationListener = () {
//print(_animation.value);
state.handleDoubleTap(
scale: _doubleClickAnimation!.value,
doubleTapPosition: pointerDownPosition);
};
_doubleClickAnimation =
_doubleClickAnimationController.drive(
Tween<double>(
begin: begin, end: end));
_doubleClickAnimation!.addListener(
_doubleClickAnimationListener);
_doubleClickAnimation!.addListener(
_doubleClickAnimationListener);
_doubleClickAnimationController.forward();
},
mode: ExtendedImageMode.gesture,
));
}),
_doubleClickAnimationController.forward();
},
mode: ExtendedImageMode.gesture,
)),
),
),
),

View File

@ -232,21 +232,23 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
final faceUrl = message.faceUrl ?? "";
final showName = message.nickName ?? message.userID ?? "";
final theme = Provider.of<TUIThemeViewModel>(context).theme;
final isSelf = message.isSelf ?? false;
return Container(
margin: const EdgeInsets.only(top: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
SizedBox(
if(!isSelf) SizedBox(
width: 40,
height: 40,
child: Avatar(faceUrl: faceUrl, showName: showName),
),
const SizedBox(
if(!isSelf) const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Text(showName,
style: TextStyle(fontSize: 12, color: theme.weakTextColor)),
@ -258,7 +260,15 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
child: _getMsgItem(message),
)
],
)
),
if(isSelf) const SizedBox(
width: 12,
),
if(isSelf) SizedBox(
width: 40,
height: 40,
child: Avatar(faceUrl: faceUrl, showName: showName),
),
],
),
);

View File

@ -4,11 +4,10 @@ import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.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:universal_html/html.dart' as html;
@ -21,11 +20,10 @@ import 'package:video_player/video_player.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class VideoScreen extends StatefulWidget {
const VideoScreen(
{required this.message,
required this.heroTag,
required this.videoElement,
Key? key})
const VideoScreen({required this.message,
required this.heroTag,
required this.videoElement,
Key? key})
: super(key: key);
final V2TimMessage message;
@ -40,13 +38,14 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
late VideoPlayerController videoPlayerController;
late ChewieController chewieController;
GlobalKey<ExtendedImageSlidePageState> slidePagekey =
GlobalKey<ExtendedImageSlidePageState>();
GlobalKey<ExtendedImageSlidePageState>();
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
bool isInit = false;
@override
initState() {
super.initState();
setVideoMessage();
setVideoPlayerController();
//
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
@ -57,14 +56,16 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
}
//
Future<void> _saveNetworkVideo(
context,
String videoUrl, {
bool isAsset = true,
}) async {
Future<void> _saveNetworkVideo(context,
String videoUrl, {
bool isAsset = true,
}) async {
if (PlatformUtils().isWeb) {
RegExp exp = RegExp(r"((\.){1}[^?]{2,4})");
String? suffix = exp.allMatches(videoUrl).last.group(0);
String? suffix = exp
.allMatches(videoUrl)
.last
.group(0);
var xhr = html.HttpRequest();
xhr.open('get', videoUrl);
xhr.responseType = 'arraybuffer';
@ -78,7 +79,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
xhr.send();
return;
}
if(PlatformUtils().isMobile){
if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission(
context,
@ -91,17 +92,17 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt) >= 33) {
final videos = await Permissions.checkPermission(
context,Permission.videos.value,
context, Permission.videos.value,
);
if(!videos){
if (!videos) {
return;
}
} else {
final storage = await Permissions.checkPermission(
context, Permission.storage.value,
);
if(!storage){
if (!storage) {
return;
}
}
@ -243,67 +244,41 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
return width;
}
setVideoMessage() async {
// Using local path while sending
// VideoPlayerController player = widget.message.videoElem!.videoUrl == null ||
// widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
// ? VideoPlayerController.file(File(
// widget.message.videoElem!.videoPath!,
// ))
// : (widget.message.videoElem?.localVideoUrl == null ||
// widget.message.videoElem?.localVideoUrl == "")
// ? VideoPlayerController.network(
// widget.message.videoElem!.videoUrl!,
// )
// : VideoPlayerController.file(File(
// widget.message.videoElem!.localVideoUrl!,
// ));
setVideoPlayerController() async {
if (!PlatformUtils().isWeb) {
if (widget.message.msgID != null || widget.message.msgID != '') {
if (model.getMessageProgress(widget.message.msgID) == 100) {
String savePath;
if (widget.message.videoElem!.localVideoUrl != null &&
widget.message.videoElem!.localVideoUrl != '') {
savePath = widget.message.videoElem!.localVideoUrl!;
} else {
savePath = model.getFileMessageLocation(widget.message.msgID);
}
File f = File(savePath);
if (f.existsSync()) {
widget.videoElement.localVideoUrl =
model.getFileMessageLocation(widget.message.msgID);
}
if (TencentUtils.checkString(widget.message.msgID) != null &&
widget.videoElement.localVideoUrl == null) {
String savePath = model.getFileMessageLocation(widget.message.msgID);
File f = File(savePath);
if (f.existsSync()) {
widget.videoElement.localVideoUrl = savePath;
}
}
}
VideoPlayerController player = PlatformUtils().isWeb
? ((widget.videoElement.videoPath != null &&
widget.videoElement.videoPath!.isNotEmpty) ||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
? VideoPlayerController.network(
widget.videoElement.videoPath!,
)
: (widget.videoElement.localVideoUrl == null ||
widget.videoElement.localVideoUrl == "")
? VideoPlayerController.network(
widget.videoElement.videoUrl!,
)
: VideoPlayerController.network(
widget.videoElement.localVideoUrl!,
))
: (widget.videoElement.videoPath != null &&
widget.videoElement.videoPath!.isNotEmpty) ||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
? VideoPlayerController.file(File(widget.videoElement.videoPath!))
: (widget.videoElement.localVideoUrl == null ||
widget.videoElement.localVideoUrl == "")
? VideoPlayerController.network(
widget.videoElement.videoUrl!,
)
: VideoPlayerController.file(File(
widget.videoElement.localVideoUrl!,
));
? ((TencentUtils.checkString(widget.videoElement.videoPath) != null) ||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
? VideoPlayerController.networkUrl(
Uri.parse(widget.videoElement.videoPath!),
)
: (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
? VideoPlayerController.networkUrl(
Uri.parse(widget.videoElement.videoUrl!),
)
: VideoPlayerController.networkUrl(
Uri.parse(widget.videoElement.localVideoUrl!),
))
: (TencentUtils.checkString(widget.videoElement.videoPath) != null ||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING)
? VideoPlayerController.file(File(widget.videoElement.videoPath!))
: (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
? VideoPlayerController.networkUrl(
Uri.parse(widget.videoElement.videoUrl!),
)
: VideoPlayerController.file(File(
widget.videoElement.localVideoUrl!,
));
await player.initialize();
WidgetsBinding.instance.addPostFrameCallback((_) {
double w = getVideoWidth();
@ -315,7 +290,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
showControlsOnInitialize: false,
allowPlaybackSpeedChanging: false,
aspectRatio: w == 0 || h == 0 ? null : w / h,
customControls: VideoCustomControls(downloadFn: () async{
customControls: VideoCustomControls(downloadFn: () async {
return await _saveVideo();
}));
setState(() {
@ -330,7 +305,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
didUpdateWidget(oldWidget) {
if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl ||
oldWidget.videoElement.videoPath != widget.videoElement.videoPath) {
setVideoMessage();
setVideoPlayerController();
}
super.didUpdateWidget(oldWidget);
}
@ -350,55 +325,70 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return OrientationBuilder(builder: ((context, orientation) {
return Scaffold(
body: Container(
color: Colors.transparent,
constraints: BoxConstraints.expand(
height: MediaQuery.of(context).size.height,
),
child: ExtendedImageSlidePage(
key: slidePagekey,
slidePageBackgroundHandler: (Offset offset, Size size) {
if (orientation == Orientation.landscape) {
return Colors.black;
}
double opacity = 0.0;
opacity = offset.distance /
(Offset(size.width, size.height).distance / 2.0);
return Colors.black
.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
},
slideType: SlideType.onlyImage,
child: ExtendedImageSlidePageHandler(
child: Container(
color: Colors.black,
child: isInit
? Chewie(
controller: chewieController,
)
: const Center(
return Material(
color: Colors.transparent,
child: Container(
color: Colors.transparent,
constraints: BoxConstraints.expand(
height: MediaQuery
.of(context)
.size
.height,
),
child: ExtendedImageSlidePage(
key: slidePagekey,
slidePageBackgroundHandler: (Offset offset, Size size) {
if (orientation == Orientation.landscape) {
return Colors.black;
}
double opacity = 0.0;
opacity = offset.distance /
(Offset(size.width, size.height).distance / 2.0);
return Colors.black
.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
},
slideType: SlideType.onlyImage,
slideEndHandler: (Offset offset, {
ExtendedImageSlidePageState? state,
ScaleEndDetails? details,
}) {
final vy = details?.velocity.pixelsPerSecond.dy ?? 0;
final oy = offset.dy;
if (vy > 300 || oy > 100) {
return true;
}
return null;
},
child: ExtendedImageSlidePageHandler(
child: Container(
color: Colors.black,
child: isInit
? Chewie(
controller: chewieController,
)
: const Center(
child:
CircularProgressIndicator(color: Colors.white))),
heroBuilderForSlidingPage: (Widget result) {
return Hero(
tag: widget.heroTag,
child: result,
flightShuttleBuilder: (BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext) {
final Hero hero =
CircularProgressIndicator(color: Colors.white))),
heroBuilderForSlidingPage: (Widget result) {
return Hero(
tag: widget.heroTag,
child: result,
flightShuttleBuilder: (BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext) {
final Hero hero =
(flightDirection == HeroFlightDirection.pop
? fromHeroContext.widget
: toHeroContext.widget) as Hero;
return hero.child;
return hero.child;
},
);
},
);
},
)),
));
)),
));
}));
}
}

View File

@ -1,9 +1,17 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:chewie_for_us/chewie_for_us.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart';
import 'package:path/path.dart' as p;
import 'package:video_player/video_player.dart';
class TUIKitWidePopup {
static OverlayEntry? entry;
@ -236,7 +244,7 @@ class TUIKitWidePopup {
if (isUseMaterialAlert) {
return showDialog(
barrierDismissible: false,
barrierDismissible: true,
context: context,
builder: (context) {
return WillPopScope(
@ -249,7 +257,8 @@ class TUIKitWidePopup {
content: contentWidget,
),
onWillPop: () {
return Future.value(false);
isShow = false;
return Future.value(true);
});
});
}
@ -276,4 +285,141 @@ class TUIKitWidePopup {
});
Overlay.of(context).insert(entry!);
}
static void showMedia({
String? mediaLocalPath,
String? mediaURL,
required BuildContext context,
required VoidCallback onClickOrigin,
double? aspectRatio,
}) async {
assert((mediaLocalPath != null) || (mediaURL != null),
"At least one of mediaLocalPath or mediaURL must be provided.");
String _removeQueryString(String urlString) {
Uri uri = Uri.parse(urlString);
Uri cleanUri = Uri(
scheme: uri.scheme,
host: uri.host,
port: uri.port,
path: uri.path,
);
return cleanUri.toString();
}
final String mediaPath = mediaLocalPath ?? mediaURL ?? "";
final isLocalResource = mediaLocalPath != null;
String fileExtension = p
.extension(isLocalResource ? mediaPath : _removeQueryString(mediaPath));
bool isVideo =
['.mp4', '.avi', '.mov', '.flv', '.wmv'].contains(fileExtension);
VideoPlayerController? videoController;
ChewieController? chewieController;
Widget mediaWidget;
double? aspectRatioFinal = aspectRatio;
if (isVideo) {
if (isLocalResource) {
videoController = VideoPlayerController.file(File(mediaPath));
} else {
videoController = VideoPlayerController.networkUrl(Uri.parse(mediaPath));
}
await videoController.initialize();
aspectRatioFinal = videoController.value.aspectRatio;
chewieController = ChewieController(
videoPlayerController: videoController,
aspectRatio: aspectRatioFinal,
autoPlay: true,
looping: false,
autoInitialize: true,
);
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),
);
}
showDialog(
barrierDismissible: true,
context: context,
builder: (context) {
return WillPopScope(
child: AlertDialog(
surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent,
backgroundColor: Colors.transparent,
titlePadding: const EdgeInsets.all(0),
contentPadding: const EdgeInsets.all(0),
content: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.85,
maxHeight: MediaQuery.of(context).size.height * 0.82,
),
child: aspectRatioFinal != null
? AspectRatio(
aspectRatio: aspectRatioFinal,
child: mediaWidget)
: mediaWidget,
),
const SizedBox(height: 10),
InkWell(
onTap: onClickOrigin,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.only(top: 0),
child: Icon(
Icons.open_in_new,
size: 14,
color: Colors.grey.shade200,
),
),
const SizedBox(
width: 8,
),
// Custom Text Widget with designer baseline
Text(
TIM_t("在新窗口中打开"),
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade200,
),
),
],
),
),
],
),
),
),
),
onWillPop: () {
if (isVideo) videoController?.dispose();
return Future.value(true);
},
);
});
}
}

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045"
url: "https://pub.dev"
source: hosted
version: "61.0.0"
version: "62.0.0"
adaptive_action_sheet:
dependency: "direct main"
description:
@ -21,18 +21,18 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834"
url: "https://pub.dev"
source: hosted
version: "5.13.0"
version: "6.0.0"
args:
dependency: transitive
description:
name: args
sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
async:
dependency: transitive
description:
@ -117,10 +117,10 @@ packages:
dependency: transitive
description:
name: build
sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc"
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
build_config:
dependency: transitive
description:
@ -141,26 +141,26 @@ packages:
dependency: transitive
description:
name: build_resolvers
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.1"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced"
sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "2.4.6"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e"
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev"
source: hosted
version: "7.2.9"
version: "7.2.10"
built_collection:
dependency: transitive
description:
@ -173,10 +173,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "7dd62d9faf105c434f3d829bbe9c4be02ec67f5ed94832222116122df67c5452"
sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
url: "https://pub.dev"
source: hosted
version: "8.6.0"
version: "8.6.1"
cached_network_image:
dependency: "direct main"
description:
@ -213,10 +213,10 @@ packages:
dependency: transitive
description:
name: camera_android
sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b
sha256: f43d07f9d7228ea1ca87d22e30881bd68da4b78484a1fbd1f1408b412a41cefb
url: "https://pub.dev"
source: hosted
version: "0.10.8+2"
version: "0.10.8+3"
camera_avfoundation:
dependency: transitive
description:
@ -285,10 +285,10 @@ packages:
dependency: transitive
description:
name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.5.0"
collection:
dependency: "direct main"
description:
@ -345,14 +345,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
dart_internal:
dependency: transitive
description:
name: dart_internal
sha256: dae3976f383beddcfcd07ad5291a422df2c8c0a8a03c52cda63ac7b4f26e0f4e
url: "https://pub.dev"
source: hosted
version: "0.2.8"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad
sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
desktop_drop:
dependency: "direct main"
description:
@ -365,10 +373,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "499c61743e13909c13374a8c209075385858c614b9c0f2487b5f9995eeaf7369"
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
url: "https://pub.dev"
source: hosted
version: "9.0.1"
version: "9.0.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -397,42 +405,42 @@ packages:
dependency: "direct main"
description:
name: extended_image
sha256: "73b7051722be79f9e8f8c120951fd9d3b22e6153fda6105c322fb88c066952d6"
sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
url: "https://pub.dev"
source: hosted
version: "8.0.1"
version: "8.0.2"
extended_image_library:
dependency: transitive
description:
name: extended_image_library
sha256: f2eba5c8500c5a7ce1f0db703b787756cb3bc65cd3818636d5fe9a9ace011ee3
sha256: af3ff1c09c23ca7663f94272313d63499a6bd19121e99378e375e0cf2ac7a3e4
url: "https://pub.dev"
source: hosted
version: "3.5.1"
version: "3.5.2"
extended_text:
dependency: "direct main"
description:
name: extended_text
sha256: "75ddf28ce7d5be33a050ff2179b6567b4b98e6225ad3e61e4c3748f7448c25f7"
sha256: dec14c9b36d9bbaaf007da5998f5dc72a2dbd5b877601d7b7970bb42524b3ced
url: "https://pub.dev"
source: hosted
version: "11.0.0"
version: "11.0.1"
extended_text_field:
dependency: "direct main"
description:
name: extended_text_field
sha256: "6cf8c090de4dc1e309cf3b24cb9448d7463c6c17926b628cf0954631bf4e56db"
sha256: e93248bb9b04a3e6c5e88a6a96e1c08cc765097657ab25aabe409e06a4f6760a
url: "https://pub.dev"
source: hosted
version: "12.0.0"
version: "12.0.1"
extended_text_library:
dependency: transitive
description:
name: extended_text_library
sha256: "308b50cfcc8e3accf46a09cb692715fbd1097333817c15b0f7527de1766bc1ff"
sha256: c06fbd8e3b6eedadf50cd6c109bbbd80921a6c43e4422d3b4ec9d4cb36ce4555
url: "https://pub.dev"
source: hosted
version: "11.0.1"
version: "11.0.2"
fast_i18n:
dependency: "direct dev"
description:
@ -469,10 +477,42 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432
url: "https://pub.dev"
source: hosted
version: "5.3.1"
version: "5.3.2"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046"
url: "https://pub.dev"
source: hosted
version: "0.9.2"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "7a6f1ae6107265664f3f7f89a66074882c4d506aef1441c9af313c1f7e6f41ce"
url: "https://pub.dev"
source: hosted
version: "0.9.3"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26"
url: "https://pub.dev"
source: hosted
version: "0.9.3"
fixnum:
dependency: transitive
description:
@ -498,10 +538,10 @@ packages:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3"
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.3.1"
flutter_easyrefresh:
dependency: "direct main"
description:
@ -530,10 +570,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0
sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
url: "https://pub.dev"
source: hosted
version: "0.6.15"
version: "0.6.17"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -562,10 +602,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: "6ff8c902c8056af9736de2689f63f81c42e2d642b9f4c79dbf8790ae48b63012"
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
version: "2.0.7"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -607,10 +647,10 @@ packages:
dependency: transitive
description:
name: html
sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted
version: "0.15.3"
version: "0.15.4"
http:
dependency: "direct main"
description:
@ -647,50 +687,74 @@ packages:
dependency: "direct main"
description:
name: image_gallery_saver
sha256: "009b7722cd8507fd72c7f2cb7cbc46d6e15ad0895469cfcc39a10f86e3556979"
sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.3"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270"
sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c
url: "https://pub.dev"
source: hosted
version: "0.8.7+5"
version: "0.8.9"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: c2f3c66400649bd132f721c88218945d6406f693092b2f741b79ae9cdb046e59
sha256: d2bab152deb2547ea6f53d82ebca9b7e77386bb706e5789e815d37e08ea475bb
url: "https://pub.dev"
source: hosted
version: "0.8.6+16"
version: "0.8.7+3"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c"
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
url: "https://pub.dev"
source: hosted
version: "2.1.12"
version: "2.2.0"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0
sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b
url: "https://pub.dev"
source: hosted
version: "0.8.7+4"
version: "0.8.8"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "02cbc21fe1706b97942b575966e5fbbeaac535e76deef70d3a242e4afb857831"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: cee2aa86c56780c13af2c77b5f2f72973464db204569e1ba2dd744459a065af4
url: "https://pub.dev"
source: hosted
version: "0.2.1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8"
sha256: "7c7b96bb9413a9c28229e717e6fd1e3edd1cc5569c1778fcca060ecf729b65ee"
url: "https://pub.dev"
source: hosted
version: "2.6.3"
version: "2.8.0"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: c3066601ea42113922232c7b7b3330a2d86f029f685bba99d82c30e799914952
url: "https://pub.dev"
source: hosted
version: "0.2.1"
intl:
dependency: "direct main"
description:
@ -775,10 +839,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
@ -831,10 +895,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "28386bbe89ab5a7919a47cea99cdd1128e5a6e0bbd7eaafe20440ead84a15de3"
sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
url: "https://pub.dev"
source: hosted
version: "4.0.1"
version: "4.0.2"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -919,58 +983,50 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
version: "2.1.6"
pedantic:
dependency: transitive
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "2.1.7"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
sha256: "415af30ba76a84faccfe1eb251fe1e4fdc790f876924c65ad7d6ed7a1404bcd6"
url: "https://pub.dev"
source: hosted
version: "10.2.0"
version: "10.4.2"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8
sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
url: "https://pub.dev"
source: hosted
version: "10.2.1"
version: "10.3.0"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85
sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
url: "https://pub.dev"
source: hosted
version: "9.0.8"
version: "9.1.3"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
sha256: "463a07cb7cc6c758a7a1c7da36ce666bb80a0b4b5e92df0fa36872e0ed456993"
url: "https://pub.dev"
source: hosted
version: "3.9.0"
version: "3.11.1"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.2"
version: "0.1.3"
petitparser:
dependency: transitive
description:
@ -1079,58 +1135,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.2.0"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
sha256: b046999bf0ff58f04c364491bb803dcfa8f42e47b19c75478f53d323684a8cc1
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.1"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shelf:
dependency: transitive
description:
@ -1147,14 +1203,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
shell:
dependency: transitive
description:
name: shell
sha256: fd3b7b97e5e7f734138543d5815c6cb6cdd2a1645a0def8ac8e05577cddfbe03
url: "https://pub.dev"
source: hosted
version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
@ -1164,10 +1212,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
sqflite:
dependency: transitive
description:
@ -1180,26 +1228,26 @@ packages:
dependency: transitive
description:
name: sqflite_common
sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555
sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
url: "https://pub.dev"
source: hosted
version: "2.4.5"
version: "2.4.5+1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -1224,6 +1272,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
tencent_chat_i18n_tool:
dependency: transitive
description:
name: tencent_chat_i18n_tool
sha256: ac8171d2574ed18babedd0cb67e937e255bf02fcb72f55408d033f74d4b11949
url: "https://pub.dev"
source: hosted
version: "2.1.3+2"
tencent_cloud_chat_sdk:
dependency: transitive
description:
@ -1236,18 +1292,18 @@ packages:
dependency: "direct main"
description:
name: tencent_cloud_uikit_core
sha256: "0131874c7b15e181001c94f8a668f0ccae3006dea6e70d4e42e5531b63313a27"
sha256: "0a0f43e4c4241b25d12a9e9f0ee91922ac800a42229d97e3d21d16041ace3104"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.8"
tencent_im_base:
dependency: "direct main"
description:
name: tencent_im_base
sha256: "9b8e712bf27ffae9b686ec532ee8417b8263eba8bab04f105e28a95de1807322"
sha256: "0db83050452486571ee4ac07280a2fb4bbc07d297a54235b5cf12e46e79267d0"
url: "https://pub.dev"
source: hosted
version: "1.0.57"
version: "2.0.1"
tencent_im_sdk_plugin_platform_interface:
dependency: transitive
description:
@ -1268,10 +1324,10 @@ packages:
dependency: "direct main"
description:
name: tencent_open_file
sha256: "01f94f618da42e5593bbad0657fcd13cfc1c2360cca805d8cdfefe898cbe5429"
sha256: bb92d2f052c150a45942c564fba13d150a1b7b47525e6fdd74ccc58058ba5dcf
url: "https://pub.dev"
source: hosted
version: "4.0.10"
version: "4.0.11"
tencent_super_tooltip:
dependency: "direct main"
description:
@ -1292,18 +1348,18 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
tim_ui_kit_sticker_plugin:
dependency: "direct main"
description:
name: tim_ui_kit_sticker_plugin
sha256: "2a825d33076f319f6c1c87d58e2b0d650c9284ae4afd8efdc206f3e6f3582e64"
sha256: c9b0c1261bb51a5dee54bb50c6a106061b1bb10731b9c815fc5175afa60175e2
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.2.1"
timing:
dependency: transitive
description:
@ -1324,10 +1380,10 @@ packages:
dependency: "direct main"
description:
name: tuple
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
typed_data:
dependency: transitive
description:
@ -1348,26 +1404,26 @@ packages:
dependency: transitive
description:
name: universal_io
sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d"
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
url: "https://pub.dev"
source: hosted
version: "6.1.11"
version: "6.1.12"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
url: "https://pub.dev"
source: hosted
version: "6.0.35"
version: "6.0.36"
url_launcher_ios:
dependency: transitive
description:
@ -1396,26 +1452,26 @@ packages:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
url: "https://pub.dev"
source: hosted
version: "2.0.17"
version: "2.0.18"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
version: "3.0.7"
uuid:
dependency: "direct main"
description:
@ -1428,26 +1484,26 @@ packages:
dependency: transitive
description:
name: vector_graphics
sha256: b96f10cbdfcbd03a65758633a43e7d04574438f059b1043104b5d61b23d38a4f
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
version: "1.1.7"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "57a8e6e24662a3bdfe3b3d61257db91768700c0b8f844e235877b56480f31c69"
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
version: "1.1.7"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "7430f5d834d0db4560d7b19863362cd892f1e52b43838553a3c5cdfc9ab28e5b"
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
version: "1.1.7"
vector_math:
dependency: transitive
description:
@ -1460,18 +1516,18 @@ packages:
dependency: "direct main"
description:
name: video_player
sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd
sha256: "3fd106c74da32f336dc7feb65021da9b0207cb3124392935f1552834f7cce822"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "2.7.0"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: ae1c7d9a71c236a1bf9e567bd7ed4c90887e389a5f233b2192593f7f7395005c
sha256: f338a5a396c845f4632959511cad3542cdf3167e1b2a1a948ef07f7123c03608
url: "https://pub.dev"
source: hosted
version: "2.4.8"
version: "2.4.9"
video_player_avfoundation:
dependency: transitive
description:
@ -1500,10 +1556,10 @@ packages:
dependency: transitive
description:
name: wakelock_for_us
sha256: b73bfa90e5e764f41155063ae92fbd1e3a04ee6372e65ff7d288d2c3057f9498
sha256: a5bdd445e51a617f7c24be8165230391447301f622aacd050038cee7b41989b4
url: "https://pub.dev"
source: hosted
version: "0.6.3"
version: "0.6.3+1"
wakelock_platform_interface:
dependency: transitive
description:
@ -1548,18 +1604,18 @@ packages:
dependency: transitive
description:
name: win32
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev"
source: hosted
version: "4.1.4"
version: "5.0.5"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
@ -1585,5 +1641,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.0.0 <3.2.0"
flutter: ">=3.10.0"

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.2
version: 2.1.3+1
homepage: https://www.tencentcloud.com/products/im?from=pub
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -29,7 +29,7 @@ dependencies:
image_picker: ^0.8.5+3
file_picker: ^5.3.0
tencent_super_tooltip: ^0.0.1
video_player: ^2.4.2
video_player: ^2.7.0
chewie_for_us: ^1.5.0
flutter_slidable_for_tencent_im: ^1.4.0
flutter_plugin_record_plus: ^0.0.16
@ -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.0.1
tencent_im_base: ^1.0.57
tim_ui_kit_sticker_plugin: ^2.2.1
tencent_im_base: ^2.0.1
fc_native_video_thumbnail: any
audioplayers: ^3.0.1
path: ^1.8.1
tencent_cloud_uikit_core: ^1.0.2
tencent_cloud_uikit_core: ^1.0.7
pasteboard: ^0.2.0
desktop_drop: ^0.4.1
device_info_plus: any