diff --git a/README_ZH.md b/README_ZH.md index dc0c2fa8..c854b0d3 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -112,38 +112,38 @@ _coreInstance.login( 全部场景码清单如下: -| 场景码 `infoCode` | 推荐提示语 `infoRecommendText` | 场景描述 | -| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| 6660101 | 好友申请已发送 | 用户申请添加其他用户为联系人 | -| 6660102 | 该用户已是好友 | 用户申请添加其他已是好友的用户为好友时,触发 `onTapAlreadyFriendsItem` 回调 | -| 6660201 | 群申请已发送 | 用户申请加入需要管理员审批的群聊 | -| 6660202 | 您已是群成员 | 用户申请加群时,判断用户已经是当前群成员,触发 `onTapExistGroup` 回调 | -| 6660401 | 无法定位到原消息 | 当用户需要跳转至@消息或者是引用消息时,在消息列表中查不到目标消息 | -| 6660402 | 视频保存成功 | 用户在消息列表,点开视频消息后,选择保存视频 | -| 6660403 | 视频保存失败 | 用户在消息列表,点开视频消息后,选择保存视频 | -| 6660404 | 说话时间太短 | 用户发送了过短的语音消息 | -| 6660405 | 发送失败,视频不能大于 100MB | 用户试图发送大于 100MB 的视频 | -| 6660406 | 图片保存成功 | 用户在消息列表,点开图片大图后,选择保存图片 | -| 6660407 | 图片保存失败 | 用户在消息列表,点开图片大图后,选择保存图片 | -| 6660408 | 已复制 | 用户在弹窗内选择复制文字消息 | -| 6660409 | 暂未实现 | 用户在弹窗内选择非标功能 | -| 6660410 | 其他文件正在接收中 | 用户点击下载文件消息时,前序下载任务还未完成 | -| 6660411 | 正在接收中 | 用户点击下载文件消息 | -| 6660412 | 视频消息仅限 mp4 格式 | 用户发送了一条非 mp4 格式的视频消息 | -| 6660413 | 已加入待下载队列,其他文件下载中 | 已加入待下载队列,其他文件下载中 | -| 6661001 | 无网络连接,无法修改 | 当用户试图在无网络环境下,修改群资料 | -| 6661002 | 无网络连接,无法查看群成员 | 当用户试图在无网络环境下,修改群资料 | -| 6661003 | 成功取消管理员身份 | 用户将群内其他用户移除管理员 | -| 6661201 | 无网络连接,无法修改 | 当用户试图在无网络环境下,修改自己或联系人的资料 | -| 6661202 | 好友添加成功 | 在资料页添加其他用户为好友,并自动添加成功,无需验证 | -| 6661203 | 好友申请已发出 | 在资料页添加其他用户为好友,对方设置需要验证 | -| 6661204 | 当前用户在黑名单 | 在资料页添加其他用户为好友,对方在自己的黑名单内 | -| 6661205 | 好友添加失败 | 在资料页添加其他用户为好友,添加失败,可能是由于对方禁止加好友 | -| 6661206 | 好友删除成功 | 在资料页删除其他用户为好友,成功 | -| 6661207 | 好友删除失败 | 在资料页删除其他用户为好友,失败 | -| 6661401 | 输入不能为空 | 当用户在录入信息时,输入了空字符串 | -| 6661402 | 请传入离开群组生命周期函数,提供返回首页或其他页面的导航方法 | 用户退出群或解散群时,为提供返回首页办法 | -| 6661403 | 设备存储空间不足,建议清理,以获得更好使用体验 | 在login成功后,会自动检测设备存储空间,如果不足1GB,会提示存储空间不足 | +| 场景码 `infoCode` | 推荐提示语 `infoRecommendText` | 场景描述 | +|------------------| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 6660101(3.0.1废弃)| 好友申请已发送 | 用户申请添加其他用户为联系人 | +| 6660102(3.0.1废弃)| 该用户已是好友 | 用户申请添加其他已是好友的用户为好友时,触发 `onTapAlreadyFriendsItem` 回调 | +| 6660201 | 群申请已发送 | 用户申请加入需要管理员审批的群聊 | +| 6660202 | 您已是群成员 | 用户申请加群时,判断用户已经是当前群成员,触发 `onTapExistGroup` 回调 | +| 6660401(3.0.1废弃)| 无法定位到原消息 | 当用户需要跳转至@消息或者是引用消息时,在消息列表中查不到目标消息 | +| 6660402 | 视频保存成功 | 用户在消息列表,点开视频消息后,选择保存视频 | +| 6660403 | 视频保存失败 | 用户在消息列表,点开视频消息后,选择保存视频 | +| 6660404 | 说话时间太短 | 用户发送了过短的语音消息 | +| 6660405(3.0.1废弃)| 发送失败,视频不能大于 100MB | 用户试图发送大于 100MB 的视频 | +| 6660406 | 图片保存成功 | 用户在消息列表,点开图片大图后,选择保存图片 | +| 6660407 | 图片保存失败 | 用户在消息列表,点开图片大图后,选择保存图片 | +| 6660408 | 已复制 | 用户在弹窗内选择复制文字消息 | +| 6660409 | 暂未实现 | 用户在弹窗内选择非标功能 | +| 6660410 | 其他文件正在接收中 | 用户点击下载文件消息时,前序下载任务还未完成 | +| 6660411 | 正在接收中 | 用户点击下载文件消息 | +| 6660412 | 视频消息仅限 mp4 格式 | 用户发送了一条非 mp4 格式的视频消息 | +| 6660413 | 已加入待下载队列,其他文件下载中 | 已加入待下载队列,其他文件下载中 | +| 6661001 | 无网络连接,无法修改 | 当用户试图在无网络环境下,修改群资料 | +| 6661002 | 无网络连接,无法查看群成员 | 当用户试图在无网络环境下,修改群资料 | +| 6661003 | 成功取消管理员身份 | 用户将群内其他用户移除管理员 | +| 6661201 | 无网络连接,无法修改 | 当用户试图在无网络环境下,修改自己或联系人的资料 | +| 6661202(3.0.1废弃)| 好友添加成功 | 在资料页添加其他用户为好友,并自动添加成功,无需验证 | +| 6661203(3.0.1废弃)| 好友申请已发出 | 在资料页添加其他用户为好友,对方设置需要验证 | +| 6661204(3.0.1废弃)| 当前用户在黑名单 | 在资料页添加其他用户为好友,对方在自己的黑名单内 | +| 6661205(3.0.1废弃)| 好友添加失败 | 在资料页添加其他用户为好友,添加失败,可能是由于对方禁止加好友 | +| 6661206(3.0.1废弃)| 好友删除成功 | 在资料页删除其他用户为好友,成功 | +| 6661207(3.0.1废弃)| 好友删除失败 | 在资料页删除其他用户为好友,失败 | +| 6661401 | 输入不能为空 | 当用户在录入信息时,输入了空字符串 | +| 6661402(3.0.1废弃)| 请传入离开群组生命周期函数,提供返回首页或其他页面的导航方法 | 用户退出群或解散群时,为提供返回首页办法 | +| 6661403 | 设备存储空间不足,建议清理,以获得更好使用体验 | 在login成功后,会自动检测设备存储空间,如果不足1GB,会提示存储空间不足 | ## TIMUIKitConversation diff --git a/lib/business_logic/separate_models/tui_chat_separate_view_model.dart b/lib/business_logic/separate_models/tui_chat_separate_view_model.dart index 7259b4f1..7b6e5028 100644 --- a/lib/business_logic/separate_models/tui_chat_separate_view_model.dart +++ b/lib/business_logic/separate_models/tui_chat_separate_view_model.dart @@ -32,7 +32,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier { final TUIChatGlobalModel globalModel = serviceLocator(); final TUIChatModelTools tools = serviceLocator(); final TUISelfInfoViewModel selfModel = serviceLocator(); - final TUIConversationViewModel conversationViewModel = serviceLocator(); + final TUIConversationViewModel conversationViewModel = + serviceLocator(); final _uuid = const Uuid(); ChatLifeCycle? lifeCycle; @@ -135,7 +136,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier { List currentHistoryMsgList = getOriginMessageList(); for (var v2TimMessage in currentHistoryMsgList) { - if (_selectedPositions.containsKey(v2TimMessage.msgID) && _selectedPositions[v2TimMessage.msgID]!) { + if (_selectedPositions.containsKey(v2TimMessage.msgID) && + _selectedPositions[v2TimMessage.msgID]!) { selectList.add(v2TimMessage); } } @@ -368,7 +370,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier { ? haveMoreLatestData = false : tempHaveMoreData = false; - // 获取当前聊天对话的历史消息列表 final currentRecordList = globalModel.messageListMap[conversationID]; @@ -478,7 +479,10 @@ class TUIChatSeparateViewModel extends ChangeNotifier { _getMsgReadReceipt(List message) async { final msgID = message - .where((e) => (e.isSelf ?? true) && (e.needReadReceipt ?? false) && (e.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC)) + .where((e) => + (e.isSelf ?? true) && + (e.needReadReceipt ?? false) && + (e.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC)) .map((e) => e.msgID ?? '') .toList(); if (msgID.isNotEmpty) { @@ -587,12 +591,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier { _notify(); } } - - void _notify(){ - try{ + + void _notify() { + try { notifyListeners(); - }catch(e){ - debugPrint(e.toString()); + } catch (e) { + debugPrint(e.toString()); } } @@ -920,9 +924,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier { _repliedMessage = null; final sendMsgRes = await _messageService.sendMessage( - cloudCustomData: - TencentUtils.checkString(messageInfoWithSender?.cloudCustomData) ?? - json.encode(cloudCustomData), + cloudCustomData: TencentUtils.checkString( + messageInfoWithSender?.cloudCustomData) ?? + json.encode(cloudCustomData), id: textMessageInfo.id as String, offlinePushInfo: tools.buildMessagePushInfo( messageInfoWithSender, convID, convType), @@ -1149,9 +1153,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier { for (var conversation in conversationList) { final convID = conversation.groupID ?? conversation.userID ?? ""; final convType = conversation.type; - List currentHistoryMsgList = globalModel.messageListMap[conversationID] ?? []; + List currentHistoryMsgList = + globalModel.messageListMap[conversationID] ?? []; for (var message in selectedMessages) { - final forwardMessageInfo = await _messageService.createForwardMessage(msgID: message.msgID!); + final forwardMessageInfo = + await _messageService.createForwardMessage(msgID: message.msgID!); final messageInfo = forwardMessageInfo!.messageInfo; if (messageInfo != null) { tools.setUserInfoForMessage(messageInfo, forwardMessageInfo.id); @@ -1159,11 +1165,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier { addSendingMessageID(messageInfo.id); // 如果转发的会话是当前会话,则直接添加到当前会话的消息列表中 if (convID == conversationID) { - if (globalModel.getMessageListPosition(convID) != HistoryMessagePosition.notShowLatest) { - currentHistoryMsgList = [ - messageInfo, - ...currentHistoryMsgList - ]; + if (globalModel.getMessageListPosition(convID) != + HistoryMessagePosition.notShowLatest) { + currentHistoryMsgList = [messageInfo, ...currentHistoryMsgList]; globalModel.setMessageList(conversationID, currentHistoryMsgList); _notify(); } @@ -1198,7 +1202,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier { for (var conversation in conversationList) { final convID = conversation.groupID ?? conversation.userID ?? ""; final convType = conversation.type; - List currentHistoryMsgList = globalModel.messageListMap[conversationID] ?? []; + List currentHistoryMsgList = + globalModel.messageListMap[conversationID] ?? []; final mergerMessageInfo = await _messageService.createMergerMessage( msgIDList: msgIDList, title: title, @@ -1211,11 +1216,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier { addSendingMessageID(messageInfo.id); // 如果转发的会话是当前会话,则直接添加到当前会话的消息列表中 if (convID == conversationID) { - if (globalModel.getMessageListPosition(convID) != HistoryMessagePosition.notShowLatest) { - currentHistoryMsgList = [ - messageInfo, - ...currentHistoryMsgList - ]; + if (globalModel.getMessageListPosition(convID) != + HistoryMessagePosition.notShowLatest) { + currentHistoryMsgList = [messageInfo, ...currentHistoryMsgList]; globalModel.setMessageList(conversationID, currentHistoryMsgList); _notify(); } @@ -1244,16 +1247,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier { return null; } - currentHistoryMsgList.removeWhere((element) => element.msgID == message.msgID); + currentHistoryMsgList + .removeWhere((element) => element.msgID == message.msgID); message.status = MessageStatus.V2TIM_MSG_STATUS_SENDING; addSendingMessageID(message.msgID); globalModel.setMessageList(convID, currentHistoryMsgList); if (globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) { - currentHistoryMsgList = [ - message, - ...currentHistoryMsgList - ]; + currentHistoryMsgList = [message, ...currentHistoryMsgList]; globalModel.setMessageList(conversationID, currentHistoryMsgList); _notify(); } @@ -1263,7 +1264,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier { msgID: message.msgID ?? "", onlineUserOnly: false); removeSendingMessageID(message.msgID ?? ""); if (globalModel.getMessageListPosition(conversationID) != - HistoryMessagePosition.notShowLatest) { + HistoryMessagePosition.notShowLatest) { globalModel.updateMessage( res, convID, message.msgID!, convType, groupType, setInputField); } @@ -1426,7 +1427,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier { for (var msgID in msgIDs) { messageList.removeWhere((element) => element.msgID == msgID); } - globalModel.setMessageList(conversationID, messageList, isDeleteMsg: true); + globalModel.setMessageList(conversationID, messageList, + isDeleteMsg: true); } } @@ -1492,10 +1494,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier { // 是否已经延迟渲染 bool? hasDelayedRenderSendingStatus(String id) { if (_sendingMessageIDMap.containsKey(id)) { - return _sendingMessageIDMap[id]; + return _sendingMessageIDMap[id]; } - print("sending test, hasDelayedRenderSendingStatus false:${id}"); return true; } diff --git a/lib/business_logic/view_models/tui_conversation_view_model.dart b/lib/business_logic/view_models/tui_conversation_view_model.dart index d01e5a77..48705a2b 100644 --- a/lib/business_logic/view_models/tui_conversation_view_model.dart +++ b/lib/business_logic/view_models/tui_conversation_view_model.dart @@ -1,5 +1,6 @@ // ignore_for_file: unnecessary_getters_setters +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; @@ -11,7 +12,8 @@ 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'; -List removeDuplicates(List list, bool Function(T first, T second) isEqual) { +List removeDuplicates( + List list, bool Function(T first, T second) isEqual) { List output = []; for (var i = 0; i < list.length; i++) { bool found = false; @@ -32,10 +34,14 @@ class TUIConversationViewModel extends ChangeNotifier { static const String conversationC2CPrefix = "c2c_"; static const String conversationGroupPrefix = "group_"; - final TUISelfInfoViewModel selfInfoViewModel = serviceLocator(); - final ConversationService _conversationService = serviceLocator(); - final FriendshipServices _friendshipServices = serviceLocator(); - final TUIChatGlobalModel _chatGlobalModel = serviceLocator(); + final TUISelfInfoViewModel selfInfoViewModel = + serviceLocator(); + final ConversationService _conversationService = + serviceLocator(); + final FriendshipServices _friendshipServices = + serviceLocator(); + final TUIChatGlobalModel _chatGlobalModel = + serviceLocator(); final MessageService _messageService = serviceLocator(); late V2TimConversationListener _conversationListener; List _conversationList = []; @@ -45,7 +51,8 @@ class TUIConversationViewModel extends ChangeNotifier { bool _haveMoreData = true; int _totalUnReadCount = 0; String? _scrollToConversation; - final TUIChatGlobalModel globalChatModel = serviceLocator(); + final TUIChatGlobalModel globalChatModel = + serviceLocator(); String _nextSeq = "0"; ConversationLifeCycle? _lifeCycle; @@ -54,10 +61,13 @@ class TUIConversationViewModel extends ChangeNotifier { if (PlatformUtils().isWeb) { try { _conversationList.sort((a, b) { - return b!.lastMessage!.timestamp!.compareTo(a!.lastMessage!.timestamp!); + return b!.lastMessage!.timestamp! + .compareTo(a!.lastMessage!.timestamp!); }); - final pinnedConversation = _conversationList.where((element) => element?.isPinned == true).toList(); + final pinnedConversation = _conversationList + .where((element) => element?.isPinned == true) + .toList(); _conversationList.removeWhere((element) => element?.isPinned == true); _conversationList = [...pinnedConversation, ..._conversationList]; // ignore: empty_catches @@ -69,7 +79,8 @@ class TUIConversationViewModel extends ChangeNotifier { } V2TimConversation? getConversation(String conversationID) { - return _conversationList.firstWhere((element) => element?.conversationID == conversationID); + return _conversationList.firstWhereOrNull( + (element) => element?.conversationID == conversationID); } String? get scrollToConversation => _scrollToConversation; @@ -116,7 +127,8 @@ class TUIConversationViewModel extends ChangeNotifier { } TUIConversationViewModel() { - _conversationListener = V2TimConversationListener(onConversationChanged: (conversationList) { + _conversationListener = + V2TimConversationListener(onConversationChanged: (conversationList) { _onConversationListChanged(conversationList); }, onNewConversation: (conversationList) { _addNewConversation(conversationList); @@ -129,7 +141,7 @@ class TUIConversationViewModel extends ChangeNotifier { if (!PlatformUtils().isWeb) { loadInitConversation(); } - }, onConversationDeleted:(List conversationIDList) { + }, onConversationDeleted: (List conversationIDList) { _onConversationDeleted(conversationIDList); for (var conversationID in conversationIDList) { String resultID = ""; @@ -162,7 +174,8 @@ class TUIConversationViewModel extends ChangeNotifier { Future loadData({required int count}) async { _haveMoreData = true; final isRefresh = _nextSeq == "0"; - final conversationResult = await _conversationService.getConversationList(nextSeq: _nextSeq, count: count); + final conversationResult = await _conversationService.getConversationList( + nextSeq: _nextSeq, count: count); _nextSeq = conversationResult?.nextSeq ?? ""; final conversationList = conversationResult?.conversationList; if (conversationList != null) { @@ -175,8 +188,12 @@ class TUIConversationViewModel extends ChangeNotifier { } else { combinedConversationList = [..._conversationList, ...conversationList]; } - final List finalConversationList = await _lifeCycle?.conversationListWillMount(combinedConversationList) ?? combinedConversationList; - _conversationList = removeDuplicates(finalConversationList, (item1, item2) => item1?.conversationID == item2?.conversationID); + final List finalConversationList = await _lifeCycle + ?.conversationListWillMount(combinedConversationList) ?? + combinedConversationList; + _conversationList = removeDuplicates( + finalConversationList, + (item1, item2) => item1?.conversationID == item2?.conversationID); notifyListeners(); } _totalUnReadCount = await _conversationService.getTotalUnreadCount(); @@ -193,11 +210,15 @@ class TUIConversationViewModel extends ChangeNotifier { required String conversationID, required bool isPinned, }) { - return _conversationService.pinConversation(conversationID: conversationID, isPinned: isPinned); + return _conversationService.pinConversation( + conversationID: conversationID, isPinned: isPinned); } - Future clearHistoryMessage({required String convID, required int convType}) async { - if (_lifeCycle?.shouldClearHistoricalMessageForConversation != null && await _lifeCycle!.shouldClearHistoricalMessageForConversation(convID) == false) { + Future clearHistoryMessage( + {required String convID, required int convType}) async { + if (_lifeCycle?.shouldClearHistoricalMessageForConversation != null && + await _lifeCycle!.shouldClearHistoricalMessageForConversation(convID) == + false) { return null; } @@ -211,17 +232,22 @@ class TUIConversationViewModel extends ChangeNotifier { } searchFriends(String searchKey) async { - final res = await _friendshipServices.searchFriends(searchParam: V2TimFriendSearchParam(keywordList: [searchKey])); + final res = await _friendshipServices.searchFriends( + searchParam: V2TimFriendSearchParam(keywordList: [searchKey])); return res; } - Future deleteConversation({required String conversationID}) async { - if (_lifeCycle?.shouldDeleteConversation != null && await _lifeCycle!.shouldDeleteConversation(conversationID) == false) { + Future deleteConversation( + {required String conversationID}) async { + if (_lifeCycle?.shouldDeleteConversation != null && + await _lifeCycle!.shouldDeleteConversation(conversationID) == false) { return null; } - final res = await _conversationService.deleteConversation(conversationID: conversationID); + final res = await _conversationService.deleteConversation( + conversationID: conversationID); if (res.code == 0) { - _conversationList.removeWhere((element) => element?.conversationID == conversationID); + _conversationList + .removeWhere((element) => element?.conversationID == conversationID); notifyListeners(); } return res; @@ -229,9 +255,11 @@ class TUIConversationViewModel extends ChangeNotifier { _onConversationListChanged(List list) { for (int element = 0; element < list.length; element++) { - int index = _conversationList.indexWhere((item) => item!.conversationID == list[element].conversationID); + int index = _conversationList.indexWhere( + (item) => item!.conversationID == list[element].conversationID); if (index > -1) { - _conversationList.setAll(index, [list[element]] as List); + _conversationList.setAll( + index, [list[element]] as List); } else { _conversationList.add(list[element]); } @@ -242,10 +270,13 @@ class TUIConversationViewModel extends ChangeNotifier { _onConversationDeleted(List list) { for (int i = 0; i < list.length; i++) { - int index = _conversationList.indexWhere((item) => item!.conversationID == list[i]); + int index = _conversationList + .indexWhere((item) => item!.conversationID == list[i]); if (index > -1) { _conversationList.removeAt(index); - _conversationList = removeDuplicates(_conversationList, (item1, item2) => item1?.conversationID == item2?.conversationID); + _conversationList = removeDuplicates( + _conversationList, + (item1, item2) => item1?.conversationID == item2?.conversationID); } } notifyListeners(); @@ -253,16 +284,19 @@ class TUIConversationViewModel extends ChangeNotifier { _addNewConversation(List list) { _conversationList.addAll(list); - _conversationList = removeDuplicates(_conversationList, (item1, item2) => item1?.conversationID == item2?.conversationID); + _conversationList = removeDuplicates(_conversationList, + (item1, item2) => item1?.conversationID == item2?.conversationID); notifyListeners(); } setConversationListener() { - _conversationService.addConversationListener(listener: _conversationListener); + _conversationService.addConversationListener( + listener: _conversationListener); } removeConversationListener() { - _conversationService.removeConversationListener(listener: _conversationListener); + _conversationService.removeConversationListener( + listener: _conversationListener); } Future setConversationDraft({ @@ -272,19 +306,25 @@ class TUIConversationViewModel extends ChangeNotifier { String? groupID, bool isAllowWeb = true, }) async { - assert(!isTopic || (groupID != null && groupID.isNotEmpty), "When 'isTopic' is true, 'groupID' must not be null or empty."); + assert(!isTopic || (groupID != null && groupID.isNotEmpty), + "When 'isTopic' is true, 'groupID' must not be null or empty."); if (PlatformUtils().isWeb && isAllowWeb) { webDraftMap[conversationID] = draftText ?? ""; return V2TimCallback(code: 0, desc: ""); } else { if (isTopic) { - final topicInfoList = await TencentImSDKPlugin.v2TIMManager.getGroupManager().getTopicInfoList(groupID: groupID!, topicIDList: [conversationID]); + final topicInfoList = await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .getTopicInfoList(groupID: groupID!, topicIDList: [conversationID]); final topicInfo = topicInfoList.data?.first.topicInfo; topicInfo?.draftText = draftText; - final res = await TencentImSDKPlugin.v2TIMManager.getGroupManager().setTopicInfo(groupID: groupID, topicInfo: topicInfo!); + final res = await TencentImSDKPlugin.v2TIMManager + .getGroupManager() + .setTopicInfo(groupID: groupID, topicInfo: topicInfo!); return res; } else { - return _conversationService.setConversationDraft(conversationID: conversationID, draftText: draftText); + return _conversationService.setConversationDraft( + conversationID: conversationID, draftText: draftText); } } } diff --git a/lib/ui/views/TIMUIKitBlackList/tim_uikit_black_list.dart b/lib/ui/views/TIMUIKitBlackList/tim_uikit_black_list.dart index ef39a699..9bd702fd 100644 --- a/lib/ui/views/TIMUIKitBlackList/tim_uikit_black_list.dart +++ b/lib/ui/views/TIMUIKitBlackList/tim_uikit_black_list.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/block_list_life_cycle.dart'; @@ -88,7 +88,8 @@ class _TIMUIKitBlackListState extends TIMUIKitState { child: Text( showName, style: TextStyle( - color: theme.black, fontSize: isDesktopScreen ? 14 : 18), + color: theme.black, + fontSize: isDesktopScreen ? 14 : 18), ), )), if (isDesktopScreen) @@ -132,7 +133,6 @@ class _TIMUIKitBlackListState extends TIMUIKitState { return widget.itemBuilder ?? _itemBuilder; } - @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { return MultiProvider( diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart index 81415ac3..38c3d6e5 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart @@ -58,7 +58,8 @@ typedef MessageRowBuilder = Widget? Function( Function onScrollToIndexBegin, ); -typedef MessageNickNameBuilder = Widget Function(BuildContext context, V2TimMessage message, TUIChatSeparateViewModel model); +typedef MessageNickNameBuilder = Widget Function( + BuildContext context, V2TimMessage message, TUIChatSeparateViewModel model); typedef MessageItemContent = Widget? Function( V2TimMessage message, @@ -71,7 +72,8 @@ class MessageHoverControlItem { Widget icon; ValueChanged onClick; - MessageHoverControlItem({required this.name, required this.icon, required this.onClick}); + MessageHoverControlItem( + {required this.name, required this.icon, required this.onClick}); } class MessageItemBuilder { @@ -139,7 +141,11 @@ class MessageToolTipItem { final String iconImageAsset; final VoidCallback onClick; - MessageToolTipItem({required this.label, required this.id, required this.iconImageAsset, required this.onClick}); + MessageToolTipItem( + {required this.label, + required this.id, + required this.iconImageAsset, + required this.onClick}); } class ToolTipsConfig { @@ -165,10 +171,12 @@ class ToolTipsConfig { bool showTranslation; /// A builder for additional custom items. We recommend using `additionalMessageToolTips` instead of this field since version 2.0, as you only need to provide the data rather than the whole widget. This makes usage easier and you don't need to worry about the UI display. - final Widget? Function(V2TimMessage message, Function() closeTooltip, [Key? key, BuildContext? context])? additionalItemBuilder; + final Widget? Function(V2TimMessage message, Function() closeTooltip, + [Key? key, BuildContext? context])? additionalItemBuilder; /// A list of additional message tooltip menu items, provided with the data only. We recommend using this field instead of the previous `additionalItemBuilder`. - List Function(V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips; + List Function( + V2TimMessage message, Function() closeTooltip)? additionalMessageToolTips; ToolTipsConfig( {this.showDeleteMessage = true, @@ -179,7 +187,8 @@ class ToolTipsConfig { this.showCopyMessage = true, this.showForwardMessage = true, this.additionalMessageToolTips, - @Deprecated("Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.") + @Deprecated( + "Please use `additionalMessageToolTips` instead. You are now only expected to specify the data, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.") this.additionalItemBuilder}); } @@ -188,10 +197,12 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { final V2TimMessage message; /// tap remote user avatar callback function - final void Function(String userID, TapDownDetails tapDetails)? onTapForOthersPortrait; + final void Function(String userID, TapDownDetails tapDetails)? + onTapForOthersPortrait; /// secondary tap remote user avatar callback function - final void Function(String userID, TapDownDetails tapDetails)? onSecondaryTapForOthersPortrait; + final void Function(String userID, TapDownDetails tapDetails)? + onSecondaryTapForOthersPortrait; /// the function use for reply message, when click replied message can scroll to it. final Function? onScrollToIndex; @@ -200,7 +211,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { final Function? onScrollToIndexBegin; /// the callback for long press event, except myself avatar - final Function(String? userId, String? nickName)? onLongPressForOthersHeadPortrait; + final Function(String? userId, String? nickName)? + onLongPressForOthersHeadPortrait; /// message item builder, works for customize all message types and row layout. final MessageItemBuilder? messageItemBuilder; @@ -220,7 +232,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { /// Auto mention user when send reply message final bool allowAtUserWhenReply; - @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") + @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") /// allow show user nick name final bool showNickName; @@ -241,16 +254,19 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { final EdgeInsetsGeometry? textPadding; /// avatar builder - final Widget Function(BuildContext context, V2TimMessage message)? userAvatarBuilder; + final Widget Function(BuildContext context, V2TimMessage message)? + userAvatarBuilder; /// theme info for message and avatar final MessageThemeData? themeData; /// builder for nick name row - final Widget Function(BuildContext context, V2TimMessage message)? topRowBuilder; + final Widget Function(BuildContext context, V2TimMessage message)? + topRowBuilder; /// builder for bottom raw which under message content - final Widget Function(BuildContext context, V2TimMessage message)? bottomRowBuilder; + final Widget Function(BuildContext context, V2TimMessage message)? + bottomRowBuilder; // open MessageReaction final bool? isUseMessageReaction; @@ -271,7 +287,9 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget { const TIMUIKitHistoryMessageListItem( {Key? key, required this.message, - @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, + @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.onScrollToIndex, this.onScrollToIndexBegin, this.onTapForOthersPortrait, @@ -308,7 +326,9 @@ class TipsActionItem extends TIMUIKitStatelessWidget { final String icon; final String? package; - TipsActionItem({Key? key, required this.label, required this.icon, this.package}) : super(key: key); + TipsActionItem( + {Key? key, required this.label, required this.icon, this.package}) + : super(key: key); @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { @@ -336,13 +356,16 @@ class TipsActionItem extends TIMUIKitStatelessWidget { } } -class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState with SingleTickerProviderStateMixin { +class _TIMUIKItHistoryMessageListItemState + extends TIMUIKitState + with SingleTickerProviderStateMixin { SuperTooltip? tooltip; late AnimationController _animationController; // ignore: unused_field final MessageService _messageService = serviceLocator(); - final TUISelfInfoViewModel selfInfoModel = serviceLocator(); + final TUISelfInfoViewModel selfInfoModel = + serviceLocator(); final TUIThemeViewModel themeModel = serviceLocator(); // bool isChecked = false; @@ -353,7 +376,9 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState model.jumpMsgID = "", - ) - : null; + final customWidget = + messageItemBuilder?.customMessageItemBuilder != null + ? messageItemBuilder!.customMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitCustomElem( message: messageItem, @@ -450,13 +484,14 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState model.jumpMsgID = "", - ) - : null; + final customWidget = + messageItemBuilder?.textReplyMessageItemBuilder != null + ? messageItemBuilder!.textReplyMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitReplyElem( message: messageItem, @@ -532,13 +567,14 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState model.jumpMsgID = "", - ) - : null; + final customWidget = + messageItemBuilder?.groupTipsMessageItemBuilder != null + ? messageItemBuilder!.groupTipsMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? Text(TIM_t("[群系统消息]")); case MessageElemType.V2TIM_ELEM_TYPE_IMAGE: final customWidget = messageItemBuilder?.imageMessageItemBuilder != null @@ -574,22 +610,24 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState model.jumpMsgID = "", - ) - : null; + final customWidget = + messageItemBuilder?.locationMessageItemBuilder != null + ? messageItemBuilder!.locationMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? Text(TIM_t("[位置]")); case MessageElemType.V2TIM_ELEM_TYPE_MERGER: - final customWidget = messageItemBuilder?.mergerMessageItemBuilder != null - ? messageItemBuilder!.mergerMessageItemBuilder!( - messageItem, - isShowJump, - () => model.jumpMsgID = "", - ) - : null; + final customWidget = + messageItemBuilder?.mergerMessageItemBuilder != null + ? messageItemBuilder!.mergerMessageItemBuilder!( + messageItem, + isShowJump, + () => model.jumpMsgID = "", + ) + : null; return customWidget ?? TIMUIKitMergerElem( messageItemBuilder: messageItemBuilder, @@ -608,7 +646,11 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState (DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp < 120; + bool isRevocable(int timestamp) => + (DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp < 120; // TODO : 继续看这里 @@ -723,21 +773,38 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState screenHeight && !(isDesktopScreen); - final tapDetails = (isDesktopScreen || isLongMessage) ? (details ?? _tapDetails) : details; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final isLongMessage = + context.size!.height + 350 > screenHeight && !(isDesktopScreen); + final tapDetails = + (isDesktopScreen || isLongMessage) ? (details ?? _tapDetails) : details; final isSelf = message.isSelf ?? true; - final targetWidth = min(MediaQuery.of(context).size.width * 0.84, 350).toDouble(); - final double dx = !isSelf ? min(tapDetails?.globalPosition.dx ?? targetWidth, screenWidth - targetWidth) : max(tapDetails?.globalPosition.dx ?? targetWidth, targetWidth).toDouble(); - final double dy = min(tapDetails?.globalPosition.dy ?? MediaQuery.of(context).size.height, MediaQuery.of(context).size.height - 320).toDouble(); + final targetWidth = + min(MediaQuery.of(context).size.width * 0.84, 350).toDouble(); + final double dx = !isSelf + ? min(tapDetails?.globalPosition.dx ?? targetWidth, + screenWidth - targetWidth) + : max(tapDetails?.globalPosition.dx ?? targetWidth, targetWidth) + .toDouble(); + final double dy = min( + tapDetails?.globalPosition.dy ?? MediaQuery.of(context).size.height, + MediaQuery.of(context).size.height - 320) + .toDouble(); final finalTapDetail = tapDetails != null ? TapDownDetails( globalPosition: Offset(dx, dy), ) : null; - initTools(context: c, model: model, isShowMoreSticker: isShowMoreSticker, details: finalTapDetail, theme: theme, isFromWideToolTip: isFromWideTooltip); + initTools( + context: c, + model: model, + isShowMoreSticker: isShowMoreSticker, + details: finalTapDetail, + theme: theme, + isFromWideToolTip: isFromWideTooltip); tooltip!.show(c, targetCenter: finalTapDetail?.globalPosition); } @@ -750,15 +817,26 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState> _modifySticker(int sticker) async { + Future> _modifySticker( + int sticker) async { return await Future.delayed(const Duration(milliseconds: 50), () async { return await MessageReactionUtils.clickOnSticker(widget.message, sticker); }); } - initTools({BuildContext? context, bool isLongMessage = false, required TUIChatSeparateViewModel model, TUITheme? theme, bool? isShowMoreSticker, TapDownDetails? details, bool? isFromWideToolTip}) { - final isUseMessageReaction = widget.message.elemType == 2 ? false : model.chatConfig.isUseMessageReaction; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + initTools( + {BuildContext? context, + bool isLongMessage = false, + required TUIChatSeparateViewModel model, + TUITheme? theme, + bool? isShowMoreSticker, + TapDownDetails? details, + bool? isFromWideToolTip}) { + final isUseMessageReaction = widget.message.elemType == 2 + ? false + : model.chatConfig.isUseMessageReaction; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; final isSelf = widget.message.isSelf ?? true; double arrowTipDistance = 30; double arrowBaseWidth = 10; @@ -767,7 +845,8 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState tooltip?.close(), onSelectSticker: (int value) { @@ -854,7 +939,8 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState getWideMessageHoverControlBar(TUIChatSeparateViewModel model, TUITheme theme) { + List getWideMessageHoverControlBar( + TUIChatSeparateViewModel model, TUITheme theme) { return [ if (widget.isUseMessageReaction ?? false) MessageHoverControlItem( @@ -907,7 +994,8 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState _conditionalDelay() async { - if (!(model.hasDelayedRenderSendingStatus(message.id ?? message.msgID!) ?? true)) { + if (!(model.hasDelayedRenderSendingStatus(message.id ?? message.msgID!) ?? + true)) { model.setDelayedRenderSendingStatus(message.id ?? message.msgID!); await Future.delayed(const Duration(seconds: 1)); } @@ -999,9 +1115,14 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState(context); - final isDownloadWaiting = context.select((value) => value.isWaiting(widget.message.msgID ?? "")); + final TUIChatSeparateViewModel model = + Provider.of(context); + final isDownloadWaiting = context.select( + (value) => value.isWaiting(widget.message.msgID ?? "")); final TUITheme theme = value.theme; final message = widget.message; final msgType = message.elemType; final isSelf = message.isSelf ?? true; - final isGroupTipsMsg = msgType == MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS; + final isGroupTipsMsg = + msgType == MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS; final revokeStatus = isRevokeMessage(message, model); final isRevokedMsg = revokeStatus.$1; @@ -1098,10 +1228,14 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState 50) { - model.addToMessageReadReceiptList(message); - } - }, - child: LayoutBuilder( - builder: (context, constraints) => Container( - padding: EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0), - margin: widget.padding ?? const EdgeInsets.only(bottom: 20), - child: Row( - key: _key, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (model.isMultiSelect) - Container( - margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0), - child: CheckBoxButton( - isChecked: model.getSelectedMessageList().contains(message), - onChanged: (value) { - model.setMessageItemChecked(message, value); - }, - ), + key: Key(message.id ?? message.msgID!), + // 判断消息是否可见 + onVisibilityChanged: (visibilityInfo) { + var visiblePercentage = visibilityInfo.visibleFraction * 100; + if (visiblePercentage > 50) { + model.addToMessageReadReceiptList(message); + } + }, + child: LayoutBuilder( + builder: (context, constraints) => Container( + padding: + EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0), + margin: widget.padding ?? const EdgeInsets.only(bottom: 20), + child: Row( + key: _key, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (model.isMultiSelect) + Container( + margin: EdgeInsets.only( + right: 12, top: 10, left: isSelf ? 16 : 0), + child: CheckBoxButton( + isChecked: model.getSelectedMessageList().contains(message), + onChanged: (value) { + model.setMessageItemChecked(message, value); + }, ), - Expanded( - child: MouseRegion( - onEnter: (_) { - if (isDesktopScreen && model.chatConfig.isUseMessageHoverBarOnDesktop) { + ), + Expanded( + child: MouseRegion( + onEnter: (_) { + if (isDesktopScreen && + model.chatConfig.isUseMessageHoverBarOnDesktop) { + setState(() { + isShowWideToolTip = true; + }); + } + }, + onExit: (_) { + if (isDesktopScreen && + model.chatConfig.isUseMessageHoverBarOnDesktop) { + Tooltip.dismissAllToolTips(); + Future.delayed(const Duration(milliseconds: 100), () { setState(() { - isShowWideToolTip = true; - }); - } - }, - onExit: (_) { - if (isDesktopScreen && model.chatConfig.isUseMessageHoverBarOnDesktop) { - Tooltip.dismissAllToolTips(); - Future.delayed(const Duration(milliseconds: 100), () { - setState(() { - isShowWideToolTip = false; - }); + isShowWideToolTip = false; }); + }); + } + }, + child: GestureDetector( + behavior: model.isMultiSelect + ? HitTestBehavior.translucent + : null, + onTap: () { + if (model.isMultiSelect) { + final checked = + model.getSelectedMessageList().contains(message); + model.setMessageItemChecked(message, !checked); + } else { + return; } }, - child: GestureDetector( - behavior: model.isMultiSelect ? HitTestBehavior.translucent : null, - onTap: () { - if (model.isMultiSelect) { - final checked = model.getSelectedMessageList().contains(message); - model.setMessageItemChecked(message, !checked); - } else { - return; - } - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start, - children: [ - if (!isSelf && widget.showAvatar) - GestureDetector( - onLongPress: () { - if (widget.onLongPressForOthersHeadPortrait != null) {} - if (model.chatConfig.isAllowLongPressAvatarToAt) { - widget.onLongPressForOthersHeadPortrait!(message.sender, message.nickName); - } - }, - onTapDown: isDesktopScreen - ? (details) { - if (widget.onTapForOthersPortrait != null && widget.allowAvatarTap) { - widget.onTapForOthersPortrait!(message.sender ?? "", details); - } + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: isSelf + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + if (!isSelf && widget.showAvatar) + GestureDetector( + onLongPress: () { + if (widget.onLongPressForOthersHeadPortrait != + null) {} + if (model.chatConfig.isAllowLongPressAvatarToAt) { + widget.onLongPressForOthersHeadPortrait!( + message.sender, message.nickName); + } + }, + onTapDown: isDesktopScreen + ? (details) { + if (widget.onTapForOthersPortrait != null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + message.sender ?? "", details); } - : null, - onTap: isDesktopScreen - ? null - : () { - if (widget.onTapForOthersPortrait != null && widget.allowAvatarTap) { - widget.onTapForOthersPortrait!(message.sender ?? "", TapDownDetails()); - } - }, - onSecondaryTap: isDesktopScreen - ? null - : () { - if (widget.onSecondaryTapForOthersPortrait != null && widget.allowAvatarTap) { - widget.onSecondaryTapForOthersPortrait!(message.sender ?? "", TapDownDetails()); - } - }, - onSecondaryTapDown: isDesktopScreen - ? (details) { - if (widget.onSecondaryTapForOthersPortrait != null && widget.allowAvatarTap) { - widget.onSecondaryTapForOthersPortrait!(message.sender ?? "", details); - } + } + : null, + onTap: isDesktopScreen + ? null + : () { + if (widget.onTapForOthersPortrait != null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + message.sender ?? "", + TapDownDetails()); } - : null, - child: widget.userAvatarBuilder != null - ? widget.userAvatarBuilder!(context, message) - : Container( - margin: (isSelf && isShowNickNameForSelf) || (!isSelf && isShowNickNameForOthers) ? const EdgeInsets.only(top: 2) : null, - child: SizedBox( - width: 40, - height: 40, - child: Avatar( - faceUrl: message.faceUrl ?? "", - showName: MessageUtils.getDisplayName(message), - ), + }, + onSecondaryTap: isDesktopScreen + ? null + : () { + if (widget.onSecondaryTapForOthersPortrait != + null && + widget.allowAvatarTap) { + widget.onSecondaryTapForOthersPortrait!( + message.sender ?? "", + TapDownDetails()); + } + }, + onSecondaryTapDown: isDesktopScreen + ? (details) { + if (widget.onSecondaryTapForOthersPortrait != + null && + widget.allowAvatarTap) { + widget.onSecondaryTapForOthersPortrait!( + message.sender ?? "", details); + } + } + : null, + child: widget.userAvatarBuilder != null + ? widget.userAvatarBuilder!(context, message) + : Container( + margin: (isSelf && isShowNickNameForSelf) || + (!isSelf && isShowNickNameForOthers) + ? const EdgeInsets.only(top: 2) + : null, + child: SizedBox( + width: 40, + height: 40, + child: Avatar( + faceUrl: message.faceUrl ?? "", + showName: MessageUtils.getDisplayName( + message), ), ), - ), - if (isSelf && widget.message.elemType == 6 && isDownloadWaiting) - Container( - margin: const EdgeInsets.only(top: 46, right: 10), - child: LoadingAnimationWidget.threeArchedCircle( - color: theme.weakTextColor ?? Colors.grey, - size: 20, - ), - ), + ), + ), + if (isSelf && + widget.message.elemType == 6 && + isDownloadWaiting) Container( - margin: widget.showAvatar ? (isSelf ? const EdgeInsets.only(right: 13) : const EdgeInsets.only(left: 13)) : null, - child: Column( - crossAxisAlignment: isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - if ((isSelf && isShowNickNameForSelf) || (!isSelf && isShowNickNameForOthers)) - widget.topRowBuilder != null - ? widget.topRowBuilder!(context, message) - : Container( - // margin: const EdgeInsets.only(bottom: 4), - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 1.7), - child: Text( - MessageUtils.getDisplayName(message), - overflow: TextOverflow.ellipsis, - style: widget.themeData?.nickNameTextStyle ?? TextStyle(fontSize: 12, color: theme.weakTextColor), - ), - )), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (isSelf) renderHoverTipAndReadStatus(model, isSelf, message, isPeerRead, theme, isDownloadWaiting), - Container( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth * 0.77, - ), - child: Builder(builder: (context) { - return GestureDetector( - child: IgnorePointer(ignoring: model.isMultiSelect, child: _getMessageItemBuilder(message, message.status, model)), - onSecondaryTapDown: (details) { - if (widget.onLongPress != null) { - widget.onLongPress!(context, message); - return; - } - if (!PlatformUtils().isMobile) { - if (widget.allowLongPress) { - _onOpenToolTip(context, message, model, theme, details, false, false); - } - } - }, - onLongPress: () { - if (widget.onLongPress != null) { - widget.onLongPress!(context, message); - return; - } - if (widget.allowLongPress && !isDesktopScreen) { - _onOpenToolTip(context, message, model, theme, null, false, false); - } - }, - onTapDown: (details) { - _tapDetails = details; - }, - ); - }), - ), - if (!isSelf && message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND && message.localCustomInt != null && message.localCustomInt != HistoryMessageDartConstant.read) - Padding(padding: const EdgeInsets.only(left: 5, bottom: 12), child: Icon(Icons.circle, color: theme.cautionColor, size: 10)), - if (!isSelf) renderHoverTipAndReadStatus(model, isSelf, message, isPeerRead, theme, isDownloadWaiting), - ], - ), - TIMUIKitTextTranslationElem( - message: message, - isUseDefaultEmoji: widget.isUseDefaultEmoji, - customEmojiStickerList: widget.customEmojiStickerList, - isFromSelf: message.isSelf ?? true, - isShowJump: false, - clearJump: () {}, - chatModel: model), - if (widget.bottomRowBuilder != null) widget.bottomRowBuilder!(context, message) - ], + margin: const EdgeInsets.only(top: 46, right: 10), + child: LoadingAnimationWidget.threeArchedCircle( + color: theme.weakTextColor ?? Colors.grey, + size: 20, ), ), - if (!isSelf && widget.message.elemType == 6 && isDownloadWaiting) - Container( - margin: const EdgeInsets.only(top: 46, left: 10), - child: LoadingAnimationWidget.threeArchedCircle( - color: theme.weakTextColor ?? Colors.grey, - size: 20, + Container( + margin: widget.showAvatar + ? (isSelf + ? const EdgeInsets.only(right: 13) + : const EdgeInsets.only(left: 13)) + : null, + child: Column( + crossAxisAlignment: isSelf + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if ((isSelf && isShowNickNameForSelf) || + (!isSelf && isShowNickNameForOthers)) + widget.topRowBuilder != null + ? widget.topRowBuilder!(context, message) + : Container( + // margin: const EdgeInsets.only(bottom: 4), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context) + .size + .width / + 1.7), + child: Text( + MessageUtils.getDisplayName(message), + overflow: TextOverflow.ellipsis, + style: widget.themeData + ?.nickNameTextStyle ?? + TextStyle( + fontSize: 12, + color: theme.weakTextColor), + ), + )), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isSelf) + renderHoverTipAndReadStatus( + model, + isSelf, + message, + isPeerRead, + theme, + isDownloadWaiting), + Container( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth * 0.77, + ), + child: Builder(builder: (context) { + return GestureDetector( + child: IgnorePointer( + ignoring: model.isMultiSelect, + child: _getMessageItemBuilder( + message, + message.status, + model)), + onSecondaryTapDown: (details) { + if (widget.onLongPress != null) { + widget.onLongPress!( + context, message); + return; + } + if (!PlatformUtils().isMobile) { + if (widget.allowLongPress) { + _onOpenToolTip( + context, + message, + model, + theme, + details, + false, + false); + } + } + }, + onLongPress: () { + if (widget.onLongPress != null) { + widget.onLongPress!( + context, message); + return; + } + if (widget.allowLongPress && + !isDesktopScreen) { + _onOpenToolTip( + context, + message, + model, + theme, + null, + false, + false); + } + }, + onTapDown: (details) { + _tapDetails = details; + }, + ); + }), + ), + if (!isSelf && + message.elemType == + MessageElemType + .V2TIM_ELEM_TYPE_SOUND && + message.localCustomInt != null && + message.localCustomInt != + HistoryMessageDartConstant.read) + Padding( + padding: const EdgeInsets.only( + left: 5, bottom: 12), + child: Icon(Icons.circle, + color: theme.cautionColor, + size: 10)), + if (!isSelf) + renderHoverTipAndReadStatus( + model, + isSelf, + message, + isPeerRead, + theme, + isDownloadWaiting), + ], ), + TIMUIKitTextTranslationElem( + message: message, + isUseDefaultEmoji: widget.isUseDefaultEmoji, + customEmojiStickerList: + widget.customEmojiStickerList, + isFromSelf: message.isSelf ?? true, + isShowJump: false, + clearJump: () {}, + chatModel: model), + if (widget.bottomRowBuilder != null) + widget.bottomRowBuilder!(context, message) + ], + ), + ), + if (!isSelf && + widget.message.elemType == 6 && + isDownloadWaiting) + Container( + margin: const EdgeInsets.only(top: 46, left: 10), + child: LoadingAnimationWidget.threeArchedCircle( + color: theme.weakTextColor ?? Colors.grey, + size: 20, ), - if (isSelf && widget.showAvatar) - widget.userAvatarBuilder != null - ? widget.userAvatarBuilder!(context, message) - : SizedBox( - width: 40, - height: 40, - child: InkWell( - onTapDown: (details) { - if (widget.onTapForOthersPortrait != null && widget.allowAvatarTap) { - widget.onTapForOthersPortrait!(message.sender ?? "", details); - } - }, - child: Avatar(faceUrl: message.faceUrl ?? "", showName: MessageUtils.getDisplayName(message)), - ), + ), + if (isSelf && widget.showAvatar) + widget.userAvatarBuilder != null + ? widget.userAvatarBuilder!(context, message) + : SizedBox( + width: 40, + height: 40, + child: InkWell( + onTapDown: (details) { + if (widget.onTapForOthersPortrait != + null && + widget.allowAvatarTap) { + widget.onTapForOthersPortrait!( + message.sender ?? "", details); + } + }, + child: Avatar( + faceUrl: message.faceUrl ?? "", + showName: MessageUtils.getDisplayName( + message)), ), - ], - ), + ), + ], ), ), ), - ], - ), + ), + ], ), + ), ), ); } diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart index 64ad25bc..842b5de9 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_message_tooltip.dart @@ -5,7 +5,6 @@ import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:image_clipboard/image_clipboard.dart'; import 'package:open_file/open_file.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart'; @@ -196,13 +195,15 @@ class TIMUIKitMessageTooltipState bool showTranslation = true; if (widget.message.localCustomData != null) { final LocalCustomDataModel localCustomData = LocalCustomDataModel.fromMap( - json.decode(TencentUtils.checkString(widget.message.localCustomData) ?? "{}")); - if (localCustomData.translatedText != null && localCustomData.translatedText != "") { + json.decode( + TencentUtils.checkString(widget.message.localCustomData) ?? + "{}")); + if (localCustomData.translatedText != null && + localCustomData.translatedText != "") { showTranslation = false; } } - final dynamicQuote = model.chatConfig.isAtWhenReplyDynamic?.call(widget.message); @@ -405,12 +406,6 @@ class TIMUIKitMessageTooltipState } catch (e) {} } - Future copyImageToClipboard(String imagePath) async { - ImageClipboard().copyImage(imagePath); - // final DesktopClipboard desktopClipboard = DesktopClipboard(); - // desktopClipboard.copyImage(imagePath); - } - _onTap(String operation, TUIChatSeparateViewModel model) async { final messageItem = widget.message; final msgID = messageItem.msgID as String; @@ -494,13 +489,6 @@ class TIMUIKitMessageTooltipState infoCode: 6660408)); // ignore: empty_catches } catch (e) {} - } else if (widget.message.elemType == - MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { - final savePath = (TencentUtils.checkString( - widget.message.imageElem!.imageList?[0]?.localUrl) ?? - TencentUtils.checkString(widget.message.imageElem?.path) ?? - ""); - copyImageToClipboard(savePath); } break; case "replyMessage": diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart index 35d5b168..5f6a54fe 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_image_elem.dart @@ -12,7 +12,7 @@ import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; @@ -172,7 +172,8 @@ class _TIMUIKitImageElem extends TIMUIKitState { if (model.getMessageProgress(widget.message.msgID) == 100) { String savePath; if (widget.message.imageElem!.path != null && - widget.message.imageElem!.path != '' && File(widget.message.imageElem!.path!).existsSync()) { + widget.message.imageElem!.path != '' && + File(widget.message.imageElem!.path!).existsSync()) { savePath = widget.message.imageElem!.path!; } else { savePath = model.getFileMessageLocation(widget.message.msgID); @@ -593,7 +594,6 @@ class _TIMUIKitImageElem extends TIMUIKitState { Widget? _renderImage(dynamic heroTag, TUITheme theme, {V2TimImage? originalImg, V2TimImage? smallImg}) { - double positionRadio = 1.0; if (smallImg?.width != null && smallImg?.height != null && diff --git a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation.dart b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation.dart index 78be3273..aa784d30 100644 --- a/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation.dart +++ b/lib/ui/views/TIMUIKitConversation/tim_uikit_conversation.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart'; import 'package:provider/provider.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; @@ -22,11 +22,15 @@ import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_uikit import 'package:tencent_cloud_chat_uikit/ui/widgets/customize_ball_pulse_header.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart'; -typedef ConversationItemBuilder = Widget Function(V2TimConversation conversationItem, [V2TimUserStatus? onlineStatus]); +typedef ConversationItemBuilder = Widget Function( + V2TimConversation conversationItem, + [V2TimUserStatus? onlineStatus]); -typedef ConversationItemSlideBuilder = List Function(V2TimConversation conversationItem); +typedef ConversationItemSlideBuilder = List + Function(V2TimConversation conversationItem); -typedef ConversationItemSecondaryMenuBuilder = Widget Function(V2TimConversation conversationItem, VoidCallback onClose); +typedef ConversationItemSecondaryMenuBuilder = Widget Function( + V2TimConversation conversationItem, VoidCallback onClose); class TIMUIKitConversation extends StatefulWidget { /// the callback after clicking conversation item @@ -140,11 +144,14 @@ class ConversationItemSlidePanel extends TIMUIKitStatelessWidget { } class _TIMUIKitConversationState extends TIMUIKitState { - final TUIConversationViewModel model = serviceLocator(); + final TUIConversationViewModel model = + serviceLocator(); late TIMUIKitConversationController _timuiKitConversationController; final TUIThemeViewModel themeViewModel = serviceLocator(); - final TUIFriendShipViewModel friendShipViewModel = serviceLocator(); - final TUIGroupListenerModel groupListenerModel = serviceLocator(); + final TUIFriendShipViewModel friendShipViewModel = + serviceLocator(); + final TUIGroupListenerModel groupListenerModel = + serviceLocator(); late AutoScrollController _autoScrollController; @override @@ -168,21 +175,30 @@ class _TIMUIKitConversationState extends TIMUIKitState { } _clearHistory(V2TimConversation conversationItem) { - _timuiKitConversationController.clearHistoryMessage(conversation: conversationItem); + _timuiKitConversationController.clearHistoryMessage( + conversation: conversationItem); } _pinConversation(V2TimConversation conversation) { - _timuiKitConversationController.pinConversation(conversationID: conversation.conversationID, isPinned: !conversation.isPinned!); + _timuiKitConversationController.pinConversation( + conversationID: conversation.conversationID, + isPinned: !conversation.isPinned!); } _deleteConversation(V2TimConversation conversation) { - _timuiKitConversationController.deleteConversation(conversationID: conversation.conversationID); + _timuiKitConversationController.deleteConversation( + conversationID: conversation.conversationID); } List getFilteredConversation() { - List filteredConversationList = model.conversationList.where((element) => (element?.groupID != null || element?.userID != null)).toList(); + List filteredConversationList = model.conversationList + .where( + (element) => (element?.groupID != null || element?.userID != null)) + .toList(); if (widget.conversationCollector != null) { - filteredConversationList = filteredConversationList.where(widget.conversationCollector!).toList(); + filteredConversationList = filteredConversationList + .where(widget.conversationCollector!) + .toList(); } return filteredConversationList; } @@ -208,7 +224,8 @@ class _TIMUIKitConversationState extends TIMUIKitState { } } - Widget _defaultSecondaryMenu(V2TimConversation conversationItem, VoidCallback onClose) { + Widget _defaultSecondaryMenu( + V2TimConversation conversationItem, VoidCallback onClose) { return TUIKitColumnMenu(data: [ if (!PlatformUtils().isWeb) ColumnMenuItem( @@ -220,7 +237,11 @@ class _TIMUIKitConversationState extends TIMUIKitState { }), ColumnMenuItem( label: conversationItem.isPinned! ? TIM_t("取消置顶") : TIM_t("置顶"), - icon: Icon(conversationItem.isPinned! ? Icons.vertical_align_bottom : Icons.vertical_align_top, size: 16), + icon: Icon( + conversationItem.isPinned! + ? Icons.vertical_align_bottom + : Icons.vertical_align_top, + size: 16), onClick: () { onClose(); _pinConversation(conversationItem); @@ -245,7 +266,8 @@ class _TIMUIKitConversationState extends TIMUIKitState { onPressed: (context) { _clearHistory(conversationItem); }, - backgroundColor: theme.conversationItemSliderClearBgColor ?? CommonColor.primaryColor, + backgroundColor: theme.conversationItemSliderClearBgColor ?? + CommonColor.primaryColor, foregroundColor: theme.conversationItemSliderTextColor, label: TIM_t("清除"), spacing: 0, @@ -255,7 +277,8 @@ class _TIMUIKitConversationState extends TIMUIKitState { onPressed: (context) { _pinConversation(conversationItem); }, - backgroundColor: theme.conversationItemSliderPinBgColor ?? CommonColor.infoColor, + backgroundColor: + theme.conversationItemSliderPinBgColor ?? CommonColor.infoColor, foregroundColor: theme.conversationItemSliderTextColor, label: conversationItem.isPinned! ? TIM_t("取消置顶") : TIM_t("置顶"), ), @@ -263,14 +286,16 @@ class _TIMUIKitConversationState extends TIMUIKitState { onPressed: (context) { _deleteConversation(conversationItem); }, - backgroundColor: theme.conversationItemSliderDeleteBgColor ?? Colors.red, + backgroundColor: + theme.conversationItemSliderDeleteBgColor ?? Colors.red, foregroundColor: theme.conversationItemSliderTextColor, label: TIM_t("删除"), ) ]; } - Widget _getSecondaryMenu(V2TimConversation conversation, VoidCallback onClose) { + Widget _getSecondaryMenu( + V2TimConversation conversation, VoidCallback onClose) { if (widget.itemSecondaryMenuBuilder != null) { return widget.itemSecondaryMenuBuilder!(conversation, onClose); } @@ -289,19 +314,23 @@ class _TIMUIKitConversationState extends TIMUIKitState { @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final theme = value.theme; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; return MultiProvider( providers: [ ChangeNotifierProvider.value(value: model), ChangeNotifierProvider.value(value: friendShipViewModel), - ChangeNotifierProvider.value(value: groupListenerModel)], + ChangeNotifierProvider.value(value: groupListenerModel) + ], builder: (BuildContext context, Widget? w) { final _model = Provider.of(context); bool haveMoreData = _model.haveMoreData; - final _friendShipViewModel = Provider.of(context); + final _friendShipViewModel = + Provider.of(context); _model.lifeCycle = widget.lifeCycle; - final TUIGroupListenerModel groupListenerModel = Provider.of(context, listen: true); + final TUIGroupListenerModel groupListenerModel = + Provider.of(context, listen: true); final NeedUpdate? needUpdate = groupListenerModel.needUpdate; if (needUpdate != null) { groupListenerModel.needUpdate = null; @@ -313,12 +342,14 @@ class _TIMUIKitConversationState extends TIMUIKitState { } else if (needUpdate.updateType == UpdateType.kickedFromGroup) { onTIMCallback(TIMCallback( type: TIMCallbackType.INFO, - infoRecommendText: "${TIM_t("您已被踢出")}${needUpdate!.extraData}", + infoRecommendText: + "${TIM_t("您已被踢出")}${needUpdate!.extraData}", infoCode: 6661402)); } } - List filteredConversationList = getFilteredConversation(); + List filteredConversationList = + getFilteredConversation(); if (TencentUtils.checkString(_model.scrollToConversation) != null) { _onScrollToConversation(_model.scrollToConversation!); @@ -340,15 +371,21 @@ class _TIMUIKitConversationState extends TIMUIKitState { final conversationItem = filteredConversationList[index]; - final V2TimUserStatus? onlineStatus = _friendShipViewModel.userStatusList.firstWhere((item) => item.userID == conversationItem?.userID, orElse: () => V2TimUserStatus(statusType: 0)); + final V2TimUserStatus? onlineStatus = + _friendShipViewModel.userStatusList.firstWhere( + (item) => item.userID == conversationItem?.userID, + orElse: () => V2TimUserStatus(statusType: 0)); if (widget.itemBuilder != null) { - return widget.itemBuilder!(conversationItem!, onlineStatus); + return widget.itemBuilder!( + conversationItem!, onlineStatus); } - final slideChildren = _getSlideBuilder()(conversationItem!); + final slideChildren = + _getSlideBuilder()(conversationItem!); - final isCurrent = conversationItem.conversationID == model.selectedConversation?.conversationID; + final isCurrent = conversationItem.conversationID == + model.selectedConversation?.conversationID; final isPined = conversationItem.isPinned ?? false; @@ -365,14 +402,21 @@ class _TIMUIKitConversationState extends TIMUIKitState { lastMessageBuilder: widget.lastMessageBuilder, faceUrl: conversationItem.faceUrl ?? "", nickName: conversationItem.showName ?? "", - isDisturb: (conversationItem.groupType == "Meeting" ? false : conversationItem - .recvOpt != 0), + isDisturb: + (conversationItem.groupType == "Meeting" + ? false + : conversationItem.recvOpt != 0), lastMsg: conversationItem.lastMessage, isPined: isPined, - groupAtInfoList: conversationItem.groupAtInfoList ?? [], + groupAtInfoList: + conversationItem.groupAtInfoList ?? [], unreadCount: conversationItem.unreadCount ?? 0, draftText: conversationItem.draftText, - onlineStatus: (widget.isShowOnlineStatus && conversationItem.userID != null && conversationItem.userID!.isNotEmpty) ? onlineStatus : null, + onlineStatus: (widget.isShowOnlineStatus && + conversationItem.userID != null && + conversationItem.userID!.isNotEmpty) + ? onlineStatus + : null, draftTimestamp: conversationItem.draftTimestamp, convType: conversationItem.type), onTap: () => onTapConvItem(conversationItem), @@ -389,12 +433,23 @@ class _TIMUIKitConversationState extends TIMUIKitState { child: InkWell( onSecondaryTapDown: (details) { TUIKitWidePopup.showPopupWindow( - operationKey: TUIKitWideModalOperationKey.conversationSecondaryMenu, + operationKey: TUIKitWideModalOperationKey + .conversationSecondaryMenu, isDarkBackground: false, - borderRadius: const BorderRadius.all(Radius.circular(4)), + borderRadius: const BorderRadius.all( + Radius.circular(4)), context: context, - offset: Offset(min(details.globalPosition.dx, MediaQuery.of(context).size.width - 80), min(details.globalPosition.dy, MediaQuery.of(context).size.height - 130)), - child: (onClose) => _getSecondaryMenu(conversationItem, onClose)); + offset: Offset( + min( + details.globalPosition.dx, + MediaQuery.of(context).size.width - + 80), + min( + details.globalPosition.dy, + MediaQuery.of(context).size.height - + 130)), + child: (onClose) => _getSecondaryMenu( + conversationItem, onClose)); }, child: conversationLineItem(), ), @@ -403,10 +458,19 @@ class _TIMUIKitConversationState extends TIMUIKitState { key: ValueKey(conversationItem.conversationID), controller: _autoScrollController, index: index, - child: Slidable(groupTag: 'conversation-list', child: conversationLineItem(), endActionPane: ActionPane(extentRatio: slideChildren.length > 2 ? 0.77 : 0.5, motion: const DrawerMotion(), children: slideChildren)), + child: Slidable( + groupTag: 'conversation-list', + child: conversationLineItem(), + endActionPane: ActionPane( + extentRatio: + slideChildren.length > 2 ? 0.77 : 0.5, + motion: const DrawerMotion(), + children: slideChildren)), )); }) - : (widget.emptyBuilder != null ? widget.emptyBuilder!() : Container()); + : (widget.emptyBuilder != null + ? widget.emptyBuilder!() + : Container()); } return TUIKitScreenUtils.getDeviceWidget( @@ -420,7 +484,9 @@ class _TIMUIKitConversationState extends TIMUIKitState { child: conversationList(), ), ), - desktopWidget: Scrollbar(controller: _autoScrollController, child: conversationList())); + desktopWidget: Scrollbar( + controller: _autoScrollController, + child: conversationList())); }); } } diff --git a/lib/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart b/lib/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart index d9bd09d3..195008a5 100644 --- a/lib/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart +++ b/lib/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; @@ -26,23 +26,34 @@ class GroupProfileGroupManage extends StatefulWidget { State createState() => GroupProfileGroupManageState(); } -class GroupProfileGroupManageState extends TIMUIKitState { +class GroupProfileGroupManageState + extends TIMUIKitState { bool isShowManageBox = false; @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final TUITheme theme = value.theme; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; final model = Provider.of(context); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration(color: Colors.white, border: isDesktopScreen ? null : Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))), + decoration: BoxDecoration( + color: Colors.white, + border: isDesktopScreen + ? null + : Border( + bottom: BorderSide( + color: theme.weakDividerColor ?? + CommonColor.weakDividerColor))), child: Column( children: [ InkWell( onTap: () { - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == + DeviceType.Desktop; if (!isDesktopScreen) { Navigator.push( context, @@ -61,12 +72,15 @@ class GroupProfileGroupManageState extends TIMUIKitState createState() => _GroupProfileGroupManagePageState(); } -class _GroupProfileGroupManagePageState extends TIMUIKitState { +class _GroupProfileGroupManagePageState + extends TIMUIKitState { int? serverTime; @override @@ -113,20 +128,38 @@ class _GroupProfileGroupManagePageState extends TIMUIKitState())], + providers: [ + ChangeNotifierProvider.value(value: widget.model), + ChangeNotifierProvider.value( + value: serviceLocator()) + ], builder: (context, w) { - final memberList = Provider.of(context).groupMemberList; + final memberList = + Provider.of(context).groupMemberList; final theme = Provider.of(context).theme; final isAllMuted = widget.model.groupInfo?.isAllMuted ?? false; - final bool isAllowMuteMember = (widget.model.groupInfo?.groupType ?? "") != GroupType.Work; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final bool isAllowMuteMember = + (widget.model.groupInfo?.groupType ?? "") != GroupType.Work; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; Widget managePage() { return Column( children: [ Container( - padding: EdgeInsets.only(top: 12, left: isDesktopScreen ? 0 : 16, bottom: isDesktopScreen ? 0 : 12, right: isDesktopScreen ? 0 : 12), - decoration: BoxDecoration(color: Colors.white, border: isDesktopScreen ? null : Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))), + padding: EdgeInsets.only( + top: 12, + left: isDesktopScreen ? 0 : 16, + bottom: isDesktopScreen ? 0 : 12, + right: isDesktopScreen ? 0 : 12), + decoration: BoxDecoration( + color: Colors.white, + border: isDesktopScreen + ? null + : Border( + bottom: BorderSide( + color: theme.weakDividerColor ?? + CommonColor.weakDividerColor))), child: InkWell( onTap: isDesktopScreen ? null @@ -134,7 +167,8 @@ class _GroupProfileGroupManagePageState extends TIMUIKitState GroupProfileSetManagerPage( + builder: (context) => + GroupProfileSetManagerPage( model: widget.model, ), )); @@ -143,12 +177,22 @@ class _GroupProfileGroupManagePageState extends TIMUIKitState serverTime! : false); - final isMember = element!.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER; + final isMute = (serverTime != null + ? (element?.muteUntil ?? 0) > serverTime! + : false); + final isMember = element!.role == + GroupMemberRoleType + .V2TIM_GROUP_MEMBER_ROLE_MEMBER; return !isMute && isMember; }).toList(), - selectCompletedHandler: (context, selectedMember) async { + selectCompletedHandler: + (context, selectedMember) async { if (selectedMember.isNotEmpty) { for (var member in selectedMember) { final userID = member!.userID; - widget.model.muteGroupMember(userID, true, serverTime); + widget.model + .muteGroupMember(userID, true, serverTime); } } }, @@ -269,29 +337,48 @@ class _GroupProfileGroupManagePageState extends TIMUIKitState muteMember()); } else { - Navigator.push(context, MaterialPageRoute(builder: (context) => muteMember())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => muteMember())); } }, ), if (!isAllMuted && isAllowMuteMember) ...memberList - .where((element) => (serverTime != null ? (element?.muteUntil ?? 0) > serverTime! : false)) + .where((element) => (serverTime != null + ? (element?.muteUntil ?? 0) > serverTime! + : false)) .map((e) => Container( - padding: isDesktopScreen ? const EdgeInsets.only(left: 16) : null, + padding: isDesktopScreen + ? const EdgeInsets.only(left: 16) + : null, child: GestureDetector( onSecondaryTapDown: (details) { TUIKitWidePopup.showPopupWindow( - operationKey: TUIKitWideModalOperationKey.setUnmute, + operationKey: + TUIKitWideModalOperationKey.setUnmute, isDarkBackground: false, - borderRadius: const BorderRadius.all(Radius.circular(4)), + borderRadius: const BorderRadius.all( + Radius.circular(4)), context: context, - offset: Offset(min(details.globalPosition.dx, MediaQuery.of(context).size.width - 80), details.globalPosition.dy), + offset: Offset( + min( + details.globalPosition.dx, + MediaQuery.of(context).size.width - + 80), + details.globalPosition.dy), child: (onClose) => TUIKitColumnMenu(data: [ ColumnMenuItem( label: TIM_t("删除"), - icon: const Icon(Icons.remove_circle_outline, size: 16), + icon: const Icon( + Icons.remove_circle_outline, + size: 16), onClick: () { - widget.model.muteGroupMember(e.userID, false, serverTime); + widget.model.muteGroupMember( + e.userID, + false, + serverTime); onClose(); }), ])); @@ -299,17 +386,21 @@ class _GroupProfileGroupManagePageState extends TIMUIKitState(context).theme; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; Widget nameItem() { return Container( @@ -390,42 +484,67 @@ Widget _buildListItem(BuildContext context, V2TimGroupMemberFullInfo memberInfo, ), title: Row( children: [ - Text(_getShowName(memberInfo), style: TextStyle(fontSize: isDesktopScreen ? 14 : 16)), + Text(_getShowName(memberInfo), + style: TextStyle(fontSize: isDesktopScreen ? 14 : 16)), ], ), onTap: () {}, ), - if (!isDesktopScreen) Divider(thickness: 1, indent: 74, endIndent: 0, color: theme.weakDividerColor, height: 0) + if (!isDesktopScreen) + Divider( + thickness: 1, + indent: 74, + endIndent: 0, + color: theme.weakDividerColor, + height: 0) ]), ); } - return TUIKitScreenUtils.getDeviceWidget(context: context, desktopWidget: nameItem(), defaultWidget: SingleChildScrollView(child: Slidable(endActionPane: endActionPane, child: nameItem()))); + return TUIKitScreenUtils.getDeviceWidget( + context: context, + desktopWidget: nameItem(), + defaultWidget: SingleChildScrollView( + child: Slidable(endActionPane: endActionPane, child: nameItem()))); } /// 选择管理员 class GroupProfileSetManagerPage extends StatefulWidget { final TUIGroupProfileModel model; - const GroupProfileSetManagerPage({Key? key, required this.model}) : super(key: key); + const GroupProfileSetManagerPage({Key? key, required this.model}) + : super(key: key); @override State createState() => _GroupProfileSetManagerPageState(); } -class _GroupProfileSetManagerPageState extends TIMUIKitState { - List _getAdminMemberList(List memberList) { - return memberList.where((member) => member?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN).toList(); +class _GroupProfileSetManagerPageState + extends TIMUIKitState { + List _getAdminMemberList( + List memberList) { + return memberList + .where((member) => + member?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN) + .toList(); } - List _getOwnerList(List memberList) { - return memberList.where((member) => member?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER).toList(); + List _getOwnerList( + List memberList) { + return memberList + .where((member) => + member?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER) + .toList(); } - _removeAdmin(BuildContext context, V2TimGroupMemberFullInfo memberFullInfo) async { + _removeAdmin( + BuildContext context, V2TimGroupMemberFullInfo memberFullInfo) async { final res = await widget.model.setMemberToNormal(memberFullInfo.userID); if (res.code == 0) { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("成功取消管理员身份"), infoCode: 6661003)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("成功取消管理员身份"), + infoCode: 6661003)); } } @@ -441,7 +560,8 @@ class _GroupProfileSetManagerPageState extends TIMUIKitState Container( - padding: isDesktopScreen ? const EdgeInsets.only(left: 16) : null, + padding: isDesktopScreen + ? const EdgeInsets.only(left: 16) + : null, child: _buildListItem(context, e!, null), ), ) @@ -478,9 +601,11 @@ class _GroupProfileSetManagerPageState extends TIMUIKitState GroupProfileAddAdmin( key: groupProfileAddAdminKey, - memberList: memberList.where((element) => element?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER).toList(), + memberList: memberList + .where((element) => + element?.role == + GroupMemberRoleType + .V2TIM_GROUP_MEMBER_ROLE_MEMBER) + .toList(), appbarTitle: TIM_t("设置管理员"), - selectCompletedHandler: (context, selectedMember) async { + selectCompletedHandler: + (context, selectedMember) async { if (selectedMember.isNotEmpty) { for (var member in selectedMember) { final userID = member!.userID; @@ -546,9 +685,15 @@ class _GroupProfileSetManagerPageState extends TIMUIKitState GroupProfileAddAdmin( key: groupProfileAddAdminKey, - memberList: memberList.where((element) => element?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER).toList(), + memberList: memberList + .where((element) => + element?.role == + GroupMemberRoleType + .V2TIM_GROUP_MEMBER_ROLE_MEMBER) + .toList(), appbarTitle: TIM_t("设置管理员"), - selectCompletedHandler: (context, selectedMember) async { + selectCompletedHandler: + (context, selectedMember) async { if (selectedMember.isNotEmpty) { for (var member in selectedMember) { final userID = member!.userID; @@ -564,15 +709,22 @@ class _GroupProfileSetManagerPageState extends TIMUIKitState GestureDetector( onSecondaryTapDown: (details) { TUIKitWidePopup.showPopupWindow( - operationKey: TUIKitWideModalOperationKey.deleteAdmin, + operationKey: + TUIKitWideModalOperationKey.deleteAdmin, isDarkBackground: false, - borderRadius: const BorderRadius.all(Radius.circular(4)), + borderRadius: + const BorderRadius.all(Radius.circular(4)), context: context, - offset: Offset(min(details.globalPosition.dx, MediaQuery.of(context).size.width - 80), details.globalPosition.dy), + offset: Offset( + min(details.globalPosition.dx, + MediaQuery.of(context).size.width - 80), + details.globalPosition.dy), child: (onClose) => TUIKitColumnMenu(data: [ ColumnMenuItem( label: TIM_t("删除"), - icon: const Icon(Icons.remove_circle_outline, size: 16), + icon: const Icon( + Icons.remove_circle_outline, + size: 16), onClick: () { _removeAdmin(context, e); onClose(); @@ -580,21 +732,26 @@ class _GroupProfileSetManagerPageState extends TIMUIKitState memberList; final String appbarTitle; - final void Function(BuildContext context, List selectedMemberList)? selectCompletedHandler; + final void Function(BuildContext context, + List selectedMemberList)? + selectCompletedHandler; - const GroupProfileAddAdmin({Key? key, required this.memberList, this.selectCompletedHandler, required this.appbarTitle}) : super(key: key); + const GroupProfileAddAdmin( + {Key? key, + required this.memberList, + this.selectCompletedHandler, + required this.appbarTitle}) + : super(key: key); @override State createState() => _GroupProfileAddAdminState(); @@ -664,8 +828,14 @@ class _GroupProfileAddAdminState extends TIMUIKitState { ), ...widget.memberList .map((e) => Container( - decoration: BoxDecoration(color: Colors.white, border: Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide( + color: theme.weakDividerColor ?? + CommonColor.weakDividerColor))), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 16), child: InkWell( onTap: () { final isChecked = selectedMemberList.contains(e); @@ -697,7 +867,8 @@ class _GroupProfileAddAdminState extends TIMUIKitState { const SizedBox( width: 10, ), - Text(_getShowName(e), style: const TextStyle(fontSize: 16)) + Text(_getShowName(e), + style: const TextStyle(fontSize: 16)) ], ), ), diff --git a/lib/ui/widgets/group_member_list.dart b/lib/ui/widgets/group_member_list.dart index 45c94fff..5130db5e 100644 --- a/lib/ui/widgets/group_member_list.dart +++ b/lib/ui/widgets/group_member_list.dart @@ -2,7 +2,7 @@ import 'package:azlistview_all_platforms/azlistview_all_platforms.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart'; import 'package:lpinyin/lpinyin.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; @@ -24,9 +24,12 @@ class GroupProfileMemberList extends StatefulWidget { // when the @ need filter some group types final String? groupType; - final Function(List selectedMember)? onSelectedMemberChange; + final Function(List selectedMember)? + onSelectedMemberChange; // notice: onTapMemberItem and onSelectedMemberChange use together will triger together - final Function(V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)? onTapMemberItem; + final Function( + V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)? + onTapMemberItem; // When sliding to the bottom bar callBack final Function()? touchBottomCallBack; @@ -53,7 +56,8 @@ class GroupProfileMemberList extends StatefulWidget { State createState() => _GroupProfileMemberListState(); } -class _GroupProfileMemberListState extends TIMUIKitState { +class _GroupProfileMemberListState + extends TIMUIKitState { List selectedMemberList = []; _getShowName(V2TimGroupMemberFullInfo? item) { @@ -70,12 +74,14 @@ class _GroupProfileMemberListState extends TIMUIKitState : userID; } - List _getShowList(List memberList) { + List _getShowList( + List memberList) { final List showList = List.empty(growable: true); for (var i = 0; i < memberList.length; i++) { final item = memberList[i]; final showName = _getShowName(item); - if (item?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER || item?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN) { + if (item?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER || + item?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN) { showList.add(ISuspensionBeanImpl(memberInfo: item, tagIndex: "@")); } else { String pinyin = PinyinHelper.getPinyinE(showName); @@ -94,17 +100,26 @@ class _GroupProfileMemberListState extends TIMUIKitState if (widget.canAtAll) { final canAtGroupType = ["Work", "Public", "Meeting"]; if (canAtGroupType.contains(widget.groupType)) { - showList.insert(0, ISuspensionBeanImpl(memberInfo: V2TimGroupMemberFullInfo(userID: GroupProfileMemberList.AT_ALL_USER_ID, nickName: TIM_t("所有人")), tagIndex: "")); + showList.insert( + 0, + ISuspensionBeanImpl( + memberInfo: V2TimGroupMemberFullInfo( + userID: GroupProfileMemberList.AT_ALL_USER_ID, + nickName: TIM_t("所有人")), + tagIndex: "")); } } return showList; } - Widget _buildListItem(BuildContext context, V2TimGroupMemberFullInfo memberInfo) { + Widget _buildListItem( + BuildContext context, V2TimGroupMemberFullInfo memberInfo) { final theme = Provider.of(context).theme; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop; - final isGroupMember = memberInfo.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop; + final isGroupMember = + memberInfo.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER; return Container( color: Colors.white, child: Slidable( @@ -117,7 +132,8 @@ class _GroupProfileMemberListState extends TIMUIKitState } }, flex: 1, - backgroundColor: theme.cautionColor ?? CommonColor.cautionColor, + backgroundColor: + theme.cautionColor ?? CommonColor.cautionColor, autoClose: true, label: TIM_t("删除"), ) @@ -134,21 +150,28 @@ class _GroupProfileMemberListState extends TIMUIKitState child: CheckBoxButton( onChanged: (isChecked) { if (isChecked) { - if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) { + if (widget.maxSelectNum != null && + selectedMemberList.length >= + widget.maxSelectNum!) { return; } selectedMemberList.add(memberInfo); } else { - selectedMemberList.removeWhere((element) => element.userID == memberInfo.userID); + selectedMemberList.removeWhere((element) => + element.userID == memberInfo.userID); } if (widget.onSelectedMemberChange != null) { - widget.onSelectedMemberChange!(selectedMemberList); + widget.onSelectedMemberChange!( + selectedMemberList); } setState(() {}); }, - isChecked: selectedMemberList.where((element) => element.userID == memberInfo.userID).toList().isNotEmpty - ), + isChecked: selectedMemberList + .where((element) => + element.userID == memberInfo.userID) + .toList() + .isNotEmpty), ), Container( width: isDesktopScreen ? 30 : 36, @@ -160,8 +183,10 @@ class _GroupProfileMemberListState extends TIMUIKitState type: 1, ), ), - Text(_getShowName(memberInfo), style: TextStyle(fontSize: isDesktopScreen ? 14 : 16)), - memberInfo.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER + Text(_getShowName(memberInfo), + style: TextStyle(fontSize: isDesktopScreen ? 14 : 16)), + memberInfo.role == + GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER ? Container( margin: const EdgeInsets.only(left: 5), child: Text(TIM_t("群主"), @@ -171,11 +196,17 @@ class _GroupProfileMemberListState extends TIMUIKitState )), padding: const EdgeInsets.fromLTRB(5, 0, 5, 0), decoration: BoxDecoration( - border: Border.all(color: theme.ownerColor ?? CommonColor.ownerColor, width: 1), - borderRadius: const BorderRadius.all(Radius.circular(4.0)), + border: Border.all( + color: theme.ownerColor ?? + CommonColor.ownerColor, + width: 1), + borderRadius: + const BorderRadius.all(Radius.circular(4.0)), ), ) - : memberInfo.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN + : memberInfo.role == + GroupMemberRoleType + .V2TIM_GROUP_MEMBER_ROLE_ADMIN ? Container( margin: const EdgeInsets.only(left: 5), child: Text(TIM_t("管理员"), @@ -185,8 +216,12 @@ class _GroupProfileMemberListState extends TIMUIKitState )), padding: const EdgeInsets.fromLTRB(5, 0, 5, 0), decoration: BoxDecoration( - border: Border.all(color: theme.adminColor ?? CommonColor.adminColor, width: 1), - borderRadius: const BorderRadius.all(Radius.circular(4.0)), + border: Border.all( + color: theme.adminColor ?? + CommonColor.adminColor, + width: 1), + borderRadius: const BorderRadius.all( + Radius.circular(4.0)), ), ) : Container() @@ -201,7 +236,8 @@ class _GroupProfileMemberListState extends TIMUIKitState if (isChecked) { selectedMemberList.remove(memberInfo); } else { - if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) { + if (widget.maxSelectNum != null && + selectedMemberList.length >= widget.maxSelectNum!) { return; } selectedMemberList.add(memberInfo); @@ -213,11 +249,17 @@ class _GroupProfileMemberListState extends TIMUIKitState } }, ), - Divider(thickness: 1, indent: 74, endIndent: 0, color: theme.weakBackgroundColor, height: 0) + Divider( + thickness: 1, + indent: 74, + endIndent: 0, + color: theme.weakBackgroundColor, + height: 0) ]))); } - static Widget getSusItem(BuildContext context, TUITheme theme, String tag, {double susHeight = 40}) { + static Widget getSusItem(BuildContext context, TUITheme theme, String tag, + {double susHeight = 40}) { if (tag == '@') { tag = TIM_t("群主、管理员"); } @@ -242,9 +284,11 @@ class _GroupProfileMemberListState extends TIMUIKitState Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final TUITheme theme = value.theme; - final isDesktopScreen = TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop; + final isDesktopScreen = + TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop; - final throteFunction = OptimizeUtils.throttle((ScrollNotification notification) { + final throteFunction = + OptimizeUtils.throttle((ScrollNotification notification) { final pixels = notification.metrics.pixels; // 总像素高度 final maxScrollExtent = notification.metrics.maxScrollExtent; @@ -272,15 +316,19 @@ class _GroupProfileMemberListState extends TIMUIKitState child: Text(TIM_t("暂无群成员")), ) : Container( - padding: isDesktopScreen ? const EdgeInsets.symmetric(horizontal: 16) : null, + padding: isDesktopScreen + ? const EdgeInsets.symmetric(horizontal: 16) + : null, child: AZListViewContainer( memberList: showList, susItemBuilder: (context, index) { final model = showList[index]; - return getSusItem(context, theme, model.getSuspensionTag()); + return getSusItem( + context, theme, model.getSuspensionTag()); }, itemBuilder: (context, index) { - final memberInfo = showList[index].memberInfo as V2TimGroupMemberFullInfo; + final memberInfo = showList[index].memberInfo + as V2TimGroupMemberFullInfo; return _buildListItem(context, memberInfo); }), diff --git a/lib/ui/widgets/video_screen.dart b/lib/ui/widgets/video_screen.dart index ff8f8563..b2e069dd 100644 --- a/lib/ui/widgets/video_screen.dart +++ b/lib/ui/widgets/video_screen.dart @@ -8,7 +8,7 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; @@ -21,7 +21,12 @@ import 'package:universal_html/html.dart' as html; import 'package:video_player/video_player.dart'; class VideoScreen extends StatefulWidget { - const VideoScreen({required this.message, required this.heroTag, required this.videoElement, Key? key}) : super(key: key); + const VideoScreen( + {required this.message, + required this.heroTag, + required this.videoElement, + Key? key}) + : super(key: key); final V2TimMessage message; final dynamic heroTag; @@ -34,7 +39,8 @@ class VideoScreen extends StatefulWidget { class _VideoScreenState extends TIMUIKitState { late VideoPlayerController videoPlayerController; late ChewieController chewieController; - GlobalKey slidePagekey = GlobalKey(); + GlobalKey slidePagekey = + GlobalKey(); final TUIChatGlobalModel model = serviceLocator(); bool isInit = false; @@ -64,7 +70,8 @@ class _VideoScreenState extends TIMUIKitState { xhr.open('get', videoUrl); xhr.responseType = 'arraybuffer'; xhr.onLoad.listen((event) { - final a = html.AnchorElement(href: html.Url.createObjectUrl(html.Blob([xhr.response]))); + final a = html.AnchorElement( + href: html.Url.createObjectUrl(html.Blob([xhr.response]))); a.download = '${md5.convert(utf8.encode(videoUrl)).toString()}$suffix'; a.click(); a.remove(); @@ -110,7 +117,8 @@ class _VideoScreenState extends TIMUIKitState { } if (model.getMessageProgress(widget.message.msgID) == 100) { String savePath; - if (widget.message.videoElem!.localVideoUrl != null && widget.message.videoElem!.localVideoUrl != '') { + if (widget.message.videoElem!.localVideoUrl != null && + widget.message.videoElem!.localVideoUrl != '') { savePath = widget.message.videoElem!.localVideoUrl!; } else { savePath = model.getFileMessageLocation(widget.message.msgID); @@ -120,35 +128,62 @@ class _VideoScreenState extends TIMUIKitState { var result = await ImageGallerySaver.saveFile(savePath); if (PlatformUtils().isIOS) { if (result['isSuccess']) { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存成功"), infoCode: 6660402)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存成功"), + infoCode: 6660402)); } else { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存失败"), infoCode: 6660403)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存失败"), + infoCode: 6660403)); } } else { if (result != null) { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存成功"), infoCode: 6660402)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存成功"), + infoCode: 6660402)); } else { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存失败"), infoCode: 6660403)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存失败"), + infoCode: 6660403)); } } } } else { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("the message is downloading"), infoCode: -1)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("the message is downloading"), + infoCode: -1)); } return; } var result = await ImageGallerySaver.saveFile(savePath); if (PlatformUtils().isIOS) { if (result['isSuccess']) { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存成功"), infoCode: 6660402)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存成功"), + infoCode: 6660402)); } else { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存失败"), infoCode: 6660403)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存失败"), + infoCode: 6660403)); } } else { if (result != null) { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存成功"), infoCode: 6660402)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存成功"), + infoCode: 6660402)); } else { - onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("视频保存失败"), infoCode: 6660403)); + onTIMCallback(TIMCallback( + type: TIMCallbackType.INFO, + infoRecommendText: TIM_t("视频保存失败"), + infoCode: 6660403)); } } return; @@ -162,7 +197,9 @@ class _VideoScreenState extends TIMUIKitState { isAsset: true, ); } - if (widget.videoElement.videoPath != '' && widget.videoElement.videoPath != null && File(widget.videoElement.videoPath!).existsSync()) { + if (widget.videoElement.videoPath != '' && + widget.videoElement.videoPath != null && + File(widget.videoElement.videoPath!).existsSync()) { File f = File(widget.videoElement.videoPath!); if (f.existsSync()) { return await _saveNetworkVideo( @@ -172,7 +209,8 @@ class _VideoScreenState extends TIMUIKitState { ); } } - if (widget.videoElement.localVideoUrl != '' && widget.videoElement.localVideoUrl != null) { + if (widget.videoElement.localVideoUrl != '' && + widget.videoElement.localVideoUrl != null) { File f = File(widget.videoElement.localVideoUrl!); if (f.existsSync()) { return await _saveNetworkVideo( @@ -211,7 +249,8 @@ class _VideoScreenState extends TIMUIKitState { setVideoPlayerController() async { if (!PlatformUtils().isWeb) { - if (TencentUtils.checkString(widget.message.msgID) != null && widget.videoElement.localVideoUrl == null) { + 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()) { @@ -221,20 +260,26 @@ class _VideoScreenState extends TIMUIKitState { } VideoPlayerController player = PlatformUtils().isWeb - ? ((TencentUtils.checkString(widget.videoElement.videoPath) != null) || widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING + ? ((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) + : (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) && File(widget.videoElement.videoPath!).existsSync()) + : ((TencentUtils.checkString(widget.videoElement.videoPath) != null || + widget.message.status == + MessageStatus.V2TIM_MSG_STATUS_SENDING) && + File(widget.videoElement.videoPath!).existsSync()) ? VideoPlayerController.file(File(widget.videoElement.videoPath!)) - : (TencentUtils.checkString(widget.videoElement.localVideoUrl) == null) + : (TencentUtils.checkString(widget.videoElement.localVideoUrl) == + null) ? VideoPlayerController.networkUrl( Uri.parse(widget.videoElement.videoUrl!), ) @@ -264,7 +309,8 @@ class _VideoScreenState extends TIMUIKitState { @override didUpdateWidget(oldWidget) { - if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl || oldWidget.videoElement.videoPath != widget.videoElement.videoPath) { + if (oldWidget.videoElement.videoUrl != widget.videoElement.videoUrl || + oldWidget.videoElement.videoPath != widget.videoElement.videoPath) { setVideoPlayerController(); } super.didUpdateWidget(oldWidget); @@ -299,8 +345,10 @@ class _VideoScreenState extends TIMUIKitState { 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))); + 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: ( @@ -322,13 +370,22 @@ class _VideoScreenState extends TIMUIKitState { ? Chewie( controller: chewieController, ) - : const Center(child: CircularProgressIndicator(color: Colors.white))), + : const Center( + child: CircularProgressIndicator( + color: Colors.white))), heroBuilderForSlidingPage: (Widget result) { return Hero( tag: widget.heroTag, child: result, - flightShuttleBuilder: (BuildContext flightContext, Animation animation, HeroFlightDirection flightDirection, BuildContext fromHeroContext, BuildContext toHeroContext) { - final Hero hero = (flightDirection == HeroFlightDirection.pop ? fromHeroContext.widget : toHeroContext.widget) as Hero; + flightShuttleBuilder: (BuildContext flightContext, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext) { + final Hero hero = + (flightDirection == HeroFlightDirection.pop + ? fromHeroContext.widget + : toHeroContext.widget) as Hero; return hero.child; }, diff --git a/pubspec.yaml b/pubspec.yaml index 7d9963be..71fd4c86 100644 --- a/pubspec.yaml +++ b/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: 3.1.0 +version: 3.1.0+2 homepage: https://trtc.io/products/chat?utm_source=gfs&utm_medium=link&utm_campaign=%E6%B8%A0%E9%81%93&_channel_track_key=k6WgfCKn repository: https://github.com/TencentCloud/chat-uikit-flutter documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html @@ -30,12 +30,12 @@ dependencies: tencent_super_tooltip: ^0.0.1 video_player: ^2.9.0 chewie: ^1.8.5 - flutter_slidable: ^3.0.1 - flutter_plugin_record_plus: ^0.0.17 + flutter_slidable_plus_plus: ^0.1.0 + flutter_plugin_record_plus: ^0.0.19 azlistview_all_platforms: ^2.1.2 lpinyin: ^2.0.3 transparent_image: ^2.0.0 - image_gallery_saver: ^2.0.1 + image_gallery_saver_plus: ^3.0.5 path_provider: ^2.0.8 cached_network_image: ^3.3.0 shared_preferences: ^2.0.13 @@ -57,7 +57,7 @@ dependencies: http: ^1.0.0 crypto: ^3.0.2 collection: ^1.15.0 - flutter_image_compress: ^1.1.3 + flutter_image_compress: ^2.3.0 uuid: ^3.0.6 open_file: ^3.3.2 tencent_keyboard_visibility: ^1.0.1 @@ -75,7 +75,6 @@ dependencies: just_audio: ^0.9.34 markdown: ^7.1.0 logger: ^2.0.1 - image_clipboard: ^1.0.0+2 visibility_detector: ^0.4.0+2 dev_dependencies: