Upgrade UIKit to 2.1.3+1
This commit is contained in:
parent
085be1c0b4
commit
2fefab6196
25
CHANGELOG.md
25
CHANGELOG.md
|
|
@ -1,3 +1,28 @@
|
|||
## 2.1.3+1
|
||||
|
||||
### New Features
|
||||
|
||||
* Introduced [a new custom internationalization language scheme](https://www.tencentcloud.com/document/product/1047/52154?from=pub) that supports adding language packs, adding or modifying entries, and makes customizing i18n more accessible. This feature helps your app achieve a more convenient globalization process and easier customer acquisition worldwide.
|
||||
* Provided a seamless experience for previewing large images and playing videos within desktop environments (applications and web) by avoiding frequent page transitions. Enhanced the user experience for image previews and video playback. Please note that video playback is currently supported only on the web and not in desktop applications.
|
||||
* Supported to integrate with the new online customer service plugin (tencent_cloud_chat_customer_service_plugin).
|
||||
* Added two new life cycle hooks, `messageDidSend` and `messageShouldMount` to `ChatLifeCycle`.
|
||||
|
||||
### Improvements
|
||||
|
||||
* Optimized the usage, interface, and interaction of the sticker panel.
|
||||
* Enhanced mobile video playback interaction and UI.
|
||||
* Refined the error prompt when sending a 0 KB file fails.
|
||||
* Enabled users to close modals on desktop by clicking the bottom gray overlay area.
|
||||
* Improved the UI and interaction of image and video messages in the message list.
|
||||
* Added the ability to open self-sent file messages without downloading.
|
||||
* Optimized the download status animation of file messages on the web.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed an issue preventing mobile image previews from being dragged after zooming.
|
||||
* Resolved an issue that might cause the message selection status not to be removed after canceling a message forward action.
|
||||
* Addressed an issue that might cause the microphone usage not to end after sending a voice message, which means the microphone was not released.
|
||||
|
||||
## 2.1.2
|
||||
|
||||
### New Features
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
|
|
@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
|
||||
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_linux
|
||||
desktop_drop
|
||||
file_selector_linux
|
||||
pasteboard
|
||||
url_launcher_linux
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import audioplayers_darwin
|
|||
import desktop_drop
|
||||
import device_info_plus
|
||||
import fc_native_video_thumbnail
|
||||
import file_selector_macos
|
||||
import package_info_plus
|
||||
import pasteboard
|
||||
import path_provider_foundation
|
||||
|
|
@ -22,6 +23,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,6 +9,7 @@
|
|||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <fc_native_video_thumbnail/fc_native_video_thumbnail_plugin_c_api.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
|
@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
|
||||
FcNativeVideoThumbnailPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FcNativeVideoThumbnailPluginCApi"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
PasteboardPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PasteboardPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
audioplayers_windows
|
||||
desktop_drop
|
||||
fc_native_video_thumbnail
|
||||
file_selector_windows
|
||||
pasteboard
|
||||
permission_handler_windows
|
||||
url_launcher_windows
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_class.dart';
|
|||
|
||||
typedef MessageFunction = Future<V2TimMessage?> Function(V2TimMessage message);
|
||||
|
||||
typedef MessageFunctionNullCallback = Function(V2TimValueCallback<V2TimMessage> res);
|
||||
|
||||
typedef MessageFunctionOptional = Future<V2TimMessage?> Function(
|
||||
V2TimMessage message);
|
||||
|
||||
|
|
@ -46,10 +48,17 @@ abstract class DefaultLifeCycle {
|
|||
return list;
|
||||
}
|
||||
|
||||
static Future<bool> defaultBooleanSolution(dynamic) async {
|
||||
static Future<bool> defaultAsyncBooleanSolution(dynamic) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool defaultBooleanSolution(dynamic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static defaultNullCallbackSolution(dynamic) {
|
||||
}
|
||||
|
||||
static Future<bool> defaultAddFriend(
|
||||
String userID, String? remark, String? friendGroup, String? addWording,
|
||||
[BuildContext? context]) async {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ class BlockListLifeCycle {
|
|||
FutureBool Function(List<String> userIDList) shouldDeleteFromBlockList;
|
||||
|
||||
BlockListLifeCycle({
|
||||
this.shouldDeleteFromBlockList = DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldDeleteFromBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
|
||||
class ChatLifeCycle {
|
||||
/// Before a new message will be added to historical message list from long connection.
|
||||
|
|
@ -12,6 +13,9 @@ class ChatLifeCycle {
|
|||
/// Returns null can block the message from sending.
|
||||
MessageFunction messageWillSend;
|
||||
|
||||
/// After a new message been sent.
|
||||
MessageFunctionNullCallback messageDidSend;
|
||||
|
||||
/// After getting the latest message list from API,
|
||||
/// and before historical message list will be rendered.
|
||||
/// You may add or delete some messages here.
|
||||
|
|
@ -27,14 +31,20 @@ class ChatLifeCycle {
|
|||
/// You can make a second confirmation here by a modal, etc.
|
||||
FutureBool Function(String conversationID) shouldClearHistoricalMessageList;
|
||||
|
||||
|
||||
/// Before rendering a message to message list.
|
||||
bool Function(V2TimMessage msgID) messageShouldMount;
|
||||
|
||||
ChatLifeCycle({
|
||||
this.shouldClearHistoricalMessageList =
|
||||
DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldDeleteMessage = DefaultLifeCycle.defaultBooleanSolution,
|
||||
DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.shouldDeleteMessage = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.messageDidSend = DefaultLifeCycle.defaultNullCallbackSolution,
|
||||
this.didGetHistoricalMessageList =
|
||||
DefaultLifeCycle.defaultMessageListSolution,
|
||||
this.messageWillSend = DefaultLifeCycle.defaultMessageSolution,
|
||||
this.modifiedMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
|
||||
this.newMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
|
||||
this.messageShouldMount = DefaultLifeCycle.defaultBooleanSolution,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ConversationLifeCycle {
|
|||
this.conversationListWillMount =
|
||||
DefaultLifeCycle.defaultConversationListSolution,
|
||||
this.shouldClearHistoricalMessageForConversation =
|
||||
DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldDeleteConversation = DefaultLifeCycle.defaultBooleanSolution,
|
||||
DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.shouldDeleteConversation = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ class NewContactLifeCycle {
|
|||
|
||||
NewContactLifeCycle({
|
||||
this.shouldAcceptContactApplication =
|
||||
DefaultLifeCycle.defaultBooleanSolution,
|
||||
DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.shouldRefuseContactApplication =
|
||||
DefaultLifeCycle.defaultBooleanSolution,
|
||||
DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ class ProfileLifeCycle {
|
|||
FutureBool Function(String userID) didRemarkUpdated;
|
||||
|
||||
ProfileLifeCycle({
|
||||
this.didRemarkUpdated = DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.didRemarkUpdated = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.didGetFriendInfo = DefaultLifeCycle.defaultFriendInfoSolution,
|
||||
this.shouldAddToBlockList = DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldAddFriend = DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldDeleteFriend = DefaultLifeCycle.defaultBooleanSolution,
|
||||
this.shouldAddToBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.shouldAddFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
this.shouldDeleteFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart';
|
||||
import 'package:tencent_im_base/tencent_im_base.dart';
|
||||
|
|
@ -173,4 +174,14 @@ class TUIChatModelTools {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> hasZeroSize(String filePath) async {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
final fileSize = await file.length();
|
||||
return fileSize == 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
// ignore: unnecessary_import
|
||||
|
|
@ -177,16 +178,18 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
setInputField = onChangeInputField;
|
||||
conversationType = convType;
|
||||
conversationID = convID;
|
||||
|
||||
_groupType = null;
|
||||
isGroupExist = true;
|
||||
_groupInfo = null;
|
||||
groupMemberList?.clear();
|
||||
selfMemberInfo = null;
|
||||
|
||||
if (conversationType == ConvType.group) {
|
||||
globalModel.refreshGroupApplicationList();
|
||||
getGroupInfo(groupID ?? convID);
|
||||
loadGroupInfo(groupID ?? convID);
|
||||
loadGroupMemberList(groupID: groupID ?? convID);
|
||||
} else {
|
||||
_groupType = null;
|
||||
isGroupExist = true;
|
||||
_groupInfo = null;
|
||||
groupMemberList?.clear();
|
||||
selfMemberInfo = null;
|
||||
notifyListeners();
|
||||
}
|
||||
if (conversationType == ConvType.c2c) {
|
||||
|
|
@ -482,18 +485,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
loadGroupInfo(String groupID) async {
|
||||
final groupInfo =
|
||||
await _groupServices.getGroupsInfo(groupIDList: [groupID]);
|
||||
if (groupInfo != null) {
|
||||
final groupRes = groupInfo.first;
|
||||
if (groupRes.resultCode == 0) {
|
||||
_groupInfo = groupRes.groupInfo;
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> loadGroupMemberList(
|
||||
{required String groupID, int count = 100, String? seq}) async {
|
||||
final String? nextSeq = await _loadGroupMemberListFunction(
|
||||
|
|
@ -503,7 +494,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
groupID: groupID, count: count, seq: nextSeq);
|
||||
} else {
|
||||
selfMemberInfo = groupMemberList
|
||||
?.firstWhere((e) => e?.userID == selfModel.loginInfo?.userID);
|
||||
?.firstWhereOrNull((e) => e?.userID == selfModel.loginInfo?.userID);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -513,38 +504,49 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
if (seq == null || seq == "" || seq == "0") {
|
||||
groupMemberList?.clear();
|
||||
}
|
||||
final res = await _groupServices.getGroupMemberList(
|
||||
groupID: groupID,
|
||||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
|
||||
count: count,
|
||||
nextSeq: seq ?? groupMemberListSeq);
|
||||
final groupMemberListRes = res.data;
|
||||
if (res.code == 0 && groupMemberListRes != null) {
|
||||
final groupMemberListTemp = groupMemberListRes.memberInfoList ?? [];
|
||||
groupMemberList = [...?groupMemberList, ...groupMemberListTemp];
|
||||
groupMemberListSeq = groupMemberListRes.nextSeq ?? "0";
|
||||
} else if (res.code == 10010) {
|
||||
isGroupExist = false;
|
||||
} else if (res.code == 10007) {
|
||||
isNotAMember = true;
|
||||
try {
|
||||
final res = await _groupServices.getGroupMemberList(
|
||||
groupID: groupID,
|
||||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
|
||||
count: count,
|
||||
nextSeq: seq ?? groupMemberListSeq);
|
||||
final groupMemberListRes = res.data;
|
||||
if (res.code == 0 && groupMemberListRes != null) {
|
||||
final groupMemberListTemp = groupMemberListRes.memberInfoList ?? [];
|
||||
groupMemberList = [...?groupMemberList, ...groupMemberListTemp];
|
||||
groupMemberListSeq = groupMemberListRes.nextSeq ?? "0";
|
||||
} else if (res.code == 10010) {
|
||||
isGroupExist = false;
|
||||
} else if (res.code == 10007) {
|
||||
isNotAMember = true;
|
||||
}
|
||||
return groupMemberListRes?.nextSeq;
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
return groupMemberListRes?.nextSeq;
|
||||
}
|
||||
|
||||
getGroupInfo(String groupID) async {
|
||||
final res = await _groupServices.getGroupsInfo(groupIDList: [groupID]);
|
||||
if (res != null) {
|
||||
final groupRes = res.first;
|
||||
Future<(V2TimGroupInfo?, GroupReceiptAllowType?)> loadGroupInfo(
|
||||
String groupID) async {
|
||||
final groupInfoList =
|
||||
await _groupServices.getGroupsInfo(groupIDList: [groupID]);
|
||||
if (groupInfoList != null && groupInfoList.isNotEmpty) {
|
||||
final groupRes = groupInfoList.first;
|
||||
if (groupRes.resultCode == 0) {
|
||||
_groupInfo = groupRes.groupInfo;
|
||||
|
||||
const groupTypeMap = {
|
||||
"Meeting": GroupReceiptAllowType.meeting,
|
||||
"Public": GroupReceiptAllowType.public,
|
||||
"Work": GroupReceiptAllowType.work
|
||||
};
|
||||
_groupInfo = groupRes.groupInfo;
|
||||
_groupType = groupTypeMap[groupRes.groupInfo?.groupType];
|
||||
|
||||
notifyListeners();
|
||||
return (_groupInfo, _groupType);
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
Future<void> updateMessageFromController(
|
||||
|
|
@ -584,6 +586,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
}) async {
|
||||
String receiver = convType == ConvType.c2c ? convID : '';
|
||||
String groupID = convType == ConvType.group ? convID : '';
|
||||
if (convType == ConvType.group && _groupType == null) {
|
||||
await loadGroupInfo(groupID);
|
||||
}
|
||||
final oldGroupType = _groupType != null
|
||||
? GroupReceptAllowType.values[_groupType!.index]
|
||||
: null;
|
||||
|
|
@ -624,6 +629,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
globalModel.updateMessage(
|
||||
sendMsgRes, convID, id, convType, groupType, setInputField);
|
||||
}
|
||||
if(lifeCycle?.messageDidSend != null){
|
||||
lifeCycle!.messageDidSend(sendMsgRes);
|
||||
}
|
||||
|
||||
return sendMsgRes;
|
||||
}
|
||||
|
|
@ -875,6 +883,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
globalModel.updateMessage(sendMsgRes, convID,
|
||||
messageInfoWithSender.id ?? "", convType, groupType, setInputField);
|
||||
if(lifeCycle?.messageDidSend != null){
|
||||
lifeCycle!.messageDidSend(sendMsgRes);
|
||||
}
|
||||
return sendMsgRes;
|
||||
}
|
||||
}
|
||||
|
|
@ -1017,6 +1028,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
|
|||
dynamic inputElement,
|
||||
required String convID,
|
||||
required ConvType convType}) async {
|
||||
if (await tools.hasZeroSize(filePath ?? "")) {
|
||||
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
|
||||
_coreServices.callOnCallback(TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: "不支持 0KB 文件的传输",
|
||||
infoCode: 6660417));
|
||||
return null;
|
||||
}
|
||||
final fileMessageInfo = await _messageService.createFileMessage(
|
||||
inputElement: inputElement,
|
||||
fileName: fileName ?? filePath?.split('/').last ?? "",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
ChatLifeCycle? _lifeCycle;
|
||||
bool _isDownloading = false;
|
||||
final List<Map<String, String>> _waitingDownloadList =
|
||||
List.empty(growable: true); // example {"savePath":"","url":"",msgId:""}
|
||||
List.empty(growable: true); // example {"savePath":"","url":"",msgId:""}
|
||||
int _totalUnreadCount = 0;
|
||||
String localKeyPrefix = "TUIKit_conversation_stored_";
|
||||
String localMsgIDListKey = "TUIKit_conversation_list";
|
||||
|
|
@ -62,7 +62,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
List<V2TimGroupApplication>? _groupApplicationList;
|
||||
String Function(V2TimMessage message)? _abstractMessageBuilder;
|
||||
final Map<String, int> _c2cMessageEditStatusMap =
|
||||
Map.from({}); // 0 normal 1 sending
|
||||
Map.from({}); // 0 normal 1 sending
|
||||
final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({});
|
||||
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
|
||||
bool _showC2cMessageEditStatus = true;
|
||||
|
|
@ -102,6 +102,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
|
||||
Map<String, String> get currentDownLoad => _waitingDownloadList.first;
|
||||
|
||||
int getWaitingListLength() {
|
||||
return _waitingDownloadList.length;
|
||||
}
|
||||
|
||||
void addWaitingList(String msgID) {
|
||||
print("add to waiting list success");
|
||||
bool contains = false;
|
||||
|
|
@ -191,15 +195,17 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
Map<String, V2TimMessageReceipt> get messageReadReceiptMap =>
|
||||
_messageReadReceiptMap;
|
||||
|
||||
String get currentSelectedConv => _currentConversationList.isNotEmpty
|
||||
? _currentConversationList[_currentConversationList.length - 1]
|
||||
String get currentSelectedConv =>
|
||||
_currentConversationList.isNotEmpty
|
||||
? _currentConversationList[_currentConversationList.length - 1]
|
||||
.conversationID
|
||||
: "";
|
||||
: "";
|
||||
|
||||
ConvType? get currentSelectedConvType => _currentConversationList.isNotEmpty
|
||||
? _currentConversationList[_currentConversationList.length - 1]
|
||||
ConvType? get currentSelectedConvType =>
|
||||
_currentConversationList.isNotEmpty
|
||||
? _currentConversationList[_currentConversationList.length - 1]
|
||||
.conversationType
|
||||
: null;
|
||||
: null;
|
||||
|
||||
setCurrentConversation(CurrentConversation value) {
|
||||
_currentConversationList.add(value);
|
||||
|
|
@ -235,13 +241,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
_c2cMessageStatusShowTimer[userID] =
|
||||
Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||
_c2cMessageEditStatusMap[userID] = 0;
|
||||
Timer? t = _c2cMessageStatusShowTimer[userID];
|
||||
if (t != null && t.isActive) {
|
||||
// 取消当前的定时器
|
||||
t.cancel();
|
||||
}
|
||||
});
|
||||
_c2cMessageEditStatusMap[userID] = 0;
|
||||
Timer? t = _c2cMessageStatusShowTimer[userID];
|
||||
if (t != null && t.isActive) {
|
||||
// 取消当前的定时器
|
||||
t.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -326,10 +332,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
prefs.remove(localMsgIDListKey);
|
||||
}
|
||||
|
||||
Future<void> updateMessageFromController(
|
||||
{required String msgID,
|
||||
required String conversationID,
|
||||
required ConvType conversationType}) async {
|
||||
Future<void> updateMessageFromController({required String msgID,
|
||||
required String conversationID,
|
||||
required ConvType conversationType}) async {
|
||||
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
|
||||
V2TimMessage? newMessage = await tools.getExistingMessageByID(
|
||||
msgID: msgID,
|
||||
|
|
@ -343,11 +348,11 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
Future<bool> refreshCurrentHistoryListForConversation(
|
||||
{HistoryMsgGetTypeEnum getType =
|
||||
HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
|
||||
int lastMsgSeq = -1,
|
||||
required int count,
|
||||
String? lastMsgID,
|
||||
required String convID,
|
||||
required ConvType convType}) async {
|
||||
int lastMsgSeq = -1,
|
||||
required int count,
|
||||
String? lastMsgID,
|
||||
required String convID,
|
||||
required ConvType convType}) async {
|
||||
final currentHistoryMsgList = messageListMap[convID];
|
||||
final response = await _messageService.getHistoryMessageList(
|
||||
count: count,
|
||||
|
|
@ -389,7 +394,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
|
||||
_preLoadImage(List<V2TimMessage> msgList) {
|
||||
List<V2TimMessage> needPreViewList =
|
||||
msgList.sublist(0, max(0, min(5, msgList.length - 1)));
|
||||
msgList.sublist(0, max(0, min(5, msgList.length - 1)));
|
||||
for (var msgItem in needPreViewList) {
|
||||
V2TimImage? getImageFromList(V2TimImageTypesEnum imgType) {
|
||||
V2TimImage? img = MessageUtils.getImageFromImgList(
|
||||
|
|
@ -407,12 +412,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
|
||||
image.resolve(configuration).addListener(
|
||||
ImageStreamListener((ImageInfo image, bool synchronousCall) {
|
||||
final tempImg = image.image;
|
||||
_preloadImageMap[msgItem.seq! +
|
||||
msgItem.timestamp.toString() +
|
||||
(msgItem.msgID ?? "")] = tempImg;
|
||||
print("cacheImage ${msgItem.msgID}");
|
||||
}));
|
||||
final tempImg = image.image;
|
||||
_preloadImageMap[msgItem.seq! +
|
||||
msgItem.timestamp.toString() +
|
||||
(msgItem.msgID ?? "")] = tempImg;
|
||||
print("cacheImage ${msgItem.msgID}");
|
||||
}));
|
||||
} catch (e) {
|
||||
print("cacheImage error ${msgItem.msgID}");
|
||||
}
|
||||
|
|
@ -513,13 +518,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
_c2cMessageActiveTimer[msg.sender ?? ""] =
|
||||
Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||
_c2cMessageFromUserActiveMap[msg.sender ?? ""] = false;
|
||||
Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""];
|
||||
if (t != null && t.isActive) {
|
||||
// 取消当前的定时器
|
||||
t.cancel();
|
||||
}
|
||||
});
|
||||
_c2cMessageFromUserActiveMap[msg.sender ?? ""] = false;
|
||||
Timer? t = _c2cMessageActiveTimer[msg.sender ?? ""];
|
||||
if (t != null && t.isActive) {
|
||||
// 取消当前的定时器
|
||||
t.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -537,14 +542,14 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
V2TimMsgCreateInfoResult? res = await _messageService.createCustomMessage(
|
||||
data: json.encode({
|
||||
"businessID": "user_typing_status",
|
||||
"typingStatus": isEditing == true ? 1 : 0,
|
||||
"userAction": 14,
|
||||
"version": 0,
|
||||
"actionParam": isEditing == true
|
||||
? "EIMAMSG_InputStatus_Ing"
|
||||
: "EIMAMSG_InputStatus_End"
|
||||
}));
|
||||
"businessID": "user_typing_status",
|
||||
"typingStatus": isEditing == true ? 1 : 0,
|
||||
"userAction": 14,
|
||||
"version": 0,
|
||||
"actionParam": isEditing == true
|
||||
? "EIMAMSG_InputStatus_Ing"
|
||||
: "EIMAMSG_InputStatus_End"
|
||||
}));
|
||||
if (res != null) {
|
||||
_sendMessage(
|
||||
id: res.id!,
|
||||
|
|
@ -559,9 +564,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
void refreshGroupApplicationList() async {
|
||||
final res = await _groupServices.getGroupApplicationList();
|
||||
_groupApplicationList = res.data?.groupApplicationList?.map((item) {
|
||||
final V2TimGroupApplication applicationItem = item!;
|
||||
return applicationItem;
|
||||
}).toList() ??
|
||||
final V2TimGroupApplication applicationItem = item!;
|
||||
return applicationItem;
|
||||
}).toList() ??
|
||||
[];
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -657,14 +662,17 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
|
||||
if (activeMessageList != null) {
|
||||
final findeIndex =
|
||||
activeMessageList.indexWhere((element) => element.msgID == msgID);
|
||||
activeMessageList.indexWhere((element) => element.msgID == msgID);
|
||||
if (findeIndex != -1) {
|
||||
final findeIndex =
|
||||
activeMessageList.indexWhere((element) => element.msgID == msgID);
|
||||
activeMessageList.indexWhere((element) => element.msgID == msgID);
|
||||
if (findeIndex != -1) {
|
||||
final targetItem = activeMessageList[findeIndex];
|
||||
targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
|
||||
targetItem.id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
targetItem.id = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch
|
||||
.toString();
|
||||
activeMessageList[findeIndex] = targetItem;
|
||||
}
|
||||
}
|
||||
|
|
@ -674,7 +682,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
|
||||
onMessageModified(V2TimMessage modifiedMessage, [String? convID]) async {
|
||||
modifiedMessage.id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
modifiedMessage.id = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch
|
||||
.toString();
|
||||
final String? exactId = TencentUtils.checkString(modifiedMessage.userID) ??
|
||||
TencentUtils.checkString(modifiedMessage.groupID);
|
||||
final activeMessageList = _messageListMap[convID ?? exactId];
|
||||
|
|
@ -729,33 +740,78 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
print("message progress: $progress");
|
||||
}
|
||||
|
||||
onMessageDownloadProgressCallback(
|
||||
Future<void> onMessageDownloadProgressCallback(
|
||||
V2TimMessageDownloadProgress messageProgress) async {
|
||||
if (messageProgress.isFinish) {
|
||||
setMessageProgress(messageProgress.msgID, 100);
|
||||
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
|
||||
downloadFile();
|
||||
if (messageProgress.type == 0) {
|
||||
final messages = await _messageService
|
||||
.findMessages(messageIDList: [messageProgress.msgID]);
|
||||
final V2TimMessage? message = messages?.first;
|
||||
if (message != null) {
|
||||
updateAsyncMessage(
|
||||
message,
|
||||
TencentUtils.checkString(message.userID) ??
|
||||
TencentUtils.checkString(message.groupID) ??
|
||||
"");
|
||||
}
|
||||
}
|
||||
print(messageProgress.toJson());
|
||||
final currentProgress = getMessageProgress(messageProgress.msgID);
|
||||
|
||||
if (messageProgress.isFinish && currentProgress < 100) {
|
||||
V2TimMessage? message = await _findAndRetrieveMessage(
|
||||
messageProgress.msgID);
|
||||
_handleFinishedDownload(messageProgress, message);
|
||||
return;
|
||||
}
|
||||
if (messageProgress.totalSize != -1) {
|
||||
int progress =
|
||||
(messageProgress.currentSize / messageProgress.totalSize * 100)
|
||||
.ceil();
|
||||
if (progress > 1) {
|
||||
setMessageProgress(messageProgress.msgID, progress);
|
||||
|
||||
_updateProgressIfNeeded(messageProgress, currentProgress);
|
||||
}
|
||||
|
||||
Future<V2TimMessage?> _findAndRetrieveMessage(String messageId) async {
|
||||
final messages = await _messageService.findMessages(
|
||||
messageIDList: [messageId]);
|
||||
return messages?.first;
|
||||
}
|
||||
|
||||
void _handleFinishedDownload(V2TimMessageDownloadProgress messageProgress,
|
||||
V2TimMessage? message) {
|
||||
if (message != null) {
|
||||
bool isImageType = message.elemType ==
|
||||
MessageElemType.V2TIM_ELEM_TYPE_IMAGE;
|
||||
bool isVideoType = message.elemType ==
|
||||
MessageElemType.V2TIM_ELEM_TYPE_VIDEO;
|
||||
if (!isImageType && !isVideoType) {
|
||||
_updateMessageLocationAndDownloadFile(messageProgress);
|
||||
} else if ((isImageType && messageProgress.type == 0) || isVideoType) {
|
||||
Future.delayed(const Duration(seconds: 1), () =>
|
||||
_updateMessageAndDownloadFile(message, messageProgress));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_updateMessageLocationAndDownloadFile(messageProgress);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateMessageAndDownloadFile(V2TimMessage message,
|
||||
V2TimMessageDownloadProgress messageProgress) {
|
||||
updateAsyncMessage(
|
||||
message,
|
||||
TencentUtils.checkString(message.userID) ??
|
||||
TencentUtils.checkString(message.groupID) ??
|
||||
"");
|
||||
|
||||
_updateMessageLocationAndDownloadFile(messageProgress);
|
||||
}
|
||||
|
||||
void _updateMessageLocationAndDownloadFile(
|
||||
V2TimMessageDownloadProgress messageProgress) {
|
||||
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
|
||||
setMessageProgress(messageProgress.msgID, 100);
|
||||
downloadFile();
|
||||
}
|
||||
|
||||
void _updateProgressIfNeeded(V2TimMessageDownloadProgress messageProgress,
|
||||
int currentProgress) {
|
||||
try{
|
||||
if (messageProgress.totalSize != -1 && !messageProgress.isFinish) {
|
||||
int progress = min(99,
|
||||
(messageProgress.currentSize / messageProgress.totalSize * 100)
|
||||
.floor());
|
||||
if (progress > 1 && progress > currentProgress) {
|
||||
setMessageProgress(messageProgress.msgID, progress);
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
print("calculate error: ${messageProgress.toJson()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -842,11 +898,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
|
||||
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
|
||||
V2TimMsgCreateInfoResult? textMessageInfo =
|
||||
await _messageService.createTextMessage(text: text);
|
||||
await _messageService.createTextMessage(text: text);
|
||||
|
||||
textMessageInfo = await _messageService.createTextAtMessage(
|
||||
text: text +
|
||||
" @${TencentUtils.checkString(messageBeenReplied.nickName) ?? TencentUtils.checkString(messageBeenReplied.sender) ?? TencentUtils.checkString(messageBeenReplied.userID)}",
|
||||
"\n@${TencentUtils.checkString(messageBeenReplied.nickName) ??
|
||||
TencentUtils.checkString(messageBeenReplied.sender) ??
|
||||
TencentUtils.checkString(messageBeenReplied.userID)}",
|
||||
atUserList: [
|
||||
TencentUtils.checkString(messageBeenReplied.sender) ??
|
||||
TencentUtils.checkString(messageBeenReplied.userID) ??
|
||||
|
|
@ -857,7 +915,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
|
||||
if (messageInfo != null) {
|
||||
final messageInfoWithSender = messageInfo.sender == null
|
||||
? tools.setUserInfoForMessage(messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
|
||||
? tools.setUserInfoForMessage(
|
||||
messageInfo, messageInfo.id ?? textMessageInfo.id ?? "")
|
||||
: messageInfo;
|
||||
|
||||
final hasNickName = messageBeenReplied.nickName != null &&
|
||||
|
|
@ -898,8 +957,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
return null;
|
||||
}
|
||||
|
||||
Future<bool> setLocalCustomData(
|
||||
String msgID, String localCustomData, String conversationID) async {
|
||||
Future<bool> setLocalCustomData(String msgID, String localCustomData,
|
||||
String conversationID) async {
|
||||
final res = await _messageService.setLocalCustomData(
|
||||
msgID: msgID, localCustomData: localCustomData);
|
||||
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
|
||||
|
|
@ -918,8 +977,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
return false;
|
||||
}
|
||||
|
||||
Future<bool> setLocalCustomInt(
|
||||
String msgID, int localCustomInt, String conversationID) async {
|
||||
Future<bool> setLocalCustomInt(String msgID, int localCustomInt,
|
||||
String conversationID) async {
|
||||
final res = await _messageService.setLocalCustomInt(
|
||||
msgID: msgID, localCustomInt: localCustomInt);
|
||||
List<V2TimMessage> messageList = _messageListMap[conversationID] ?? [];
|
||||
|
|
@ -956,7 +1015,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
String receiver = convType == ConvType.c2c ? convID : '';
|
||||
String groupID = convType == ConvType.group ? convID : '';
|
||||
final oldGroupType =
|
||||
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
|
||||
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
|
||||
final sendMsgRes = await _messageService.sendMessage(
|
||||
id: id,
|
||||
receiver: receiver,
|
||||
|
|
@ -964,8 +1023,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
chatConfig.isShowGroupReadingStatus &&
|
||||
convType == ConvType.group &&
|
||||
((chatConfig.groupReadReceiptPermissionList != null &&
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
.contains(groupType)) ||
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
.contains(groupType)) ||
|
||||
(chatConfig.groupReadReceiptPermisionList != null &&
|
||||
chatConfig.groupReadReceiptPermisionList!
|
||||
.contains(oldGroupType))),
|
||||
|
|
@ -985,6 +1044,9 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
if (isEditStatusMessage == false) {
|
||||
updateMessage(sendMsgRes, convID, id, convType, groupType, setInputField);
|
||||
}
|
||||
if(_lifeCycle?.messageDidSend != null){
|
||||
_lifeCycle!.messageDidSend(sendMsgRes);
|
||||
}
|
||||
|
||||
return sendMsgRes;
|
||||
}
|
||||
|
|
@ -998,8 +1060,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
updateMessage(
|
||||
V2TimValueCallback<V2TimMessage> sendMsgRes,
|
||||
updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes,
|
||||
String convID,
|
||||
String id,
|
||||
ConvType convType,
|
||||
|
|
@ -1014,10 +1075,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
|
||||
final findIdIndex =
|
||||
currentHistoryMsgList.indexWhere((element) => element.id == id);
|
||||
currentHistoryMsgList.indexWhere((element) => element.id == id);
|
||||
final targetIndex = findIdIndex == -1
|
||||
? currentHistoryMsgList
|
||||
.indexWhere((element) => element.msgID == sendMsgResData.msgID)
|
||||
.indexWhere((element) => element.msgID == sendMsgResData.msgID)
|
||||
: findIdIndex;
|
||||
if (targetIndex != -1) {
|
||||
currentHistoryMsgList[targetIndex] = sendMsgResData;
|
||||
|
|
@ -1028,13 +1089,13 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
loadingMessage[convID]!.removeWhere((element) => element.id == id);
|
||||
}
|
||||
final oldGroupType =
|
||||
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
|
||||
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
|
||||
if (chatConfig.isShowGroupReadingStatus &&
|
||||
convType == ConvType.group &&
|
||||
sendMsgRes.data?.msgID != null &&
|
||||
((chatConfig.groupReadReceiptPermissionList != null &&
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
.contains(groupType)) ||
|
||||
chatConfig.groupReadReceiptPermissionList!
|
||||
.contains(groupType)) ||
|
||||
(chatConfig.groupReadReceiptPermisionList != null &&
|
||||
chatConfig.groupReadReceiptPermisionList!
|
||||
.contains(oldGroupType)))) {
|
||||
|
|
@ -1045,11 +1106,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateAsyncMessage(
|
||||
V2TimMessage message,
|
||||
String convID,
|
||||
) {
|
||||
message.id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
void updateAsyncMessage(V2TimMessage message,
|
||||
String convID,) {
|
||||
message.id = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch
|
||||
.toString();
|
||||
|
||||
final activeMessageList = _messageListMap[convID];
|
||||
if (activeMessageList == null || activeMessageList.isEmpty) {
|
||||
|
|
@ -1068,18 +1130,19 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
|
||||
List<V2TimMessage>? getMessageList(String conversationID) {
|
||||
final list = messageListMap[conversationID]?.reversed.toList() ?? [];
|
||||
final list = (messageListMap[conversationID]?.reversed.toList() ?? [])
|
||||
.where((element) => _lifeCycle?.messageShouldMount(element) ?? true);
|
||||
final List<V2TimMessage> listWithTimestamp = [];
|
||||
final interval = chatConfig.timeDividerConfig?.timeInterval ?? 300;
|
||||
for (var item in list) {
|
||||
{
|
||||
if (listWithTimestamp.isEmpty ||
|
||||
(listWithTimestamp[listWithTimestamp.length - 1].timestamp !=
|
||||
null &&
|
||||
null &&
|
||||
item.timestamp != null &&
|
||||
(item.timestamp! -
|
||||
listWithTimestamp[listWithTimestamp.length - 1]
|
||||
.timestamp! >
|
||||
listWithTimestamp[listWithTimestamp.length - 1]
|
||||
.timestamp! >
|
||||
interval))) {
|
||||
listWithTimestamp.add(V2TimMessage(
|
||||
userID: '',
|
||||
|
|
@ -1098,7 +1161,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
|
||||
HistoryMessagePosition getMessageListPosition(String? conversationID) {
|
||||
final HistoryMessagePosition? position =
|
||||
_historyMessagePositionMap[conversationID];
|
||||
_historyMessagePositionMap[conversationID];
|
||||
if (position == null) {
|
||||
_historyMessagePositionMap[conversationID ?? currentSelectedConv] =
|
||||
HistoryMessagePosition.bottom;
|
||||
|
|
@ -1108,8 +1171,8 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
|
|||
}
|
||||
}
|
||||
|
||||
void setMessageListPosition(
|
||||
String conversationID, HistoryMessagePosition position) {
|
||||
void setMessageListPosition(String conversationID,
|
||||
HistoryMessagePosition position) {
|
||||
_historyMessagePositionMap[conversationID] = position;
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ class TUIConversationViewModel extends ChangeNotifier {
|
|||
final isRefresh = _nextSeq == "0";
|
||||
final conversationResult = await _conversationService.getConversationList(
|
||||
nextSeq: _nextSeq, count: count);
|
||||
_nextSeq = conversationResult!.nextSeq ?? "";
|
||||
final conversationList = conversationResult.conversationList;
|
||||
_nextSeq = conversationResult?.nextSeq ?? "";
|
||||
final conversationList = conversationResult?.conversationList;
|
||||
if (conversationList != null) {
|
||||
if (conversationList.isEmpty || conversationList.length < count) {
|
||||
_haveMoreData = false;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_setting_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
import 'package:tencent_im_base/tencent_im_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart';
|
||||
|
|
@ -14,7 +16,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dar
|
|||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support.dart'
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support_implement.dart';
|
||||
if (dart.library.html) 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support_implement.dart';
|
||||
|
||||
typedef EmptyAvatarBuilder = Widget Function(BuildContext context);
|
||||
|
||||
|
|
@ -61,7 +63,7 @@ class CoreServicesImpl implements CoreServices {
|
|||
|
||||
setGlobalConfig(TIMUIKitConfig? config) {
|
||||
final TUISelfInfoViewModel selfInfoViewModel =
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
final TUISettingModel settingModel = serviceLocator<TUISettingModel>();
|
||||
selfInfoViewModel.globalConfig = config;
|
||||
settingModel.init();
|
||||
|
|
@ -73,21 +75,20 @@ class CoreServicesImpl implements CoreServices {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool?> init(
|
||||
{
|
||||
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
|
||||
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
|
||||
required int sdkAppID,
|
||||
required LogLevelEnum loglevel,
|
||||
required V2TimSDKListener listener,
|
||||
LanguageEnum? language,
|
||||
String? extraLanguage,
|
||||
TIMUIKitConfig? config,
|
||||
Future<bool?> init({
|
||||
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
|
||||
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
|
||||
required int sdkAppID,
|
||||
required LogLevelEnum loglevel,
|
||||
required V2TimSDKListener listener,
|
||||
LanguageEnum? language,
|
||||
String? extraLanguage,
|
||||
TIMUIKitConfig? config,
|
||||
|
||||
/// Specify the current device platform, mobile or desktop, based on your needs.
|
||||
/// TUIKit will automatically determine the platform if no specification is provided. DeviceType? platform,
|
||||
DeviceType? platform,
|
||||
VoidCallback? onWebLoginSuccess}) async {
|
||||
/// Specify the current device platform, mobile or desktop, based on your needs.
|
||||
/// TUIKit will automatically determine the platform if no specification is provided. DeviceType? platform,
|
||||
DeviceType? platform,
|
||||
VoidCallback? onWebLoginSuccess}) async {
|
||||
if (platform != null) {
|
||||
TUIKitScreenUtils.deviceType = platform;
|
||||
}
|
||||
|
|
@ -166,13 +167,13 @@ class CoreServicesImpl implements CoreServices {
|
|||
|
||||
void addInitListener() {
|
||||
final TUIFriendShipViewModel tuiFriendShipViewModel =
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
final TUIConversationViewModel tuiConversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final TUIChatGlobalModel tuiChatViewModel =
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
final TUIGroupListenerModel tuiGroupListenerModel =
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
|
||||
tuiFriendShipViewModel.addFriendListener();
|
||||
tuiConversationViewModel.setConversationListener();
|
||||
|
|
@ -182,13 +183,13 @@ class CoreServicesImpl implements CoreServices {
|
|||
|
||||
void removeListener() {
|
||||
final TUIFriendShipViewModel tuiFriendShipViewModel =
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
final TUIConversationViewModel tuiConversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final TUIChatGlobalModel tuiChatViewModel =
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
final TUIGroupListenerModel tuiGroupListenerModel =
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
|
||||
tuiFriendShipViewModel.removeFriendshipListener();
|
||||
tuiConversationViewModel.removeConversationListener();
|
||||
|
|
@ -203,15 +204,16 @@ class CoreServicesImpl implements CoreServices {
|
|||
});
|
||||
} else {
|
||||
print(
|
||||
"TUIKit Callback: ${callbackValue.type} - ${callbackValue.stackTrace}");
|
||||
"TUIKit Callback: ${callbackValue.type} - ${callbackValue
|
||||
.stackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
initDataModel() {
|
||||
final TUIFriendShipViewModel tuiFriendShipViewModel =
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
final TUIConversationViewModel tuiConversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
|
||||
tuiFriendShipViewModel.initFriendShipModel();
|
||||
tuiConversationViewModel.initConversation();
|
||||
|
|
@ -219,11 +221,11 @@ class CoreServicesImpl implements CoreServices {
|
|||
|
||||
clearData() {
|
||||
final TUIFriendShipViewModel tuiFriendShipViewModel =
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
final TUIConversationViewModel tuiConversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
final TUIChatGlobalModel tuiChatViewModel =
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
|
||||
tuiFriendShipViewModel.clearData();
|
||||
tuiConversationViewModel.clearData();
|
||||
|
|
@ -233,18 +235,18 @@ class CoreServicesImpl implements CoreServices {
|
|||
updateUserStatusList(List<V2TimUserStatus> newUserStatusList) {
|
||||
try {
|
||||
final TUISelfInfoViewModel selfInfoViewModel =
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TUIFriendShipViewModel tuiFriendShipViewModel =
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
serviceLocator<TUIFriendShipViewModel>();
|
||||
final currentUserStatusList = tuiFriendShipViewModel.userStatusList;
|
||||
|
||||
for (int i = 0; i < newUserStatusList.length; i++) {
|
||||
final int indexInCurrentUserList = currentUserStatusList.indexWhere(
|
||||
(element) => element.userID == newUserStatusList[i].userID);
|
||||
(element) => element.userID == newUserStatusList[i].userID);
|
||||
if (indexInCurrentUserList == -1) {
|
||||
currentUserStatusList.add(newUserStatusList[i]);
|
||||
} else {
|
||||
|
|
@ -285,28 +287,50 @@ class CoreServicesImpl implements CoreServices {
|
|||
isLoginSuccess = true;
|
||||
addInitListener();
|
||||
initDataModel();
|
||||
|
||||
if (TencentUtils.checkString(_userID) == null) {
|
||||
V2TimValueCallback<String> getLoginUserRes =
|
||||
await TencentImSDKPlugin.v2TIMManager.getLoginUser();
|
||||
if (getLoginUserRes.code == 0) {
|
||||
_userID = getLoginUserRes.data ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
getUsersInfoWithRetry();
|
||||
}
|
||||
|
||||
void getUsersInfoWithRetry() async {
|
||||
V2TimValueCallback<List<V2TimUserFullInfo>>? res;
|
||||
bool success = false;
|
||||
|
||||
while (!success) {
|
||||
res = await getUsersInfo(userIDList: [_userID]);
|
||||
if (res.code == 0 &&
|
||||
res.data != null &&
|
||||
res.data!.isNotEmpty &&
|
||||
res.data!.firstWhereOrNull((element) => element.userID == _userID) !=
|
||||
null) {
|
||||
success = true;
|
||||
} else {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
}
|
||||
}
|
||||
|
||||
_loginInfo =
|
||||
res?.data!.firstWhereOrNull((element) => element.userID == _userID);
|
||||
final TUISelfInfoViewModel selfInfoViewModel =
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
getUsersInfo(userIDList: [_userID]).then((res) => {
|
||||
if (res.code == 0)
|
||||
{
|
||||
_loginInfo = res.data![0],
|
||||
selfInfoViewModel.setLoginInfo(_loginInfo!)
|
||||
}
|
||||
});
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
if (_loginInfo != null) {
|
||||
selfInfoViewModel.setLoginInfo(_loginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
void didLoginOut() {
|
||||
removeListener();
|
||||
clearData();
|
||||
getUsersInfo(userIDList: [_userID]).then((res) => {
|
||||
if (res.code == 0)
|
||||
{
|
||||
_loginInfo = res.data![0],
|
||||
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo!)
|
||||
}
|
||||
});
|
||||
_loginInfo = null;
|
||||
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -350,10 +374,10 @@ class CoreServicesImpl implements CoreServices {
|
|||
return TencentImSDKPlugin.v2TIMManager
|
||||
.getOfflinePushManager()
|
||||
.setOfflinePushConfig(
|
||||
businessID: businessID?.toDouble() ?? 0,
|
||||
token: token,
|
||||
isTPNSToken: isTPNSToken,
|
||||
);
|
||||
businessID: businessID?.toDouble() ?? 0,
|
||||
token: token,
|
||||
isTPNSToken: isTPNSToken,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ class GroupServicesImpl extends GroupServices {
|
|||
count: count,
|
||||
offset: offset);
|
||||
if (res.code != 0) {
|
||||
|
||||
_coreService.callOnCallback(TIMCallback(
|
||||
type: TIMCallbackType.API_ERROR,
|
||||
errorMsg: res.desc,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -689,5 +689,13 @@
|
|||
"k_066fxsz": "查看文件夹",
|
||||
"k_0k432y2": "无法发送,包含文件夹",
|
||||
"k_002wb4y": "会话",
|
||||
"k_0od4qyh": "视频文件异常"
|
||||
"k_0od4qyh": "视频文件异常",
|
||||
"k_1bfkxg9": "不支持 0KB 文件的传输",
|
||||
"k_0vvsw7g": "文件处理异常",
|
||||
"k_06e224q": "[消息被管理员撤回]",
|
||||
"k_1u1mjcl": "[消息被撤回]",
|
||||
"k_1qcqxea": "选择多个会话",
|
||||
"k_1qgmc20": "选择一个会话",
|
||||
"k_1d8nx6f": "在新窗口中打开",
|
||||
"k_1hz05ax": "正在下载原始资源,请稍候..."
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
// ignore_for_file: avoid_print
|
||||
// ignore_for_file: avoid_print, empty_catches
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
|
|
@ -139,11 +139,13 @@ class TIMUIKitChatController {
|
|||
assert(groupID != null || convType != ConvType.group);
|
||||
assert(userID != null || convType != ConvType.c2c);
|
||||
if (isNavigateToMessageListBottom && scrollController != null) {
|
||||
scrollController!.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
try{
|
||||
scrollController?.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}catch(e){}
|
||||
}
|
||||
return globalChatModel.sendMessageFromController(
|
||||
priority: priority,
|
||||
|
|
@ -160,11 +162,13 @@ class TIMUIKitChatController {
|
|||
} else if (model != null) {
|
||||
/// Sends a message to the current conversation specified on `TIMUIKitChat`. 发送到 `TIMUIKitChat` 中指定的当前对话。
|
||||
if (isNavigateToMessageListBottom && scrollController != null) {
|
||||
scrollController?.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
try{
|
||||
scrollController?.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}catch(e){}
|
||||
}
|
||||
return model!.sendMessageFromController(
|
||||
priority: priority,
|
||||
|
|
@ -230,11 +234,13 @@ class TIMUIKitChatController {
|
|||
assert(groupID != null || convType != ConvType.group);
|
||||
assert(userID != null || convType != ConvType.c2c);
|
||||
if (isNavigateToMessageListBottom && scrollController != null) {
|
||||
scrollController!.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
try{
|
||||
scrollController?.animateTo(
|
||||
scrollController!.position.minScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}catch(e){}
|
||||
}
|
||||
return globalChatModel.sendReplyMessageFromController(
|
||||
text: messageText,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// ignore_for_file: prefer_typing_uninitialized_variables
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tencent_im_base/i18n/i18n_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
|
||||
class TimeAgo {
|
||||
List<String> dayMap() {
|
||||
|
|
|
|||
|
|
@ -1413,7 +1413,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
widget.message.elemType == 6 &&
|
||||
isDownloadWaiting)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 2),
|
||||
margin: const EdgeInsets.only(top: 46, right: 10),
|
||||
child: LoadingAnimationWidget.threeArchedCircle(
|
||||
color: theme.weakTextColor ?? Colors.grey,
|
||||
size: 20,
|
||||
|
|
@ -1566,7 +1566,7 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
widget.message.elemType == 6 &&
|
||||
isDownloadWaiting)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 24, left: 6),
|
||||
margin: const EdgeInsets.only(top: 46, left: 10),
|
||||
child: LoadingAnimationWidget.threeArchedCircle(
|
||||
color: theme.weakTextColor ?? Colors.grey,
|
||||
size: 20,
|
||||
|
|
|
|||
|
|
@ -104,24 +104,14 @@ class TIMUIKitMessageTooltipState
|
|||
}
|
||||
if (PlatformUtils().isDesktop) {
|
||||
if (widget.message.fileElem != null) {
|
||||
if (globalModal.getMessageProgress(widget.message.msgID) == 100) {
|
||||
String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
globalModal.getFileMessageLocation(widget.message.msgID);
|
||||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.message.msgID != null) {
|
||||
filePath = savePath;
|
||||
isShowOpenFile = true;
|
||||
return;
|
||||
}
|
||||
isShowOpenFile = false;
|
||||
return;
|
||||
}
|
||||
String savePath = widget.message.fileElem!.localUrl ?? '';
|
||||
String savePath = TencentUtils.checkString(
|
||||
globalModal.getFileMessageLocation(widget.message.msgID)) ??
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
widget.message.fileElem?.path ??
|
||||
"";
|
||||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.message.msgID != null) {
|
||||
filePath = savePath;
|
||||
globalModal.setMessageProgress(widget.message.msgID!, 100);
|
||||
isShowOpenFile = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -399,14 +389,14 @@ class TIMUIKitMessageTooltipState
|
|||
}
|
||||
|
||||
_onOpenDesktop(String path) {
|
||||
if (PlatformUtils().isDesktop) {
|
||||
OpenFile.open(path);
|
||||
} else {
|
||||
launchUrl(
|
||||
Uri.parse(path),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
|
||||
launchUrl(Uri.file(path));
|
||||
} else {
|
||||
OpenFile.open(path);
|
||||
}
|
||||
// ignore: empty_catches
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
_onTap(String operation, TUIChatSeparateViewModel model) async {
|
||||
|
|
@ -415,32 +405,40 @@ class TIMUIKitMessageTooltipState
|
|||
switch (operation) {
|
||||
case "open":
|
||||
if (widget.message.fileElem != null) {
|
||||
_onOpenDesktop(widget.message.fileElem!.localUrl ??
|
||||
_onOpenDesktop(TencentUtils.checkString(
|
||||
globalModal.getFileMessageLocation(widget.message.msgID)) ??
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
widget.message.fileElem?.path ??
|
||||
"");
|
||||
} else if (widget.message.imageElem != null) {
|
||||
_onOpenDesktop(widget.message.imageElem!.imageList?[0]?.localUrl ??
|
||||
widget.message.imageElem?.path ??
|
||||
_onOpenDesktop(TencentUtils.checkString(
|
||||
widget.message.imageElem!.imageList?[0]?.localUrl) ??
|
||||
TencentUtils.checkString(widget.message.imageElem?.path) ??
|
||||
"");
|
||||
} else if (widget.message.videoElem != null) {
|
||||
_onOpenDesktop(widget.message.videoElem!.localVideoUrl ??
|
||||
widget.message.videoElem?.videoPath ??
|
||||
_onOpenDesktop(TencentUtils.checkString(
|
||||
widget.message.videoElem!.localVideoUrl) ??
|
||||
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
|
||||
"");
|
||||
}
|
||||
break;
|
||||
case "finder":
|
||||
String savePath = "";
|
||||
if (widget.message.fileElem != null) {
|
||||
savePath = (widget.message.fileElem!.localUrl ??
|
||||
savePath = (TencentUtils.checkString(
|
||||
globalModal.getFileMessageLocation(widget.message.msgID)) ??
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
widget.message.fileElem?.path ??
|
||||
"");
|
||||
} else if (widget.message.imageElem != null) {
|
||||
savePath = (widget.message.imageElem!.imageList?[0]?.localUrl ??
|
||||
widget.message.imageElem?.path ??
|
||||
savePath = (TencentUtils.checkString(
|
||||
widget.message.imageElem!.imageList?[0]?.localUrl) ??
|
||||
TencentUtils.checkString(widget.message.imageElem?.path) ??
|
||||
"");
|
||||
} else if (widget.message.videoElem != null) {
|
||||
savePath = (widget.message.videoElem!.localVideoUrl ??
|
||||
widget.message.videoElem?.videoPath ??
|
||||
savePath = (TencentUtils.checkString(
|
||||
widget.message.videoElem!.localVideoUrl) ??
|
||||
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
|
||||
"");
|
||||
}
|
||||
final String fileDir = path.dirname(savePath);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart
|
|||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar_title.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_im_base/i18n/i18n_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
|
||||
class TIMUIKitAppBarTitle extends StatelessWidget {
|
||||
final Widget? title;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart';
|
||||
import 'package:tencent_open_file/tencent_open_file.dart';
|
||||
|
|
@ -49,10 +50,12 @@ class TIMUIKitFileElem extends StatefulWidget {
|
|||
|
||||
class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
||||
String filePath = "";
|
||||
bool isDownloading = false;
|
||||
bool isWebDownloading = false;
|
||||
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
|
||||
int downloadProgress = 0;
|
||||
late V2TimAdvancedMsgListener advancedMsgListener;
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
double? containerHeight;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
|
@ -75,18 +78,18 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
(V2TimMessageDownloadProgress messageProgress) async {
|
||||
if (messageProgress.msgID == widget.message.msgID) {
|
||||
if (messageProgress.isFinish) {
|
||||
if(mounted){
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if(mounted){
|
||||
final currentProgress =
|
||||
(messageProgress.currentSize / messageProgress.totalSize * 100)
|
||||
.floor();
|
||||
if (mounted && currentProgress > downloadProgress) {
|
||||
setState(() {
|
||||
downloadProgress = (messageProgress.currentSize /
|
||||
messageProgress.totalSize *
|
||||
100)
|
||||
.ceil();
|
||||
downloadProgress = currentProgress;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -110,30 +113,22 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
if (PlatformUtils().isWeb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (model.getMessageProgress(widget.messageID) == 100 ||
|
||||
downloadProgress == 100) {
|
||||
String savePath =
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
model.getFileMessageLocation(widget.messageID);
|
||||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.messageID != null) {
|
||||
filePath = savePath;
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
String savePath = widget.message.fileElem!.localUrl ?? '';
|
||||
String savePath = TencentUtils.checkString(
|
||||
model.getFileMessageLocation(widget.messageID)) ??
|
||||
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
|
||||
widget.message.fileElem?.path ??
|
||||
'';
|
||||
File f = File(savePath);
|
||||
if (f.existsSync() && widget.messageID != null) {
|
||||
filePath = savePath;
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
model.setMessageProgress(widget.messageID!, 100);
|
||||
if (downloadProgress != 100) {
|
||||
setState(() {
|
||||
downloadProgress = 100;
|
||||
});
|
||||
}
|
||||
if (model.getMessageProgress(widget.messageID) != 100) {
|
||||
model.setMessageProgress(widget.messageID!, 100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -151,10 +146,12 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
}
|
||||
}
|
||||
|
||||
addUrlToWaitingPath() async {
|
||||
addUrlToWaitingPath(TUITheme theme) async {
|
||||
if (widget.messageID != null) {
|
||||
model.addWaitingList(widget.messageID!);
|
||||
print("add path success");
|
||||
}
|
||||
if (model.getWaitingListLength() == 1) {
|
||||
await downloadFile(theme);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +192,25 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
await model.downloadFile();
|
||||
}
|
||||
|
||||
Future<bool> hasZeroSize(String filePath) async {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
final fileSize = await file.length();
|
||||
return fileSize == 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tryOpenFile(context, theme) async {
|
||||
if (!PlatformUtils().isWeb &&
|
||||
(await hasZeroSize(filePath) || widget.message.status == 3)) {
|
||||
onTIMCallback(TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: "不支持 0KB 文件的传输",
|
||||
infoCode: 6660417));
|
||||
return;
|
||||
}
|
||||
if (PlatformUtils().isMobile) {
|
||||
if (PlatformUtils().isIOS) {
|
||||
if (!await Permissions.checkPermission(
|
||||
|
|
@ -229,6 +244,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
}
|
||||
|
||||
void downloadWebFile(String fileUrl) async {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isWebDownloading = true;
|
||||
});
|
||||
}
|
||||
String fileName = Uri.parse(fileUrl).pathSegments.last;
|
||||
try {
|
||||
http.Response response = await http.get(
|
||||
|
|
@ -255,6 +275,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
..style.display = "none"
|
||||
..click();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isWebDownloading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -280,111 +305,144 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
|
|||
final String fileName = widget.fileElem!.fileName!;
|
||||
fileFormat = fileName.split(".")[max(fileName.split(".").length - 1, 0)];
|
||||
}
|
||||
return TIMUIKitMessageReactionWrapper(
|
||||
chatModel: widget.chatModel,
|
||||
isShowJump: widget.isShowJump,
|
||||
clearJump: widget.clearJump,
|
||||
isFromSelf: widget.message.isSelf ?? true,
|
||||
isShowMessageReaction: widget.isShowMessageReaction ?? true,
|
||||
message: widget.message,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (PlatformUtils().isWeb) {
|
||||
downloadWebFile(widget.fileElem?.path ?? "");
|
||||
return;
|
||||
}
|
||||
if (await hasFile()) {
|
||||
if (received == 100) {
|
||||
tryOpenFile(context, theme);
|
||||
} else {
|
||||
// 正在下载中,文件可能不完整
|
||||
onTIMCallback(
|
||||
TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: TIM_t("正在下载中"),
|
||||
infoCode: 6660411,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final RenderBox? containerRenderBox =
|
||||
containerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (containerRenderBox != null) {
|
||||
containerHeight = containerRenderBox.size.height;
|
||||
}
|
||||
|
||||
if (checkIsWaiting()) {
|
||||
onTIMCallback(
|
||||
TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
|
||||
infoCode: 6660413),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
await addUrlToWaitingPath();
|
||||
}
|
||||
await downloadFile(theme);
|
||||
},
|
||||
child: Container(
|
||||
width: 237,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color:
|
||||
theme.weakDividerColor ?? CommonColor.weakDividerColor,
|
||||
),
|
||||
borderRadius: borderRadius),
|
||||
child: Stack(children: [
|
||||
ClipRRect(
|
||||
//剪裁为圆角矩形
|
||||
borderRadius: borderRadius,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 66,
|
||||
value: (received == 100 ? 0 : received) / 100,
|
||||
backgroundColor: received == 100
|
||||
? theme.weakBackgroundColor
|
||||
: Colors.white,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
theme.lightPrimaryMaterialColor.shade50)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: widget.isSelf
|
||||
? MainAxisAlignment.end
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: 160),
|
||||
child: LayoutBuilder(
|
||||
builder: (buildContext, boxConstraints) {
|
||||
return CustomText(
|
||||
fileName,
|
||||
width: boxConstraints.maxWidth,
|
||||
style: TextStyle(
|
||||
color: theme.darkTextColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (fileSize != null)
|
||||
Text(
|
||||
showFileSize(fileSize),
|
||||
// "${received > 0 ? (received / 1024).ceil() : (received / 1024).ceil()} KB",
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: theme.weakTextColor),
|
||||
)
|
||||
],
|
||||
)),
|
||||
TIMUIKitFileIcon(
|
||||
fileFormat: fileFormat,
|
||||
return Row(
|
||||
key: containerKey,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.isSelf && isWebDownloading)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 2),
|
||||
child: LoadingAnimationWidget.threeArchedCircle(
|
||||
color: theme.weakTextColor ?? Colors.grey,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
TIMUIKitMessageReactionWrapper(
|
||||
chatModel: widget.chatModel,
|
||||
isShowJump: widget.isShowJump,
|
||||
clearJump: widget.clearJump,
|
||||
isFromSelf: widget.message.isSelf ?? true,
|
||||
isShowMessageReaction: widget.isShowMessageReaction ?? true,
|
||||
message: widget.message,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
if (PlatformUtils().isWeb) {
|
||||
if (!isWebDownloading) {
|
||||
downloadWebFile(widget.fileElem?.path ?? "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (await hasFile()) {
|
||||
if (received == 100) {
|
||||
tryOpenFile(context, theme);
|
||||
} else {
|
||||
onTIMCallback(
|
||||
TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: TIM_t("正在下载中"),
|
||||
infoCode: 6660411,
|
||||
),
|
||||
])),
|
||||
]),
|
||||
)));
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (checkIsWaiting()) {
|
||||
onTIMCallback(
|
||||
TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: TIM_t("已加入待下载队列,其他文件下载中"),
|
||||
infoCode: 6660413),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
await addUrlToWaitingPath(theme);
|
||||
}
|
||||
} catch (e) {
|
||||
onTIMCallback(TIMCallback(
|
||||
type: TIMCallbackType.INFO,
|
||||
infoRecommendText: "文件处理异常",
|
||||
infoCode: 6660416));
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 237,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: theme.weakDividerColor ??
|
||||
CommonColor.weakDividerColor,
|
||||
),
|
||||
borderRadius: borderRadius),
|
||||
child: Stack(children: [
|
||||
ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: ((containerHeight) ?? 72) - 6,
|
||||
value: (received == 100 ? 0 : received) / 100,
|
||||
backgroundColor: received == 100
|
||||
? theme.weakBackgroundColor
|
||||
: Colors.white,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
theme.lightPrimaryMaterialColor.shade50)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: widget.isSelf
|
||||
? MainAxisAlignment.end
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: 160),
|
||||
child: LayoutBuilder(
|
||||
builder: (buildContext, boxConstraints) {
|
||||
return CustomText(
|
||||
fileName,
|
||||
width: boxConstraints.maxWidth,
|
||||
style: TextStyle(
|
||||
color: theme.darkTextColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (fileSize != null)
|
||||
Text(
|
||||
showFileSize(fileSize),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: theme.weakTextColor),
|
||||
)
|
||||
],
|
||||
)),
|
||||
TIMUIKitFileIcon(
|
||||
fileFormat: fileFormat,
|
||||
),
|
||||
])),
|
||||
]),
|
||||
))),
|
||||
if (!widget.isSelf && isWebDownloading)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 2),
|
||||
child: LoadingAnimationWidget.threeArchedCircle(
|
||||
color: theme.weakTextColor ?? Colors.grey,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
|
||||
import 'package:tencent_open_file/tencent_open_file.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'dart:io';
|
||||
|
|
@ -68,7 +69,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
String getBigPicUrl() {
|
||||
String getOriginImgURL() {
|
||||
// 实际拿的是原图
|
||||
V2TimImage? img = MessageUtils.getImageFromImgList(
|
||||
widget.message.imageElem!.imageList,
|
||||
|
|
@ -257,7 +258,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
return await _saveImageToLocal(context, path,
|
||||
isAsset: true, theme: theme);
|
||||
} else {
|
||||
String imgUrl = getBigPicUrl();
|
||||
String imgUrl = getOriginImgURL();
|
||||
if (widget.message.imageElem!.imageList![0]!.localUrl != '' &&
|
||||
widget.message.imageElem!.imageList![0]!.localUrl != null) {
|
||||
File f = File(widget.message.imageElem!.imageList![0]!.localUrl!);
|
||||
|
|
@ -300,6 +301,14 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
return img;
|
||||
}
|
||||
|
||||
void launchDesktopFile(String path) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(path);
|
||||
} else {
|
||||
launchUrl(Uri.file(path));
|
||||
}
|
||||
}
|
||||
|
||||
Widget errorPage(theme) => Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
color: theme.black,
|
||||
|
|
@ -310,16 +319,85 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
child: errorDisplay(context, theme),
|
||||
));
|
||||
|
||||
bool checkIfDownloadSuccess() {
|
||||
final localUrl = TencentUtils.checkString(
|
||||
model.getFileMessageLocation(widget.message.msgID)) ??
|
||||
widget.message.imageElem!.imageList![0]!.localUrl;
|
||||
return TencentUtils.checkString(localUrl) != null &&
|
||||
File(localUrl!).existsSync();
|
||||
}
|
||||
|
||||
_onClickOpenImageInNewWindow() {
|
||||
final localUrl = TencentUtils.checkString(
|
||||
model.getFileMessageLocation(widget.message.msgID)) ??
|
||||
widget.message.imageElem!.imageList![0]!.localUrl;
|
||||
Future.delayed(const Duration(milliseconds: 0), () async {
|
||||
final isDownloaded = checkIfDownloadSuccess();
|
||||
if (isDownloaded) {
|
||||
launchDesktopFile(localUrl ?? "");
|
||||
} else {
|
||||
onTIMCallback(TIMCallback(
|
||||
infoCode: 6660414,
|
||||
infoRecommendText: TIM_t("正在下载原始资源,请稍候..."),
|
||||
type: TIMCallbackType.INFO));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleOnTapPreviewImageOnDesktop({
|
||||
double? positionRadio,
|
||||
String? originImgUrl,
|
||||
}) {
|
||||
final localUrl = TencentUtils.checkString(
|
||||
model.getFileMessageLocation(widget.message.msgID)) ??
|
||||
widget.message.imageElem!.imageList![0]!.localUrl;
|
||||
if (checkIfDownloadSuccess()) {
|
||||
TUIKitWidePopup.showMedia(
|
||||
aspectRatio: positionRadio,
|
||||
context: context,
|
||||
mediaLocalPath: localUrl ?? "",
|
||||
onClickOrigin: () => _onClickOpenImageInNewWindow());
|
||||
} else {
|
||||
if (TencentUtils.checkString(originImgUrl) != null) {
|
||||
TUIKitWidePopup.showMedia(
|
||||
aspectRatio: positionRadio,
|
||||
context: context,
|
||||
mediaURL: originImgUrl,
|
||||
onClickOrigin: () => _onClickOpenImageInNewWindow());
|
||||
} else {
|
||||
onTIMCallback(TIMCallback(
|
||||
infoCode: 6660414,
|
||||
infoRecommendText: TIM_t("正在下载中"),
|
||||
type: TIMCallbackType.INFO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _renderNetworkImage(
|
||||
dynamic heroTag, double? positionRadio, TUITheme? theme,
|
||||
{String? path, V2TimImage? originalImg, V2TimImage? smallImg}) {
|
||||
try {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
String bigImgUrl = originalImg?.url ?? getBigPicUrl();
|
||||
if (bigImgUrl.isEmpty && smallImg?.url != null) {
|
||||
bigImgUrl = smallImg!.url!;
|
||||
String originImgUrl = originalImg?.url ?? getOriginImgURL();
|
||||
if (originImgUrl.isEmpty && smallImg?.url != null) {
|
||||
originImgUrl = smallImg!.url!;
|
||||
}
|
||||
|
||||
final imageWidget = Hero(
|
||||
tag: heroTag,
|
||||
child: PlatformUtils().isWeb
|
||||
? Image.network(path ?? smallImg?.url ?? originalImg!.url!,
|
||||
fit: BoxFit.contain)
|
||||
: CachedNetworkImage(
|
||||
alignment: Alignment.topCenter,
|
||||
imageUrl: path ?? smallImg?.url ?? originalImg!.url!,
|
||||
errorWidget: (context, error, stackTrace) => errorPage(theme),
|
||||
fit: BoxFit.contain,
|
||||
cacheKey: smallImg?.uuid ?? originalImg!.uuid,
|
||||
placeholder: (context, url) =>
|
||||
Image(image: MemoryImage(kTransparentImage)),
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
));
|
||||
|
||||
return Stack(
|
||||
alignment: widget.message.isSelf ?? true
|
||||
? AlignmentDirectional.topEnd
|
||||
|
|
@ -327,40 +405,30 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
children: [
|
||||
getImage(
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
if (PlatformUtils().isWeb) {
|
||||
launchUrl(
|
||||
Uri.parse(widget.message.imageElem?.path ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
TUIKitWidePopup.showMedia(
|
||||
aspectRatio: positionRadio,
|
||||
context: context,
|
||||
mediaURL: widget.message.imageElem?.path ?? "",
|
||||
onClickOrigin: () => launchUrl(
|
||||
Uri.parse(widget.message.imageElem?.path ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
));
|
||||
return;
|
||||
}
|
||||
if (isDesktopScreen) {
|
||||
if (TencentUtils.checkString(widget
|
||||
.message.imageElem!.imageList![0]!.localUrl) !=
|
||||
null &&
|
||||
File(widget.message.imageElem!.imageList![0]!.localUrl!)
|
||||
.existsSync()) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(
|
||||
widget.message.imageElem!.imageList![0]!.localUrl);
|
||||
} else {
|
||||
launchUrl(Uri.file(widget
|
||||
.message.imageElem!.imageList![0]!.localUrl!));
|
||||
}
|
||||
}else{
|
||||
onTIMCallback(TIMCallback(
|
||||
infoCode: 6660414,
|
||||
infoRecommendText: TIM_t("正在下载中"),
|
||||
type: TIMCallbackType.INFO));
|
||||
}
|
||||
if (PlatformUtils().isDesktop) {
|
||||
_handleOnTapPreviewImageOnDesktop(
|
||||
positionRadio: positionRadio,
|
||||
originImgUrl: originImgUrl,
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
opaque: false, // set to false
|
||||
pageBuilder: (_, __, ___) => ImageScreen(
|
||||
imageProvider: CachedNetworkImageProvider(
|
||||
path ?? bigImgUrl,
|
||||
path ?? originImgUrl,
|
||||
cacheKey: widget.message.msgID,
|
||||
),
|
||||
heroTag: heroTag,
|
||||
|
|
@ -371,32 +439,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
constraints:
|
||||
const BoxConstraints(minWidth: 20, minHeight: 20),
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: PlatformUtils().isWeb
|
||||
? Image.network(
|
||||
path ?? smallImg?.url ?? originalImg!.url!,
|
||||
fit: BoxFit.contain)
|
||||
:
|
||||
// Image.network(smallImg?.url ?? ""),
|
||||
CachedNetworkImage(
|
||||
// width: double.infinity,
|
||||
alignment: Alignment.topCenter,
|
||||
imageUrl:
|
||||
path ?? smallImg?.url ?? originalImg!.url!,
|
||||
// use small image in message list as priority
|
||||
errorWidget: (context, error, stackTrace) =>
|
||||
errorPage(theme),
|
||||
fit: BoxFit.contain,
|
||||
cacheKey: smallImg?.uuid ?? originalImg!.uuid,
|
||||
placeholder: (context, url) =>
|
||||
Image(image: MemoryImage(kTransparentImage)),
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
)),
|
||||
),
|
||||
child: positionRadio != null
|
||||
? AspectRatio(
|
||||
aspectRatio: positionRadio,
|
||||
child: imageWidget,
|
||||
)
|
||||
: imageWidget,
|
||||
),
|
||||
imageElem: e)
|
||||
],
|
||||
|
|
@ -436,6 +484,25 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
|
||||
final imageWidget = Hero(
|
||||
tag: heroTag,
|
||||
child: preloadImage != null
|
||||
? FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: RawImage(
|
||||
image: preloadImage,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: Image.file(
|
||||
File(smallImage),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
children: [
|
||||
|
|
@ -450,11 +517,11 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
GestureDetector(
|
||||
onTap: () {
|
||||
if (PlatformUtils().isDesktop) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(showImage);
|
||||
} else {
|
||||
launchUrl(Uri.file(showImage));
|
||||
}
|
||||
TUIKitWidePopup.showMedia(
|
||||
aspectRatio: positionRadio,
|
||||
mediaLocalPath: showImage,
|
||||
context: context,
|
||||
onClickOrigin: () => launchDesktopFile(showImage));
|
||||
} else {
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
|
|
@ -470,22 +537,12 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
constraints:
|
||||
const BoxConstraints(minWidth: 20, minHeight: 20),
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: preloadImage != null
|
||||
? RawImage(
|
||||
image: preloadImage,
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
: Image.file(
|
||||
File(smallImage),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
)),
|
||||
child: positionRadio != null
|
||||
? AspectRatio(
|
||||
aspectRatio: positionRadio,
|
||||
child: imageWidget,
|
||||
)
|
||||
: imageWidget),
|
||||
imageElem: null)
|
||||
],
|
||||
);
|
||||
|
|
@ -623,6 +680,8 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
if (widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING) {
|
||||
isSent = true;
|
||||
}
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final heroTag =
|
||||
"${widget.message.msgID ?? widget.message.id ?? widget.message.timestamp ?? DateTime.now().millisecondsSinceEpoch}${widget.isFrom}";
|
||||
|
||||
|
|
@ -639,7 +698,7 @@ class _TIMUIKitImageElem extends TIMUIKitState<TIMUIKitImageElem> {
|
|||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: constraints.maxWidth * 0.5,
|
||||
maxWidth: constraints.maxWidth * (isDesktopScreen ? 0.4 : 0.5),
|
||||
minWidth: 64,
|
||||
maxHeight: 256,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
|
|||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_wrapper.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/video_screen.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
|
||||
import 'package:tencent_open_file/tencent_open_file.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
|
@ -166,6 +167,15 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
downloadMessageDetailAndSave();
|
||||
}
|
||||
|
||||
void launchDesktopFile(String path) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(path);
|
||||
} else {
|
||||
launchUrl(Uri.file(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final theme = value.theme;
|
||||
|
|
@ -175,10 +185,14 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (PlatformUtils().isWeb) {
|
||||
launchUrl(
|
||||
Uri.parse(widget.message.videoElem?.videoPath ?? ""),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
final url = widget.message.videoElem?.videoUrl ?? widget.message.videoElem?.videoPath ?? "";
|
||||
TUIKitWidePopup.showMedia(
|
||||
context: context,
|
||||
mediaURL: url,
|
||||
onClickOrigin: () => launchUrl(
|
||||
Uri.parse(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
));
|
||||
return;
|
||||
}
|
||||
if (PlatformUtils().isDesktop) {
|
||||
|
|
@ -188,19 +202,20 @@ class _TIMUIKitVideoElemState extends TIMUIKitState<TIMUIKitVideoElem> {
|
|||
TencentUtils.checkString(videoElem.localVideoUrl);
|
||||
final videoPath = TencentUtils.checkString(videoElem.videoPath);
|
||||
final videoUrl = videoElem.videoUrl;
|
||||
|
||||
if (localVideoUrl != null) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(localVideoUrl);
|
||||
} else {
|
||||
launchUrl(Uri.file(localVideoUrl));
|
||||
}
|
||||
launchDesktopFile(localVideoUrl);
|
||||
// todo
|
||||
// TUIKitWidePopup.showMedia(
|
||||
// context: context,
|
||||
// mediaPath: localVideoUrl,
|
||||
// onClickOrigin: () => launchDesktopFile(localVideoUrl));
|
||||
} else if (videoPath != null) {
|
||||
if (PlatformUtils().isWindows) {
|
||||
OpenFile.open(videoPath);
|
||||
} else {
|
||||
launchUrl(Uri.file(videoPath));
|
||||
}
|
||||
launchDesktopFile(videoPath);
|
||||
// todo
|
||||
// TUIKitWidePopup.showMedia(
|
||||
// context: context,
|
||||
// mediaPath: videoPath,
|
||||
// onClickOrigin: () => launchDesktopFile(videoPath));
|
||||
} else if (TencentUtils.isTextNotEmpty(videoUrl)) {
|
||||
onTIMCallback(TIMCallback(
|
||||
infoCode: 6660414,
|
||||
|
|
|
|||
|
|
@ -455,9 +455,16 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
|
|||
TUITheme theme,
|
||||
) async {
|
||||
try {
|
||||
if (!await Permissions.checkPermission(
|
||||
context,
|
||||
Permission.camera.value,
|
||||
theme,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
if (!await Permissions.checkPermission(
|
||||
context,
|
||||
Permission.camera.value,
|
||||
Permission.photos.value,
|
||||
theme,
|
||||
)) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class TIMUIKitInputTextField extends StatefulWidget {
|
|||
/// conversation id
|
||||
final String conversationID;
|
||||
|
||||
final TIMUIKitChatConfig? chatConfig;
|
||||
|
||||
/// conversation type
|
||||
final ConvType conversationType;
|
||||
|
||||
|
|
@ -116,7 +118,8 @@ class TIMUIKitInputTextField extends StatefulWidget {
|
|||
required this.currentConversation,
|
||||
this.groupType,
|
||||
this.atMemberPanelScroll,
|
||||
this.groupID})
|
||||
this.groupID,
|
||||
this.chatConfig})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -147,15 +150,51 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
currentCursor = value;
|
||||
}
|
||||
|
||||
void deleteStickerFromText() {
|
||||
String originalText = textEditingController.text;
|
||||
String text;
|
||||
final cursorPosition =
|
||||
currentCursor ?? originalText.length;
|
||||
|
||||
if (originalText == zeroWidthSpace) {
|
||||
_handleSoftKeyBoardDelete();
|
||||
} else if (originalText.isNotEmpty) {
|
||||
if (cursorPosition == originalText.length) {
|
||||
text = originalText.characters.skipLast(1).toString();
|
||||
currentCursor = null;
|
||||
} else if (cursorPosition > 0 && cursorPosition < originalText.length) {
|
||||
final firstString = originalText.substring(0, cursorPosition - 2);
|
||||
final secondString = originalText.substring(cursorPosition);
|
||||
text = '$firstString$secondString';
|
||||
if(currentCursor != null){
|
||||
currentCursor = currentCursor! - 2;
|
||||
}
|
||||
} else {
|
||||
text = originalText.characters.skipLast(1).toString();
|
||||
currentCursor = null;
|
||||
}
|
||||
textEditingController.text = text;
|
||||
|
||||
if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) {
|
||||
textEditingController.selection = TextSelection.fromPosition(TextPosition(
|
||||
offset: currentCursor ?? textEditingController.text.length));
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addStickerToText(String sticker) {
|
||||
final oldText = textEditingController.text;
|
||||
if (currentCursor != null && currentCursor! > -1) {
|
||||
final firstString = oldText.substring(0, currentCursor);
|
||||
final secondString = oldText.substring(currentCursor!);
|
||||
final currentText = textEditingController.text;
|
||||
if (currentCursor != null &&
|
||||
currentCursor! > -1 &&
|
||||
currentCursor! < currentText.length + 1) {
|
||||
final firstString = currentText.substring(0, currentCursor);
|
||||
final secondString = currentText.substring(currentCursor!);
|
||||
currentCursor = currentCursor! + sticker.length;
|
||||
textEditingController.text = "$firstString$sticker$secondString";
|
||||
} else {
|
||||
textEditingController.text = "$oldText$sticker";
|
||||
currentCursor = null;
|
||||
textEditingController.text = "$currentText$sticker";
|
||||
}
|
||||
|
||||
if (TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop) {
|
||||
|
|
@ -169,7 +208,8 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
return text.replaceAll(RegExp(r'\ufeff'), "");
|
||||
}
|
||||
|
||||
Future handleSetDraftText([String? id, ConvType? convType]) async {
|
||||
Future handleSetDraftText(
|
||||
{String? id, ConvType? convType, String? groupID}) async {
|
||||
String text = textEditingController.text;
|
||||
String convID = id ?? widget.conversationID;
|
||||
final isTopic = convID.contains("@TOPIC#");
|
||||
|
|
@ -180,25 +220,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
: "group_$convID");
|
||||
String draftText = _filterU200b(text);
|
||||
return await conversationModel.setConversationDraft(
|
||||
groupID: widget.groupID,
|
||||
groupID: groupID ?? widget.groupID,
|
||||
isTopic: isTopic,
|
||||
isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb,
|
||||
conversationID: conversationID,
|
||||
draftText: draftText);
|
||||
}
|
||||
|
||||
backSpaceText() {
|
||||
String originalText = textEditingController.text;
|
||||
dynamic text;
|
||||
|
||||
if (originalText == zeroWidthSpace) {
|
||||
_handleSoftKeyBoardDelete();
|
||||
} else {
|
||||
text = originalText.characters.skipLast(1);
|
||||
textEditingController.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
// 和onSubmitted一样,只是保持焦点的不同
|
||||
onEmojiSubmitted() {
|
||||
lastText = "";
|
||||
|
|
@ -592,6 +620,8 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
KeyEventResult handleDesktopKeyEvent(FocusNode node, RawKeyEvent event) {
|
||||
final activeIndex = widget.model.activeAtIndex;
|
||||
final showMemberList = widget.model.showAtMemberList;
|
||||
final isEneter = (event.physicalKey == PhysicalKeyboardKey.enter) ||
|
||||
(event.physicalKey == PhysicalKeyboardKey.numpadEnter);
|
||||
if (event.runtimeType == RawKeyDownEvent) {
|
||||
if (event.physicalKey == PhysicalKeyboardKey.backspace) {
|
||||
if (textEditingController.text.isEmpty && lastText.isEmpty) {
|
||||
|
|
@ -602,7 +632,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
event.isAltPressed ||
|
||||
event.isControlPressed ||
|
||||
event.isMetaPressed) &&
|
||||
event.physicalKey == PhysicalKeyboardKey.enter) {
|
||||
isEneter) {
|
||||
final offset = textEditingController.selection.baseOffset;
|
||||
textEditingController.text =
|
||||
'${lastText.substring(0, offset)}\n${lastText.substring(offset)}';
|
||||
|
|
@ -611,7 +641,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
lastText = textEditingController.text;
|
||||
|
||||
return KeyEventResult.handled;
|
||||
} else if (event.physicalKey == PhysicalKeyboardKey.enter) {
|
||||
} else if (isEneter) {
|
||||
if (!_isComposingText) {
|
||||
if (!isAddingAtSearchWords || widget.model.showAtMemberList.isEmpty) {
|
||||
onSubmitted();
|
||||
|
|
@ -707,7 +737,10 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.conversationID != oldWidget.conversationID) {
|
||||
mentionedMembersMap.clear();
|
||||
handleSetDraftText(oldWidget.conversationID, oldWidget.conversationType);
|
||||
handleSetDraftText(
|
||||
id: oldWidget.conversationID,
|
||||
convType: oldWidget.conversationType,
|
||||
groupID: oldWidget.groupID);
|
||||
if (oldWidget.initText != widget.initText) {
|
||||
textEditingController.text = widget.initText ?? "";
|
||||
} else {
|
||||
|
|
@ -832,7 +865,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
defaultWidget: TIMUIKitTextFieldLayoutNarrow(
|
||||
onEmojiSubmitted: onEmojiSubmitted,
|
||||
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
|
||||
backSpaceText: backSpaceText,
|
||||
backSpaceText: deleteStickerFromText,
|
||||
addStickerToText: addStickerToText,
|
||||
customStickerPanel: widget.customStickerPanel,
|
||||
forbiddenText: forbiddenText,
|
||||
|
|
@ -864,11 +897,12 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
|
|||
showMorePanel: widget.showMorePanel,
|
||||
customEmojiStickerList: widget.customEmojiStickerList),
|
||||
desktopWidget: TIMUIKitTextFieldLayoutWide(
|
||||
chatConfig: widget.chatConfig ?? widget.model.chatConfig,
|
||||
theme: theme,
|
||||
currentConversation: widget.currentConversation,
|
||||
onEmojiSubmitted: onEmojiSubmitted,
|
||||
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
|
||||
backSpaceText: backSpaceText,
|
||||
backSpaceText: deleteStickerFromText,
|
||||
addStickerToText: addStickerToText,
|
||||
customStickerPanel: widget.customStickerPanel,
|
||||
forbiddenText: forbiddenText,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
/// show send audio icon
|
||||
final bool showSendAudio;
|
||||
|
||||
final TIMUIKitChatConfig chatConfig;
|
||||
|
||||
/// on text changed
|
||||
final void Function(String)? onChanged;
|
||||
|
||||
|
|
@ -183,7 +185,8 @@ class TIMUIKitTextFieldLayoutWide extends StatefulWidget {
|
|||
required this.customEmojiStickerList,
|
||||
this.controller,
|
||||
required this.currentConversation,
|
||||
required this.theme})
|
||||
required this.theme,
|
||||
required this.chatConfig})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -288,12 +291,10 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
children: [
|
||||
Text(
|
||||
TIM_t("回复 "),
|
||||
style: TextStyle(
|
||||
color: hexToColor("8f959e"), fontSize: 14),
|
||||
style: TextStyle(color: hexToColor("8f959e"), fontSize: 14),
|
||||
),
|
||||
Text(
|
||||
MessageUtils.getDisplayName(
|
||||
widget.model.repliedMessage!),
|
||||
MessageUtils.getDisplayName(widget.model.repliedMessage!),
|
||||
softWrap: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
@ -367,7 +368,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
child: Container(
|
||||
child: widget.customStickerPanel != null
|
||||
? widget.customStickerPanel!(
|
||||
height: 400,
|
||||
height: widget.chatConfig.desktopStickerPanelHeight,
|
||||
width: 350,
|
||||
sendTextMessage: () {
|
||||
widget.onEmojiSubmitted();
|
||||
|
|
@ -495,7 +496,8 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
final double? dx = (offset?.dx != null) ? offset!.dx : null;
|
||||
final double? dy =
|
||||
(offset?.dy != null && alignBox?.size.height != null)
|
||||
? offset!.dy - 420
|
||||
? offset!.dy -
|
||||
(widget.chatConfig.desktopStickerPanelHeight + 20)
|
||||
: null;
|
||||
e.onClick((dx != null && dy != null) ? Offset(dx, dy) : null);
|
||||
},
|
||||
|
|
@ -813,8 +815,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
|
||||
generateDefaultControlBarItems() {
|
||||
final DesktopControlBarConfig config =
|
||||
widget.model.chatConfig.desktopControlBarConfig ??
|
||||
DesktopControlBarConfig();
|
||||
widget.chatConfig.desktopControlBarConfig ?? DesktopControlBarConfig();
|
||||
final List<DesktopControlBarItem> itemsList = [
|
||||
if (config.showStickerPanel)
|
||||
DesktopControlBarItem(
|
||||
|
|
@ -898,7 +899,7 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
TUIChatSeparateViewModel model, TUITheme theme) {
|
||||
final List<DesktopControlBarItem> itemsList = [
|
||||
...defaultControlBarItems,
|
||||
...(widget.model.chatConfig.additionalDesktopControlBarItems ?? [])
|
||||
...(widget.chatConfig.additionalDesktopControlBarItems ?? [])
|
||||
];
|
||||
|
||||
return generateBarIcons(itemsList, theme);
|
||||
|
|
@ -1021,10 +1022,10 @@ class _TIMUIKitTextFieldLayoutWideState
|
|||
child: ExtendedTextField(
|
||||
scrollController: _scrollController,
|
||||
autofocus: true,
|
||||
maxLines: widget
|
||||
.model.chatConfig.desktopMessageInputFieldLines,
|
||||
minLines: widget
|
||||
.model.chatConfig.desktopMessageInputFieldLines,
|
||||
maxLines:
|
||||
widget.chatConfig.desktopMessageInputFieldLines,
|
||||
minLines:
|
||||
widget.chatConfig.desktopMessageInputFieldLines,
|
||||
focusNode: widget.focusNode,
|
||||
onChanged: debounceFunc,
|
||||
keyboardType: TextInputType.multiline,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
|
||||
/// use for customize avatar
|
||||
final Widget Function(BuildContext context, V2TimMessage message)?
|
||||
userAvatarBuilder;
|
||||
userAvatarBuilder;
|
||||
|
||||
/// Use for show conversation name.
|
||||
/// This field is not necessary to be provided, when `conversation` is provided, unless you want to cover this field manually.
|
||||
|
|
@ -64,7 +64,7 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
|
||||
/// Avatar and name in message reaction secondary tap callback.
|
||||
final void Function(String userID, TapDownDetails tapDetails)?
|
||||
onSecondaryTapAvatar;
|
||||
onSecondaryTapAvatar;
|
||||
|
||||
@Deprecated(
|
||||
"Nickname will not shows in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
|
||||
|
|
@ -142,6 +142,8 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
|
||||
final Widget? customAppBar;
|
||||
|
||||
final Widget? inputTopBuilder;
|
||||
|
||||
/// Custom emoji panel.
|
||||
final CustomStickerPanel? customStickerPanel;
|
||||
|
||||
|
|
@ -154,46 +156,47 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
/// Custom text field
|
||||
final Widget Function(BuildContext context)? textFieldBuilder;
|
||||
|
||||
TIMUIKitChat({Key? key,
|
||||
this.groupID,
|
||||
required this.conversation,
|
||||
this.conversationID,
|
||||
this.conversationType,
|
||||
this.conversationShowName,
|
||||
this.abstractMessageBuilder,
|
||||
this.onTapAvatar,
|
||||
@Deprecated(
|
||||
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead") this.showNickName = false,
|
||||
this.showTotalUnReadCount = false,
|
||||
this.messageItemBuilder,
|
||||
@Deprecated(
|
||||
"Please use [extraTipsActionItemBuilder] instead") this.exteraTipsActionItemBuilder,
|
||||
this.extraTipsActionItemBuilder,
|
||||
this.draftText,
|
||||
this.textFieldHintText,
|
||||
this.initFindingMsg,
|
||||
this.userAvatarBuilder,
|
||||
this.appBarConfig,
|
||||
this.controller,
|
||||
this.morePanelConfig,
|
||||
this.customStickerPanel,
|
||||
this.config = const TIMUIKitChatConfig(),
|
||||
this.tongueItemBuilder,
|
||||
this.groupAtInfoList,
|
||||
this.mainHistoryListConfig,
|
||||
this.onDealWithGroupApplication,
|
||||
this.toolTipsConfig,
|
||||
this.lifeCycle,
|
||||
this.topFixWidget = const SizedBox(),
|
||||
this.textFieldBuilder,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.customAppBar,
|
||||
this.onSecondaryTapAvatar,
|
||||
this.customMessageHoverBarOnDesktop})
|
||||
TIMUIKitChat(
|
||||
{Key? key,
|
||||
this.groupID,
|
||||
required this.conversation,
|
||||
this.conversationID,
|
||||
this.conversationType,
|
||||
this.conversationShowName,
|
||||
this.abstractMessageBuilder,
|
||||
this.onTapAvatar,
|
||||
@Deprecated(
|
||||
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
|
||||
this.showNickName = false,
|
||||
this.showTotalUnReadCount = false,
|
||||
this.messageItemBuilder,
|
||||
@Deprecated("Please use [extraTipsActionItemBuilder] instead")
|
||||
this.exteraTipsActionItemBuilder,
|
||||
this.extraTipsActionItemBuilder,
|
||||
this.draftText,
|
||||
this.textFieldHintText,
|
||||
this.initFindingMsg,
|
||||
this.userAvatarBuilder,
|
||||
this.appBarConfig,
|
||||
this.controller,
|
||||
this.morePanelConfig,
|
||||
this.customStickerPanel,
|
||||
this.config = const TIMUIKitChatConfig(),
|
||||
this.tongueItemBuilder,
|
||||
this.groupAtInfoList,
|
||||
this.mainHistoryListConfig,
|
||||
this.onDealWithGroupApplication,
|
||||
this.toolTipsConfig,
|
||||
this.lifeCycle,
|
||||
this.topFixWidget = const SizedBox(),
|
||||
this.textFieldBuilder,
|
||||
this.customEmojiStickerList = const [],
|
||||
this.customAppBar,
|
||||
this.inputTopBuilder,
|
||||
this.onSecondaryTapAvatar,
|
||||
this.customMessageHoverBarOnDesktop})
|
||||
: super(key: key) {
|
||||
startTime = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch;
|
||||
startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -203,15 +206,15 @@ class TIMUIKitChat extends StatefulWidget {
|
|||
class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
||||
TUIChatSeparateViewModel model = TUIChatSeparateViewModel();
|
||||
final TUISelfInfoViewModel selfInfoViewModel =
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
serviceLocator<TUISelfInfoViewModel>();
|
||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||
final TUIConversationViewModel conversationViewModel =
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
serviceLocator<TUIConversationViewModel>();
|
||||
TIMUIKitInputTextFieldController textFieldController =
|
||||
TIMUIKitInputTextFieldController();
|
||||
TIMUIKitInputTextFieldController();
|
||||
bool isInit = false;
|
||||
final TUIChatGlobalModel chatGlobalModel =
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
serviceLocator<TUIChatGlobalModel>();
|
||||
bool _dragging = false;
|
||||
|
||||
final GlobalKey alignKey = GlobalKey();
|
||||
|
|
@ -219,19 +222,13 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
|
||||
late AutoScrollController autoController = AutoScrollController(
|
||||
viewportBoundaryGetter: () =>
|
||||
Rect.fromLTRB(0, 0, 0, MediaQuery
|
||||
.of(context)
|
||||
.padding
|
||||
.bottom),
|
||||
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
|
||||
axis: Axis.vertical,
|
||||
);
|
||||
|
||||
late AutoScrollController atMemberPanelScroll = AutoScrollController(
|
||||
viewportBoundaryGetter: () =>
|
||||
Rect.fromLTRB(0, 0, 0, MediaQuery
|
||||
.of(context)
|
||||
.padding
|
||||
.bottom),
|
||||
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
|
||||
axis: Axis.vertical,
|
||||
);
|
||||
|
||||
|
|
@ -245,9 +242,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
model.onTapAvatar = widget.onTapAvatar;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (kProfileMode) {
|
||||
widget.endTime = DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch;
|
||||
widget.endTime = DateTime.now().millisecondsSinceEpoch;
|
||||
int timeSpend = widget.endTime - widget.startTime;
|
||||
print("Page render time:$timeSpend ms");
|
||||
}
|
||||
|
|
@ -300,8 +295,8 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
final topicInfoList = await TencentImSDKPlugin.v2TIMManager
|
||||
.getGroupManager()
|
||||
.getTopicInfoList(
|
||||
groupID: widget.groupID!,
|
||||
topicIDList: [widget.conversation.conversationID]);
|
||||
groupID: widget.groupID!,
|
||||
topicIDList: [widget.conversation.conversationID]);
|
||||
final topicInfo = topicInfoList.data?.first.topicInfo;
|
||||
final draftText = topicInfo?.draftText;
|
||||
if (TencentUtils.checkString(draftText) != null) {
|
||||
|
|
@ -367,7 +362,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final TUITheme theme = value.theme;
|
||||
final closePanel =
|
||||
OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60);
|
||||
OptimizeUtils.throttle((_) => textFieldController.hideAllPanel(), 60);
|
||||
final isBuild = isInit;
|
||||
isInit = true;
|
||||
|
||||
|
|
@ -386,7 +381,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
],
|
||||
builder: (context, model, w) {
|
||||
final TUIChatGlobalModel chatGlobalModel =
|
||||
Provider.of<TUIChatGlobalModel>(context, listen: true);
|
||||
Provider.of<TUIChatGlobalModel>(context, listen: true);
|
||||
|
||||
widget.controller?.model = model;
|
||||
widget.controller?.textFieldController = textFieldController;
|
||||
|
|
@ -396,14 +391,14 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
widget.onDealWithGroupApplication != null) {
|
||||
filteredApplicationList =
|
||||
chatGlobalModel.groupApplicationList.where((item) {
|
||||
return (item.groupID == widget.conversationID) &&
|
||||
item.handleStatus == 0;
|
||||
}).toList();
|
||||
return (item.groupID == widget.conversationID) &&
|
||||
item.handleStatus == 0;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
final selfUserID = selfInfoViewModel.loginInfo?.userID;
|
||||
final TUIGroupListenerModel groupListenerModel =
|
||||
Provider.of<TUIGroupListenerModel>(context, listen: true);
|
||||
Provider.of<TUIGroupListenerModel>(context, listen: true);
|
||||
final NeedUpdate? needUpdate = groupListenerModel.needUpdate;
|
||||
if (needUpdate != null &&
|
||||
needUpdate.groupID == widget.conversationID) {
|
||||
|
|
@ -429,13 +424,13 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
resizeToAvoidBottomInset: false,
|
||||
appBar: (widget.customAppBar == null)
|
||||
? TIMUIKitAppBar(
|
||||
showTotalUnReadCount: widget.showTotalUnReadCount,
|
||||
config: widget.appBarConfig,
|
||||
conversationShowName: _getTitle(),
|
||||
conversationID: _getConvID(),
|
||||
showC2cMessageEditStatus:
|
||||
widget.config?.showC2cMessageEditStatus ?? true,
|
||||
)
|
||||
showTotalUnReadCount: widget.showTotalUnReadCount,
|
||||
config: widget.appBarConfig,
|
||||
conversationShowName: _getTitle(),
|
||||
conversationID: _getConvID(),
|
||||
showC2cMessageEditStatus:
|
||||
widget.config?.showC2cMessageEditStatus ?? true,
|
||||
)
|
||||
: null,
|
||||
body: DropTarget(
|
||||
onDragDone: (detail) {
|
||||
|
|
@ -463,6 +458,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.customAppBar != null) widget.customAppBar!,
|
||||
if (filteredApplicationList.isNotEmpty)
|
||||
|
|
@ -471,110 +467,110 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
if (widget.topFixWidget != null) widget.topFixWidget!,
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: theme.chatBgColor,
|
||||
child: Align(
|
||||
key: alignKey,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Listener(
|
||||
onPointerMove: closePanel,
|
||||
child: TIMUIKitHistoryMessageListContainer(
|
||||
customMessageHoverBarOnDesktop: widget
|
||||
.customMessageHoverBarOnDesktop,
|
||||
conversation: widget.conversation,
|
||||
groupMemberInfo: model.groupMemberList
|
||||
?.firstWhere(
|
||||
(element) =>
|
||||
element?.userID == selfUserID,
|
||||
color: theme.chatBgColor,
|
||||
child: Align(
|
||||
key: alignKey,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Listener(
|
||||
onPointerMove: closePanel,
|
||||
child: TIMUIKitHistoryMessageListContainer(
|
||||
customMessageHoverBarOnDesktop:
|
||||
widget.customMessageHoverBarOnDesktop,
|
||||
conversation: widget.conversation,
|
||||
groupMemberInfo: model.groupMemberList
|
||||
?.firstWhere(
|
||||
(element) =>
|
||||
element?.userID == selfUserID,
|
||||
orElse: () => null),
|
||||
textFieldController: textFieldController,
|
||||
customEmojiStickerList:
|
||||
textFieldController: textFieldController,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
isUseDefaultEmoji:
|
||||
isUseDefaultEmoji:
|
||||
widget.config!.isUseDefaultEmoji,
|
||||
key: listContainerKey,
|
||||
isAllowScroll: true,
|
||||
userAvatarBuilder: widget
|
||||
.userAvatarBuilder,
|
||||
toolTipsConfig: widget.toolTipsConfig,
|
||||
groupAtInfoList: widget.groupAtInfoList,
|
||||
tongueItemBuilder: widget
|
||||
.tongueItemBuilder,
|
||||
onLongPressForOthersHeadPortrait:
|
||||
(String? userId, String? nickName) {
|
||||
textFieldController.longPressToAt(
|
||||
nickName, userId);
|
||||
},
|
||||
mainHistoryListConfig:
|
||||
key: listContainerKey,
|
||||
isAllowScroll: true,
|
||||
userAvatarBuilder: widget.userAvatarBuilder,
|
||||
toolTipsConfig: widget.toolTipsConfig,
|
||||
groupAtInfoList: widget.groupAtInfoList,
|
||||
tongueItemBuilder: widget.tongueItemBuilder,
|
||||
onLongPressForOthersHeadPortrait:
|
||||
(String? userId, String? nickName) {
|
||||
textFieldController.longPressToAt(
|
||||
nickName, userId);
|
||||
},
|
||||
mainHistoryListConfig:
|
||||
widget.mainHistoryListConfig,
|
||||
initFindingMsg: widget.initFindingMsg,
|
||||
extraTipsActionItemBuilder:
|
||||
initFindingMsg: widget.initFindingMsg,
|
||||
extraTipsActionItemBuilder:
|
||||
widget.extraTipsActionItemBuilder ??
|
||||
widget.exteraTipsActionItemBuilder,
|
||||
conversationType: _getConvType(),
|
||||
scrollController: autoController,
|
||||
onSecondaryTapAvatar:
|
||||
conversationType: _getConvType(),
|
||||
scrollController: autoController,
|
||||
onSecondaryTapAvatar:
|
||||
widget.onSecondaryTapAvatar,
|
||||
onTapAvatar: widget.onTapAvatar,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
showNickName: widget.showNickName,
|
||||
messageItemBuilder:
|
||||
onTapAvatar: widget.onTapAvatar,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
showNickName: widget.showNickName,
|
||||
messageItemBuilder:
|
||||
widget.messageItemBuilder,
|
||||
conversationID: _getConvID(),
|
||||
),
|
||||
)),
|
||||
)),
|
||||
conversationID: _getConvID(),
|
||||
),
|
||||
)),
|
||||
)),
|
||||
widget.inputTopBuilder ?? Container(),
|
||||
Selector<TUIChatSeparateViewModel, bool>(
|
||||
builder: (context, value, child) {
|
||||
return value
|
||||
? MultiSelectPanel(
|
||||
conversationType: _getConvType(),
|
||||
)
|
||||
conversationType: _getConvType(),
|
||||
)
|
||||
: (widget.textFieldBuilder != null
|
||||
? widget.textFieldBuilder!(context)
|
||||
: TIMUIKitInputTextField(
|
||||
groupID: widget.groupID,
|
||||
atMemberPanelScroll:
|
||||
atMemberPanelScroll,
|
||||
groupType:
|
||||
widget.conversation.groupType,
|
||||
currentConversation:
|
||||
widget.conversation,
|
||||
model: model,
|
||||
controller: textFieldController,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
isUseDefaultEmoji:
|
||||
widget.config!.isUseDefaultEmoji,
|
||||
customStickerPanel:
|
||||
widget.customStickerPanel,
|
||||
morePanelConfig:
|
||||
widget.morePanelConfig,
|
||||
scrollController: autoController,
|
||||
conversationID: _getConvID(),
|
||||
conversationType: _getConvType(),
|
||||
initText: TencentUtils.checkString(
|
||||
widget.draftText) ??
|
||||
(PlatformUtils().isWeb
|
||||
? TencentUtils.checkString(
|
||||
conversationViewModel
|
||||
.getWebDraft(
|
||||
conversationID: widget
|
||||
.conversation
|
||||
.conversationID))
|
||||
: TencentUtils.checkString(
|
||||
widget.conversation
|
||||
.draftText)),
|
||||
hintText: widget.textFieldHintText,
|
||||
showMorePanel: widget.config
|
||||
?.isAllowShowMorePanel ??
|
||||
true,
|
||||
showSendAudio: widget.config
|
||||
?.isAllowSoundMessage ??
|
||||
true,
|
||||
showSendEmoji: widget
|
||||
.config?.isAllowEmojiPanel ??
|
||||
true,
|
||||
));
|
||||
? widget.textFieldBuilder!(context)
|
||||
: TIMUIKitInputTextField(
|
||||
chatConfig: widget.config,
|
||||
groupID: widget.groupID,
|
||||
atMemberPanelScroll:
|
||||
atMemberPanelScroll,
|
||||
groupType:
|
||||
widget.conversation.groupType,
|
||||
currentConversation:
|
||||
widget.conversation,
|
||||
model: model,
|
||||
controller: textFieldController,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
isUseDefaultEmoji:
|
||||
widget.config!.isUseDefaultEmoji,
|
||||
customStickerPanel:
|
||||
widget.customStickerPanel,
|
||||
morePanelConfig:
|
||||
widget.morePanelConfig,
|
||||
scrollController: autoController,
|
||||
conversationID: _getConvID(),
|
||||
conversationType: _getConvType(),
|
||||
initText: TencentUtils.checkString(
|
||||
widget.draftText) ??
|
||||
(PlatformUtils().isWeb
|
||||
? TencentUtils.checkString(
|
||||
conversationViewModel
|
||||
.getWebDraft(
|
||||
conversationID: widget
|
||||
.conversation
|
||||
.conversationID))
|
||||
: TencentUtils.checkString(
|
||||
widget.conversation
|
||||
.draftText)),
|
||||
hintText: widget.textFieldHintText,
|
||||
showMorePanel: widget.config
|
||||
?.isAllowShowMorePanel ??
|
||||
true,
|
||||
showSendAudio: widget.config
|
||||
?.isAllowSoundMessage ??
|
||||
true,
|
||||
showSendEmoji: widget
|
||||
.config?.isAllowEmojiPanel ??
|
||||
true,
|
||||
));
|
||||
},
|
||||
selector: (c, model) {
|
||||
return model.isMultiSelect;
|
||||
|
|
@ -603,13 +599,13 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
|
|||
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
|
||||
TUIChatSeparateViewModel? model;
|
||||
final TUIGroupListenerModel groupListenerModel =
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
serviceLocator<TUIGroupListenerModel>();
|
||||
final TUIThemeViewModel themeViewModel = serviceLocator<TUIThemeViewModel>();
|
||||
final Widget? child;
|
||||
|
||||
/// You could get the model from here, and transfer it to other widget from TUIKit.
|
||||
final Widget Function(BuildContext, TUIChatSeparateViewModel, Widget?)
|
||||
builder;
|
||||
builder;
|
||||
final List<SingleChildWidget>? providers;
|
||||
|
||||
/// `TIMUIKitChatController` needs to be provided if you use it outside.
|
||||
|
|
@ -636,20 +632,21 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
|
|||
|
||||
final AutoScrollController? scrollController;
|
||||
|
||||
TIMUIKitChatProviderScope({Key? key,
|
||||
this.child,
|
||||
this.providers,
|
||||
this.textFieldController,
|
||||
required this.builder,
|
||||
this.model,
|
||||
this.groupID,
|
||||
this.isBuild,
|
||||
required this.conversationID,
|
||||
required this.conversationType,
|
||||
this.controller,
|
||||
this.config,
|
||||
this.lifeCycle,
|
||||
this.scrollController})
|
||||
TIMUIKitChatProviderScope(
|
||||
{Key? key,
|
||||
this.child,
|
||||
this.providers,
|
||||
this.textFieldController,
|
||||
required this.builder,
|
||||
this.model,
|
||||
this.groupID,
|
||||
this.isBuild,
|
||||
required this.conversationID,
|
||||
required this.conversationType,
|
||||
this.controller,
|
||||
this.config,
|
||||
this.lifeCycle,
|
||||
this.scrollController})
|
||||
: super(key: key) {
|
||||
if (isBuild ?? false) {
|
||||
return;
|
||||
|
|
@ -665,7 +662,7 @@ class TIMUIKitChatProviderScope extends StatelessWidget {
|
|||
model?.initForEachConversation(
|
||||
conversationType,
|
||||
conversationID,
|
||||
(String value) {
|
||||
(String value) {
|
||||
textFieldController?.textEditingController?.text = value;
|
||||
},
|
||||
groupID: groupID,
|
||||
|
|
|
|||
|
|
@ -199,9 +199,17 @@ class TIMUIKitChatConfig {
|
|||
/// [Default]: false
|
||||
final bool isGroupAdminRecallEnabled;
|
||||
|
||||
/// Defines the height of the sticker panel on desktop platforms.
|
||||
/// If the height of the sticker list exceeds this container height,
|
||||
/// the sticker list will automatically become scrollable.
|
||||
///
|
||||
/// [Default]: 400
|
||||
final double desktopStickerPanelHeight;
|
||||
|
||||
const TIMUIKitChatConfig(
|
||||
{this.onTapLink,
|
||||
this.timeDividerConfig,
|
||||
this.desktopStickerPanelHeight = 400,
|
||||
this.isGroupAdminRecallEnabled = false,
|
||||
this.isAutoReportRead = true,
|
||||
this.faceURIPrefix,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal
|
|||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
|
@ -31,15 +32,27 @@ sendFileWithConfirmation(
|
|||
required BuildContext context}) async {
|
||||
bool isCanSend = true;
|
||||
|
||||
files.map((e) => e.path).any((filePath) {
|
||||
final directory = Directory(filePath);
|
||||
final isDirectoryExists = directory.existsSync();
|
||||
if (isDirectoryExists) {
|
||||
isCanSend = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!PlatformUtils().isWeb) {
|
||||
files.map((e) => e.path).any((filePath) {
|
||||
final directory = Directory(filePath);
|
||||
final isDirectoryExists = directory.existsSync();
|
||||
if (isDirectoryExists) {
|
||||
isCanSend = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
files.map((e) => e.name).any((fileName) {
|
||||
String fileExtension = path.extension(fileName);
|
||||
bool hasNoExtension = fileExtension.isEmpty;
|
||||
if (hasNoExtension) {
|
||||
isCanSend = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!isCanSend) {
|
||||
TUIKitWidePopup.showSecondaryConfirmDialog(
|
||||
|
|
@ -71,6 +84,9 @@ sendFileWithConfirmation(
|
|||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final file = files[index];
|
||||
final fileName = PlatformUtils().isWeb
|
||||
? file.name
|
||||
: path.basename(file.path);
|
||||
return Material(
|
||||
color: theme.wideBackgroundColor,
|
||||
child: InkWell(
|
||||
|
|
@ -84,18 +100,13 @@ sendFileWithConfirmation(
|
|||
children: [
|
||||
TIMUIKitFileIcon(
|
||||
size: 44,
|
||||
fileFormat: path
|
||||
.extension(file.path)
|
||||
.split(".")[path
|
||||
.extension(file.path)
|
||||
.split(".")
|
||||
.length -
|
||||
1],
|
||||
fileFormat: fileName.split(
|
||||
".")[fileName.split(".").length - 1],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
path.basename(file.path),
|
||||
fileName,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: theme.darkTextColor),
|
||||
|
|
@ -154,9 +165,12 @@ Future<void> sendFiles(
|
|||
ConvType conversationType,
|
||||
BuildContext context) async {
|
||||
for (final file in files) {
|
||||
final fileName = file.name;
|
||||
final filePath = file.path;
|
||||
await MessageUtils.handleMessageError(
|
||||
model.sendFileMessage(
|
||||
filePath: file.path,
|
||||
fileName: fileName,
|
||||
filePath: filePath,
|
||||
convID: _getConvID(conversation),
|
||||
convType: conversationType),
|
||||
context);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ class ForwardMessageScreen extends StatefulWidget {
|
|||
{Key? key,
|
||||
this.isMergerForward = false,
|
||||
required this.conversationType,
|
||||
required this.model, this.onClose})
|
||||
required this.model,
|
||||
this.onClose})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -74,19 +75,25 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
|
|||
}
|
||||
widget.model.updateMultiSelectStatus(false);
|
||||
|
||||
if(widget.onClose != null){
|
||||
widget.onClose!();
|
||||
}else{
|
||||
if (widget.onClose != null) {
|
||||
// widget.onClose!();
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.model.updateMultiSelectStatus(false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
final isDesktopScreen =
|
||||
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
|
||||
final TUITheme theme = value.theme;
|
||||
if(isDesktopScreen){
|
||||
if (isDesktopScreen) {
|
||||
isMultiSelect = true;
|
||||
return RecentForwardList(
|
||||
isMultiSelect: isMultiSelect,
|
||||
|
|
@ -101,16 +108,17 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
|
|||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
TIM_t("选择"),
|
||||
isMultiSelect ? TIM_t("选择多个会话") : TIM_t("选择一个会话"),
|
||||
style: TextStyle(
|
||||
color: theme.appbarTextColor,
|
||||
fontSize: 17,
|
||||
),
|
||||
),
|
||||
shadowColor: theme.weakBackgroundColor,
|
||||
backgroundColor: theme.appbarBgColor ??
|
||||
theme.primaryColor,
|
||||
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
|
||||
leadingWidth: 80,
|
||||
leading: TextButton(
|
||||
onPressed: () {
|
||||
if (isMultiSelect) {
|
||||
|
|
@ -120,9 +128,9 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
|
|||
});
|
||||
} else {
|
||||
widget.model.updateMultiSelectStatus(false);
|
||||
if(widget.onClose != null){
|
||||
if (widget.onClose != null) {
|
||||
widget.onClose!();
|
||||
}else{
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +143,6 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
leadingWidth: 80,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
|
|
@ -149,9 +156,9 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
|
|||
},
|
||||
child: Text(
|
||||
!isMultiSelect ? TIM_t("多选") : TIM_t("完成"),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
style: TextStyle(
|
||||
color: theme.appbarTextColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -122,124 +122,113 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
|
|||
},
|
||||
child: GestureDetector(
|
||||
onTap: close,
|
||||
child: ExtendedImageGesturePageView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: ExtendedPageController(
|
||||
initialPage: 0,
|
||||
pageSpacing: 0,
|
||||
shouldIgnorePointerWhenScrolling: false,
|
||||
),
|
||||
itemCount: 1,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return HeroWidget(
|
||||
tag: widget.heroTag,
|
||||
slidePagekey: slidePagekey,
|
||||
child: ExtendedImage(
|
||||
image: widget.imageProvider,
|
||||
extendedImageGestureKey:
|
||||
extendedImageGestureKey,
|
||||
enableSlideOutPage: true,
|
||||
// fit: BoxFit.scaleDown,
|
||||
initGestureConfigHandler: (state) {
|
||||
return GestureConfig(
|
||||
minScale: 0.8,
|
||||
animationMinScale: 0.6,
|
||||
maxScale: 2 * fittedScale,
|
||||
animationMaxScale: 2.5 * fittedScale,
|
||||
speed: 1.0,
|
||||
inertialSpeed: 100.0,
|
||||
initialScale: initialScale,
|
||||
initialAlignment:
|
||||
InitialAlignment.topCenter,
|
||||
hitTestBehavior: HitTestBehavior.opaque,
|
||||
);
|
||||
},
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
switch (state.extendedImageLoadState) {
|
||||
case LoadState.loading:
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white)));
|
||||
case LoadState.completed:
|
||||
final screenHeight =
|
||||
MediaQuery.of(context).size.height;
|
||||
final screenWidth =
|
||||
MediaQuery.of(context).size.width;
|
||||
final imgHeight = state.extendedImageInfo
|
||||
?.image.height ??
|
||||
1;
|
||||
final imgWidth = state
|
||||
.extendedImageInfo?.image.width ??
|
||||
0;
|
||||
final imgRatio = imgWidth / imgHeight;
|
||||
final screenRatio =
|
||||
screenWidth / screenHeight;
|
||||
final fitWidthScale =
|
||||
screenRatio / imgRatio;
|
||||
if (screenRatio > imgRatio) {
|
||||
// Long Image
|
||||
// initialScale = fitWidthScale;
|
||||
fittedScale = fitWidthScale;
|
||||
doubleTapScales[1] = fitWidthScale;
|
||||
} else {
|
||||
fittedScale =
|
||||
1 / fitWidthScale; // fittedHeight
|
||||
doubleTapScales[1] = 1 / fitWidthScale;
|
||||
}
|
||||
return GesturedImage(state,
|
||||
key: extendedImageGestureKey);
|
||||
case LoadState.failed:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onDoubleTap: (ExtendedImageGestureState state) {
|
||||
///you can use define pointerDownPosition as you can,
|
||||
///default value is double tap pointer down postion.
|
||||
final Offset? pointerDownPosition =
|
||||
state.pointerDownPosition;
|
||||
final double? begin =
|
||||
state.gestureDetails!.totalScale;
|
||||
double end;
|
||||
child: HeroWidget(
|
||||
tag: widget.heroTag,
|
||||
slidePagekey: slidePagekey,
|
||||
child: ExtendedImage(
|
||||
image: widget.imageProvider,
|
||||
extendedImageGestureKey:
|
||||
extendedImageGestureKey,
|
||||
enableSlideOutPage: true,
|
||||
// fit: BoxFit.scaleDown,
|
||||
initGestureConfigHandler: (state) {
|
||||
return GestureConfig(
|
||||
minScale: 0.8,
|
||||
animationMinScale: 0.6,
|
||||
maxScale: 2 * fittedScale,
|
||||
animationMaxScale: 2.5 * fittedScale,
|
||||
speed: 1.0,
|
||||
inertialSpeed: 100.0,
|
||||
initialScale: initialScale,
|
||||
initialAlignment:
|
||||
InitialAlignment.topCenter,
|
||||
hitTestBehavior: HitTestBehavior.opaque,
|
||||
);
|
||||
},
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
switch (state.extendedImageLoadState) {
|
||||
case LoadState.loading:
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white)));
|
||||
case LoadState.completed:
|
||||
final screenHeight =
|
||||
MediaQuery.of(context).size.height;
|
||||
final screenWidth =
|
||||
MediaQuery.of(context).size.width;
|
||||
final imgHeight = state.extendedImageInfo
|
||||
?.image.height ??
|
||||
1;
|
||||
final imgWidth = state
|
||||
.extendedImageInfo?.image.width ??
|
||||
0;
|
||||
final imgRatio = imgWidth / imgHeight;
|
||||
final screenRatio =
|
||||
screenWidth / screenHeight;
|
||||
final fitWidthScale =
|
||||
screenRatio / imgRatio;
|
||||
if (screenRatio > imgRatio) {
|
||||
// Long Image
|
||||
// initialScale = fitWidthScale;
|
||||
fittedScale = fitWidthScale;
|
||||
doubleTapScales[1] = fitWidthScale;
|
||||
} else {
|
||||
fittedScale =
|
||||
1 / fitWidthScale; // fittedHeight
|
||||
doubleTapScales[1] = 1 / fitWidthScale;
|
||||
}
|
||||
return GesturedImage(state,
|
||||
key: extendedImageGestureKey);
|
||||
case LoadState.failed:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onDoubleTap: (ExtendedImageGestureState state) {
|
||||
///you can use define pointerDownPosition as you can,
|
||||
///default value is double tap pointer down postion.
|
||||
final Offset? pointerDownPosition =
|
||||
state.pointerDownPosition;
|
||||
final double? begin =
|
||||
state.gestureDetails!.totalScale;
|
||||
double end;
|
||||
|
||||
//remove old
|
||||
_doubleClickAnimation?.removeListener(
|
||||
_doubleClickAnimationListener);
|
||||
//remove old
|
||||
_doubleClickAnimation?.removeListener(
|
||||
_doubleClickAnimationListener);
|
||||
|
||||
//stop pre
|
||||
_doubleClickAnimationController.stop();
|
||||
//stop pre
|
||||
_doubleClickAnimationController.stop();
|
||||
|
||||
//reset to use
|
||||
_doubleClickAnimationController.reset();
|
||||
//reset to use
|
||||
_doubleClickAnimationController.reset();
|
||||
|
||||
if (begin == doubleTapScales[0]) {
|
||||
end = doubleTapScales[1];
|
||||
} else {
|
||||
end = doubleTapScales[0];
|
||||
}
|
||||
if (begin == doubleTapScales[0]) {
|
||||
end = doubleTapScales[1];
|
||||
} else {
|
||||
end = doubleTapScales[0];
|
||||
}
|
||||
|
||||
_doubleClickAnimationListener = () {
|
||||
//print(_animation.value);
|
||||
state.handleDoubleTap(
|
||||
scale: _doubleClickAnimation!.value,
|
||||
doubleTapPosition: pointerDownPosition);
|
||||
};
|
||||
_doubleClickAnimation =
|
||||
_doubleClickAnimationController.drive(
|
||||
Tween<double>(
|
||||
begin: begin, end: end));
|
||||
_doubleClickAnimationListener = () {
|
||||
//print(_animation.value);
|
||||
state.handleDoubleTap(
|
||||
scale: _doubleClickAnimation!.value,
|
||||
doubleTapPosition: pointerDownPosition);
|
||||
};
|
||||
_doubleClickAnimation =
|
||||
_doubleClickAnimationController.drive(
|
||||
Tween<double>(
|
||||
begin: begin, end: end));
|
||||
|
||||
_doubleClickAnimation!.addListener(
|
||||
_doubleClickAnimationListener);
|
||||
_doubleClickAnimation!.addListener(
|
||||
_doubleClickAnimationListener);
|
||||
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
mode: ExtendedImageMode.gesture,
|
||||
));
|
||||
}),
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
mode: ExtendedImageMode.gesture,
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -232,21 +232,23 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
|
|||
final faceUrl = message.faceUrl ?? "";
|
||||
final showName = message.nickName ?? message.userID ?? "";
|
||||
final theme = Provider.of<TUIThemeViewModel>(context).theme;
|
||||
final isSelf = message.isSelf ?? false;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
if(!isSelf) SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Avatar(faceUrl: faceUrl, showName: showName),
|
||||
),
|
||||
const SizedBox(
|
||||
if(!isSelf) const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(showName,
|
||||
style: TextStyle(fontSize: 12, color: theme.weakTextColor)),
|
||||
|
|
@ -258,7 +260,15 @@ class MergerMessageScreenState extends TIMUIKitState<MergerMessageScreen> {
|
|||
child: _getMsgItem(message),
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
if(isSelf) const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
if(isSelf) SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Avatar(faceUrl: faceUrl, showName: showName),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import 'dart:math';
|
|||
import 'package:crypto/crypto.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
import 'package:tencent_im_base/tencent_im_base.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
|
@ -21,11 +20,10 @@ import 'package:video_player/video_player.dart';
|
|||
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
|
||||
|
||||
class VideoScreen extends StatefulWidget {
|
||||
const VideoScreen(
|
||||
{required this.message,
|
||||
required this.heroTag,
|
||||
required this.videoElement,
|
||||
Key? key})
|
||||
const VideoScreen({required this.message,
|
||||
required this.heroTag,
|
||||
required this.videoElement,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
|
||||
final V2TimMessage message;
|
||||
|
|
@ -40,13 +38,14 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
late VideoPlayerController videoPlayerController;
|
||||
late ChewieController chewieController;
|
||||
GlobalKey<ExtendedImageSlidePageState> slidePagekey =
|
||||
GlobalKey<ExtendedImageSlidePageState>();
|
||||
GlobalKey<ExtendedImageSlidePageState>();
|
||||
final TUIChatGlobalModel model = serviceLocator<TUIChatGlobalModel>();
|
||||
bool isInit = false;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
setVideoMessage();
|
||||
setVideoPlayerController();
|
||||
// 允许横屏
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeLeft,
|
||||
|
|
@ -57,14 +56,16 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
}
|
||||
|
||||
//保存网络视频到本地
|
||||
Future<void> _saveNetworkVideo(
|
||||
context,
|
||||
String videoUrl, {
|
||||
bool isAsset = true,
|
||||
}) async {
|
||||
Future<void> _saveNetworkVideo(context,
|
||||
String videoUrl, {
|
||||
bool isAsset = true,
|
||||
}) async {
|
||||
if (PlatformUtils().isWeb) {
|
||||
RegExp exp = RegExp(r"((\.){1}[^?]{2,4})");
|
||||
String? suffix = exp.allMatches(videoUrl).last.group(0);
|
||||
String? suffix = exp
|
||||
.allMatches(videoUrl)
|
||||
.last
|
||||
.group(0);
|
||||
var xhr = html.HttpRequest();
|
||||
xhr.open('get', videoUrl);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
|
@ -78,7 +79,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
xhr.send();
|
||||
return;
|
||||
}
|
||||
if(PlatformUtils().isMobile){
|
||||
if (PlatformUtils().isMobile) {
|
||||
if (PlatformUtils().isIOS) {
|
||||
if (!await Permissions.checkPermission(
|
||||
context,
|
||||
|
|
@ -91,17 +92,17 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
if ((androidInfo.version.sdkInt) >= 33) {
|
||||
final videos = await Permissions.checkPermission(
|
||||
context,Permission.videos.value,
|
||||
context, Permission.videos.value,
|
||||
);
|
||||
|
||||
if(!videos){
|
||||
if (!videos) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
final storage = await Permissions.checkPermission(
|
||||
context, Permission.storage.value,
|
||||
);
|
||||
if(!storage){
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -243,67 +244,41 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
return width;
|
||||
}
|
||||
|
||||
setVideoMessage() async {
|
||||
// Using local path while sending
|
||||
// VideoPlayerController player = widget.message.videoElem!.videoUrl == null ||
|
||||
// widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
|
||||
// ? VideoPlayerController.file(File(
|
||||
// widget.message.videoElem!.videoPath!,
|
||||
// ))
|
||||
// : (widget.message.videoElem?.localVideoUrl == null ||
|
||||
// widget.message.videoElem?.localVideoUrl == "")
|
||||
// ? VideoPlayerController.network(
|
||||
// widget.message.videoElem!.videoUrl!,
|
||||
// )
|
||||
// : VideoPlayerController.file(File(
|
||||
// widget.message.videoElem!.localVideoUrl!,
|
||||
// ));
|
||||
setVideoPlayerController() async {
|
||||
if (!PlatformUtils().isWeb) {
|
||||
if (widget.message.msgID != null || widget.message.msgID != '') {
|
||||
if (model.getMessageProgress(widget.message.msgID) == 100) {
|
||||
String savePath;
|
||||
if (widget.message.videoElem!.localVideoUrl != null &&
|
||||
widget.message.videoElem!.localVideoUrl != '') {
|
||||
savePath = widget.message.videoElem!.localVideoUrl!;
|
||||
} else {
|
||||
savePath = model.getFileMessageLocation(widget.message.msgID);
|
||||
}
|
||||
File f = File(savePath);
|
||||
if (f.existsSync()) {
|
||||
widget.videoElement.localVideoUrl =
|
||||
model.getFileMessageLocation(widget.message.msgID);
|
||||
}
|
||||
if (TencentUtils.checkString(widget.message.msgID) != null &&
|
||||
widget.videoElement.localVideoUrl == null) {
|
||||
String savePath = model.getFileMessageLocation(widget.message.msgID);
|
||||
File f = File(savePath);
|
||||
if (f.existsSync()) {
|
||||
widget.videoElement.localVideoUrl = savePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoPlayerController player = PlatformUtils().isWeb
|
||||
? ((widget.videoElement.videoPath != null &&
|
||||
widget.videoElement.videoPath!.isNotEmpty) ||
|
||||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
|
||||
? VideoPlayerController.network(
|
||||
widget.videoElement.videoPath!,
|
||||
)
|
||||
: (widget.videoElement.localVideoUrl == null ||
|
||||
widget.videoElement.localVideoUrl == "")
|
||||
? VideoPlayerController.network(
|
||||
widget.videoElement.videoUrl!,
|
||||
)
|
||||
: VideoPlayerController.network(
|
||||
widget.videoElement.localVideoUrl!,
|
||||
))
|
||||
: (widget.videoElement.videoPath != null &&
|
||||
widget.videoElement.videoPath!.isNotEmpty) ||
|
||||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
|
||||
? VideoPlayerController.file(File(widget.videoElement.videoPath!))
|
||||
: (widget.videoElement.localVideoUrl == null ||
|
||||
widget.videoElement.localVideoUrl == "")
|
||||
? VideoPlayerController.network(
|
||||
widget.videoElement.videoUrl!,
|
||||
)
|
||||
: VideoPlayerController.file(File(
|
||||
widget.videoElement.localVideoUrl!,
|
||||
));
|
||||
? ((TencentUtils.checkString(widget.videoElement.videoPath) != null) ||
|
||||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING
|
||||
? VideoPlayerController.networkUrl(
|
||||
Uri.parse(widget.videoElement.videoPath!),
|
||||
)
|
||||
: (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
|
||||
? VideoPlayerController.networkUrl(
|
||||
Uri.parse(widget.videoElement.videoUrl!),
|
||||
)
|
||||
: VideoPlayerController.networkUrl(
|
||||
Uri.parse(widget.videoElement.localVideoUrl!),
|
||||
))
|
||||
: (TencentUtils.checkString(widget.videoElement.videoPath) != null ||
|
||||
widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING)
|
||||
? VideoPlayerController.file(File(widget.videoElement.videoPath!))
|
||||
: (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null)
|
||||
? VideoPlayerController.networkUrl(
|
||||
Uri.parse(widget.videoElement.videoUrl!),
|
||||
)
|
||||
: VideoPlayerController.file(File(
|
||||
widget.videoElement.localVideoUrl!,
|
||||
));
|
||||
await player.initialize();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
double w = getVideoWidth();
|
||||
|
|
@ -315,7 +290,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
showControlsOnInitialize: false,
|
||||
allowPlaybackSpeedChanging: false,
|
||||
aspectRatio: w == 0 || h == 0 ? null : w / h,
|
||||
customControls: VideoCustomControls(downloadFn: () async{
|
||||
customControls: VideoCustomControls(downloadFn: () async {
|
||||
return await _saveVideo();
|
||||
}));
|
||||
setState(() {
|
||||
|
|
@ -330,7 +305,7 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
didUpdateWidget(oldWidget) {
|
||||
if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl ||
|
||||
oldWidget.videoElement.videoPath != widget.videoElement.videoPath) {
|
||||
setVideoMessage();
|
||||
setVideoPlayerController();
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
|
@ -350,55 +325,70 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
|
|||
@override
|
||||
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
|
||||
return OrientationBuilder(builder: ((context, orientation) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
color: Colors.transparent,
|
||||
constraints: BoxConstraints.expand(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: ExtendedImageSlidePage(
|
||||
key: slidePagekey,
|
||||
slidePageBackgroundHandler: (Offset offset, Size size) {
|
||||
if (orientation == Orientation.landscape) {
|
||||
return Colors.black;
|
||||
}
|
||||
double opacity = 0.0;
|
||||
opacity = offset.distance /
|
||||
(Offset(size.width, size.height).distance / 2.0);
|
||||
return Colors.black
|
||||
.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
|
||||
},
|
||||
slideType: SlideType.onlyImage,
|
||||
child: ExtendedImageSlidePageHandler(
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: isInit
|
||||
? Chewie(
|
||||
controller: chewieController,
|
||||
)
|
||||
: const Center(
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
constraints: BoxConstraints.expand(
|
||||
height: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height,
|
||||
),
|
||||
child: ExtendedImageSlidePage(
|
||||
key: slidePagekey,
|
||||
slidePageBackgroundHandler: (Offset offset, Size size) {
|
||||
if (orientation == Orientation.landscape) {
|
||||
return Colors.black;
|
||||
}
|
||||
double opacity = 0.0;
|
||||
opacity = offset.distance /
|
||||
(Offset(size.width, size.height).distance / 2.0);
|
||||
return Colors.black
|
||||
.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
|
||||
},
|
||||
slideType: SlideType.onlyImage,
|
||||
slideEndHandler: (Offset offset, {
|
||||
ExtendedImageSlidePageState? state,
|
||||
ScaleEndDetails? details,
|
||||
}) {
|
||||
final vy = details?.velocity.pixelsPerSecond.dy ?? 0;
|
||||
final oy = offset.dy;
|
||||
if (vy > 300 || oy > 100) {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
child: ExtendedImageSlidePageHandler(
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: isInit
|
||||
? Chewie(
|
||||
controller: chewieController,
|
||||
)
|
||||
: const Center(
|
||||
child:
|
||||
CircularProgressIndicator(color: Colors.white))),
|
||||
heroBuilderForSlidingPage: (Widget result) {
|
||||
return Hero(
|
||||
tag: widget.heroTag,
|
||||
child: result,
|
||||
flightShuttleBuilder: (BuildContext flightContext,
|
||||
Animation<double> animation,
|
||||
HeroFlightDirection flightDirection,
|
||||
BuildContext fromHeroContext,
|
||||
BuildContext toHeroContext) {
|
||||
final Hero hero =
|
||||
CircularProgressIndicator(color: Colors.white))),
|
||||
heroBuilderForSlidingPage: (Widget result) {
|
||||
return Hero(
|
||||
tag: widget.heroTag,
|
||||
child: result,
|
||||
flightShuttleBuilder: (BuildContext flightContext,
|
||||
Animation<double> animation,
|
||||
HeroFlightDirection flightDirection,
|
||||
BuildContext fromHeroContext,
|
||||
BuildContext toHeroContext) {
|
||||
final Hero hero =
|
||||
(flightDirection == HeroFlightDirection.pop
|
||||
? fromHeroContext.widget
|
||||
: toHeroContext.widget) as Hero;
|
||||
|
||||
return hero.child;
|
||||
return hero.child;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)),
|
||||
));
|
||||
)),
|
||||
));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:chewie_for_us/chewie_for_us.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
|
||||
import 'package:tencent_cloud_chat_uikit/ui/widgets/drag_widget.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class TUIKitWidePopup {
|
||||
static OverlayEntry? entry;
|
||||
|
|
@ -236,7 +244,7 @@ class TUIKitWidePopup {
|
|||
|
||||
if (isUseMaterialAlert) {
|
||||
return showDialog(
|
||||
barrierDismissible: false,
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return WillPopScope(
|
||||
|
|
@ -249,7 +257,8 @@ class TUIKitWidePopup {
|
|||
content: contentWidget,
|
||||
),
|
||||
onWillPop: () {
|
||||
return Future.value(false);
|
||||
isShow = false;
|
||||
return Future.value(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -276,4 +285,141 @@ class TUIKitWidePopup {
|
|||
});
|
||||
Overlay.of(context).insert(entry!);
|
||||
}
|
||||
|
||||
static void showMedia({
|
||||
String? mediaLocalPath,
|
||||
String? mediaURL,
|
||||
required BuildContext context,
|
||||
required VoidCallback onClickOrigin,
|
||||
double? aspectRatio,
|
||||
}) async {
|
||||
assert((mediaLocalPath != null) || (mediaURL != null),
|
||||
"At least one of mediaLocalPath or mediaURL must be provided.");
|
||||
|
||||
String _removeQueryString(String urlString) {
|
||||
Uri uri = Uri.parse(urlString);
|
||||
Uri cleanUri = Uri(
|
||||
scheme: uri.scheme,
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
path: uri.path,
|
||||
);
|
||||
return cleanUri.toString();
|
||||
}
|
||||
|
||||
final String mediaPath = mediaLocalPath ?? mediaURL ?? "";
|
||||
final isLocalResource = mediaLocalPath != null;
|
||||
|
||||
String fileExtension = p
|
||||
.extension(isLocalResource ? mediaPath : _removeQueryString(mediaPath));
|
||||
bool isVideo =
|
||||
['.mp4', '.avi', '.mov', '.flv', '.wmv'].contains(fileExtension);
|
||||
|
||||
VideoPlayerController? videoController;
|
||||
ChewieController? chewieController;
|
||||
Widget mediaWidget;
|
||||
double? aspectRatioFinal = aspectRatio;
|
||||
|
||||
if (isVideo) {
|
||||
if (isLocalResource) {
|
||||
videoController = VideoPlayerController.file(File(mediaPath));
|
||||
} else {
|
||||
videoController = VideoPlayerController.networkUrl(Uri.parse(mediaPath));
|
||||
}
|
||||
|
||||
await videoController.initialize();
|
||||
aspectRatioFinal = videoController.value.aspectRatio;
|
||||
|
||||
chewieController = ChewieController(
|
||||
videoPlayerController: videoController,
|
||||
aspectRatio: aspectRatioFinal,
|
||||
autoPlay: true,
|
||||
looping: false,
|
||||
autoInitialize: true,
|
||||
);
|
||||
|
||||
mediaWidget = Chewie(controller: chewieController);
|
||||
} else {
|
||||
mediaWidget = FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: isLocalResource
|
||||
? Image.file(File(mediaPath), fit: BoxFit.contain)
|
||||
: Image.network(mediaPath, fit: BoxFit.contain),
|
||||
);
|
||||
}
|
||||
|
||||
showDialog(
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return WillPopScope(
|
||||
child: AlertDialog(
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
backgroundColor: Colors.transparent,
|
||||
titlePadding: const EdgeInsets.all(0),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
content: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.85,
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.82,
|
||||
),
|
||||
child: aspectRatioFinal != null
|
||||
? AspectRatio(
|
||||
aspectRatio: aspectRatioFinal,
|
||||
child: mediaWidget)
|
||||
: mediaWidget,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
InkWell(
|
||||
onTap: onClickOrigin,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 0),
|
||||
child: Icon(
|
||||
Icons.open_in_new,
|
||||
size: 14,
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
// Custom Text Widget with designer baseline
|
||||
Text(
|
||||
TIM_t("在新窗口中打开"),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onWillPop: () {
|
||||
if (isVideo) videoController?.dispose();
|
||||
return Future.value(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
358
pubspec.lock
358
pubspec.lock
|
|
@ -5,10 +5,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
|
||||
sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "61.0.0"
|
||||
version: "62.0.0"
|
||||
adaptive_action_sheet:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -21,18 +21,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
|
||||
sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
version: "6.0.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -117,10 +117,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc"
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -141,26 +141,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
|
||||
sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced"
|
||||
sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
version: "2.4.6"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e"
|
||||
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.9"
|
||||
version: "7.2.10"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -173,10 +173,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "7dd62d9faf105c434f3d829bbe9c4be02ec67f5ed94832222116122df67c5452"
|
||||
sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.6.0"
|
||||
version: "8.6.1"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -213,10 +213,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: camera_android
|
||||
sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b
|
||||
sha256: f43d07f9d7228ea1ca87d22e30881bd68da4b78484a1fbd1f1408b412a41cefb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.8+2"
|
||||
version: "0.10.8+3"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -285,10 +285,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
||||
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
version: "4.5.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -345,14 +345,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dart_internal:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_internal
|
||||
sha256: dae3976f383beddcfcd07ad5291a422df2c8c0a8a03c52cda63ac7b4f26e0f4e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad
|
||||
sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.3.2"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -365,10 +373,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "499c61743e13909c13374a8c209075385858c614b9c0f2487b5f9995eeaf7369"
|
||||
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.1"
|
||||
version: "9.0.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -397,42 +405,42 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_image
|
||||
sha256: "73b7051722be79f9e8f8c120951fd9d3b22e6153fda6105c322fb88c066952d6"
|
||||
sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.1"
|
||||
version: "8.0.2"
|
||||
extended_image_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_image_library
|
||||
sha256: f2eba5c8500c5a7ce1f0db703b787756cb3bc65cd3818636d5fe9a9ace011ee3
|
||||
sha256: af3ff1c09c23ca7663f94272313d63499a6bd19121e99378e375e0cf2ac7a3e4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
version: "3.5.2"
|
||||
extended_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_text
|
||||
sha256: "75ddf28ce7d5be33a050ff2179b6567b4b98e6225ad3e61e4c3748f7448c25f7"
|
||||
sha256: dec14c9b36d9bbaaf007da5998f5dc72a2dbd5b877601d7b7970bb42524b3ced
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
version: "11.0.1"
|
||||
extended_text_field:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_text_field
|
||||
sha256: "6cf8c090de4dc1e309cf3b24cb9448d7463c6c17926b628cf0954631bf4e56db"
|
||||
sha256: e93248bb9b04a3e6c5e88a6a96e1c08cc765097657ab25aabe409e06a4f6760a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0"
|
||||
version: "12.0.1"
|
||||
extended_text_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_text_library
|
||||
sha256: "308b50cfcc8e3accf46a09cb692715fbd1097333817c15b0f7527de1766bc1ff"
|
||||
sha256: c06fbd8e3b6eedadf50cd6c109bbbd80921a6c43e4422d3b4ec9d4cb36ce4555
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.0.2"
|
||||
fast_i18n:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
@ -469,10 +477,42 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
|
||||
sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
version: "5.3.2"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.2"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "7a6f1ae6107265664f3f7f89a66074882c4d506aef1441c9af313c1f7e6f41ce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -498,10 +538,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3"
|
||||
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
version: "3.3.1"
|
||||
flutter_easyrefresh:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -530,10 +570,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0
|
||||
sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.15"
|
||||
version: "0.6.17"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -562,10 +602,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "6ff8c902c8056af9736de2689f63f81c42e2d642b9f4c79dbf8790ae48b63012"
|
||||
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
version: "2.0.7"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -607,10 +647,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
|
||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.3"
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -647,50 +687,74 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: image_gallery_saver
|
||||
sha256: "009b7722cd8507fd72c7f2cb7cbc46d6e15ad0895469cfcc39a10f86e3556979"
|
||||
sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.3"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270"
|
||||
sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.7+5"
|
||||
version: "0.8.9"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: c2f3c66400649bd132f721c88218945d6406f693092b2f741b79ae9cdb046e59
|
||||
sha256: d2bab152deb2547ea6f53d82ebca9b7e77386bb706e5789e815d37e08ea475bb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.6+16"
|
||||
version: "0.8.7+3"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c"
|
||||
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.12"
|
||||
version: "2.2.0"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0
|
||||
sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.7+4"
|
||||
version: "0.8.8"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "02cbc21fe1706b97942b575966e5fbbeaac535e76deef70d3a242e4afb857831"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: cee2aa86c56780c13af2c77b5f2f72973464db204569e1ba2dd744459a065af4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8"
|
||||
sha256: "7c7b96bb9413a9c28229e717e6fd1e3edd1cc5569c1778fcca060ecf729b65ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.3"
|
||||
version: "2.8.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: c3066601ea42113922232c7b7b3330a2d86f029f685bba99d82c30e799914952
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -775,10 +839,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -831,10 +895,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "28386bbe89ab5a7919a47cea99cdd1128e5a6e0bbd7eaafe20440ead84a15de3"
|
||||
sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
version: "4.0.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -919,58 +983,50 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
|
||||
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "2.1.7"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
|
||||
sha256: "415af30ba76a84faccfe1eb251fe1e4fdc790f876924c65ad7d6ed7a1404bcd6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
version: "10.4.2"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8
|
||||
sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.1"
|
||||
version: "10.3.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85
|
||||
sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.8"
|
||||
version: "9.1.3"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
|
||||
sha256: "463a07cb7cc6c758a7a1c7da36ce666bb80a0b4b5e92df0fa36872e0ed456993"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
version: "3.11.1"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
version: "0.1.3"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1079,58 +1135,58 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
|
||||
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.2.0"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
|
||||
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
|
||||
sha256: b046999bf0ff58f04c364491bb803dcfa8f42e47b19c75478f53d323684a8cc1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.3.1"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
|
||||
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
|
||||
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
|
||||
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.2.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
|
||||
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1147,14 +1203,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
shell:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shell
|
||||
sha256: fd3b7b97e5e7f734138543d5815c6cb6cdd2a1645a0def8ac8e05577cddfbe03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -1164,10 +1212,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1180,26 +1228,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555
|
||||
sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
version: "2.4.5+1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1224,6 +1272,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
tencent_chat_i18n_tool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tencent_chat_i18n_tool
|
||||
sha256: ac8171d2574ed18babedd0cb67e937e255bf02fcb72f55408d033f74d4b11949
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3+2"
|
||||
tencent_cloud_chat_sdk:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1236,18 +1292,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: tencent_cloud_uikit_core
|
||||
sha256: "0131874c7b15e181001c94f8a668f0ccae3006dea6e70d4e42e5531b63313a27"
|
||||
sha256: "0a0f43e4c4241b25d12a9e9f0ee91922ac800a42229d97e3d21d16041ace3104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.8"
|
||||
tencent_im_base:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tencent_im_base
|
||||
sha256: "9b8e712bf27ffae9b686ec532ee8417b8263eba8bab04f105e28a95de1807322"
|
||||
sha256: "0db83050452486571ee4ac07280a2fb4bbc07d297a54235b5cf12e46e79267d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.57"
|
||||
version: "2.0.1"
|
||||
tencent_im_sdk_plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1268,10 +1324,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: tencent_open_file
|
||||
sha256: "01f94f618da42e5593bbad0657fcd13cfc1c2360cca805d8cdfefe898cbe5429"
|
||||
sha256: bb92d2f052c150a45942c564fba13d150a1b7b47525e6fdd74ccc58058ba5dcf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.10"
|
||||
version: "4.0.11"
|
||||
tencent_super_tooltip:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1292,18 +1348,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.1"
|
||||
tim_ui_kit_sticker_plugin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tim_ui_kit_sticker_plugin
|
||||
sha256: "2a825d33076f319f6c1c87d58e2b0d650c9284ae4afd8efdc206f3e6f3582e64"
|
||||
sha256: c9b0c1261bb51a5dee54bb50c6a106061b1bb10731b9c815fc5175afa60175e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.2.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1324,10 +1380,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: tuple
|
||||
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1348,26 +1404,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d"
|
||||
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
|
||||
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.11"
|
||||
version: "6.1.12"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
|
||||
sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.35"
|
||||
version: "6.0.36"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1396,26 +1452,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
||||
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
|
||||
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.17"
|
||||
version: "2.0.18"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
|
||||
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1428,26 +1484,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: b96f10cbdfcbd03a65758633a43e7d04574438f059b1043104b5d61b23d38a4f
|
||||
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
version: "1.1.7"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "57a8e6e24662a3bdfe3b3d61257db91768700c0b8f844e235877b56480f31c69"
|
||||
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
version: "1.1.7"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "7430f5d834d0db4560d7b19863362cd892f1e52b43838553a3c5cdfc9ab28e5b"
|
||||
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
version: "1.1.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1460,18 +1516,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: video_player
|
||||
sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd
|
||||
sha256: "3fd106c74da32f336dc7feb65021da9b0207cb3124392935f1552834f7cce822"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "2.7.0"
|
||||
video_player_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_android
|
||||
sha256: ae1c7d9a71c236a1bf9e567bd7ed4c90887e389a5f233b2192593f7f7395005c
|
||||
sha256: f338a5a396c845f4632959511cad3542cdf3167e1b2a1a948ef07f7123c03608
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.4.9"
|
||||
video_player_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1500,10 +1556,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_for_us
|
||||
sha256: b73bfa90e5e764f41155063ae92fbd1e3a04ee6372e65ff7d288d2c3057f9498
|
||||
sha256: a5bdd445e51a617f7c24be8165230391447301f622aacd050038cee7b41989b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
version: "0.6.3+1"
|
||||
wakelock_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1548,18 +1604,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.4"
|
||||
version: "5.0.5"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
|
||||
sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1585,5 +1641,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.0.0 <3.2.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
|
|
|||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -1,6 +1,6 @@
|
|||
name: tencent_cloud_chat_uikit
|
||||
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
|
||||
version: 2.1.2
|
||||
version: 2.1.3+1
|
||||
homepage: https://www.tencentcloud.com/products/im?from=pub
|
||||
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
|
||||
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
|
||||
|
|
@ -29,7 +29,7 @@ dependencies:
|
|||
image_picker: ^0.8.5+3
|
||||
file_picker: ^5.3.0
|
||||
tencent_super_tooltip: ^0.0.1
|
||||
video_player: ^2.4.2
|
||||
video_player: ^2.7.0
|
||||
chewie_for_us: ^1.5.0
|
||||
flutter_slidable_for_tencent_im: ^1.4.0
|
||||
flutter_plugin_record_plus: ^0.0.16
|
||||
|
|
@ -62,12 +62,12 @@ dependencies:
|
|||
uuid: ^3.0.6
|
||||
tencent_open_file: ^4.0.10
|
||||
tencent_keyboard_visibility: ^1.0.1
|
||||
tim_ui_kit_sticker_plugin: ^2.0.1
|
||||
tencent_im_base: ^1.0.57
|
||||
tim_ui_kit_sticker_plugin: ^2.2.1
|
||||
tencent_im_base: ^2.0.1
|
||||
fc_native_video_thumbnail: any
|
||||
audioplayers: ^3.0.1
|
||||
path: ^1.8.1
|
||||
tencent_cloud_uikit_core: ^1.0.2
|
||||
tencent_cloud_uikit_core: ^1.0.7
|
||||
pasteboard: ^0.2.0
|
||||
desktop_drop: ^0.4.1
|
||||
device_info_plus: any
|
||||
|
|
|
|||
Loading…
Reference in New Issue