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 ## 2.1.2
### New Features ### 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> <br>

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import audioplayers_darwin
import desktop_drop import desktop_drop
import device_info_plus import device_info_plus
import fc_native_video_thumbnail import fc_native_video_thumbnail
import file_selector_macos
import package_info_plus import package_info_plus
import pasteboard import pasteboard
import path_provider_foundation import path_provider_foundation
@ -22,6 +23,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin")) FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 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 <audioplayers_windows/audioplayers_windows_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h> #include <desktop_drop/desktop_drop_plugin.h>
#include <fc_native_video_thumbnail/fc_native_video_thumbnail_plugin_c_api.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 <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DesktopDropPlugin")); registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FcNativeVideoThumbnailPluginCApiRegisterWithRegistrar( FcNativeVideoThumbnailPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FcNativeVideoThumbnailPluginCApi")); registry->GetRegistrarForPlugin("FcNativeVideoThumbnailPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
PasteboardPluginRegisterWithRegistrar( PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin")); registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows audioplayers_windows
desktop_drop desktop_drop
fc_native_video_thumbnail fc_native_video_thumbnail
file_selector_windows
pasteboard pasteboard
permission_handler_windows permission_handler_windows
url_launcher_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 MessageFunction = Future<V2TimMessage?> Function(V2TimMessage message);
typedef MessageFunctionNullCallback = Function(V2TimValueCallback<V2TimMessage> res);
typedef MessageFunctionOptional = Future<V2TimMessage?> Function( typedef MessageFunctionOptional = Future<V2TimMessage?> Function(
V2TimMessage message); V2TimMessage message);
@ -46,10 +48,17 @@ abstract class DefaultLifeCycle {
return list; return list;
} }
static Future<bool> defaultBooleanSolution(dynamic) async { static Future<bool> defaultAsyncBooleanSolution(dynamic) async {
return true; return true;
} }
static bool defaultBooleanSolution(dynamic) {
return true;
}
static defaultNullCallbackSolution(dynamic) {
}
static Future<bool> defaultAddFriend( static Future<bool> defaultAddFriend(
String userID, String? remark, String? friendGroup, String? addWording, String userID, String? remark, String? friendGroup, String? addWording,
[BuildContext? context]) async { [BuildContext? context]) async {

View File

@ -7,6 +7,6 @@ class BlockListLifeCycle {
FutureBool Function(List<String> userIDList) shouldDeleteFromBlockList; FutureBool Function(List<String> userIDList) shouldDeleteFromBlockList;
BlockListLifeCycle({ 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/business_logic/life_cycle/base_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class ChatLifeCycle { class ChatLifeCycle {
/// Before a new message will be added to historical message list from long connection. /// 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. /// Returns null can block the message from sending.
MessageFunction messageWillSend; MessageFunction messageWillSend;
/// After a new message been sent.
MessageFunctionNullCallback messageDidSend;
/// After getting the latest message list from API, /// After getting the latest message list from API,
/// and before historical message list will be rendered. /// and before historical message list will be rendered.
/// You may add or delete some messages here. /// 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. /// You can make a second confirmation here by a modal, etc.
FutureBool Function(String conversationID) shouldClearHistoricalMessageList; FutureBool Function(String conversationID) shouldClearHistoricalMessageList;
/// Before rendering a message to message list.
bool Function(V2TimMessage msgID) messageShouldMount;
ChatLifeCycle({ ChatLifeCycle({
this.shouldClearHistoricalMessageList = this.shouldClearHistoricalMessageList =
DefaultLifeCycle.defaultBooleanSolution, DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteMessage = DefaultLifeCycle.defaultBooleanSolution, this.shouldDeleteMessage = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.messageDidSend = DefaultLifeCycle.defaultNullCallbackSolution,
this.didGetHistoricalMessageList = this.didGetHistoricalMessageList =
DefaultLifeCycle.defaultMessageListSolution, DefaultLifeCycle.defaultMessageListSolution,
this.messageWillSend = DefaultLifeCycle.defaultMessageSolution, this.messageWillSend = DefaultLifeCycle.defaultMessageSolution,
this.modifiedMessageWillMount = DefaultLifeCycle.defaultMessageSolution, this.modifiedMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.newMessageWillMount = DefaultLifeCycle.defaultMessageSolution, this.newMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.messageShouldMount = DefaultLifeCycle.defaultBooleanSolution,
}); });
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
ChatLifeCycle? _lifeCycle; ChatLifeCycle? _lifeCycle;
bool _isDownloading = false; bool _isDownloading = false;
final List<Map<String, String>> _waitingDownloadList = 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; int _totalUnreadCount = 0;
String localKeyPrefix = "TUIKit_conversation_stored_"; String localKeyPrefix = "TUIKit_conversation_stored_";
String localMsgIDListKey = "TUIKit_conversation_list"; String localMsgIDListKey = "TUIKit_conversation_list";
@ -62,7 +62,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
List<V2TimGroupApplication>? _groupApplicationList; List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder; String Function(V2TimMessage message)? _abstractMessageBuilder;
final Map<String, int> _c2cMessageEditStatusMap = 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, bool> _c2cMessageFromUserActiveMap = Map.from({});
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({}); final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
bool _showC2cMessageEditStatus = true; bool _showC2cMessageEditStatus = true;
@ -102,6 +102,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
Map<String, String> get currentDownLoad => _waitingDownloadList.first; Map<String, String> get currentDownLoad => _waitingDownloadList.first;
int getWaitingListLength() {
return _waitingDownloadList.length;
}
void addWaitingList(String msgID) { void addWaitingList(String msgID) {
print("add to waiting list success"); print("add to waiting list success");
bool contains = false; bool contains = false;
@ -191,15 +195,17 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
Map<String, V2TimMessageReceipt> get messageReadReceiptMap => Map<String, V2TimMessageReceipt> get messageReadReceiptMap =>
_messageReadReceiptMap; _messageReadReceiptMap;
String get currentSelectedConv => _currentConversationList.isNotEmpty String get currentSelectedConv =>
? _currentConversationList[_currentConversationList.length - 1] _currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
.conversationID .conversationID
: ""; : "";
ConvType? get currentSelectedConvType => _currentConversationList.isNotEmpty ConvType? get currentSelectedConvType =>
? _currentConversationList[_currentConversationList.length - 1] _currentConversationList.isNotEmpty
? _currentConversationList[_currentConversationList.length - 1]
.conversationType .conversationType
: null; : null;
setCurrentConversation(CurrentConversation value) { setCurrentConversation(CurrentConversation value) {
_currentConversationList.add(value); _currentConversationList.add(value);
@ -235,13 +241,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
_c2cMessageStatusShowTimer[userID] = _c2cMessageStatusShowTimer[userID] =
Timer.periodic(const Duration(seconds: 5), (timer) { Timer.periodic(const Duration(seconds: 5), (timer) {
_c2cMessageEditStatusMap[userID] = 0; _c2cMessageEditStatusMap[userID] = 0;
Timer? t = _c2cMessageStatusShowTimer[userID]; Timer? t = _c2cMessageStatusShowTimer[userID];
if (t != null && t.isActive) { if (t != null && t.isActive) {
// //
t.cancel(); t.cancel();
} }
}); });
} }
notifyListeners(); notifyListeners();
} }
@ -326,10 +332,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
prefs.remove(localMsgIDListKey); prefs.remove(localMsgIDListKey);
} }
Future<void> updateMessageFromController( Future<void> updateMessageFromController({required String msgID,
{required String msgID, required String conversationID,
required String conversationID, required ConvType conversationType}) async {
required ConvType conversationType}) async {
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>(); final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
V2TimMessage? newMessage = await tools.getExistingMessageByID( V2TimMessage? newMessage = await tools.getExistingMessageByID(
msgID: msgID, msgID: msgID,
@ -343,11 +348,11 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
Future<bool> refreshCurrentHistoryListForConversation( Future<bool> refreshCurrentHistoryListForConversation(
{HistoryMsgGetTypeEnum getType = {HistoryMsgGetTypeEnum getType =
HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG, HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
int lastMsgSeq = -1, int lastMsgSeq = -1,
required int count, required int count,
String? lastMsgID, String? lastMsgID,
required String convID, required String convID,
required ConvType convType}) async { required ConvType convType}) async {
final currentHistoryMsgList = messageListMap[convID]; final currentHistoryMsgList = messageListMap[convID];
final response = await _messageService.getHistoryMessageList( final response = await _messageService.getHistoryMessageList(
count: count, count: count,
@ -389,7 +394,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_preLoadImage(List<V2TimMessage> msgList) { _preLoadImage(List<V2TimMessage> msgList) {
List<V2TimMessage> needPreViewList = 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) { for (var msgItem in needPreViewList) {
V2TimImage? getImageFromList(V2TimImageTypesEnum imgType) { V2TimImage? getImageFromList(V2TimImageTypesEnum imgType) {
V2TimImage? img = MessageUtils.getImageFromImgList( V2TimImage? img = MessageUtils.getImageFromImgList(
@ -407,12 +412,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
image.resolve(configuration).addListener( image.resolve(configuration).addListener(
ImageStreamListener((ImageInfo image, bool synchronousCall) { ImageStreamListener((ImageInfo image, bool synchronousCall) {
final tempImg = image.image; final tempImg = image.image;
_preloadImageMap[msgItem.seq! + _preloadImageMap[msgItem.seq! +
msgItem.timestamp.toString() + msgItem.timestamp.toString() +
(msgItem.msgID ?? "")] = tempImg; (msgItem.msgID ?? "")] = tempImg;
print("cacheImage ${msgItem.msgID}"); print("cacheImage ${msgItem.msgID}");
})); }));
} catch (e) { } catch (e) {
print("cacheImage error ${msgItem.msgID}"); print("cacheImage error ${msgItem.msgID}");
} }
@ -513,13 +518,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
_c2cMessageActiveTimer[msg.sender ?? ""] = _c2cMessageActiveTimer[msg.sender ?? ""] =
Timer.periodic(const Duration(seconds: 30), (timer) { Timer.periodic(const Duration(seconds: 30), (timer) {
_c2cMessageFromUserActiveMap[msg.sender ?? ""] = false; _c2cMessageFromUserActiveMap[msg.sender ?? ""] = false;
Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""]; Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""];
if (t != null && t.isActive) { if (t != null && t.isActive) {
// //
t.cancel(); t.cancel();
} }
}); });
} }
} }
} catch (err) { } catch (err) {
@ -537,14 +542,14 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
V2TimMsgCreateInfoResult? res = await _messageService.createCustomMessage( V2TimMsgCreateInfoResult? res = await _messageService.createCustomMessage(
data: json.encode({ data: json.encode({
"businessID": "user_typing_status", "businessID": "user_typing_status",
"typingStatus": isEditing == true ? 1 : 0, "typingStatus": isEditing == true ? 1 : 0,
"userAction": 14, "userAction": 14,
"version": 0, "version": 0,
"actionParam": isEditing == true "actionParam": isEditing == true
? "EIMAMSG_InputStatus_Ing" ? "EIMAMSG_InputStatus_Ing"
: "EIMAMSG_InputStatus_End" : "EIMAMSG_InputStatus_End"
})); }));
if (res != null) { if (res != null) {
_sendMessage( _sendMessage(
id: res.id!, id: res.id!,
@ -559,9 +564,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
void refreshGroupApplicationList() async { void refreshGroupApplicationList() async {
final res = await _groupServices.getGroupApplicationList(); final res = await _groupServices.getGroupApplicationList();
_groupApplicationList = res.data?.groupApplicationList?.map((item) { _groupApplicationList = res.data?.groupApplicationList?.map((item) {
final V2TimGroupApplication applicationItem = item!; final V2TimGroupApplication applicationItem = item!;
return applicationItem; return applicationItem;
}).toList() ?? }).toList() ??
[]; [];
notifyListeners(); notifyListeners();
} }
@ -657,14 +662,17 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
final activeMessageList = _messageListMap[convID ?? currentSelectedConv]; final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
if (activeMessageList != null) { if (activeMessageList != null) {
final findeIndex = final findeIndex =
activeMessageList.indexWhere((element) => element.msgID == msgID); activeMessageList.indexWhere((element) => element.msgID == msgID);
if (findeIndex != -1) { if (findeIndex != -1) {
final findeIndex = final findeIndex =
activeMessageList.indexWhere((element) => element.msgID == msgID); activeMessageList.indexWhere((element) => element.msgID == msgID);
if (findeIndex != -1) { if (findeIndex != -1) {
final targetItem = activeMessageList[findeIndex]; final targetItem = activeMessageList[findeIndex];
targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED; targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
targetItem.id = DateTime.now().millisecondsSinceEpoch.toString(); targetItem.id = DateTime
.now()
.millisecondsSinceEpoch
.toString();
activeMessageList[findeIndex] = targetItem; activeMessageList[findeIndex] = targetItem;
} }
} }
@ -674,7 +682,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
onMessageModified(V2TimMessage modifiedMessage, [String? convID]) async { 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) ?? final String? exactId = TencentUtils.checkString(modifiedMessage.userID) ??
TencentUtils.checkString(modifiedMessage.groupID); TencentUtils.checkString(modifiedMessage.groupID);
final activeMessageList = _messageListMap[convID ?? exactId]; final activeMessageList = _messageListMap[convID ?? exactId];
@ -729,33 +740,78 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
print("message progress: $progress"); print("message progress: $progress");
} }
onMessageDownloadProgressCallback( Future<void> onMessageDownloadProgressCallback(
V2TimMessageDownloadProgress messageProgress) async { V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.isFinish) { print(messageProgress.toJson());
setMessageProgress(messageProgress.msgID, 100); final currentProgress = getMessageProgress(messageProgress.msgID);
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
downloadFile(); if (messageProgress.isFinish && currentProgress < 100) {
if (messageProgress.type == 0) { V2TimMessage? message = await _findAndRetrieveMessage(
final messages = await _messageService messageProgress.msgID);
.findMessages(messageIDList: [messageProgress.msgID]); _handleFinishedDownload(messageProgress, message);
final V2TimMessage? message = messages?.first;
if (message != null) {
updateAsyncMessage(
message,
TencentUtils.checkString(message.userID) ??
TencentUtils.checkString(message.groupID) ??
"");
}
}
return; return;
} }
if (messageProgress.totalSize != -1) {
int progress = _updateProgressIfNeeded(messageProgress, currentProgress);
(messageProgress.currentSize / messageProgress.totalSize * 100) }
.ceil();
if (progress > 1) { Future<V2TimMessage?> _findAndRetrieveMessage(String messageId) async {
setMessageProgress(messageProgress.msgID, progress); 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>(); final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? []; List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
V2TimMsgCreateInfoResult? textMessageInfo = V2TimMsgCreateInfoResult? textMessageInfo =
await _messageService.createTextMessage(text: text); await _messageService.createTextMessage(text: text);
textMessageInfo = await _messageService.createTextAtMessage( textMessageInfo = await _messageService.createTextAtMessage(
text: text + 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: [ atUserList: [
TencentUtils.checkString(messageBeenReplied.sender) ?? TencentUtils.checkString(messageBeenReplied.sender) ??
TencentUtils.checkString(messageBeenReplied.userID) ?? TencentUtils.checkString(messageBeenReplied.userID) ??
@ -857,7 +915,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (messageInfo != null) { if (messageInfo != null) {
final messageInfoWithSender = messageInfo.sender == null final messageInfoWithSender = messageInfo.sender == null
? tools.setUserInfoForMessage(messageInfo, messageInfo.id ?? textMessageInfo.id ?? "") ? tools.setUserInfoForMessage(
messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
: messageInfo; : messageInfo;
final hasNickName = messageBeenReplied.nickName != null && final hasNickName = messageBeenReplied.nickName != null &&
@ -898,8 +957,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return null; return null;
} }
Future<bool> setLocalCustomData( Future<bool> setLocalCustomData(String msgID, String localCustomData,
String msgID, String localCustomData, String conversationID) async { String conversationID) async {
final res = await _messageService.setLocalCustomData( final res = await _messageService.setLocalCustomData(
msgID: msgID, localCustomData: localCustomData); msgID: msgID, localCustomData: localCustomData);
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? []; List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
@ -918,8 +977,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return false; return false;
} }
Future<bool> setLocalCustomInt( Future<bool> setLocalCustomInt(String msgID, int localCustomInt,
String msgID, int localCustomInt, String conversationID) async { String conversationID) async {
final res = await _messageService.setLocalCustomInt( final res = await _messageService.setLocalCustomInt(
msgID: msgID, localCustomInt: localCustomInt); msgID: msgID, localCustomInt: localCustomInt);
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? []; List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
@ -956,7 +1015,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
String receiver = convType == ConvType.c2c ? convID : ''; String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : ''; String groupID = convType == ConvType.group ? convID : '';
final oldGroupType = final oldGroupType =
groupType != null ? GroupReceptAllowType.values[groupType.index] : null; groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
final sendMsgRes = await _messageService.sendMessage( final sendMsgRes = await _messageService.sendMessage(
id: id, id: id,
receiver: receiver, receiver: receiver,
@ -964,8 +1023,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
chatConfig.isShowGroupReadingStatus && chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group && convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null && ((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList! chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) || .contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && (chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList! chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))), .contains(oldGroupType))),
@ -985,6 +1044,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (isEditStatusMessage == false) { if (isEditStatusMessage == false) {
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField); updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
} }
if(_lifeCycle?.messageDidSend != null){
_lifeCycle!.messageDidSend(sendMsgRes);
}
return sendMsgRes; return sendMsgRes;
} }
@ -998,8 +1060,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners(); notifyListeners();
} }
updateMessage( updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes,
V2TimValueCallback<V2TimMessage> sendMsgRes,
String convID, String convID,
String id, String id,
ConvType convType, ConvType convType,
@ -1014,10 +1075,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
final findIdIndex = final findIdIndex =
currentHistoryMsgList.indexWhere((element) => element.id == id); currentHistoryMsgList.indexWhere((element) => element.id == id);
final targetIndex = findIdIndex == -1 final targetIndex = findIdIndex == -1
? currentHistoryMsgList ? currentHistoryMsgList
.indexWhere((element) => element.msgID == sendMsgResData.msgID) .indexWhere((element) => element.msgID == sendMsgResData.msgID)
: findIdIndex; : findIdIndex;
if (targetIndex != -1) { if (targetIndex != -1) {
currentHistoryMsgList[targetIndex] = sendMsgResData; currentHistoryMsgList[targetIndex] = sendMsgResData;
@ -1028,13 +1089,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
loadingMessage[convID]!.removeWhere((element) => element.id == id); loadingMessage[convID]!.removeWhere((element) => element.id == id);
} }
final oldGroupType = final oldGroupType =
groupType != null ? GroupReceptAllowType.values[groupType.index] : null; groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
if (chatConfig.isShowGroupReadingStatus && if (chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group && convType == ConvType.group &&
sendMsgRes.data?.msgID != null && sendMsgRes.data?.msgID != null &&
((chatConfig.groupReadReceiptPermissionList != null && ((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList! chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) || .contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && (chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList! chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType)))) { .contains(oldGroupType)))) {
@ -1045,11 +1106,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners(); notifyListeners();
} }
void updateAsyncMessage( void updateAsyncMessage(V2TimMessage message,
V2TimMessage message, String convID,) {
String convID, message.id = DateTime
) { .now()
message.id = DateTime.now().millisecondsSinceEpoch.toString(); .millisecondsSinceEpoch
.toString();
final activeMessageList = _messageListMap[convID]; final activeMessageList = _messageListMap[convID];
if (activeMessageList == null || activeMessageList.isEmpty) { if (activeMessageList == null || activeMessageList.isEmpty) {
@ -1068,18 +1130,19 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
List<V2TimMessage>? getMessageList(String conversationID) { 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 List<V2TimMessage> listWithTimestamp = [];
final interval = chatConfig.timeDividerConfig?.timeInterval ?? 300; final interval = chatConfig.timeDividerConfig?.timeInterval ?? 300;
for (var item in list) { for (var item in list) {
{ {
if (listWithTimestamp.isEmpty || if (listWithTimestamp.isEmpty ||
(listWithTimestamp[listWithTimestamp.length - 1].timestamp != (listWithTimestamp[listWithTimestamp.length - 1].timestamp !=
null && null &&
item.timestamp != null && item.timestamp != null &&
(item.timestamp! - (item.timestamp! -
listWithTimestamp[listWithTimestamp.length - 1] listWithTimestamp[listWithTimestamp.length - 1]
.timestamp! > .timestamp! >
interval))) { interval))) {
listWithTimestamp.add(V2TimMessage( listWithTimestamp.add(V2TimMessage(
userID: '', userID: '',
@ -1098,7 +1161,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
HistoryMessagePosition getMessageListPosition(String? conversationID) { HistoryMessagePosition getMessageListPosition(String? conversationID) {
final HistoryMessagePosition? position = final HistoryMessagePosition? position =
_historyMessagePositionMap[conversationID]; _historyMessagePositionMap[conversationID];
if (position == null) { if (position == null) {
_historyMessagePositionMap[conversationID ?? currentSelectedConv] = _historyMessagePositionMap[conversationID ?? currentSelectedConv] =
HistoryMessagePosition.bottom; HistoryMessagePosition.bottom;
@ -1108,8 +1171,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
} }
void setMessageListPosition( void setMessageListPosition(String conversationID,
String conversationID, HistoryMessagePosition position) { HistoryMessagePosition position) {
_historyMessagePositionMap[conversationID] = position; _historyMessagePositionMap[conversationID] = position;
notifyListeners(); notifyListeners();
} }

View File

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

View File

@ -1,7 +1,9 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.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/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_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/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/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.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' 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); typedef EmptyAvatarBuilder = Widget Function(BuildContext context);
@ -61,7 +63,7 @@ class CoreServicesImpl implements CoreServices {
setGlobalConfig(TIMUIKitConfig? config) { setGlobalConfig(TIMUIKitConfig? config) {
final TUISelfInfoViewModel selfInfoViewModel = final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>(); serviceLocator<TUISelfInfoViewModel>();
final TUISettingModel settingModel = serviceLocator<TUISettingModel>(); final TUISettingModel settingModel = serviceLocator<TUISettingModel>();
selfInfoViewModel.globalConfig = config; selfInfoViewModel.globalConfig = config;
settingModel.init(); settingModel.init();
@ -73,21 +75,20 @@ class CoreServicesImpl implements CoreServices {
} }
@override @override
Future<bool?> init( Future<bool?> init({
{ /// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error. ValueChanged<TIMCallback>? onTUIKitCallbackListener,
ValueChanged<TIMCallback>? onTUIKitCallbackListener, required int sdkAppID,
required int sdkAppID, required LogLevelEnum loglevel,
required LogLevelEnum loglevel, required V2TimSDKListener listener,
required V2TimSDKListener listener, LanguageEnum? language,
LanguageEnum? language, String? extraLanguage,
String? extraLanguage, TIMUIKitConfig? config,
TIMUIKitConfig? config,
/// Specify the current device platform, mobile or desktop, based on your needs. /// 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, /// TUIKit will automatically determine the platform if no specification is provided. DeviceType? platform,
DeviceType? platform, DeviceType? platform,
VoidCallback? onWebLoginSuccess}) async { VoidCallback? onWebLoginSuccess}) async {
if (platform != null) { if (platform != null) {
TUIKitScreenUtils.deviceType = platform; TUIKitScreenUtils.deviceType = platform;
} }
@ -166,13 +167,13 @@ class CoreServicesImpl implements CoreServices {
void addInitListener() { void addInitListener() {
final TUIFriendShipViewModel tuiFriendShipViewModel = final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>(); serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>(); serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>(); serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel = final TUIGroupListenerModel tuiGroupListenerModel =
serviceLocator<TUIGroupListenerModel>(); serviceLocator<TUIGroupListenerModel>();
tuiFriendShipViewModel.addFriendListener(); tuiFriendShipViewModel.addFriendListener();
tuiConversationViewModel.setConversationListener(); tuiConversationViewModel.setConversationListener();
@ -182,13 +183,13 @@ class CoreServicesImpl implements CoreServices {
void removeListener() { void removeListener() {
final TUIFriendShipViewModel tuiFriendShipViewModel = final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>(); serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>(); serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>(); serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel = final TUIGroupListenerModel tuiGroupListenerModel =
serviceLocator<TUIGroupListenerModel>(); serviceLocator<TUIGroupListenerModel>();
tuiFriendShipViewModel.removeFriendshipListener(); tuiFriendShipViewModel.removeFriendshipListener();
tuiConversationViewModel.removeConversationListener(); tuiConversationViewModel.removeConversationListener();
@ -203,15 +204,16 @@ class CoreServicesImpl implements CoreServices {
}); });
} else { } else {
print( print(
"TUIKit Callback: ${callbackValue.type} - ${callbackValue.stackTrace}"); "TUIKit Callback: ${callbackValue.type} - ${callbackValue
.stackTrace}");
} }
} }
initDataModel() { initDataModel() {
final TUIFriendShipViewModel tuiFriendShipViewModel = final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>(); serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>(); serviceLocator<TUIConversationViewModel>();
tuiFriendShipViewModel.initFriendShipModel(); tuiFriendShipViewModel.initFriendShipModel();
tuiConversationViewModel.initConversation(); tuiConversationViewModel.initConversation();
@ -219,11 +221,11 @@ class CoreServicesImpl implements CoreServices {
clearData() { clearData() {
final TUIFriendShipViewModel tuiFriendShipViewModel = final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>(); serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = final TUIConversationViewModel tuiConversationViewModel =
serviceLocator<TUIConversationViewModel>(); serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = final TUIChatGlobalModel tuiChatViewModel =
serviceLocator<TUIChatGlobalModel>(); serviceLocator<TUIChatGlobalModel>();
tuiFriendShipViewModel.clearData(); tuiFriendShipViewModel.clearData();
tuiConversationViewModel.clearData(); tuiConversationViewModel.clearData();
@ -233,18 +235,18 @@ class CoreServicesImpl implements CoreServices {
updateUserStatusList(List<V2TimUserStatus> newUserStatusList) { updateUserStatusList(List<V2TimUserStatus> newUserStatusList) {
try { try {
final TUISelfInfoViewModel selfInfoViewModel = final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>(); serviceLocator<TUISelfInfoViewModel>();
if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) { if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) {
return; return;
} }
final TUIFriendShipViewModel tuiFriendShipViewModel = final TUIFriendShipViewModel tuiFriendShipViewModel =
serviceLocator<TUIFriendShipViewModel>(); serviceLocator<TUIFriendShipViewModel>();
final currentUserStatusList = tuiFriendShipViewModel.userStatusList; final currentUserStatusList = tuiFriendShipViewModel.userStatusList;
for (int i = 0; i < newUserStatusList.length; i++) { for (int i = 0; i < newUserStatusList.length; i++) {
final int indexInCurrentUserList = currentUserStatusList.indexWhere( final int indexInCurrentUserList = currentUserStatusList.indexWhere(
(element) => element.userID == newUserStatusList[i].userID); (element) => element.userID == newUserStatusList[i].userID);
if (indexInCurrentUserList == -1) { if (indexInCurrentUserList == -1) {
currentUserStatusList.add(newUserStatusList[i]); currentUserStatusList.add(newUserStatusList[i]);
} else { } else {
@ -285,28 +287,50 @@ class CoreServicesImpl implements CoreServices {
isLoginSuccess = true; isLoginSuccess = true;
addInitListener(); addInitListener();
initDataModel(); 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 = final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>(); serviceLocator<TUISelfInfoViewModel>();
getUsersInfo(userIDList: [_userID]).then((res) => { if (_loginInfo != null) {
if (res.code == 0) selfInfoViewModel.setLoginInfo(_loginInfo);
{ }
_loginInfo = res.data![0],
selfInfoViewModel.setLoginInfo(_loginInfo!)
}
});
} }
// Deprecated // Deprecated
void didLoginOut() { void didLoginOut() {
removeListener(); removeListener();
clearData(); clearData();
getUsersInfo(userIDList: [_userID]).then((res) => { _loginInfo = null;
if (res.code == 0) serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo);
{
_loginInfo = res.data![0],
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo!)
}
});
} }
@override @override
@ -350,10 +374,10 @@ class CoreServicesImpl implements CoreServices {
return TencentImSDKPlugin.v2TIMManager return TencentImSDKPlugin.v2TIMManager
.getOfflinePushManager() .getOfflinePushManager()
.setOfflinePushConfig( .setOfflinePushConfig(
businessID: businessID?.toDouble() ?? 0, businessID: businessID?.toDouble() ?? 0,
token: token, token: token,
isTPNSToken: isTPNSToken, isTPNSToken: isTPNSToken,
); );
} }
@override @override

View File

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

File diff suppressed because one or more lines are too long

View File

@ -689,5 +689,13 @@
"k_066fxsz": "查看文件夹", "k_066fxsz": "查看文件夹",
"k_0k432y2": "无法发送,包含文件夹", "k_0k432y2": "无法发送,包含文件夹",
"k_002wb4y": "会话", "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:flutter/cupertino.dart';
import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:scroll_to_index/scroll_to_index.dart';
@ -139,11 +139,13 @@ class TIMUIKitChatController {
assert(groupID != null || convType != ConvType.group); assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c); assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) { if (isNavigateToMessageListBottom && scrollController != null) {
scrollController!.animateTo( try{
scrollController!.position.minScrollExtent, scrollController?.animateTo(
duration: const Duration(milliseconds: 200), scrollController!.position.minScrollExtent,
curve: Curves.ease, duration: const Duration(milliseconds: 200),
); curve: Curves.ease,
);
}catch(e){}
} }
return globalChatModel.sendMessageFromController( return globalChatModel.sendMessageFromController(
priority: priority, priority: priority,
@ -160,11 +162,13 @@ class TIMUIKitChatController {
} else if (model != null) { } else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat` /// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat`
if (isNavigateToMessageListBottom && scrollController != null) { if (isNavigateToMessageListBottom && scrollController != null) {
scrollController?.animateTo( try{
scrollController!.position.minScrollExtent, scrollController?.animateTo(
duration: const Duration(milliseconds: 200), scrollController!.position.minScrollExtent,
curve: Curves.ease, duration: const Duration(milliseconds: 200),
); curve: Curves.ease,
);
}catch(e){}
} }
return model!.sendMessageFromController( return model!.sendMessageFromController(
priority: priority, priority: priority,
@ -230,11 +234,13 @@ class TIMUIKitChatController {
assert(groupID != null || convType != ConvType.group); assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c); assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) { if (isNavigateToMessageListBottom && scrollController != null) {
scrollController!.animateTo( try{
scrollController!.position.minScrollExtent, scrollController?.animateTo(
duration: const Duration(milliseconds: 200), scrollController!.position.minScrollExtent,
curve: Curves.ease, duration: const Duration(milliseconds: 200),
); curve: Curves.ease,
);
}catch(e){}
} }
return globalChatModel.sendReplyMessageFromController( return globalChatModel.sendReplyMessageFromController(
text: messageText, text: messageText,

View File

@ -1,7 +1,7 @@
// ignore_for_file: prefer_typing_uninitialized_variables // ignore_for_file: prefer_typing_uninitialized_variables
import 'package:intl/intl.dart'; 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 { class TimeAgo {
List<String> dayMap() { List<String> dayMap() {

View File

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

View File

@ -104,24 +104,14 @@ class TIMUIKitMessageTooltipState
} }
if (PlatformUtils().isDesktop) { if (PlatformUtils().isDesktop) {
if (widget.message.fileElem != null) { if (widget.message.fileElem != null) {
if (globalModal.getMessageProgress(widget.message.msgID) == 100) { String savePath = TencentUtils.checkString(
String savePath = globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
globalModal.getFileMessageLocation(widget.message.msgID); widget.message.fileElem?.path ??
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 ?? '';
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) { if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath; filePath = savePath;
globalModal.setMessageProgress(widget.message.msgID!, 100);
isShowOpenFile = true; isShowOpenFile = true;
return; return;
} }
@ -399,14 +389,14 @@ class TIMUIKitMessageTooltipState
} }
_onOpenDesktop(String path) { _onOpenDesktop(String path) {
if (PlatformUtils().isDesktop) { try {
OpenFile.open(path); if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
} else { launchUrl(Uri.file(path));
launchUrl( } else {
Uri.parse(path), OpenFile.open(path);
mode: LaunchMode.externalApplication, }
); // ignore: empty_catches
} } catch (e) {}
} }
_onTap(String operation, TUIChatSeparateViewModel model) async { _onTap(String operation, TUIChatSeparateViewModel model) async {
@ -415,32 +405,40 @@ class TIMUIKitMessageTooltipState
switch (operation) { switch (operation) {
case "open": case "open":
if (widget.message.fileElem != null) { 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 ?? widget.message.fileElem?.path ??
""); "");
} else if (widget.message.imageElem != null) { } else if (widget.message.imageElem != null) {
_onOpenDesktop(widget.message.imageElem!.imageList?[0]?.localUrl ?? _onOpenDesktop(TencentUtils.checkString(
widget.message.imageElem?.path ?? widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
""); "");
} else if (widget.message.videoElem != null) { } else if (widget.message.videoElem != null) {
_onOpenDesktop(widget.message.videoElem!.localVideoUrl ?? _onOpenDesktop(TencentUtils.checkString(
widget.message.videoElem?.videoPath ?? widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
""); "");
} }
break; break;
case "finder": case "finder":
String savePath = ""; String savePath = "";
if (widget.message.fileElem != null) { 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 ?? widget.message.fileElem?.path ??
""); "");
} else if (widget.message.imageElem != null) { } else if (widget.message.imageElem != null) {
savePath = (widget.message.imageElem!.imageList?[0]?.localUrl ?? savePath = (TencentUtils.checkString(
widget.message.imageElem?.path ?? widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
""); "");
} else if (widget.message.videoElem != null) { } else if (widget.message.videoElem != null) {
savePath = (widget.message.videoElem!.localVideoUrl ?? savePath = (TencentUtils.checkString(
widget.message.videoElem?.videoPath ?? widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
""); "");
} }
final String fileDir = path.dirname(savePath); 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/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.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/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar_title.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar_title.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.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/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TIMUIKitAppBarTitle extends StatelessWidget { class TIMUIKitAppBarTitle extends StatelessWidget {
final Widget? title; final Widget? title;

View File

@ -5,6 +5,7 @@ import 'dart:math';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.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/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart';
import 'package:tencent_open_file/tencent_open_file.dart'; import 'package:tencent_open_file/tencent_open_file.dart';
@ -49,10 +50,12 @@ class TIMUIKitFileElem extends StatefulWidget {
class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> { class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
String filePath = ""; String filePath = "";
bool isDownloading = false; bool isWebDownloading = false;
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
int downloadProgress = 0; int downloadProgress = 0;
late V2TimAdvancedMsgListener advancedMsgListener; late V2TimAdvancedMsgListener advancedMsgListener;
final GlobalKey containerKey = GlobalKey();
double? containerHeight;
@override @override
void dispose() { void dispose() {
@ -75,18 +78,18 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
(V2TimMessageDownloadProgress messageProgress) async { (V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.msgID == widget.message.msgID) { if (messageProgress.msgID == widget.message.msgID) {
if (messageProgress.isFinish) { if (messageProgress.isFinish) {
if(mounted){ if (mounted) {
setState(() { setState(() {
downloadProgress = 100; downloadProgress = 100;
}); });
} }
} else { } else {
if(mounted){ final currentProgress =
(messageProgress.currentSize / messageProgress.totalSize * 100)
.floor();
if (mounted && currentProgress > downloadProgress) {
setState(() { setState(() {
downloadProgress = (messageProgress.currentSize / downloadProgress = currentProgress;
messageProgress.totalSize *
100)
.ceil();
}); });
} }
} }
@ -110,30 +113,22 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
return true; return true;
} }
String savePath = TencentUtils.checkString(
if (model.getMessageProgress(widget.messageID) == 100 || model.getFileMessageLocation(widget.messageID)) ??
downloadProgress == 100) { TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
String savePath = widget.message.fileElem?.path ??
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 ?? '';
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.messageID != null) { if (f.existsSync() && widget.messageID != null) {
filePath = savePath; filePath = savePath;
setState(() { if (downloadProgress != 100) {
downloadProgress = 100; setState(() {
}); downloadProgress = 100;
model.setMessageProgress(widget.messageID!, 100); });
}
if (model.getMessageProgress(widget.messageID) != 100) {
model.setMessageProgress(widget.messageID!, 100);
}
return true; return true;
} }
return false; return false;
@ -151,10 +146,12 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
} }
addUrlToWaitingPath() async { addUrlToWaitingPath(TUITheme theme) async {
if (widget.messageID != null) { if (widget.messageID != null) {
model.addWaitingList(widget.messageID!); 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(); 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 { 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().isMobile) {
if (PlatformUtils().isIOS) { if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission( if (!await Permissions.checkPermission(
@ -229,6 +244,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
} }
void downloadWebFile(String fileUrl) async { void downloadWebFile(String fileUrl) async {
if (mounted) {
setState(() {
isWebDownloading = true;
});
}
String fileName = Uri.parse(fileUrl).pathSegments.last; String fileName = Uri.parse(fileUrl).pathSegments.last;
try { try {
http.Response response = await http.get( http.Response response = await http.get(
@ -255,6 +275,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
..style.display = "none" ..style.display = "none"
..click(); ..click();
} }
if (mounted) {
setState(() {
isWebDownloading = false;
});
}
} }
@override @override
@ -280,111 +305,144 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
final String fileName = widget.fileElem!.fileName!; final String fileName = widget.fileElem!.fileName!;
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)]; fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
} }
return TIMUIKitMessageReactionWrapper( final RenderBox? containerRenderBox =
chatModel: widget.chatModel, containerKey.currentContext?.findRenderObject() as RenderBox?;
isShowJump: widget.isShowJump, if (containerRenderBox != null) {
clearJump: widget.clearJump, containerHeight = containerRenderBox.size.height;
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;
}
if (checkIsWaiting()) { return Row(
onTIMCallback( key: containerKey,
TIMCallback( mainAxisSize: MainAxisSize.min,
type: TIMCallbackType.INFO, children: [
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"), if (widget.isSelf && isWebDownloading)
infoCode: 6660413), Container(
); margin: const EdgeInsets.only(top: 2),
return; child: LoadingAnimationWidget.threeArchedCircle(
} else { color: theme.weakTextColor ?? Colors.grey,
await addUrlToWaitingPath(); size: 20,
} ),
await downloadFile(theme); ),
}, TIMUIKitMessageReactionWrapper(
child: Container( chatModel: widget.chatModel,
width: 237, isShowJump: widget.isShowJump,
decoration: BoxDecoration( clearJump: widget.clearJump,
border: Border.all( isFromSelf: widget.message.isSelf ?? true,
color: isShowMessageReaction: widget.isShowMessageReaction ?? true,
theme.weakDividerColor ?? CommonColor.weakDividerColor, message: widget.message,
), child: GestureDetector(
borderRadius: borderRadius), onTap: () async {
child: Stack(children: [ try {
ClipRRect( if (PlatformUtils().isWeb) {
// if (!isWebDownloading) {
borderRadius: borderRadius, downloadWebFile(widget.fileElem?.path ?? "");
child: LinearProgressIndicator( }
minHeight: 66, return;
value: (received == 100 ? 0 : received) / 100, }
backgroundColor: received == 100 if (await hasFile()) {
? theme.weakBackgroundColor if (received == 100) {
: Colors.white, tryOpenFile(context, theme);
valueColor: AlwaysStoppedAnimation( } else {
theme.lightPrimaryMaterialColor.shade50)), onTIMCallback(
), TIMCallback(
Padding( type: TIMCallbackType.INFO,
padding: infoRecommendText: TIM_t("正在下载中"),
const EdgeInsets.symmetric(vertical: 8, horizontal: 12), infoCode: 6660411,
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;
}
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/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/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.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:tencent_open_file/tencent_open_file.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'dart:io'; import 'dart:io';
@ -68,7 +69,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
String getBigPicUrl() { String getOriginImgURL() {
// //
V2TimImage? img = MessageUtils.getImageFromImgList( V2TimImage? img = MessageUtils.getImageFromImgList(
widget.message.imageElem!.imageList, widget.message.imageElem!.imageList,
@ -257,7 +258,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
return await _saveImageToLocal(context, path, return await _saveImageToLocal(context, path,
isAsset: true, theme: theme); isAsset: true, theme: theme);
} else { } else {
String imgUrl = getBigPicUrl(); String imgUrl = getOriginImgURL();
if (widget.message.imageElem!.imageList![0]!.localUrl != '' && if (widget.message.imageElem!.imageList![0]!.localUrl != '' &&
widget.message.imageElem!.imageList![0]!.localUrl != null) { widget.message.imageElem!.imageList![0]!.localUrl != null) {
File f = File(widget.message.imageElem!.imageList![0]!.localUrl!); File f = File(widget.message.imageElem!.imageList![0]!.localUrl!);
@ -300,6 +301,14 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
return img; return img;
} }
void launchDesktopFile(String path) {
if (PlatformUtils().isWindows) {
OpenFile.open(path);
} else {
launchUrl(Uri.file(path));
}
}
Widget errorPage(theme) => Container( Widget errorPage(theme) => Container(
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
color: theme.black, color: theme.black,
@ -310,16 +319,85 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
child: errorDisplay(context, theme), 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( Widget _renderNetworkImage(
dynamic heroTag, double? positionRadio, TUITheme? theme, dynamic heroTag, double? positionRadio, TUITheme? theme,
{String? path, V2TimImage? originalImg, V2TimImage? smallImg}) { {String? path, V2TimImage? originalImg, V2TimImage? smallImg}) {
try { try {
final isDesktopScreen = String originImgUrl = originalImg?.url ?? getOriginImgURL();
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; if (originImgUrl.isEmpty && smallImg?.url != null) {
String bigImgUrl = originalImg?.url ?? getBigPicUrl(); originImgUrl = smallImg!.url!;
if (bigImgUrl.isEmpty && smallImg?.url != null) {
bigImgUrl = 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( return Stack(
alignment: widget.message.isSelf ?? true alignment: widget.message.isSelf ?? true
? AlignmentDirectional.topEnd ? AlignmentDirectional.topEnd
@ -327,40 +405,30 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
children: [ children: [
getImage( getImage(
GestureDetector( GestureDetector(
onTap: () { onTap: () async {
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
launchUrl( TUIKitWidePopup.showMedia(
Uri.parse(widget.message.imageElem?.path ?? ""), aspectRatio: positionRadio,
mode: LaunchMode.externalApplication, context: context,
); mediaURL: widget.message.imageElem?.path ?? "",
onClickOrigin: () => launchUrl(
Uri.parse(widget.message.imageElem?.path ?? ""),
mode: LaunchMode.externalApplication,
));
return; return;
} }
if (isDesktopScreen) { if (PlatformUtils().isDesktop) {
if (TencentUtils.checkString(widget _handleOnTapPreviewImageOnDesktop(
.message.imageElem!.imageList![0]!.localUrl) != positionRadio: positionRadio,
null && originImgUrl: originImgUrl,
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));
}
} else { } else {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
opaque: false, // set to false opaque: false, // set to false
pageBuilder: (_, __, ___) => ImageScreen( pageBuilder: (_, __, ___) => ImageScreen(
imageProvider: CachedNetworkImageProvider( imageProvider: CachedNetworkImageProvider(
path ?? bigImgUrl, path ?? originImgUrl,
cacheKey: widget.message.msgID, cacheKey: widget.message.msgID,
), ),
heroTag: heroTag, heroTag: heroTag,
@ -371,32 +439,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
); );
} }
}, },
child: Container( child: positionRadio != null
constraints: ? AspectRatio(
const BoxConstraints(minWidth: 20, minHeight: 20), aspectRatio: positionRadio,
child: Hero( child: imageWidget,
tag: heroTag, )
child: PlatformUtils().isWeb : imageWidget,
? 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),
)),
),
), ),
imageElem: e) imageElem: e)
], ],
@ -436,6 +484,25 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
final isDesktopScreen = final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; 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( return Stack(
alignment: AlignmentDirectional.topStart, alignment: AlignmentDirectional.topStart,
children: [ children: [
@ -450,11 +517,11 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (PlatformUtils().isDesktop) { if (PlatformUtils().isDesktop) {
if (PlatformUtils().isWindows) { TUIKitWidePopup.showMedia(
OpenFile.open(showImage); aspectRatio: positionRadio,
} else { mediaLocalPath: showImage,
launchUrl(Uri.file(showImage)); context: context,
} onClickOrigin: () => launchDesktopFile(showImage));
} else { } else {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
@ -470,22 +537,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
); );
} }
}, },
child: Container( child: positionRadio != null
constraints: ? AspectRatio(
const BoxConstraints(minWidth: 20, minHeight: 20), aspectRatio: positionRadio,
child: Hero( child: imageWidget,
tag: heroTag, )
child: preloadImage != null : imageWidget),
? RawImage(
image: preloadImage,
fit: BoxFit.contain,
)
: Image.file(
File(smallImage),
fit: BoxFit.contain,
),
),
)),
imageElem: null) imageElem: null)
], ],
); );
@ -623,6 +680,8 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
if (widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING) { if (widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING) {
isSent = true; isSent = true;
} }
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final heroTag = final heroTag =
"${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}"; "${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) { builder: (BuildContext context, BoxConstraints constraints) {
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: constraints.maxWidth * 0.5, maxWidth: constraints.maxWidth * (isDesktopScreen ? 0.4 : 0.5),
minWidth: 64, minWidth: 64,
maxHeight: 256, 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/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/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/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:tencent_open_file/tencent_open_file.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -166,6 +167,15 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
downloadMessageDetailAndSave(); downloadMessageDetailAndSave();
} }
void launchDesktopFile(String path) {
if (PlatformUtils().isWindows) {
OpenFile.open(path);
} else {
launchUrl(Uri.file(path));
}
}
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme; final theme = value.theme;
@ -175,10 +185,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
launchUrl( final url = widget.message.videoElem?.videoUrl ?? widget.message.videoElem?.videoPath ?? "";
Uri.parse(widget.message.videoElem?.videoPath ?? ""), TUIKitWidePopup.showMedia(
mode: LaunchMode.externalApplication, context: context,
); mediaURL: url,
onClickOrigin: () => launchUrl(
Uri.parse(url),
mode: LaunchMode.externalApplication,
));
return; return;
} }
if (PlatformUtils().isDesktop) { if (PlatformUtils().isDesktop) {
@ -188,19 +202,20 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
TencentUtils.checkString(videoElem.localVideoUrl); TencentUtils.checkString(videoElem.localVideoUrl);
final videoPath = TencentUtils.checkString(videoElem.videoPath); final videoPath = TencentUtils.checkString(videoElem.videoPath);
final videoUrl = videoElem.videoUrl; final videoUrl = videoElem.videoUrl;
if (localVideoUrl != null) { if (localVideoUrl != null) {
if (PlatformUtils().isWindows) { launchDesktopFile(localVideoUrl);
OpenFile.open(localVideoUrl); // todo
} else { // TUIKitWidePopup.showMedia(
launchUrl(Uri.file(localVideoUrl)); // context: context,
} // mediaPath: localVideoUrl,
// onClickOrigin: () => launchDesktopFile(localVideoUrl));
} else if (videoPath != null) { } else if (videoPath != null) {
if (PlatformUtils().isWindows) { launchDesktopFile(videoPath);
OpenFile.open(videoPath); // todo
} else { // TUIKitWidePopup.showMedia(
launchUrl(Uri.file(videoPath)); // context: context,
} // mediaPath: videoPath,
// onClickOrigin: () => launchDesktopFile(videoPath));
} else if (TencentUtils.isTextNotEmpty(videoUrl)) { } else if (TencentUtils.isTextNotEmpty(videoUrl)) {
onTIMCallback(TIMCallback( onTIMCallback(TIMCallback(
infoCode: 6660414, infoCode: 6660414,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -232,21 +232,23 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
final faceUrl = message.faceUrl ?? ""; final faceUrl = message.faceUrl ?? "";
final showName = message.nickName ?? message.userID ?? ""; final showName = message.nickName ?? message.userID ?? "";
final theme = Provider.of<TUIThemeViewModel>(context).theme; final theme = Provider.of<TUIThemeViewModel>(context).theme;
final isSelf = message.isSelf ?? false;
return Container( return Container(
margin: const EdgeInsets.only(top: 20), margin: const EdgeInsets.only(top: 20),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [ children: [
SizedBox( if(!isSelf) SizedBox(
width: 40, width: 40,
height: 40, height: 40,
child: Avatar(faceUrl: faceUrl, showName: showName), child: Avatar(faceUrl: faceUrl, showName: showName),
), ),
const SizedBox( if(!isSelf) const SizedBox(
width: 12, width: 12,
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [ children: [
Text(showName, Text(showName,
style: TextStyle(fontSize: 12, color: theme.weakTextColor)), style: TextStyle(fontSize: 12, color: theme.weakTextColor)),
@ -258,7 +260,15 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
child: _getMsgItem(message), 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:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:image_gallery_saver/image_gallery_saver.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:extended_image/extended_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:universal_html/html.dart' as html; 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'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class VideoScreen extends StatefulWidget { class VideoScreen extends StatefulWidget {
const VideoScreen( const VideoScreen({required this.message,
{required this.message, required this.heroTag,
required this.heroTag, required this.videoElement,
required this.videoElement, Key? key})
Key? key})
: super(key: key); : super(key: key);
final V2TimMessage message; final V2TimMessage message;
@ -40,13 +38,14 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
late VideoPlayerController videoPlayerController; late VideoPlayerController videoPlayerController;
late ChewieController chewieController; late ChewieController chewieController;
GlobalKey<ExtendedImageSlidePageState> slidePagekey = GlobalKey<ExtendedImageSlidePageState> slidePagekey =
GlobalKey<ExtendedImageSlidePageState>(); GlobalKey<ExtendedImageSlidePageState>();
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
bool isInit = false; bool isInit = false;
@override @override
initState() { initState() {
super.initState(); super.initState();
setVideoMessage(); setVideoPlayerController();
// //
SystemChrome.setPreferredOrientations([ SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeLeft,
@ -57,14 +56,16 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
} }
// //
Future<void> _saveNetworkVideo( Future<void> _saveNetworkVideo(context,
context, String videoUrl, {
String videoUrl, { bool isAsset = true,
bool isAsset = true, }) async {
}) async {
if (PlatformUtils().isWeb) { if (PlatformUtils().isWeb) {
RegExp exp = RegExp(r"((\.){1}[^?]{2,4})"); 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(); var xhr = html.HttpRequest();
xhr.open('get', videoUrl); xhr.open('get', videoUrl);
xhr.responseType = 'arraybuffer'; xhr.responseType = 'arraybuffer';
@ -78,7 +79,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
xhr.send(); xhr.send();
return; return;
} }
if(PlatformUtils().isMobile){ if (PlatformUtils().isMobile) {
if (PlatformUtils().isIOS) { if (PlatformUtils().isIOS) {
if (!await Permissions.checkPermission( if (!await Permissions.checkPermission(
context, context,
@ -91,17 +92,17 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if ((androidInfo.version.sdkInt) >= 33) { if ((androidInfo.version.sdkInt) >= 33) {
final videos = await Permissions.checkPermission( final videos = await Permissions.checkPermission(
context,Permission.videos.value, context, Permission.videos.value,
); );
if(!videos){ if (!videos) {
return; return;
} }
} else { } else {
final storage = await Permissions.checkPermission( final storage = await Permissions.checkPermission(
context, Permission.storage.value, context, Permission.storage.value,
); );
if(!storage){ if (!storage) {
return; return;
} }
} }
@ -243,67 +244,41 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
return width; return width;
} }
setVideoMessage() async { setVideoPlayerController() 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!,
// ));
if (!PlatformUtils().isWeb) { if (!PlatformUtils().isWeb) {
if (widget.message.msgID != null || widget.message.msgID != '') { if (TencentUtils.checkString(widget.message.msgID) != null &&
if (model.getMessageProgress(widget.message.msgID) == 100) { widget.videoElement.localVideoUrl == null) {
String savePath; String savePath = model.getFileMessageLocation(widget.message.msgID);
if (widget.message.videoElem!.localVideoUrl != null && File f = File(savePath);
widget.message.videoElem!.localVideoUrl != '') { if (f.existsSync()) {
savePath = widget.message.videoElem!.localVideoUrl!; widget.videoElement.localVideoUrl = savePath;
} else {
savePath = model.getFileMessageLocation(widget.message.msgID);
}
File f = File(savePath);
if (f.existsSync()) {
widget.videoElement.localVideoUrl =
model.getFileMessageLocation(widget.message.msgID);
}
} }
} }
} }
VideoPlayerController player = PlatformUtils().isWeb VideoPlayerController player = PlatformUtils().isWeb
? ((widget.videoElement.videoPath != null && ? ((TencentUtils.checkString(widget.videoElement.videoPath) != null) ||
widget.videoElement.videoPath!.isNotEmpty) || widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING ? VideoPlayerController.networkUrl(
? VideoPlayerController.network( Uri.parse(widget.videoElement.videoPath!),
widget.videoElement.videoPath!, )
) : (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
: (widget.videoElement.localVideoUrl == null || ? VideoPlayerController.networkUrl(
widget.videoElement.localVideoUrl == "") Uri.parse(widget.videoElement.videoUrl!),
? VideoPlayerController.network( )
widget.videoElement.videoUrl!, : VideoPlayerController.networkUrl(
) Uri.parse(widget.videoElement.localVideoUrl!),
: VideoPlayerController.network( ))
widget.videoElement.localVideoUrl!, : (TencentUtils.checkString(widget.videoElement.videoPath) != null ||
)) widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING)
: (widget.videoElement.videoPath != null && ? VideoPlayerController.file(File(widget.videoElement.videoPath!))
widget.videoElement.videoPath!.isNotEmpty) || : (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING ? VideoPlayerController.networkUrl(
? VideoPlayerController.file(File(widget.videoElement.videoPath!)) Uri.parse(widget.videoElement.videoUrl!),
: (widget.videoElement.localVideoUrl == null || )
widget.videoElement.localVideoUrl == "") : VideoPlayerController.file(File(
? VideoPlayerController.network( widget.videoElement.localVideoUrl!,
widget.videoElement.videoUrl!, ));
)
: VideoPlayerController.file(File(
widget.videoElement.localVideoUrl!,
));
await player.initialize(); await player.initialize();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
double w = getVideoWidth(); double w = getVideoWidth();
@ -315,7 +290,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
showControlsOnInitialize: false, showControlsOnInitialize: false,
allowPlaybackSpeedChanging: false, allowPlaybackSpeedChanging: false,
aspectRatio: w == 0 || h == 0 ? null : w / h, aspectRatio: w == 0 || h == 0 ? null : w / h,
customControls: VideoCustomControls(downloadFn: () async{ customControls: VideoCustomControls(downloadFn: () async {
return await _saveVideo(); return await _saveVideo();
})); }));
setState(() { setState(() {
@ -330,7 +305,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
didUpdateWidget(oldWidget) { didUpdateWidget(oldWidget) {
if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl || if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl ||
oldWidget.videoElement.videoPath != widget.videoElement.videoPath) { oldWidget.videoElement.videoPath != widget.videoElement.videoPath) {
setVideoMessage(); setVideoPlayerController();
} }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@ -350,55 +325,70 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return OrientationBuilder(builder: ((context, orientation) { return OrientationBuilder(builder: ((context, orientation) {
return Scaffold( return Material(
body: Container( color: Colors.transparent,
color: Colors.transparent, child: Container(
constraints: BoxConstraints.expand( color: Colors.transparent,
height: MediaQuery.of(context).size.height, constraints: BoxConstraints.expand(
), height: MediaQuery
child: ExtendedImageSlidePage( .of(context)
key: slidePagekey, .size
slidePageBackgroundHandler: (Offset offset, Size size) { .height,
if (orientation == Orientation.landscape) { ),
return Colors.black; child: ExtendedImageSlidePage(
} key: slidePagekey,
double opacity = 0.0; slidePageBackgroundHandler: (Offset offset, Size size) {
opacity = offset.distance / if (orientation == Orientation.landscape) {
(Offset(size.width, size.height).distance / 2.0); return Colors.black;
return Colors.black }
.withOpacity(min(1.0, max(1.0 - opacity, 0.0))); double opacity = 0.0;
}, opacity = offset.distance /
slideType: SlideType.onlyImage, (Offset(size.width, size.height).distance / 2.0);
child: ExtendedImageSlidePageHandler( return Colors.black
child: Container( .withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
color: Colors.black, },
child: isInit slideType: SlideType.onlyImage,
? Chewie( slideEndHandler: (Offset offset, {
controller: chewieController, ExtendedImageSlidePageState? state,
) ScaleEndDetails? details,
: const Center( }) {
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: child:
CircularProgressIndicator(color: Colors.white))), CircularProgressIndicator(color: Colors.white))),
heroBuilderForSlidingPage: (Widget result) { heroBuilderForSlidingPage: (Widget result) {
return Hero( return Hero(
tag: widget.heroTag, tag: widget.heroTag,
child: result, child: result,
flightShuttleBuilder: (BuildContext flightContext, flightShuttleBuilder: (BuildContext flightContext,
Animation<double> animation, Animation<double> animation,
HeroFlightDirection flightDirection, HeroFlightDirection flightDirection,
BuildContext fromHeroContext, BuildContext fromHeroContext,
BuildContext toHeroContext) { BuildContext toHeroContext) {
final Hero hero = final Hero hero =
(flightDirection == HeroFlightDirection.pop (flightDirection == HeroFlightDirection.pop
? fromHeroContext.widget ? fromHeroContext.widget
: toHeroContext.widget) as Hero; : 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: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/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/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/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.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: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 { class TUIKitWidePopup {
static OverlayEntry? entry; static OverlayEntry? entry;
@ -236,7 +244,7 @@ class TUIKitWidePopup {
if (isUseMaterialAlert) { if (isUseMaterialAlert) {
return showDialog( return showDialog(
barrierDismissible: false, barrierDismissible: true,
context: context, context: context,
builder: (context) { builder: (context) {
return WillPopScope( return WillPopScope(
@ -249,7 +257,8 @@ class TUIKitWidePopup {
content: contentWidget, content: contentWidget,
), ),
onWillPop: () { onWillPop: () {
return Future.value(false); isShow = false;
return Future.value(true);
}); });
}); });
} }
@ -276,4 +285,141 @@ class TUIKitWidePopup {
}); });
Overlay.of(context).insert(entry!); 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 dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "61.0.0" version: "62.0.0"
adaptive_action_sheet: adaptive_action_sheet:
dependency: "direct main" dependency: "direct main"
description: description:
@ -21,18 +21,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.13.0" version: "6.0.0"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -117,10 +117,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -141,26 +141,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.1"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced" sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "2.4.6"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.9" version: "7.2.10"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "7dd62d9faf105c434f3d829bbe9c4be02ec67f5ed94832222116122df67c5452" sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.6.0" version: "8.6.1"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -213,10 +213,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: camera_android name: camera_android
sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b sha256: f43d07f9d7228ea1ca87d22e30881bd68da4b78484a1fbd1f1408b412a41cefb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.10.8+2" version: "0.10.8+3"
camera_avfoundation: camera_avfoundation:
dependency: transitive dependency: transitive
description: description:
@ -285,10 +285,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.5.0"
collection: collection:
dependency: "direct main" dependency: "direct main"
description: description:
@ -345,14 +345,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" 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: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
desktop_drop: desktop_drop:
dependency: "direct main" dependency: "direct main"
description: description:
@ -365,10 +373,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "499c61743e13909c13374a8c209075385858c614b9c0f2487b5f9995eeaf7369" sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.1" version: "9.0.2"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -397,42 +405,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: extended_image name: extended_image
sha256: "73b7051722be79f9e8f8c120951fd9d3b22e6153fda6105c322fb88c066952d6" sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.1" version: "8.0.2"
extended_image_library: extended_image_library:
dependency: transitive dependency: transitive
description: description:
name: extended_image_library name: extended_image_library
sha256: f2eba5c8500c5a7ce1f0db703b787756cb3bc65cd3818636d5fe9a9ace011ee3 sha256: af3ff1c09c23ca7663f94272313d63499a6bd19121e99378e375e0cf2ac7a3e4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.1" version: "3.5.2"
extended_text: extended_text:
dependency: "direct main" dependency: "direct main"
description: description:
name: extended_text name: extended_text
sha256: "75ddf28ce7d5be33a050ff2179b6567b4b98e6225ad3e61e4c3748f7448c25f7" sha256: dec14c9b36d9bbaaf007da5998f5dc72a2dbd5b877601d7b7970bb42524b3ced
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.0.0" version: "11.0.1"
extended_text_field: extended_text_field:
dependency: "direct main" dependency: "direct main"
description: description:
name: extended_text_field name: extended_text_field
sha256: "6cf8c090de4dc1e309cf3b24cb9448d7463c6c17926b628cf0954631bf4e56db" sha256: e93248bb9b04a3e6c5e88a6a96e1c08cc765097657ab25aabe409e06a4f6760a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.0" version: "12.0.1"
extended_text_library: extended_text_library:
dependency: transitive dependency: transitive
description: description:
name: extended_text_library name: extended_text_library
sha256: "308b50cfcc8e3accf46a09cb692715fbd1097333817c15b0f7527de1766bc1ff" sha256: c06fbd8e3b6eedadf50cd6c109bbbd80921a6c43e4422d3b4ec9d4cb36ce4555
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.0.1" version: "11.0.2"
fast_i18n: fast_i18n:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -469,10 +477,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432
url: "https://pub.dev" url: "https://pub.dev"
source: hosted 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: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -498,10 +538,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.3.1"
flutter_easyrefresh: flutter_easyrefresh:
dependency: "direct main" dependency: "direct main"
description: description:
@ -530,10 +570,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0 sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.15" version: "0.6.17"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -562,10 +602,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: "6ff8c902c8056af9736de2689f63f81c42e2d642b9f4c79dbf8790ae48b63012" sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.6" version: "2.0.7"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -607,10 +647,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: html name: html
sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.3" version: "0.15.4"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -647,50 +687,74 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_gallery_saver name: image_gallery_saver
sha256: "009b7722cd8507fd72c7f2cb7cbc46d6e15ad0895469cfcc39a10f86e3556979" sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.3"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker name: image_picker
sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270" sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.7+5" version: "0.8.9"
image_picker_android: image_picker_android:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: c2f3c66400649bd132f721c88218945d6406f693092b2f741b79ae9cdb046e59 sha256: d2bab152deb2547ea6f53d82ebca9b7e77386bb706e5789e815d37e08ea475bb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.6+16" version: "0.8.7+3"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
name: image_picker_for_web name: image_picker_for_web
sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.12" version: "2.2.0"
image_picker_ios: image_picker_ios:
dependency: transitive dependency: transitive
description: description:
name: image_picker_ios name: image_picker_ios
sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted 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: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: image_picker_platform_interface name: image_picker_platform_interface
sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" sha256: "7c7b96bb9413a9c28229e717e6fd1e3edd1cc5569c1778fcca060ecf729b65ee"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted 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: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -775,10 +839,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -831,10 +895,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "28386bbe89ab5a7919a47cea99cdd1128e5a6e0bbd7eaafe20440ead84a15de3" sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.1" version: "4.0.2"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -919,58 +983,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.6" version: "2.1.7"
pedantic:
dependency: transitive
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
permission_handler: permission_handler:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" sha256: "415af30ba76a84faccfe1eb251fe1e4fdc790f876924c65ad7d6ed7a1404bcd6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.2.0" version: "10.4.2"
permission_handler_android: permission_handler_android:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.2.1" version: "10.3.0"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_apple name: permission_handler_apple
sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.8" version: "9.1.3"
permission_handler_platform_interface: permission_handler_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" sha256: "463a07cb7cc6c758a7a1c7da36ce666bb80a0b4b5e92df0fa36872e0ed456993"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.9.0" version: "3.11.1"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_windows name: permission_handler_windows
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.3"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -1079,58 +1135,58 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.2.0"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb sha256: b046999bf0ff58f04c364491bb803dcfa8f42e47b19c75478f53d323684a8cc1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.3.1"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.0"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.0"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -1147,14 +1203,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
shell:
dependency: transitive
description:
name: shell
sha256: fd3b7b97e5e7f734138543d5815c6cb6cdd2a1645a0def8ac8e05577cddfbe03
url: "https://pub.dev"
source: hosted
version: "2.0.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -1164,10 +1212,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
sqflite: sqflite:
dependency: transitive dependency: transitive
description: description:
@ -1180,26 +1228,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5" version: "2.4.5+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@ -1224,6 +1272,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" 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: tencent_cloud_chat_sdk:
dependency: transitive dependency: transitive
description: description:
@ -1236,18 +1292,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: tencent_cloud_uikit_core name: tencent_cloud_uikit_core
sha256: "0131874c7b15e181001c94f8a668f0ccae3006dea6e70d4e42e5531b63313a27" sha256: "0a0f43e4c4241b25d12a9e9f0ee91922ac800a42229d97e3d21d16041ace3104"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.8"
tencent_im_base: tencent_im_base:
dependency: "direct main" dependency: "direct main"
description: description:
name: tencent_im_base name: tencent_im_base
sha256: "9b8e712bf27ffae9b686ec532ee8417b8263eba8bab04f105e28a95de1807322" sha256: "0db83050452486571ee4ac07280a2fb4bbc07d297a54235b5cf12e46e79267d0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.57" version: "2.0.1"
tencent_im_sdk_plugin_platform_interface: tencent_im_sdk_plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1268,10 +1324,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: tencent_open_file name: tencent_open_file
sha256: "01f94f618da42e5593bbad0657fcd13cfc1c2360cca805d8cdfefe898cbe5429" sha256: bb92d2f052c150a45942c564fba13d150a1b7b47525e6fdd74ccc58058ba5dcf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.10" version: "4.0.11"
tencent_super_tooltip: tencent_super_tooltip:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1292,18 +1348,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.1"
tim_ui_kit_sticker_plugin: tim_ui_kit_sticker_plugin:
dependency: "direct main" dependency: "direct main"
description: description:
name: tim_ui_kit_sticker_plugin name: tim_ui_kit_sticker_plugin
sha256: "2a825d33076f319f6c1c87d58e2b0d650c9284ae4afd8efdc206f3e6f3582e64" sha256: c9b0c1261bb51a5dee54bb50c6a106061b1bb10731b9c815fc5175afa60175e2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.2.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -1324,10 +1380,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: tuple name: tuple
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -1348,26 +1404,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: universal_io name: universal_io
sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.11" version: "6.1.12"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.35" version: "6.0.36"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -1396,26 +1452,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.17" version: "2.0.18"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.7"
uuid: uuid:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1428,26 +1484,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: b96f10cbdfcbd03a65758633a43e7d04574438f059b1043104b5d61b23d38a4f sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.6" version: "1.1.7"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "57a8e6e24662a3bdfe3b3d61257db91768700c0b8f844e235877b56480f31c69" sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.6" version: "1.1.7"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "7430f5d834d0db4560d7b19863362cd892f1e52b43838553a3c5cdfc9ab28e5b" sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.6" version: "1.1.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -1460,18 +1516,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: video_player name: video_player
sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd sha256: "3fd106c74da32f336dc7feb65021da9b0207cb3124392935f1552834f7cce822"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:
name: video_player_android name: video_player_android
sha256: ae1c7d9a71c236a1bf9e567bd7ed4c90887e389a5f233b2192593f7f7395005c sha256: f338a5a396c845f4632959511cad3542cdf3167e1b2a1a948ef07f7123c03608
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.8" version: "2.4.9"
video_player_avfoundation: video_player_avfoundation:
dependency: transitive dependency: transitive
description: description:
@ -1500,10 +1556,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: wakelock_for_us name: wakelock_for_us
sha256: b73bfa90e5e764f41155063ae92fbd1e3a04ee6372e65ff7d288d2c3057f9498 sha256: a5bdd445e51a617f7c24be8165230391447301f622aacd050038cee7b41989b4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.3" version: "0.6.3+1"
wakelock_platform_interface: wakelock_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1548,18 +1604,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.4" version: "5.0.5"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
name: win32_registry name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1585,5 +1641,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.0.0 <3.2.0"
flutter: ">=3.10.0" flutter: ">=3.10.0"

View File

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