feat: Upgrade to 3.1.0

This commit is contained in:
vinsonswang 2024-11-27 22:27:29 +08:00
parent 157f48e653
commit 8ad7673b7b
66 changed files with 1318 additions and 922 deletions

View File

@ -1,3 +1,9 @@
# 3.1.0
## Bug Fixes
* The interface for deleting messages is changed to the interface for deleting cloud messages.
* C2C messages support read receipts
* Fix and optimize some issues
# 3.0.0 # 3.0.0
## Breaking Changes ## Breaking Changes
* Migrated to Flutter 3.24.0 * Migrated to Flutter 3.24.0

View File

@ -1,3 +1,6 @@
#
It is recommended to download the source code from [pub.dev](https://pub.dev/packages/tencent_cloud_chat_uikit/versions)
## Product Introduction ## Product Introduction
You only need to integrate Chat SDK to easily gain chat, conversation, group capabilities, and you can also communicate with other products such as whiteboards through signaling messages. Chat can cover various business scenarios, support the access and use of various platforms, and fully meet the communication needs. You only need to integrate Chat SDK to easily gain chat, conversation, group capabilities, and you can also communicate with other products such as whiteboards through signaling messages. Chat can cover various business scenarios, support the access and use of various platforms, and fully meet the communication needs.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,2 +0,0 @@
<EFBFBD>
<EFBFBD>

View File

@ -3,7 +3,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
@ -15,6 +14,8 @@ class NeedUpdate {
final String groupID; final String groupID;
final UpdateType updateType; final UpdateType updateType;
final String extraData; final String extraData;
int? groupInfoSubType;
String? ownerID;
NeedUpdate(this.groupID, this.updateType, this.extraData); NeedUpdate(this.groupID, this.updateType, this.extraData);
} }
@ -44,10 +45,11 @@ class TUIGroupListenerModel extends ChangeNotifier {
onMemberKicked: (groupID, opUser, memberList) async { onMemberKicked: (groupID, opUser, memberList) async {
if (_isLoginUserKickedFromGroup(groupID, memberList)) { if (_isLoginUserKickedFromGroup(groupID, memberList)) {
_deleteGroupConversation(groupID); _deleteGroupConversation(groupID);
}
final groupName = await _getGroupName(groupID); final groupName = await _getGroupName(groupID);
_needUpdate = NeedUpdate(groupID, UpdateType.kickedFromGroup, groupName); _needUpdate = NeedUpdate(groupID, UpdateType.kickedFromGroup, groupName);
notifyListeners(); notifyListeners();
}
}, },
onMemberEnter: (String groupID, List<V2TimGroupMemberInfo> memberList) { onMemberEnter: (String groupID, List<V2TimGroupMemberInfo> memberList) {
_needUpdate = NeedUpdate(groupID, UpdateType.memberList, ""); _needUpdate = NeedUpdate(groupID, UpdateType.memberList, "");
@ -59,6 +61,12 @@ class TUIGroupListenerModel extends ChangeNotifier {
}, },
onGroupInfoChanged: (groupID, changeInfos) { onGroupInfoChanged: (groupID, changeInfos) {
_needUpdate = NeedUpdate(groupID, UpdateType.groupInfo, ""); _needUpdate = NeedUpdate(groupID, UpdateType.groupInfo, "");
for (V2TimGroupChangeInfo info in changeInfos) {
if (info.type == GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER) {
_needUpdate!.groupInfoSubType = GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER;
_needUpdate!.ownerID = info.value;
}
}
notifyListeners(); notifyListeners();
}, },
onReceiveJoinApplication: onReceiveJoinApplication:

View File

@ -5,13 +5,12 @@ import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import
import 'package:flutter/foundation.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/chat_life_cycle.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/chat_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_model_tools.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_model_tools.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
@ -33,6 +32,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>(); final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>(); final TUIChatModelTools tools = serviceLocator<TUIChatModelTools>();
final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>(); final TUISelfInfoViewModel selfModel = serviceLocator<TUISelfInfoViewModel>();
final TUIConversationViewModel conversationViewModel = serviceLocator<TUIConversationViewModel>();
final _uuid = const Uuid(); final _uuid = const Uuid();
ChatLifeCycle? lifeCycle; ChatLifeCycle? lifeCycle;
@ -45,7 +45,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
bool haveMoreLatestData = false; bool haveMoreLatestData = false;
String _currentPlayedMsgId = ""; String _currentPlayedMsgId = "";
GroupReceiptAllowType? _groupType; GroupReceiptAllowType? _groupType;
List<V2TimMessage> _multiSelectedMessageList = []; // List<V2TimMessage> _multiSelectedMessageList = [];
Map<String, bool> _selectedPositions = {};
V2TimMessage? _repliedMessage; V2TimMessage? _repliedMessage;
String _jumpMsgID = ""; String _jumpMsgID = "";
bool _isGroupExist = true; bool _isGroupExist = true;
@ -68,7 +69,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
String? _groupID; String? _groupID;
Map<String, String> get groupUserShowName => _groupUserShowName; Map<String, String> get groupUserShowName => _groupUserShowName;
final List<String> _sendingMessageIDList = []; // value bool
final Map<String, bool> _sendingMessageIDMap = {};
Map<String, V2TimMessage> _readReceiptMap = {};
set groupUserShowName(Map<String, String> value) { set groupUserShowName(Map<String, String> value) {
_groupUserShowName = value; _groupUserShowName = value;
@ -124,11 +127,35 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
_notify(); _notify();
} }
List<V2TimMessage> get multiSelectedMessageList => _multiSelectedMessageList; List<V2TimMessage> getSelectedMessageList() {
List<V2TimMessage> selectList = [];
if (_selectedPositions.isEmpty) {
return selectList;
}
set multiSelectedMessageList(List<V2TimMessage> value) { List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
_multiSelectedMessageList = value; for (var v2TimMessage in currentHistoryMsgList) {
_notify(); if (_selectedPositions.containsKey(v2TimMessage.msgID) && _selectedPositions[v2TimMessage.msgID]!) {
selectList.add(v2TimMessage);
}
}
return selectList.reversed.toList();
}
List<String> getSelectedMessageIDList() {
List<String> selectList = [];
if (_selectedPositions.isEmpty) {
return selectList;
}
for (String msgID in _selectedPositions.keys) {
if (_selectedPositions[msgID]!) {
selectList.add(msgID);
}
}
return selectList;
} }
V2TimMessage? get repliedMessage => _repliedMessage; V2TimMessage? get repliedMessage => _repliedMessage;
@ -233,7 +260,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.setMessageListPosition( globalModel.setMessageListPosition(
conversationID, HistoryMessagePosition.bottom); conversationID, HistoryMessagePosition.bottom);
globalModel.setChatConfig(chatConfig); globalModel.setChatConfig(chatConfig);
globalModel.clearRecivedNewMessageCount(); globalModel.clearReceivedNewMessageCount();
if (conversationType == ConvType.group) { if (conversationType == ConvType.group) {
_groupID = groupID; _groupID = groupID;
@ -290,7 +317,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
required int seq, required int seq,
}) async { }) async {
List<V2TimMessage> msgList = []; List<V2TimMessage> msgList = [];
haveMoreData = false; bool tempHaveMoreData = false;
final previousResponse = final previousResponse =
await _messageService.getHistoryMessageListWithComplete( await _messageService.getHistoryMessageListWithComplete(
@ -300,7 +327,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
groupID: conversationType == ConvType.group ? conversationID : null, groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgSeq: max(seq, 0)); lastMsgSeq: max(seq, 0));
msgList = previousResponse?.messageList ?? []; msgList = previousResponse?.messageList ?? [];
haveMoreData = !(previousResponse?.isFinished ?? false); tempHaveMoreData = !(previousResponse?.isFinished ?? false);
haveMoreLatestData = true; haveMoreLatestData = true;
globalModel.setMessageListPosition( globalModel.setMessageListPosition(
conversationID, HistoryMessagePosition.notShowLatest); conversationID, HistoryMessagePosition.notShowLatest);
@ -318,15 +345,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.setMessageList(conversationID, msgList, globalModel.setMessageList(conversationID, msgList,
needResetNewMessageCount: false); needResetNewMessageCount: false);
if (chatConfig.isShowGroupReadingStatus && if (chatConfig.isShowReadingStatus) {
conversationType == ConvType.group) {
_getMsgReadReceipt(msgList); _getMsgReadReceipt(msgList);
} }
if (chatConfig.isReportGroupReadingStatus &&
conversationType == ConvType.group) {
_setMsgReadReceipt(msgList);
}
haveMoreData = tempHaveMoreData;
return haveMoreData; return haveMoreData;
} }
@ -339,10 +362,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
LoadDirection direction = LoadDirection.previous, LoadDirection direction = LoadDirection.previous,
}) async { }) async {
try { try {
bool tempHaveMoreData = false;
// //
direction == LoadDirection.latest direction == LoadDirection.latest
? haveMoreLatestData = false ? haveMoreLatestData = false
: haveMoreData = false; : tempHaveMoreData = false;
// //
final currentRecordList = globalModel.messageListMap[conversationID]; final currentRecordList = globalModel.messageListMap[conversationID];
@ -368,8 +393,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
if (direction == LoadDirection.latest) { if (direction == LoadDirection.latest) {
haveMoreLatestData = !response.isFinished; haveMoreLatestData = !response.isFinished;
} else { } else {
haveMoreData = !response.isFinished; tempHaveMoreData = !response.isFinished;
} }
_notify(); _notify();
// lastMsgID判断是否为分页加载 // lastMsgID判断是否为分页加载
@ -380,7 +406,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
// //
if (direction == LoadDirection.latest) { if (direction == LoadDirection.latest) {
globalModel.receivedNewMessageCount = globalModel.receivedNewMessageCount =
globalModel.receivedMessageListCount + messageList.length; globalModel.receivedNewMessageCount + messageList.length;
messageList = messageList.reversed.toList(); messageList = messageList.reversed.toList();
newList = _combineMessageList(messageList, currentRecordList); newList = _combineMessageList(messageList, currentRecordList);
} else { } else {
@ -413,16 +439,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
// //
if (chatConfig.isShowGroupReadingStatus && if (chatConfig.isShowReadingStatus && response.messageList.isNotEmpty) {
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_getMsgReadReceipt(response.messageList); _getMsgReadReceipt(response.messageList);
} }
if (chatConfig.isReportGroupReadingStatus &&
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_setMsgReadReceipt(response.messageList);
}
// //
if (direction == LoadDirection.latest && !haveMoreLatestData) { if (direction == LoadDirection.latest && !haveMoreLatestData) {
@ -431,6 +450,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
_notify(); _notify();
haveMoreData = tempHaveMoreData;
return haveMoreData; return haveMoreData;
} catch (e) { } catch (e) {
// ignore: avoid_print // ignore: avoid_print
@ -458,7 +478,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
_getMsgReadReceipt(List<V2TimMessage> message) async { _getMsgReadReceipt(List<V2TimMessage> message) async {
final msgID = message final msgID = message
.where((e) => (e.isSelf ?? true) && (e.needReadReceipt ?? false)) .where((e) => (e.isSelf ?? true) && (e.needReadReceipt ?? false) && (e.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC))
.map((e) => e.msgID ?? '') .map((e) => e.msgID ?? '')
.toList(); .toList();
if (msgID.isNotEmpty) { if (msgID.isNotEmpty) {
@ -491,9 +511,21 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
msgID: message.msgID!, localCustomData: message.localCustomData ?? ""); msgID: message.msgID!, localCustomData: message.localCustomData ?? "");
} }
_setMsgReadReceipt(List<V2TimMessage> message) async { addToMessageReadReceiptList(V2TimMessage message) {
if (chatConfig.isShowReadingStatus) {
if (message.msgID != null) {
_readReceiptMap[message.msgID!] = message;
}
Future.delayed(const Duration(milliseconds: 200), () {
_setMsgReadReceipt(_readReceiptMap.values.toList());
});
}
}
_setMsgReadReceipt(List<V2TimMessage> messageList) async {
final msgIDList = List<String>.empty(growable: true); final msgIDList = List<String>.empty(growable: true);
for (var item in message) { for (var item in messageList) {
final isSelf = item.isSelf ?? true; final isSelf = item.isSelf ?? true;
final needReadReceipt = item.needReadReceipt ?? false; final needReadReceipt = item.needReadReceipt ?? false;
final isRead = item.isRead ?? false; final isRead = item.isRead ?? false;
@ -514,7 +546,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
markMessageAsRead() async { markMessageAsRead() async {
globalModel.unreadCountForConversation = 0;
if (conversationType == ConvType.c2c) { if (conversationType == ConvType.c2c) {
return _messageService.markC2CMessageAsRead(userID: conversationID); return _messageService.markC2CMessageAsRead(userID: conversationID);
} }
@ -655,9 +686,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
if (convType == ConvType.group && _groupType == null) { if (convType == ConvType.group && _groupType == null) {
await loadGroupInfo(groupID); await loadGroupInfo(groupID);
} }
final oldGroupType = _groupType != null
? GroupReceptAllowType.values[_groupType!.index]
: null;
if (messageInfo != null) { if (messageInfo != null) {
setLoadingMessageMap(convID, messageInfo); setLoadingMessageMap(convID, messageInfo);
} }
@ -667,15 +695,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false, isExcludedFromUnreadCount: isExcludedFromUnreadCount ?? false,
id: id, id: id,
receiver: receiver, receiver: receiver,
needReadReceipt: needReadReceipt ?? needReadReceipt: needReadReceipt ?? chatConfig.isShowReadingStatus,
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(_groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID, groupID: groupID,
offlinePushInfo: offlinePushInfo, offlinePushInfo: offlinePushInfo,
onlineUserOnly: onlineUserOnly ?? false, onlineUserOnly: onlineUserOnly ?? false,
@ -708,7 +728,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
int getConversationUnreadCount() { int getConversationUnreadCount() {
return globalModel.unreadCountForConversation; return globalModel.unreadCountForTongue;
} }
Future<V2TimValueCallback<V2TimMessage>?> sendTextAtMessage( Future<V2TimValueCallback<V2TimMessage>?> sendTextAtMessage(
@ -871,9 +891,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
final V2TimMessage? messageInfo = textMessageInfo!.messageInfo; final V2TimMessage? messageInfo = textMessageInfo!.messageInfo;
final receiver = convType == ConvType.c2c ? convID : ''; final receiver = convType == ConvType.c2c ? convID : '';
final groupID = convType == ConvType.group ? convID : ''; final groupID = convType == ConvType.group ? convID : '';
final oldGroupType = _groupType != null
? GroupReceptAllowType.values[_groupType!.index]
: null;
if (messageInfo != null) { if (messageInfo != null) {
V2TimMessage messageInfoWithSender = V2TimMessage messageInfoWithSender =
tools.setUserInfoForMessage(messageInfo, textMessageInfo.id!); tools.setUserInfoForMessage(messageInfo, textMessageInfo.id!);
@ -909,14 +926,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
id: textMessageInfo.id as String, id: textMessageInfo.id as String,
offlinePushInfo: tools.buildMessagePushInfo( offlinePushInfo: tools.buildMessagePushInfo(
messageInfoWithSender, convID, convType), messageInfoWithSender, convID, convType),
needReadReceipt: chatConfig.isShowGroupReadingStatus && needReadReceipt: chatConfig.isShowReadingStatus,
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(_groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null &&
chatConfig.groupReadReceiptPermisionList!
.contains(oldGroupType))),
groupID: groupID, groupID: groupID,
receiver: receiver); receiver: receiver);
_notify(); _notify();
@ -1135,15 +1145,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
sendForwardMessage({ sendForwardMessage({
required List<V2TimConversation> conversationList, required List<V2TimConversation> conversationList,
}) async { }) async {
final selectedMessages = List.from(_multiSelectedMessageList); final selectedMessages = getSelectedMessageList();
if (conversationType == ConvType.c2c) {
selectedMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
} else if (conversationType == ConvType.group) {
selectedMessages.sort((a, b) => a.seq.compareTo(b.seq));
}
for (var conversation in conversationList) { for (var conversation in conversationList) {
final convID = conversation.groupID ?? conversation.userID ?? ""; final convID = conversation.groupID ?? conversation.userID ?? "";
final convType = conversation.type; final convType = conversation.type;
List<V2TimMessage> currentHistoryMsgList = globalModel.messageListMap[conversationID] ?? [];
for (var message in selectedMessages) { 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; final messageInfo = forwardMessageInfo!.messageInfo;
@ -1151,6 +1157,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
tools.setUserInfoForMessage(messageInfo, forwardMessageInfo.id); tools.setUserInfoForMessage(messageInfo, forwardMessageInfo.id);
messageInfo.status = MessageStatus.V2TIM_MSG_STATUS_SENDING; messageInfo.status = MessageStatus.V2TIM_MSG_STATUS_SENDING;
addSendingMessageID(messageInfo.id); addSendingMessageID(messageInfo.id);
//
if (convID == conversationID) {
if (globalModel.getMessageListPosition(convID) != HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
messageInfo,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
_notify();
}
}
await Future.delayed(Duration(milliseconds: 100), () { await Future.delayed(Duration(milliseconds: 100), () {
_sendMessage( _sendMessage(
id: forwardMessageInfo.id!, id: forwardMessageInfo.id!,
@ -1174,13 +1191,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
required List<String> abstractList, required List<String> abstractList,
required BuildContext context, required BuildContext context,
}) async { }) async {
final List<String> msgIDList = _multiSelectedMessageList final List<String> msgIDList = getSelectedMessageList()
.map((e) => e.msgID ?? "") .map((e) => e.msgID ?? "")
.where((element) => element != "") .where((element) => element != "")
.toList(); .toList();
for (var conversation in conversationList) { for (var conversation in conversationList) {
final convID = conversation.groupID ?? conversation.userID ?? ""; final convID = conversation.groupID ?? conversation.userID ?? "";
final convType = conversation.type; final convType = conversation.type;
List<V2TimMessage> currentHistoryMsgList = globalModel.messageListMap[conversationID] ?? [];
final mergerMessageInfo = await _messageService.createMergerMessage( final mergerMessageInfo = await _messageService.createMergerMessage(
msgIDList: msgIDList, msgIDList: msgIDList,
title: title, title: title,
@ -1191,6 +1209,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
tools.setUserInfoForMessage(messageInfo, mergerMessageInfo.id); tools.setUserInfoForMessage(messageInfo, mergerMessageInfo.id);
messageInfo.status = MessageStatus.V2TIM_MSG_STATUS_SENDING; messageInfo.status = MessageStatus.V2TIM_MSG_STATUS_SENDING;
addSendingMessageID(messageInfo.id); addSendingMessageID(messageInfo.id);
//
if (convID == conversationID) {
if (globalModel.getMessageListPosition(convID) != HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
messageInfo,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
_notify();
}
}
_sendMessage( _sendMessage(
id: mergerMessageInfo.id!, id: mergerMessageInfo.id!,
convID: convID, convID: convID,
@ -1215,24 +1244,34 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null; return null;
} }
int messageIndex = currentHistoryMsgList.indexWhere((element) => element.msgID == message.msgID); currentHistoryMsgList.removeWhere((element) => element.msgID == message.msgID);
if (messageIndex != -1) { message.status = MessageStatus.V2TIM_MSG_STATUS_SENDING;
// sending
currentHistoryMsgList[messageIndex].status = MessageStatus.V2TIM_MSG_STATUS_SENDING;
addSendingMessageID(message.msgID); addSendingMessageID(message.msgID);
globalModel.setMessageList(convID, currentHistoryMsgList); globalModel.setMessageList(convID, currentHistoryMsgList);
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
message,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
_notify();
}
// //
final res = await _messageService.reSendMessage( final res = await _messageService.reSendMessage(
msgID: message.msgID ?? "", onlineUserOnly: false); msgID: message.msgID ?? "", onlineUserOnly: false);
removeSendingMessageID(message.msgID ?? ""); removeSendingMessageID(message.msgID ?? "");
final messageInfo = res.data; if (globalModel.getMessageListPosition(conversationID) !=
// HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList[messageIndex] = messageInfo!; globalModel.updateMessage(
globalModel.setMessageList(convID, currentHistoryMsgList); res, convID, message.msgID!, convType, groupType, setInputField);
return res;
} }
return null; if (lifeCycle?.messageDidSend != null) {
lifeCycle!.messageDidSend(res);
}
return res;
} }
Future<V2TimValueCallback<V2TimMessage>?> sendTextMessage( Future<V2TimValueCallback<V2TimMessage>?> sendTextMessage(
@ -1322,8 +1361,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return; return;
} }
final messageList = getOriginMessageList(); final messageList = getOriginMessageList();
final res = await _messageService.deleteMessageFromLocalStorage( final res = await _messageService.deleteMessages(
msgID: msgID, webMessageInstance: webMessageInstance); msgIDs: [msgID], webMessageInstanceList: [webMessageInstance]);
if (res.code == 0) { if (res.code == 0) {
messageList.removeWhere((element) { messageList.removeWhere((element) {
return element.msgID == msgID || (id != null && element.id == id); return element.msgID == msgID || (id != null && element.id == id);
@ -1368,26 +1407,18 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return res; return res;
} }
addToMultiSelectedMessageList(V2TimMessage message) { setMessageItemChecked(V2TimMessage message, bool isChecked) {
_multiSelectedMessageList.add(message); if (message.msgID != null) {
_notify(); _selectedPositions[message.msgID!] = isChecked;
} }
removeFromMultiSelectedMessageList(V2TimMessage message) {
_multiSelectedMessageList.remove(message);
_notify(); _notify();
} }
deleteSelectedMsg() async { deleteSelectedMsg() async {
List<V2TimMessage> messageList = getOriginMessageList(); List<V2TimMessage> messageList = getOriginMessageList();
final msgIDs = _multiSelectedMessageList final msgIDs = getSelectedMessageIDList();
.map((e) => e.msgID ?? "") final webMessageInstanceList = getSelectedMessageIDList();
.where((element) => element != "")
.toList();
final webMessageInstanceList = _multiSelectedMessageList
.map((e) => e.messageFromWeb)
.where((element) => element != null)
.toList();
final res = await _messageService.deleteMessages( final res = await _messageService.deleteMessages(
msgIDs: msgIDs, webMessageInstanceList: webMessageInstanceList); msgIDs: msgIDs, webMessageInstanceList: webMessageInstanceList);
@ -1395,14 +1426,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
for (var msgID in msgIDs) { for (var msgID in msgIDs) {
messageList.removeWhere((element) => element.msgID == msgID); messageList.removeWhere((element) => element.msgID == msgID);
} }
globalModel.setMessageList(conversationID, messageList); globalModel.setMessageList(conversationID, messageList, isDeleteMsg: true);
} }
} }
updateMultiSelectStatus(bool isSelect) { updateMultiSelectStatus(bool isSelect) {
_isMultiSelect = isSelect; _isMultiSelect = isSelect;
if (!isSelect) { if (!isSelect) {
_multiSelectedMessageList.clear(); _selectedPositions.clear();
} }
_notify(); _notify();
} }
@ -1440,6 +1471,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
} }
showLatestUnread() { showLatestUnread() {
globalModel.unreadCountForTongue = 0;
markMessageAsRead(); markMessageAsRead();
globalModel.setMessageListPosition( globalModel.setMessageListPosition(
conversationID, HistoryMessagePosition.bottom); conversationID, HistoryMessagePosition.bottom);
@ -1448,25 +1480,56 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
// id msgID(id 使 msgID) // id msgID(id 使 msgID)
void addSendingMessageID(String? id) { void addSendingMessageID(String? id) {
if (id?.isNotEmpty == true) { if (id?.isNotEmpty == true) {
_sendingMessageIDList.add(id!); _sendingMessageIDMap[id!] = false;
} }
} }
// id msgID(id 使 msgID) // id msgID(id 使 msgID)
void removeSendingMessageID(String id) { void removeSendingMessageID(String id) {
bool hasID = _sendingMessageIDList.contains(id); _sendingMessageIDMap.remove(id);
if (hasID) { }
_sendingMessageIDList.remove(id);
//
bool? hasDelayedRenderSendingStatus(String id) {
if (_sendingMessageIDMap.containsKey(id)) {
return _sendingMessageIDMap[id];
}
print("sending test, hasDelayedRenderSendingStatus false:${id}");
return true;
}
//
void setDelayedRenderSendingStatus(String id) {
if (_sendingMessageIDMap.containsKey(id)) {
_sendingMessageIDMap[id] = true;
} }
} }
// id msgID(id 使 msgID) bool isVoteMessage(V2TimMessage message) {
bool hasSendingMessageID(String id) { bool isVote = false;
return _sendingMessageIDList.contains(id); V2TimCustomElem? custom = message.customElem;
if (custom != null) {
String? data = custom.data;
if (data != null && data.isNotEmpty) {
try {
Map<String, dynamic> mapData = json.decode(data);
if (mapData["businessID"] == "group_poll") {
isVote = true;
}
} catch (err) {
// err
}
}
}
return isVote;
} }
@override @override
void dispose() { void dispose() {
markMessageAsRead();
globalModel.unreadCountForTongue = 0;
globalModel.clearCurrentConversation(); globalModel.clearCurrentConversation();
_isInit = false; _isInit = false;
super.dispose(); super.dispose();

View File

@ -25,7 +25,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
List<V2TimGroupMemberFullInfo?>? _groupMemberList; List<V2TimGroupMemberFullInfo?>? _groupMemberList;
String _groupMemberListSeq = "0"; String _groupMemberListSeq = "0";
V2TimGroupInfo? _groupInfo; V2TimGroupInfo? _groupInfo;
Function(String userID, TapDownDetails? tapDetails)? onClickUser; Function(V2TimGroupMemberFullInfo groupMemberFullInfo, TapDownDetails? tapDetails)? onClickUser;
GroupProfileLifeCycle? get lifeCycle => _lifeCycle; GroupProfileLifeCycle? get lifeCycle => _lifeCycle;
@ -233,6 +233,33 @@ class TUIGroupProfileModel extends ChangeNotifier {
return res; return res;
} }
void onOwnerChanged(String? userID) {
if (userID == null) {
return;
}
//
final preOwnerIndex = _groupMemberList!.indexWhere((e) => e!.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER);
if (preOwnerIndex != -1) {
final preOwnerElem = _groupMemberList![preOwnerIndex];
preOwnerElem?.role = GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER;
print("preOwnerUserID: ${preOwnerElem?.userID}");
}
//
final targetIndex = _groupMemberList!.indexWhere((e) => e!.userID == userID);
if (targetIndex != -1) {
final targetElem = _groupMemberList![targetIndex];
targetElem?.role = GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER;
_groupMemberList![targetIndex] = targetElem;
print("newOwnerUserID: ${targetElem?.userID}");
}
notifyListeners();
}
bool canInviteMember() { bool canInviteMember() {
final groupType = _groupInfo?.groupType; final groupType = _groupInfo?.groupType;
return groupType == GroupType.Work || groupType == "Private"; return groupType == GroupType.Work || groupType == "Private";

View File

@ -143,7 +143,7 @@ class TUIProfileViewModel extends ChangeNotifier {
} }
} }
Future<V2TimFriendOperationResult?> deleteFriend(String userID) async { Future<V2TimFriendOperationResult?> deleteFriend(String userID, {bool needUpdateData = true}) async {
if (_lifeCycle?.shouldDeleteFriend != null && if (_lifeCycle?.shouldDeleteFriend != null &&
await _lifeCycle!.shouldDeleteFriend(userID) == false) { await _lifeCycle!.shouldDeleteFriend(userID) == false) {
return null; return null;
@ -152,9 +152,13 @@ class TUIProfileViewModel extends ChangeNotifier {
userIDList: [userID], userIDList: [userID],
deleteType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH); deleteType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
if (res != null) { if (res != null) {
_conversationService.deleteConversation(conversationID: "c2c_$userID");
if (needUpdateData) {
loadData(userID: userID); loadData(userID: userID);
}
return res.first; return res.first;
} }
return null; return null;
} }
@ -171,49 +175,6 @@ class TUIProfileViewModel extends ChangeNotifier {
return res; return res;
} }
// 1 2
Future<V2TimCallback> updateGender(int gender) async {
final res = await _coreServices.setSelfInfo(
userFullInfo: V2TimUserFullInfo.fromJson(
{"gender": gender},
),
);
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.gender = gender;
notifyListeners();
}
return res;
}
Future<V2TimCallback> updateNickName(String nickName) async {
final res = await _coreServices.setSelfInfo(
userFullInfo: V2TimUserFullInfo.fromJson(
{"nickName": nickName},
),
);
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.nickName = nickName;
notifyListeners();
}
return res;
}
Future<V2TimCallback> updateSelfSignature(String selfSignature) async {
final res = await _coreServices.setSelfInfo(
userFullInfo: V2TimUserFullInfo.fromJson(
{"selfSignature": selfSignature},
),
);
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.selfSignature = selfSignature;
notifyListeners();
}
return res;
}
Future<V2TimFriendOperationResult?> addFriend(String userID) async { Future<V2TimFriendOperationResult?> addFriend(String userID) async {
if (_lifeCycle?.shouldAddFriend != null && if (_lifeCycle?.shouldAddFriend != null &&
await _lifeCycle!.shouldAddFriend(userID) == false) { await _lifeCycle!.shouldAddFriend(userID) == false) {
@ -225,6 +186,7 @@ class TUIProfileViewModel extends ChangeNotifier {
loadData(userID: userID); loadData(userID: userID);
return res.data; return res.data;
} }
return null; return null;
} }
@ -291,13 +253,14 @@ class TUIProfileViewModel extends ChangeNotifier {
newSelfInfo, newSelfInfo,
), ),
); );
if (res.code == 0) { if (res.code == 0) {
newSelfInfo.forEach((key, value) { newSelfInfo.forEach((key, value) {
updateUserInfo(key, value); updateUserInfo(key, value);
}); });
notifyListeners(); notifyListeners();
} }
return res; return res;
} }
} }

View File

@ -50,10 +50,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
String localMsgIDListKey = "TUIKit_conversation_list"; String localMsgIDListKey = "TUIKit_conversation_list";
late V2TimAdvancedMsgListener advancedMsgListener; late V2TimAdvancedMsgListener advancedMsgListener;
int _unreadCountForConversation = 0; int _unreadCountForTongue = 0;
// use for generate a new sliver list to show received message list // use for generate a new sliver list to show received message list
int _receivedNewMessageCount = 0; int _receivedNewMessageCount = 0;
final List<V2TimMessage> _receivedUnreadMessageList = [];
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig(); TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
List<V2TimGroupApplication>? _groupApplicationList; List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder; String Function(V2TimMessage message)? _abstractMessageBuilder;
@ -170,15 +172,23 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return _totalUnreadCount; return _totalUnreadCount;
} }
int get receivedMessageListCount { set totalUnReadCount(int newValue) {
return _receivedNewMessageCount; _totalUnreadCount = newValue;
notifyListeners();
} }
int get receivedNewMessageCount => _receivedNewMessageCount;
set receivedNewMessageCount(int value) { set receivedNewMessageCount(int value) {
_receivedNewMessageCount = value; _receivedNewMessageCount = value;
} }
int get unreadCountForConversation => _unreadCountForConversation; int get unreadCountForTongue => _unreadCountForTongue;
set unreadCountForTongue(int value) {
_unreadCountForTongue = value;
notifyListeners();
}
List<V2TimGroupApplication> get groupApplicationList => _groupApplicationList ?? []; List<V2TimGroupApplication> get groupApplicationList => _groupApplicationList ?? [];
@ -201,9 +211,15 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (_currentConversationList.isNotEmpty) { if (_currentConversationList.isNotEmpty) {
_currentConversationList.removeLast(); _currentConversationList.removeLast();
} }
_receivedUnreadMessageList.clear();
// notifyListeners(); // notifyListeners();
} }
void removeMessageList(String conversationID) {
_messageListMap.remove(conversationID);
}
V2TimMessageReceipt? getMessageReadReceipt(String msgID) { V2TimMessageReceipt? getMessageReadReceipt(String msgID) {
return messageReadReceiptMap[msgID]; return messageReadReceiptMap[msgID];
} }
@ -250,16 +266,6 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_groupApplicationList = value; _groupApplicationList = value;
} }
set unreadCountForConversation(int value) {
_unreadCountForConversation = value;
notifyListeners();
}
set totalUnReadCount(int newValue) {
_totalUnreadCount = newValue;
notifyListeners();
}
setChatConfig(TIMUIKitChatConfig config) { setChatConfig(TIMUIKitChatConfig config) {
chatConfig = config; chatConfig = config;
} }
@ -324,7 +330,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners(); notifyListeners();
} }
clearRecivedNewMessageCount() { clearReceivedNewMessageCount() {
_receivedNewMessageCount = 0; _receivedNewMessageCount = 0;
} }
@ -523,12 +529,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_checkFromUserisActive(msgComing); _checkFromUserisActive(msgComing);
final convType = TencentUtils.checkString(newMsg.groupID) != null ? ConvType.group : ConvType.c2c; final convType = TencentUtils.checkString(newMsg.groupID) != null ? ConvType.group : ConvType.c2c;
if (convID != null && convID == currentSelectedConv) { if (convID != null && convID == currentSelectedConv) {
final position = getMessageListPosition(convID); // when receive new message in the current chat page, we need to mark the message as read.
if (position == HistoryMessagePosition.notShowLatest) {
return;
}
if (position == HistoryMessagePosition.bottom && unreadCountForConversation == 0) {
_unreadCountForConversation = 0;
if (chatConfig.isAutoReportRead) { if (chatConfig.isAutoReportRead) {
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
markMessageAsRead( markMessageAsRead(
@ -537,22 +538,22 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
); );
}); });
} }
final position = getMessageListPosition(convID);
if (position == HistoryMessagePosition.notShowLatest) {
return;
}
if (position == HistoryMessagePosition.bottom && unreadCountForTongue == 0) {
_unreadCountForTongue = 0;
_receivedNewMessageCount = 0; _receivedNewMessageCount = 0;
final tempCurrentMsgList = _messageListMap[convID] ?? []; final tempCurrentMsgList = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...tempCurrentMsgList]; _messageListMap[convID] = [newMsg, ...tempCurrentMsgList];
notifyListeners(); notifyListeners();
final messageID = newMsg.msgID;
final needReadReceipt = newMsg.needReadReceipt ?? false;
if (needReadReceipt && messageID != null && msgComing.groupID != null && chatConfig.isReportGroupReadingStatus && chatConfig.isAutoReportRead) {
// only group message send message read receipt
Future.delayed(const Duration(seconds: 1), () {
sendMessageReadReceipts([messageID]);
});
}
} else { } else {
if (convID == currentSelectedConv) { if (convID == currentSelectedConv) {
unreadCountForConversation++; unreadCountForTongue++;
_receivedNewMessageCount++; _receivedNewMessageCount++;
_receivedUnreadMessageList.add(newMsg);
final currentMsg = _messageListMap[convID] ?? []; final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg]; _messageListMap[convID] = [newMsg, ...currentMsg];
notifyListeners(); notifyListeners();
@ -565,11 +566,6 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
} }
} }
sendMessageReadReceipts(List<String> messageIDList) async {
final res = await _messageService.sendMessageReadReceipts(messageIDList: messageIDList);
return res;
}
onMessageRevoked(String msgID, [String? convID]) { onMessageRevoked(String msgID, [String? convID]) {
final activeMessageList = _messageListMap[convID ?? currentSelectedConv]; final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
if (activeMessageList != null) { if (activeMessageList != null) {
@ -581,8 +577,23 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED; targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
targetItem.id = DateTime.now().millisecondsSinceEpoch.toString(); targetItem.id = DateTime.now().millisecondsSinceEpoch.toString();
activeMessageList[findeIndex] = targetItem; activeMessageList[findeIndex] = targetItem;
bool isUnreadMessage = _receivedUnreadMessageList.any((element) => element.msgID == msgID);
if (!(targetItem.isSelf ?? true) && isUnreadMessage) {
if (_unreadCountForTongue > 0) {
if (_unreadCountForTongue > 0) {
_unreadCountForTongue--;
}
if (_receivedNewMessageCount > 0) {
_receivedNewMessageCount--;
}
_receivedUnreadMessageList.removeWhere((element) => element.msgID == targetItem.msgID);
} }
} }
}
}
_messageListMap[convID ?? currentSelectedConv] = activeMessageList; _messageListMap[convID ?? currentSelectedConv] = activeMessageList;
notifyListeners(); notifyListeners();
} }
@ -884,15 +895,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}) async { }) async {
String receiver = convType == ConvType.c2c ? convID : ''; String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : ''; String groupID = convType == ConvType.group ? convID : '';
final oldGroupType = groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
final sendMsgRes = await _messageService.sendMessage( final sendMsgRes = await _messageService.sendMessage(
id: id, id: id,
receiver: receiver, receiver: receiver,
needReadReceipt: needReadReceipt ?? needReadReceipt: needReadReceipt ?? chatConfig.isShowReadingStatus,
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null && chatConfig.groupReadReceiptPermissionList!.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && chatConfig.groupReadReceiptPermisionList!.contains(oldGroupType))),
groupID: groupID, groupID: groupID,
priority: priority, priority: priority,
localCustomData: localCustomData, localCustomData: localCustomData,
@ -916,21 +922,25 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return sendMsgRes; return sendMsgRes;
} }
void setMessageList(String conversationID, List<V2TimMessage> messageList, {bool needResetNewMessageCount = true}) { void setMessageList(String conversationID, List<V2TimMessage> messageList, {bool needResetNewMessageCount = true, bool isDeleteMsg = false}) {
_messageListMap[conversationID] = messageList; _messageListMap[conversationID] = messageList;
if (needResetNewMessageCount) { if (needResetNewMessageCount) {
_receivedNewMessageCount = 0; _receivedNewMessageCount = 0;
} }
if (isDeleteMsg) {
HistoryMessagePosition position = getMessageListPosition(conversationID);
if (position == HistoryMessagePosition.awayTwoScreen) {
_historyMessagePositionMap[conversationID] = HistoryMessagePosition.notShowLatest;
}
}
notifyListeners(); notifyListeners();
} }
updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes, String convID, String id, ConvType convType, GroupReceiptAllowType? groupType, ValueChanged<String>? setInputField) { updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes, String convID, String id, ConvType convType, GroupReceiptAllowType? groupType, ValueChanged<String>? setInputField) {
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? []; List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
final V2TimMessage sendMsgResData = sendMsgRes.data as V2TimMessage; final V2TimMessage sendMsgResData = sendMsgRes.data as V2TimMessage;
if ([80001, 80002].contains(sendMsgRes.code) && sendMsgRes.data?.textElem?.text != null && setInputField != null) {
setInputField(sendMsgRes.data!.textElem!.text ?? "");
}
final findIdIndex = currentHistoryMsgList.indexWhere((element) => element.id == id); final findIdIndex = currentHistoryMsgList.indexWhere((element) => element.id == id);
final targetIndex = findIdIndex == -1 ? currentHistoryMsgList.indexWhere((element) => element.msgID == sendMsgResData.msgID) : findIdIndex; final targetIndex = findIdIndex == -1 ? currentHistoryMsgList.indexWhere((element) => element.msgID == sendMsgResData.msgID) : findIdIndex;
if (targetIndex != -1) { if (targetIndex != -1) {
@ -941,12 +951,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (loadingMessage[convID] != null && loadingMessage[convID]!.isNotEmpty) { if (loadingMessage[convID] != null && loadingMessage[convID]!.isNotEmpty) {
loadingMessage[convID]!.removeWhere((element) => element.id == id); loadingMessage[convID]!.removeWhere((element) => element.id == id);
} }
final oldGroupType = groupType != null ? GroupReceptAllowType.values[groupType.index] : null; if (chatConfig.isShowReadingStatus && sendMsgRes.data?.msgID != null) {
if (chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
sendMsgRes.data?.msgID != null &&
((chatConfig.groupReadReceiptPermissionList != null && chatConfig.groupReadReceiptPermissionList!.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && chatConfig.groupReadReceiptPermisionList!.contains(oldGroupType)))) {
_messageReadReceiptMap[sendMsgRes.data!.msgID!] = V2TimMessageReceipt(timestamp: 0, userID: "", readCount: 0); _messageReadReceiptMap[sendMsgRes.data!.msgID!] = V2TimMessageReceipt(timestamp: 0, userID: "", readCount: 0);
} }
_messageListMap[convID] = currentHistoryMsgList; _messageListMap[convID] = currentHistoryMsgList;

View File

@ -29,6 +29,9 @@ List<T> removeDuplicates<T>(List<T> list, bool Function(T first, T second) isEqu
} }
class TUIConversationViewModel extends ChangeNotifier { class TUIConversationViewModel extends ChangeNotifier {
static const String conversationC2CPrefix = "c2c_";
static const String conversationGroupPrefix = "group_";
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>(); final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
final ConversationService _conversationService = serviceLocator<ConversationService>(); final ConversationService _conversationService = serviceLocator<ConversationService>();
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>(); final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
@ -65,6 +68,10 @@ class TUIConversationViewModel extends ChangeNotifier {
return _conversationList; return _conversationList;
} }
V2TimConversation? getConversation(String conversationID) {
return _conversationList.firstWhere((element) => element?.conversationID == conversationID);
}
String? get scrollToConversation => _scrollToConversation; String? get scrollToConversation => _scrollToConversation;
set scrollToConversation(String? value) { set scrollToConversation(String? value) {
@ -124,6 +131,18 @@ class TUIConversationViewModel extends ChangeNotifier {
} }
}, onConversationDeleted:(List<String> conversationIDList) { }, onConversationDeleted:(List<String> conversationIDList) {
_onConversationDeleted(conversationIDList); _onConversationDeleted(conversationIDList);
for (var conversationID in conversationIDList) {
String resultID = "";
if (conversationID.startsWith(conversationC2CPrefix)) {
resultID = conversationID.replaceFirst(conversationC2CPrefix, "");
} else if (conversationID.startsWith(conversationGroupPrefix)) {
resultID = conversationID.replaceFirst(conversationGroupPrefix, "");
}
if (resultID != "") {
_chatGlobalModel.removeMessageList(resultID);
}
}
}); });
} }

View File

@ -171,6 +171,17 @@ class TUIFriendShipViewModel extends ChangeNotifier {
return; return;
} }
Future<bool> isFriend(String userID) async {
final List<V2TimFriendInfo> res = await _friendshipServices.getFriendList() ?? [];
for (V2TimFriendInfo info in res) {
if (info.userID == userID) {
return true;
}
}
return false;
}
Future<void> loadBlockListData() async { Future<void> loadBlockListData() async {
final blockListRes = await _friendshipServices.getBlackList(); final blockListRes = await _friendshipServices.getBlackList();
_blockList = blockListRes ?? []; _blockList = blockListRes ?? [];

View File

@ -1,6 +1,7 @@
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart'; import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/error_message_converter.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
class FriendshipServicesImpl implements FriendshipServices { class FriendshipServicesImpl implements FriendshipServices {
@ -77,17 +78,34 @@ class FriendshipServicesImpl implements FriendshipServices {
friendGroup: friendGroup, friendGroup: friendGroup,
addSource: addSource, addSource: addSource,
); );
if (result.code != 0 || if (result.code != 0) {
(result.code == 0 && _coreService.callOnCallback(TIMCallback(
result.data?.resultCode != 0 && type: TIMCallbackType.API_ERROR,
result.data?.resultCode != 30539 && errorMsg: result.desc,
result.data?.resultCode != 30515)) { errorCode: result.code,
infoRecommendText: TIM_t("好友添加失败"),
));
} else if (result.code == 0 && result.data?.resultCode != 0) {
String recommendText = "";
if (result.data != null && result.data!.resultCode != null) {
recommendText = ErrorMessageConverter.getErrorMessage(result.data!.resultCode!);
}
_coreService.callOnCallback(TIMCallback( _coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR, type: TIMCallbackType.API_ERROR,
errorMsg: result.code == 0 ? result.data?.resultInfo : result.desc, errorMsg: result.code == 0 ? result.data?.resultInfo : result.desc,
errorCode: result.code == 0 ? result.data?.resultCode : result.code, errorCode: result.code == 0 ? result.data?.resultCode : result.code,
infoRecommendText: recommendText,
));
} else {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
errorCode: result.code,
infoRecommendText: TIM_t("好友添加成功"),
)); ));
} }
return result; return result;
} }
@ -118,12 +136,18 @@ class FriendshipServicesImpl implements FriendshipServices {
.getFriendshipManager() .getFriendshipManager()
.deleteFromFriendList(userIDList: userIDList, deleteType: deleteType); .deleteFromFriendList(userIDList: userIDList, deleteType: deleteType);
if (res.code == 0) { if (res.code == 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,
errorCode: res.code,
infoRecommendText: TIM_t("好友删除成功")));
return res.data; return res.data;
} else { } else {
_coreService.callOnCallback(TIMCallback( _coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR, type: TIMCallbackType.API_ERROR,
errorMsg: res.desc, errorMsg: res.desc,
errorCode: res.code)); errorCode: res.code,
infoRecommendText: TIM_t("好友删除失败")));
return null; return null;
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart'; import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/error_message_converter.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/optimize_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/optimize_utils.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
@ -249,10 +250,12 @@ class GroupServicesImpl extends GroupServices {
final result = await TencentImSDKPlugin.v2TIMManager final result = await TencentImSDKPlugin.v2TIMManager
.joinGroup(groupID: groupID, message: message); .joinGroup(groupID: groupID, message: message);
if (result.code != 0) { if (result.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(result.code);
_coreService.callOnCallback(TIMCallback( _coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR, type: TIMCallbackType.API_ERROR,
errorMsg: result.desc, errorMsg: result.desc,
errorCode: result.code)); errorCode: result.code,
infoRecommendText: recommendText));
} }
return result; return result;
} }

View File

@ -3,9 +3,11 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart'; import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/error_message_converter.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
@ -271,10 +273,12 @@ class MessageServiceImpl extends MessageService {
.getMessageManager() .getMessageManager()
.reSendMessage(msgID: msgID, onlineUserOnly: onlineUserOnly ?? false); .reSendMessage(msgID: msgID, onlineUserOnly: onlineUserOnly ?? false);
if (res.code != 0) { if (res.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(res.code);
_coreService.callOnCallback(TIMCallback( _coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR, type: TIMCallbackType.API_ERROR,
errorMsg: res.desc, errorMsg: res.desc,
errorCode: res.code)); errorCode: res.code,
infoRecommendText: recommendText));
} }
return res; return res;
} }
@ -359,10 +363,12 @@ class MessageServiceImpl extends MessageService {
cloudCustomData: cloudCustomData, cloudCustomData: cloudCustomData,
); );
if (result.code != 0) { if (result.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(result.code);
_coreService.callOnCallback(TIMCallback( _coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR, type: TIMCallbackType.API_ERROR,
errorMsg: result.desc, errorMsg: result.desc,
errorCode: result.code)); errorCode: result.code,
infoRecommendText: recommendText));
} }
return result; return result;
} }
@ -471,7 +477,7 @@ class MessageServiceImpl extends MessageService {
return TencentImSDKPlugin.v2TIMManager return TencentImSDKPlugin.v2TIMManager
.getConversationManager() .getConversationManager()
.cleanConversationUnreadMessageCount( .cleanConversationUnreadMessageCount(
conversationID: "c2c_$userID", conversationID: "${TUIConversationViewModel.conversationC2CPrefix}$userID",
cleanTimestamp: 0, cleanTimestamp: 0,
cleanSequence: 0, cleanSequence: 0,
); );
@ -486,7 +492,7 @@ class MessageServiceImpl extends MessageService {
return TencentImSDKPlugin.v2TIMManager return TencentImSDKPlugin.v2TIMManager
.getConversationManager() .getConversationManager()
.cleanConversationUnreadMessageCount( .cleanConversationUnreadMessageCount(
conversationID: "group_$groupID", conversationID: "${TUIConversationViewModel.conversationGroupPrefix}$groupID",
cleanTimestamp: 0, cleanTimestamp: 0,
cleanSequence: 0, cleanSequence: 0,
); );

View File

@ -81,14 +81,28 @@ class TIMUIKitProfileController {
} }
Future<V2TimCallback> updateSelfSignature(String selfSignature) { Future<V2TimCallback> updateSelfSignature(String selfSignature) {
return model.updateSelfSignature(selfSignature); Map<String, dynamic> infoMap = {"selfSignature": selfSignature};
return model.updateSelfInfo(infoMap);
} }
Future<V2TimCallback> updateNickName(String nickName) { Future<V2TimCallback> updateNickName(String nickName) {
return model.updateNickName(nickName); Map<String, dynamic> infoMap = {"nickName": nickName};
return model.updateSelfInfo(infoMap);
} }
/// 1 2
Future<V2TimCallback> updateGender(int gender) { Future<V2TimCallback> updateGender(int gender) {
return model.updateGender(gender); Map<String, dynamic> infoMap = {"gender": gender};
return model.updateSelfInfo(infoMap);
}
Future<V2TimCallback> updateBirthday(int birthday) {
Map<String, dynamic> infoMap = {"birthday": birthday};
return model.updateSelfInfo(infoMap);
}
Future<V2TimCallback> updateAvatar(String url) {
Map<String, dynamic> infoMap = {"faceUrl" : url};
return model.updateSelfInfo(infoMap);
} }
} }

View File

@ -0,0 +1,20 @@
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class ErrorMessageConverter {
static Map<int, String> errorCodeMap = {
10007: TIM_t("操作权限不足"),
20007: TIM_t("发送单聊消息,被对方拉黑,禁止发送。"),
30010: TIM_t("您的好友数已达系统上限"),
30014: TIM_t("对方的好友数已达系统上限"),
30015: TIM_t("对方已是您的好友"),
30515: TIM_t("被加好友在自己的黑名单中"),
30516: TIM_t("对方已禁止加好友"),
30525: TIM_t("您已被被对方设置为黑名单"),
30539: TIM_t("等待好友审核同意"),
};
static String getErrorMessage(int code) {
return errorCodeMap[code] ?? "";
}
}

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
// import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_plugin_record_plus/const/play_state.dart'; import 'package:flutter_plugin_record_plus/const/play_state.dart';
import 'package:flutter_plugin_record_plus/const/response.dart'; import 'package:flutter_plugin_record_plus/const/response.dart';
import 'package:flutter_plugin_record_plus/index.dart'; import 'package:flutter_plugin_record_plus/index.dart';

View File

@ -27,6 +27,16 @@ class TimeAgo {
return (month.length == 1 ? '0' : '') + month + '/' + (date.length == 1 ? '0' : '') + date; return (month.length == 1 ? '0' : '') + month + '/' + (date.length == 1 ? '0' : '') + date;
} }
static String getMonth(DateTime dateTime) {
String month = dateTime.month.toString();
return (month.length == 1 ? '0' : '') + month;
}
static String getDay(DateTime dateTime) {
String day = dateTime.day.toString();
return (day.length == 1 ? '0' : '') + day;
}
String? getTimeStringForChat(int timeStamp) { String? getTimeStringForChat(int timeStamp) {
final DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000); final DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000);
final DateTime epochLimit = DateTime.utc(1971); final DateTime epochLimit = DateTime.utc(1971);

View File

@ -71,10 +71,6 @@ class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
if (checkFriend != null) { if (checkFriend != null) {
final res = checkFriend.first; final res = checkFriend.first;
if (res.resultCode == 0 && res.resultType != 0) { if (res.resultCode == 0 && res.resultType != 0) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("该用户已是好友"),
infoCode: 6660102));
widget.onTapAlreadyFriendsItem(userID); widget.onTapAlreadyFriendsItem(userID);
return; return;
} }

View File

@ -203,29 +203,12 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
return; return;
} }
final res = await _friendshipServices.addFriend( _friendshipServices.addFriend(
userID: userID, userID: userID,
addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH, addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
remark: remark, remark: remark,
addWording: addWording, addWording: addWording,
friendGroup: friendGroup); friendGroup: friendGroup);
if (res.code == 0 && res.data?.resultCode == 0) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友添加成功"),
infoCode: 6661202));
} else if (res.code == 0 && res.data?.resultCode == 30539) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友申请已发出"),
infoCode: 6661203));
} else if (res.code == 0 && res.data?.resultCode == 30515) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("当前用户在黑名单"),
infoCode: 6661204));
}
}, },
child: Text(TIM_t("发送"))), child: Text(TIM_t("发送"))),
) )

View File

@ -84,11 +84,11 @@ class _TIMUIKitHistoryMessageListTongueContainerState extends TIMUIKitState<TIMU
} }
} }
if ((widget.conversation.unreadCount ?? 0) > 20 && !isClickShowPrevious) { // if ((widget.conversation.unreadCount ?? 0) > 20 && !isClickShowPrevious) {
return MessageListTongueType.showPrevious; // return MessageListTongueType.showPrevious;
} // }
if (globalModel.unreadCountForConversation > 0) { if (globalModel.unreadCountForTongue > 0) {
return MessageListTongueType.showUnread; return MessageListTongueType.showUnread;
} }
@ -116,12 +116,12 @@ class _TIMUIKitHistoryMessageListTongueContainerState extends TIMUIKitState<TIMU
child: TIMUIKitHistoryMessageListTongue( child: TIMUIKitHistoryMessageListTongue(
previousCount: widget.conversation.unreadCount ?? 0, previousCount: widget.conversation.unreadCount ?? 0,
tongueItemBuilder: widget.tongueItemBuilder, tongueItemBuilder: widget.tongueItemBuilder,
unreadCount: globalModel.unreadCountForConversation, unreadCount: value.item2,
onClick: () async { onClick: () async {
if (groupAtInfoList != null && groupAtInfoList!.isNotEmpty) { if (groupAtInfoList != null && groupAtInfoList!.isNotEmpty) {
if (groupAtInfoList?.length == 1) { if (groupAtInfoList?.length == 1) {
widget.scrollToIndexBySeq(groupAtInfoList![0]!.seq); widget.scrollToIndexBySeq(groupAtInfoList![0]!.seq);
widget.model.markMessageAsRead();
setState(() { setState(() {
groupAtInfoList = []; groupAtInfoList = [];
isFinishJumpToAt = true; isFinishJumpToAt = true;
@ -146,7 +146,7 @@ class _TIMUIKitHistoryMessageListTongueContainerState extends TIMUIKitState<TIMU
// TODO: // TODO:
} }
// widget.model.loadListForSpecificMessage(seq: count); // widget.model.loadListForSpecificMessage(seq: count);
} else if (value.item1 == HistoryMessagePosition.awayTwoScreen || globalModel.unreadCountForConversation > 0) { } else if (value.item1 == HistoryMessagePosition.awayTwoScreen || value.item2 > 0) {
widget.model.showLatestUnread(); widget.model.showLatestUnread();
widget.scrollController.animateTo( widget.scrollController.animateTo(
widget.scrollController.position.minScrollExtent, widget.scrollController.position.minScrollExtent,
@ -162,9 +162,9 @@ class _TIMUIKitHistoryMessageListTongueContainerState extends TIMUIKitState<TIMU
); );
}, },
selector: (c, model) { selector: (c, model) {
final mesageListPosition = model.getMessageListPosition(widget.model.conversationID); final messageListPosition = model.getMessageListPosition(widget.model.conversationID);
final unreadCountForConversation = model.unreadCountForConversation; final unreadCountForConversation = model.unreadCountForTongue;
return Tuple2(mesageListPosition, unreadCountForConversation); return Tuple2(messageListPosition, unreadCountForConversation);
}, },
); );
} }

View File

@ -33,14 +33,14 @@ class TIMUIKitTongueItem extends TIMUIKitStatelessWidget {
Map<MessageListTongueType, String> textType(BuildContext context) { Map<MessageListTongueType, String> textType(BuildContext context) {
final option1 = unreadCount.toString(); final option1 = unreadCount.toString();
final option2 = atNum.toString(); final option2 = atNum.toString();
final option3 = previousCount.toString(); // final option3 = previousCount.toString();
final String atMeString = option2 != "" final String atMeString = option2 != ""
? TIM_t_para("有{{option2}}条@我消息", "$option2条@我消息")(option2: option2) ? TIM_t_para("有{{option2}}条@我消息", "$option2条@我消息")(option2: option2)
: TIM_t("有人@我"); : TIM_t("有人@我");
return { return {
MessageListTongueType.showPrevious: // MessageListTongueType.showPrevious:
TIM_t_para("{{option3}}条未读消息", "$option3条未读消息")(option3: option3), // TIM_t_para("{{option3}}条未读消息", "$option3条未读消息")(option3: option3),
MessageListTongueType.toLatest: TIM_t("回到最新位置"), MessageListTongueType.toLatest: TIM_t("回到最新位置"),
MessageListTongueType.showUnread: MessageListTongueType.showUnread:
TIM_t_para("{{option1}}条新消息", "$option1条新消息")(option1: option1), TIM_t_para("{{option1}}条新消息", "$option1条新消息")(option1: option1),

View File

@ -75,7 +75,7 @@ class TIMUIKitHistoryMessageList extends StatefulWidget {
final V2TimMessage? initFindingMsg; final V2TimMessage? initFindingMsg;
/// use for load more message /// use for load more message
final Future<void> Function(String?, LoadDirection direction, [int?]) onLoadMore; final Future<bool> Function(String?, LoadDirection direction, [int?, int?]) onLoadMore;
/// configuration for list view /// configuration for list view
final TIMUIKitHistoryMessageListConfig? mainHistoryListConfig; final TIMUIKitHistoryMessageListConfig? mainHistoryListConfig;
@ -111,6 +111,7 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
late TIMUIKitHistoryMessageListController _controller; late TIMUIKitHistoryMessageListController _controller;
late AutoScrollController _autoScrollController; late AutoScrollController _autoScrollController;
LoadingPlace loadingPlace = LoadingPlace.none; LoadingPlace loadingPlace = LoadingPlace.none;
bool maybeHaveMoreMessageForFind = true;
@override @override
void initState() { void initState() {
@ -165,7 +166,7 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("无法定位到原消息"), infoCode: 6660401)); onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("无法定位到原消息"), infoCode: 6660401));
} }
_onScrollToIndex(V2TimMessage targetMsg) { _onScrollToIndex(V2TimMessage targetMsg) async {
// This method called by @ messages or messages been searched, aims to jump to target message // This method called by @ messages or messages been searched, aims to jump to target message
loadingPlace = LoadingPlace.top; loadingPlace = LoadingPlace.top;
const int singleLoadAmount = kIsWeb ? 15 : 40; const int singleLoadAmount = kIsWeb ? 15 : 40;
@ -204,21 +205,21 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
showCantFindMsg(); showCantFindMsg();
} }
} else { } else {
if (widget.model.haveMoreData) { if (maybeHaveMoreMessageForFind) {
// if the target message not in current message list, load more // if the target message not in current message list, load more
findingMsg = targetMsg; findingMsg = targetMsg;
final lastMsgId = _getMessageId(widget.messageList.length - 1); final lastMsgId = _getMessageId(widget.messageList.length - 1);
widget.onLoadMore(lastMsgId, LoadDirection.previous, singleLoadAmount); maybeHaveMoreMessageForFind = await widget.onLoadMore(lastMsgId, LoadDirection.previous, singleLoadAmount);
} else { } else {
showCantFindMsg(); showCantFindMsg();
} }
} }
} }
_onScrollToIndexBySeq(String targetSeq) { _onScrollToIndexBySeq(String targetSeq) async {
// This method called by tongue request jumping to target @ message // This method called by tongue request jumping to target @ message
loadingPlace = LoadingPlace.top; loadingPlace = LoadingPlace.top;
const int singleLoadAmount = 40; // const int singleLoadAmount = 40;
final msgList = widget.messageList; final msgList = widget.messageList;
String lastSeq = ""; String lastSeq = "";
for (int i = msgList.length - 1; i >= 0; i--) { for (int i = msgList.length - 1; i >= 0; i--) {
@ -258,9 +259,10 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
showCantFindMsg(); showCantFindMsg();
} }
} else { } else {
if (widget.model.haveMoreData) { if (maybeHaveMoreMessageForFind) {
findingSeq = targetSeq; findingSeq = targetSeq;
widget.onLoadMore(_getMessageId(widget.messageList.length - 1), LoadDirection.previous, singleLoadAmount); int requestCount = int.parse(lastSeq) - int.parse(targetSeq);
maybeHaveMoreMessageForFind = await widget.onLoadMore(_getMessageId(widget.messageList.length - 1), LoadDirection.previous, requestCount, int.parse(lastSeq));
} else { } else {
showCantFindMsg(); showCantFindMsg();
} }
@ -313,9 +315,9 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
final messageList = widget.messageList; final messageList = widget.messageList;
final globalModel = context.read<TUIChatGlobalModel>(); final globalModel = context.read<TUIChatGlobalModel>();
final receivedNewMessageList = globalModel.receivedMessageListCount; final receivedNewMessageCount = globalModel.receivedNewMessageCount;
final shouldShowUnreadMessage = receivedNewMessageList > 0; final shouldShowUnreadMessage = receivedNewMessageCount > 0;
final unreadMessageList = _getReceivedMessageList(receivedNewMessageList); final unreadMessageList = _getReceivedMessageList(receivedNewMessageCount);
final readMessageList = messageList.sublist(unreadMessageList.length, messageList.length).toList(); final readMessageList = messageList.sublist(unreadMessageList.length, messageList.length).toList();
final throttleFunction = OptimizeUtils.multiThrottle((index, LoadDirection direction) async { final throttleFunction = OptimizeUtils.multiThrottle((index, LoadDirection direction) async {
@ -429,7 +431,7 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
); );
} }
} }
if (index == 0 && widget.model.haveMoreLatestData == true && globalModel.receivedMessageListCount < 10) { if (index == 0 && widget.model.haveMoreLatestData == true && globalModel.receivedNewMessageCount < 10) {
throttleFunction(index, LoadDirection.latest); throttleFunction(index, LoadDirection.latest);
} }
outputLogger.i("Rendering a read message: ${getMessageIdentifier(messageItem, 0)}, message Type: ${messageItem?.elemType}"); outputLogger.i("Rendering a read message: ${getMessageIdentifier(messageItem, 0)}, message Type: ${messageItem?.elemType}");

View File

@ -34,6 +34,7 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/forward_message_screen.dart'
import 'package:tencent_cloud_chat_uikit/ui/widgets/radio_button.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/radio_button.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_super_tooltip/tencent_super_tooltip.dart'; import 'package:tencent_super_tooltip/tencent_super_tooltip.dart';
import 'package:visibility_detector/visibility_detector.dart';
import '../TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_select_emoji.dart'; import '../TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_select_emoji.dart';
@ -143,25 +144,25 @@ class MessageToolTipItem {
class ToolTipsConfig { class ToolTipsConfig {
/// Whether to show the reply to a message option. /// Whether to show the reply to a message option.
final bool showReplyMessage; bool showReplyMessage;
/// Whether to show the multiple-choice option for messages. /// Whether to show the multiple-choice option for messages.
final bool showMultipleChoiceMessage; bool showMultipleChoiceMessage;
/// Whether to show the option to delete a message. /// Whether to show the option to delete a message.
final bool showDeleteMessage; bool showDeleteMessage;
/// Whether to show the option to recall a message. /// Whether to show the option to recall a message.
final bool showRecallMessage; bool showRecallMessage;
/// Whether to show the option to copy a message. /// Whether to show the option to copy a message.
final bool showCopyMessage; bool showCopyMessage;
/// Whether to show the option to forward a message. /// Whether to show the option to forward a message.
final bool showForwardMessage; bool showForwardMessage;
/// Whether to show the option to translate a text message. This module is not available by default. Please contact your Tencent Cloud sales representative or customer service team to enable this feature. /// Whether to show the option to translate a text message. This module is not available by default. Please contact your Tencent Cloud sales representative or customer service team to enable this feature.
final bool showTranslation; 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. /// 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;
@ -208,10 +209,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
final bool showAvatar; final bool showAvatar;
/// message is read status /// message is read status
final bool showMessageReadRecipt; final bool showMessageReadReceipt;
/// message read status in group
final bool showGroupMessageReadRecipt;
/// allow message can long press /// allow message can long press
final bool allowLongPress; final bool allowLongPress;
@ -280,11 +278,10 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
this.messageItemBuilder, this.messageItemBuilder,
this.onLongPressForOthersHeadPortrait, this.onLongPressForOthersHeadPortrait,
this.showAvatar = true, this.showAvatar = true,
this.showMessageReadRecipt = true, this.showMessageReadReceipt = true,
this.allowLongPress = true, this.allowLongPress = true,
this.toolTipsConfig, this.toolTipsConfig,
this.onLongPress, this.onLongPress,
this.showGroupMessageReadRecipt = false,
this.allowAtUserWhenReply = true, this.allowAtUserWhenReply = true,
this.allowAvatarTap = true, this.allowAvatarTap = true,
this.userAvatarBuilder, this.userAvatarBuilder,
@ -809,6 +806,18 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
} }
} }
if (widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) {
widget.toolTipsConfig?.showReplyMessage = true;
} else {
widget.toolTipsConfig?.showReplyMessage = false;
}
if (widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL && !(widget.message.hasRiskContent ?? false)) {
widget.toolTipsConfig?.showForwardMessage = true;
} else {
widget.toolTipsConfig?.showForwardMessage = false;
}
tooltip = SuperTooltip( tooltip = SuperTooltip(
popupDirection: popupDirection, popupDirection: popupDirection,
minimumOutSidePadding: 0, minimumOutSidePadding: 0,
@ -887,27 +896,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
} }
} }
bool isVoteMessage(V2TimMessage message) { List<MessageHoverControlItem> getWideMessageHoverControlBar(TUIChatSeparateViewModel model, TUITheme theme) {
bool isvote = false;
V2TimCustomElem? custom = message.customElem;
if (custom != null) {
String? data = custom.data;
if (data != null && data.isNotEmpty) {
try {
Map<String, dynamic> mapData = json.decode(data);
if (mapData["businessID"] == "group_poll") {
isvote = true;
}
} catch (err) {
// err
}
}
}
return isvote;
}
List<MessageHoverControlItem> getMessageHoverControlBar(TUIChatSeparateViewModel model, TUITheme theme) {
return [ return [
if (widget.isUseMessageReaction ?? false) if (widget.isUseMessageReaction ?? false)
MessageHoverControlItem( MessageHoverControlItem(
@ -939,7 +928,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
widget.onLongPressForOthersHeadPortrait!(!isAtWhenReply ? null : widget.message.sender, !isAtWhenReply ? null : widget.message.nickName); widget.onLongPressForOthersHeadPortrait!(!isAtWhenReply ? null : widget.message.sender, !isAtWhenReply ? null : widget.message.nickName);
}, },
), ),
if ((widget.toolTipsConfig?.showForwardMessage ?? true) && !isVoteMessage(widget.message)) if ((widget.toolTipsConfig?.showForwardMessage ?? true) && !model.isVoteMessage(widget.message))
MessageHoverControlItem( MessageHoverControlItem(
name: TIM_t("转发"), name: TIM_t("转发"),
icon: Icon( icon: Icon(
@ -948,7 +937,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
color: hexToColor("8f959e"), color: hexToColor("8f959e"),
), ),
onClick: (_) { onClick: (_) {
model.addToMultiSelectedMessageList(widget.message); model.setMessageItemChecked(widget.message, true);
TUIKitWidePopup.showPopupWindow( TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.forward, operationKey: TUIKitWideModalOperationKey.forward,
context: context, context: context,
@ -995,10 +984,16 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model, bool isSelf, V2TimMessage message, bool isPeerRead, TUITheme theme, bool isDownloadWaiting) { Widget renderHoverTipAndReadStatus(TUIChatSeparateViewModel model, bool isSelf, V2TimMessage message, bool isPeerRead, TUITheme theme, bool isDownloadWaiting) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final customHoverBar = widget.customMessageHoverBarOnDesktop != null ? widget.customMessageHoverBarOnDesktop!(message) : null; final customHoverBar = widget.customMessageHoverBarOnDesktop != null ? widget.customMessageHoverBarOnDesktop!(message) : null;
final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null) ? getWideMessageHoverControlBar(model, theme) : [];
final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null) ? getMessageHoverControlBar(model, theme) : [];
final lastItemName = wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : ""; final lastItemName = wideHoverTipList.isNotEmpty ? wideHoverTipList.last.name : "";
Future<void> _conditionalDelay() async {
if (!(model.hasDelayedRenderSendingStatus(message.id ?? message.msgID!) ?? true)) {
model.setDelayedRenderSendingStatus(message.id ?? message.msgID!);
await Future.delayed(const Duration(seconds: 1));
}
}
return Column( return Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -1041,7 +1036,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
if (isDesktopScreen && isShowWideToolTip && customHoverBar != null) customHoverBar, if (isDesktopScreen && isShowWideToolTip && customHoverBar != null) customHoverBar,
if (!isDesktopScreen || (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null && !isShowWideToolTip)) if (!isDesktopScreen || (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null && !isShowWideToolTip))
const SizedBox( const SizedBox(
height: 24, height: 20,
), ),
if (isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) if (isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL)
Container( Container(
@ -1056,18 +1051,17 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
}, },
child: Icon(Icons.error, color: theme.cautionColor, size: 18), child: Icon(Icons.error, color: theme.cautionColor, size: 18),
)), )),
if (isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING && model.hasSendingMessageID(message.id ?? message.msgID!)) if (isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING)
FutureBuilder( FutureBuilder(
future: Future.delayed(const Duration(seconds: 1)), future: _conditionalDelay(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) { builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done && model.hasSendingMessageID(message.id ?? message.msgID!)) { if (snapshot.connectionState == ConnectionState.done) {
return Container( return Container(
padding: const EdgeInsets.only(bottom: 3), padding: const EdgeInsets.only(bottom: 3),
margin: const EdgeInsets.only(right: 6), margin: const EdgeInsets.only(right: 6),
child: RotationTransition( width: 12.0,
turns: Tween(begin: 0.0, end: 1.0).animate(_animationController), height: 15.0,
child: Icon(Icons.rotate_right, color: theme.cautionColor, size: 18), child: CircularProgressIndicator(strokeWidth: 1.0),
),
); );
} else { } else {
return Container(); return Container();
@ -1075,21 +1069,9 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
}, },
), ),
if (model.chatConfig.isShowReadingStatus && if (model.chatConfig.isShowReadingStatus &&
widget.showMessageReadRecipt && isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC &&
model.conversationType == ConvType.c2c && (message.needReadReceipt ?? false) &&
isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC) !model.isVoteMessage(widget.message))
Container(
padding: const EdgeInsets.only(bottom: 3),
margin: const EdgeInsets.only(right: 6),
child: Text(
isPeerRead ? TIM_t("已读") : TIM_t("未读"),
style: TextStyle(color: theme.chatMessageItemUnreadStatusTextColor, fontSize: 12),
),
),
if (model.chatConfig.isShowGroupReadingStatus &&
model.chatConfig.isShowGroupMessageReadReceipt &&
model.conversationType == ConvType.group &&
isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC)
TIMUIKitMessageReadReceipt( TIMUIKitMessageReadReceipt(
messageItem: widget.message, messageItem: widget.message,
onTapAvatar: widget.onTapForOthersPortrait, onTapAvatar: widget.onTapForOthersPortrait,
@ -1164,7 +1146,16 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
} }
} }
return LayoutBuilder( return VisibilityDetector(
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( builder: (context, constraints) => Container(
padding: EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0), padding: EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0),
margin: widget.padding ?? const EdgeInsets.only(bottom: 20), margin: widget.padding ?? const EdgeInsets.only(bottom: 20),
@ -1177,13 +1168,9 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
Container( Container(
margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0), margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0),
child: CheckBoxButton( child: CheckBoxButton(
isChecked: model.multiSelectedMessageList.contains(message), isChecked: model.getSelectedMessageList().contains(message),
onChanged: (value) { onChanged: (value) {
if (value) { model.setMessageItemChecked(message, value);
model.addToMultiSelectedMessageList(message);
} else {
model.removeFromMultiSelectedMessageList(message);
}
}, },
), ),
), ),
@ -1210,12 +1197,8 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
behavior: model.isMultiSelect ? HitTestBehavior.translucent : null, behavior: model.isMultiSelect ? HitTestBehavior.translucent : null,
onTap: () { onTap: () {
if (model.isMultiSelect) { if (model.isMultiSelect) {
final checked = model.multiSelectedMessageList.contains(message); final checked = model.getSelectedMessageList().contains(message);
if (checked) { model.setMessageItemChecked(message, !checked);
model.removeFromMultiSelectedMessageList(message);
} else {
model.addToMultiSelectedMessageList(message);
}
} else { } else {
return; return;
} }
@ -1291,7 +1274,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
widget.topRowBuilder != null widget.topRowBuilder != null
? widget.topRowBuilder!(context, message) ? widget.topRowBuilder!(context, message)
: Container( : Container(
margin: const EdgeInsets.only(bottom: 4), // margin: const EdgeInsets.only(bottom: 4),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 1.7), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 1.7),
child: Text( child: Text(
@ -1309,10 +1292,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
maxWidth: constraints.maxWidth * 0.77, maxWidth: constraints.maxWidth * 0.77,
), ),
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Column( return GestureDetector(
crossAxisAlignment: (message.isSelf ?? true) ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
GestureDetector(
child: IgnorePointer(ignoring: model.isMultiSelect, child: _getMessageItemBuilder(message, message.status, model)), child: IgnorePointer(ignoring: model.isMultiSelect, child: _getMessageItemBuilder(message, message.status, model)),
onSecondaryTapDown: (details) { onSecondaryTapDown: (details) {
if (widget.onLongPress != null) { if (widget.onLongPress != null) {
@ -1337,16 +1317,6 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
onTapDown: (details) { onTapDown: (details) {
_tapDetails = details; _tapDetails = details;
}, },
),
TIMUIKitTextTranslationElem(
message: message,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
isFromSelf: message.isSelf ?? true,
isShowJump: false,
clearJump: () {},
chatModel: model)
],
); );
}), }),
), ),
@ -1355,6 +1325,14 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
if (!isSelf) renderHoverTipAndReadStatus(model, isSelf, message, isPeerRead, theme, isDownloadWaiting), 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 (widget.bottomRowBuilder != null) widget.bottomRowBuilder!(context, message)
], ],
), ),
@ -1390,6 +1368,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
], ],
), ),
), ),
),
); );
} }
} }

View File

@ -10,6 +10,7 @@ import 'package:open_file/open_file.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_select_emoji.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/tim_uikit_message_reaction_select_emoji.dart';
@ -162,26 +163,6 @@ class TIMUIKitMessageTooltipState
); );
} }
bool isVoteMessage(V2TimMessage message) {
bool isvote = false;
V2TimCustomElem? custom = message.customElem;
if (custom != null) {
String? data = custom.data;
if (data != null && data.isNotEmpty) {
try {
Map<String, dynamic> mapData = json.decode(data);
if (mapData["businessID"] == "group_poll") {
isvote = true;
}
} catch (err) {
// err
}
}
}
return isvote;
}
bool isAdminCanRecall() { bool isAdminCanRecall() {
if (widget.model.chatConfig.isGroupAdminRecallEnabled) { if (widget.model.chatConfig.isGroupAdminRecallEnabled) {
final selfMemberInfo = final selfMemberInfo =
@ -212,6 +193,15 @@ class TIMUIKitMessageTooltipState
(isDesktopScreen && (isDesktopScreen &&
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE && widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE &&
fileBeenDownloaded); fileBeenDownloaded);
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 != "") {
showTranslation = false;
}
}
final dynamicQuote = final dynamicQuote =
model.chatConfig.isAtWhenReplyDynamic?.call(widget.message); model.chatConfig.isAtWhenReplyDynamic?.call(widget.message);
@ -235,7 +225,7 @@ class TIMUIKitMessageTooltipState
id: "copyMessage", id: "copyMessage",
iconImageAsset: "images/copy_message.png", iconImageAsset: "images/copy_message.png",
onClick: () => _onTap("copyMessage", model)), onClick: () => _onTap("copyMessage", model)),
if (shouldShowForwardAction && !isVoteMessage(widget.message)) if (shouldShowForwardAction && !model.isVoteMessage(widget.message))
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("转发"), label: TIM_t("转发"),
id: "forwardMessage", id: "forwardMessage",
@ -253,16 +243,17 @@ class TIMUIKitMessageTooltipState
id: "multiSelect", id: "multiSelect",
iconImageAsset: "images/multi_message.png", iconImageAsset: "images/multi_message.png",
onClick: () => _onTap("multiSelect", model)), onClick: () => _onTap("multiSelect", model)),
MessageToolTipItem(
label: TIM_t("翻译"),
id: "translate",
iconImageAsset: "images/translate.png",
onClick: () => _onTap("translate", model)),
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("删除"), label: TIM_t("删除"),
id: "delete", id: "delete",
iconImageAsset: "images/delete_message.png", iconImageAsset: "images/delete_message.png",
onClick: () => _onTap("delete", model)), onClick: () => _onTap("delete", model)),
if (showTranslation)
MessageToolTipItem(
label: TIM_t("翻译"),
id: "translate",
iconImageAsset: "images/translate.png",
onClick: () => _onTap("translate", model)),
if (shouldShowRevokeAction) if (shouldShowRevokeAction)
MessageToolTipItem( MessageToolTipItem(
label: TIM_t("撤回"), label: TIM_t("撤回"),
@ -480,10 +471,10 @@ class TIMUIKitMessageTooltipState
break; break;
case "multiSelect": case "multiSelect":
model.updateMultiSelectStatus(true); model.updateMultiSelectStatus(true);
model.addToMultiSelectedMessageList(widget.message); model.setMessageItemChecked(widget.message, true);
break; break;
case "forwardMessage": case "forwardMessage":
model.addToMultiSelectedMessageList(widget.message); model.setMessageItemChecked(widget.message, true);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(

View File

@ -125,15 +125,18 @@ class _TIMUIKitHistoryMessageListContainerState
List<V2TimMessage?> historyMessageList = []; List<V2TimMessage?> historyMessageList = [];
Future<void> requestForData(String? lastMsgID, LoadDirection direction, Future<bool> requestForData(String? lastMsgID, LoadDirection direction,
TUIChatSeparateViewModel model, TUIChatSeparateViewModel model,
[int? count]) async { [int? count, int? lastSeq]) async {
if ((direction == LoadDirection.previous && model.haveMoreData) || if ((direction == LoadDirection.previous) ||
(direction == LoadDirection.latest && model.haveMoreLatestData)) { (direction == LoadDirection.latest && model.haveMoreLatestData)) {
await model.loadChatRecord( return await model.loadChatRecord(
direction: direction, direction: direction,
count: count ?? (kIsWeb ? 15 : HistoryMessageDartConstant.getCount), count: count ?? (kIsWeb ? 15 : HistoryMessageDartConstant.getCount),
lastMsgID: lastMsgID); lastMsgID: lastMsgID,
lastMsgSeq: lastSeq ?? -1,);
} else {
return false;
} }
} }
@ -203,8 +206,8 @@ class _TIMUIKitHistoryMessageListContainerState
tongueItemBuilder: widget.tongueItemBuilder, tongueItemBuilder: widget.tongueItemBuilder,
initFindingMsg: widget.initFindingMsg, initFindingMsg: widget.initFindingMsg,
messageList: messageList, messageList: messageList,
onLoadMore: (String? a, LoadDirection direction, [int? b]) async { onLoadMore: (String? a, LoadDirection direction, [int? b, int? lastSeq]) async {
return await requestForData(a, direction, model, b); return await requestForData(a, direction, model, b, lastSeq);
}, },
); );
}, },

View File

@ -21,15 +21,25 @@ class TIMUIKitMessageReadReceipt extends TIMUIKitStatelessWidget {
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
final TUIChatSeparateViewModel model = final TUIChatSeparateViewModel model = Provider.of<TUIChatSeparateViewModel>(context, listen: false);
Provider.of<TUIChatSeparateViewModel>(context, listen: false);
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Selector<TUIChatGlobalModel, V2TimMessageReceipt?>( return Selector<TUIChatGlobalModel, V2TimMessageReceipt?>(
builder: (context, value, child) { builder: (context, value, child) {
// if (value == null || value.unreadCount == 0 && value.readCount == 0) { if (model.conversationType == ConvType.c2c) {
// return Container(); bool isPeerRead = false;
// } if (value != null) {
isPeerRead = value.isPeerRead ?? false;
}
return Container(
padding: const EdgeInsets.only(bottom: 3),
margin: const EdgeInsets.only(right: 6),
child: Text(
isPeerRead ? TIM_t("已读") : TIM_t("未读"),
style: TextStyle(color: theme.chatMessageItemUnreadStatusTextColor, fontSize: 12),
),
);
} else {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
@ -95,6 +105,7 @@ class TIMUIKitMessageReadReceipt extends TIMUIKitStatelessWidget {
), ),
), ),
); );
}
}, },
selector: (c, model) { selector: (c, model) {
return model.getMessageReadReceipt(messageItem.msgID ?? ""); return model.getMessageReadReceipt(messageItem.msgID ?? "");

View File

@ -143,8 +143,10 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
TencentUtils.checkString(widget.message.fileElem!.localUrl) ?? TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ?? widget.message.fileElem?.path ??
''; '';
File f = File(savePath); File f = File(savePath);
if (f.existsSync() && widget.messageID != null) { if (widget.messageID != null) {
if (f.existsSync()) {
filePath = savePath; filePath = savePath;
if (downloadProgress != 100) { if (downloadProgress != 100) {
setState(() { setState(() {
@ -161,7 +163,11 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
advancedMsgListener = null; advancedMsgListener = null;
} }
return true; return true;
} else {
model.setMessageProgress(widget.messageID!, 0);
} }
}
return false; return false;
} }
@ -409,7 +415,7 @@ class _TIMUIKitFileElemState extends TIMUIKitState<TIMUIKitFileElem> {
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 72), constraints: const BoxConstraints(maxHeight: 72),
child: Container( child: Container(
width: 237, width: 170,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: theme.weakDividerColor ?? color: theme.weakDividerColor ??

View File

@ -289,13 +289,12 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
} }
void _jumpToRawMsg() { void _jumpToRawMsg() {
if (rawMessage?.timestamp != null) { if (rawMessage?.status != MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED && rawMessage?.timestamp != null) {
widget.scrollToIndex(rawMessage); widget.scrollToIndex(rawMessage);
} else { } else {
onTIMCallback(TIMCallback( onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO, type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("无法定位到原消息"), infoRecommendText: TIM_t("无法定位到原消息")));
infoCode: 6660401));
} }
} }

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
// import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
@ -76,6 +75,10 @@ class _TIMUIKitSoundElemState extends TIMUIKitState<TIMUIKitSoundElem> {
} else { } else {
SoundPlayer.play(url: stateElement.url!); SoundPlayer.play(url: stateElement.url!);
widget.chatModel.currentPlayedMsgId = widget.msgID; widget.chatModel.currentPlayedMsgId = widget.msgID;
setState(() {
isPlaying = widget.chatModel.currentPlayedMsgId != '' && widget.chatModel.currentPlayedMsgId == widget.msgID;
});
} }
} }
@ -98,14 +101,6 @@ class _TIMUIKitSoundElemState extends TIMUIKitState<TIMUIKitSoundElem> {
} }
} }
@override
void didUpdateWidget(oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() {
isPlaying = widget.chatModel.currentPlayedMsgId != '' && widget.chatModel.currentPlayedMsgId == widget.msgID;
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -113,6 +108,9 @@ class _TIMUIKitSoundElemState extends TIMUIKitState<TIMUIKitSoundElem> {
subscription = SoundPlayer.playStateListener(listener: (PlayerState state) { subscription = SoundPlayer.playStateListener(listener: (PlayerState state) {
if (state.processingState == ProcessingState.completed) { if (state.processingState == ProcessingState.completed) {
widget.chatModel.currentPlayedMsgId = ""; widget.chatModel.currentPlayedMsgId = "";
setState(() {
isPlaying = false;
});
} }
}); });

View File

@ -253,6 +253,7 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
true, true,
customEmojiStickerList: widget.customEmojiStickerList, customEmojiStickerList: widget.customEmojiStickerList,
showAtBackground: true, showAtBackground: true,
checkHttpLink: true,
)), )),
// If the link preview info is available, render the preview card. // If the link preview info is available, render the preview card.
if (_renderPreviewWidget() != null && if (_renderPreviewWidget() != null &&

View File

@ -13,6 +13,7 @@ class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder {
this.isUseTencentCloudChatPackage = false, this.isUseTencentCloudChatPackage = false,
this.customEmojiStickerList = const [], this.customEmojiStickerList = const [],
this.showAtBackground = false, this.showAtBackground = false,
this.checkHttpLink = true,
}); });
/// whether show background for @somebody /// whether show background for @somebody
@ -22,6 +23,8 @@ class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder {
final bool isUseTencentCloudChatPackage; final bool isUseTencentCloudChatPackage;
final bool checkHttpLink;
final List<CustomEmojiFaceData> customEmojiStickerList; final List<CustomEmojiFaceData> customEmojiStickerList;
@override @override
@ -40,7 +43,7 @@ class DefaultSpecialTextSpanBuilder extends SpecialTextSpanBuilder {
start: index! - (EmojiText.flag.length - 1), start: index! - (EmojiText.flag.length - 1),
isUseQQPackage: isUseQQPackage, isUseQQPackage: isUseQQPackage,
customEmojiStickerList: customEmojiStickerList); customEmojiStickerList: customEmojiStickerList);
} else if (isStart(flag, HttpText.flag)) { } else if (isStart(flag, HttpText.flag) && checkHttpLink) {
return HttpText(textStyle, onTap, return HttpText(textStyle, onTap,
start: index! - (HttpText.flag.length - 1)); start: index! - (HttpText.flag.length - 1));
} }

View File

@ -8,7 +8,7 @@ class HttpText extends SpecialText {
HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap,
{this.start}) {this.start})
: super(flag, flag, textStyle, onTap: onTap); : super(flag, flag, textStyle, onTap: onTap);
static const String flag = '\$'; static const String flag = '!@TURL#*&\$';
final int? start; final int? start;
@override @override
InlineSpan finishText() { InlineSpan finishText() {
@ -30,16 +30,3 @@ class HttpText extends SpecialText {
}); });
} }
} }
List<String> dollarList = <String>[
'\$Dota2\$',
'\$Dota2 Ti9\$',
'\$CN dota best dota\$',
'\$Flutter\$',
'\$CN dev best dev\$',
'\$UWP\$',
'\$Nevermore\$',
'\$FlutterCandies\$',
'\$ExtendedImage\$',
'\$ExtendedText\$',
];

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.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_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart'; import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
@ -17,9 +18,7 @@ class AtText extends StatefulWidget {
final V2TimGroupInfo? groupInfo; final V2TimGroupInfo? groupInfo;
final List<V2TimGroupMemberFullInfo?>? groupMemberList; final List<V2TimGroupMemberFullInfo?>? groupMemberList;
final VoidCallback? closeFunc; final VoidCallback? closeFunc;
final Function( final Function(List<V2TimGroupMemberFullInfo> memberInfo)? onChooseMember;
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)?
onChooseMember;
final bool canAtAll; final bool canAtAll;
// some Group type cant @all // some Group type cant @all
@ -42,10 +41,13 @@ class AtText extends StatefulWidget {
class _AtTextState extends TIMUIKitState<AtText> { class _AtTextState extends TIMUIKitState<AtText> {
final GroupServices _groupServices = serviceLocator<GroupServices>(); final GroupServices _groupServices = serviceLocator<GroupServices>();
final TUISelfInfoViewModel _selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
List<V2TimGroupMemberFullInfo?>? groupMemberList; List<V2TimGroupMemberFullInfo?>? groupMemberList;
List<V2TimGroupMemberFullInfo?>? searchMemberList; List<V2TimGroupMemberFullInfo?>? searchMemberList;
List<V2TimGroupMemberFullInfo> selectedGroupMemberList = [];
@override @override
void initState() { void initState() {
groupMemberList = widget.groupMemberList; groupMemberList = widget.groupMemberList;
@ -58,16 +60,15 @@ class _AtTextState extends TIMUIKitState<AtText> {
super.dispose(); super.dispose();
} }
_onTapMemberItem( void _submitAtMemberList() {
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails) {
if (widget.closeFunc != null) { if (widget.closeFunc != null) {
widget.closeFunc!(); widget.closeFunc!();
} }
if (widget.onChooseMember != null) { if (widget.onChooseMember != null) {
widget.onChooseMember!(memberInfo, tapDetails); widget.onChooseMember!(selectedGroupMemberList);
} else { } else {
Navigator.pop(context, memberInfo); Navigator.pop(context, selectedGroupMemberList);
} }
} }
@ -113,13 +114,30 @@ class _AtTextState extends TIMUIKitState<AtText> {
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
V2TimUserFullInfo? loginUserInfo = _selfInfoViewModel.loginInfo;
if (loginUserInfo != null) {
searchMemberList?.removeWhere((memberInfo) {
return memberInfo?.userID == loginUserInfo.userID;
});
}
Widget mentionedMembersBody() { Widget mentionedMembersBody() {
return GroupProfileMemberList( return GroupProfileMemberList(
groupType: widget.groupType ?? "", groupType: widget.groupType ?? "",
memberList: searchMemberList ?? [], memberList: searchMemberList ?? [],
onTapMemberItem: _onTapMemberItem,
canAtAll: widget.canAtAll, canAtAll: widget.canAtAll,
canSelectMember: true,
canSlideDelete: false, canSlideDelete: false,
onSelectedMemberChange: (selectedMemberList) {
selectedGroupMemberList = selectedMemberList;
bool isAtAllSelected = selectedGroupMemberList.where((element) {
return element.userID == GroupProfileMemberList.AT_ALL_USER_ID;
}).isNotEmpty;
if (isAtAllSelected) {
_submitAtMemberList();
}
},
touchBottomCallBack: () { touchBottomCallBack: () {
// Get all by once, unnecessary to load more // Get all by once, unnecessary to load more
}, },
@ -168,6 +186,20 @@ class _AtTextState extends TIMUIKitState<AtText> {
fontSize: 17, fontSize: 17,
), ),
), ),
actions: [
TextButton(
onPressed: () {
_submitAtMemberList();
},
child: Text(
TIM_t("确定"),
style: TextStyle(
color: theme.appbarTextColor,
fontSize: 14,
),
),
)
],
), ),
body: mentionedMembersBody())); body: mentionedMembersBody()));
} }

View File

@ -32,6 +32,10 @@ import 'package:universal_html/html.dart' as html;
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
class MorePanelConfig { class MorePanelConfig {
static final int FILE_MAX_SIZE = 100 * 1024 * 1024;
static final int VIDEO_MAX_SIZE = 100 * 1024 * 1024;
static final int IMAGE_MAX_SIZE = 28 * 1024 * 1024;
final bool showGalleryPickAction; final bool showGalleryPickAction;
final bool showCameraAction; final bool showCameraAction;
final bool showFilePickAction; final bool showFilePickAction;
@ -316,20 +320,19 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
}).toList(); }).toList();
} }
_sendVideoMessage(AssetEntity asset, TUIChatSeparateViewModel model) async { _sendVideoMessage(AssetEntity asset, int size, TUIChatSeparateViewModel model) async {
final plugin = FcNativeVideoThumbnail(); if (size >= MorePanelConfig.VIDEO_MAX_SIZE) {
final originFile = await asset.originFile;
final size = await originFile!.length();
if (size >= 104857600) {
onTIMCallback(TIMCallback( onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO, type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("发送失败,视频不能大于100MB"), infoRecommendText: TIM_t("文件大小超出了限制")));
infoCode: 6660405));
return; return;
} }
final plugin = FcNativeVideoThumbnail();
final originFile = await asset.originFile;
final duration = asset.videoDuration.inSeconds; final duration = asset.videoDuration.inSeconds;
final filePath = originFile.path; final filePath = originFile!.path;
final convID = widget.conversationID; final convID = widget.conversationID;
final convType = widget.conversationType; final convType = widget.conversationType;
@ -341,9 +344,9 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
srcFile: originFile.path, srcFile: originFile.path,
destFile: tempPath, destFile: tempPath,
format: 'jpeg', format: 'jpeg',
width: 128, width: 1280,
quality: 100, quality: 100,
height: 128, height: 1280,
); );
MessageUtils.handleMessageError( MessageUtils.handleMessageError(
model.sendVideoMessage( model.sendVideoMessage(
@ -407,8 +410,16 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
final originFile = await asset.originFile; final originFile = await asset.originFile;
final filePath = originFile?.path; final filePath = originFile?.path;
final type = asset.type; final type = asset.type;
final size = await originFile!.length();
if (filePath != null) { if (filePath != null) {
if (type == AssetType.image) { if (type == AssetType.image) {
if (size >= MorePanelConfig.IMAGE_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
MessageUtils.handleMessageError( MessageUtils.handleMessageError(
model.sendImageMessage( model.sendImageMessage(
imagePath: filePath, imagePath: filePath,
@ -418,7 +429,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
} }
if (type == AssetType.video) { if (type == AssetType.video) {
_sendVideoMessage(asset, model); _sendVideoMessage(asset, size, model);
} }
} }
} }
@ -480,7 +491,15 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
final originFile = await pickedFile?.originFile; final originFile = await pickedFile?.originFile;
if (originFile != null) { if (originFile != null) {
final type = pickedFile!.type; final type = pickedFile!.type;
final size = await originFile!.length();
if (type == AssetType.image) { if (type == AssetType.image) {
if (size >= MorePanelConfig.IMAGE_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
MessageUtils.handleMessageError( MessageUtils.handleMessageError(
model.sendImageMessage( model.sendImageMessage(
imagePath: originFile.path, imagePath: originFile.path,
@ -489,7 +508,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
context); context);
} }
if (type == AssetType.video) { if (type == AssetType.video) {
_sendVideoMessage(pickedFile, model); _sendVideoMessage(pickedFile, size, model);
} }
} else { } else {
// Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context); // Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context);
@ -591,6 +610,13 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
File file = File(result.files.single.path!); File file = File(result.files.single.path!);
final int size = file.lengthSync(); final int size = file.lengthSync();
if (size >= MorePanelConfig.FILE_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
final String savePath = file.path; final String savePath = file.path;
MessageUtils.handleMessageError( MessageUtils.handleMessageError(
@ -643,14 +669,22 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
_goToVideoUI(String type) async { _goToVideoUI(String type) async {
if (!PlatformUtils().isWeb) { if (!PlatformUtils().isWeb) {
final hasCameraPermission = type == TYPE_VIDEO bool hasCameraPermission = false;
? await Permissions.checkPermission(context, Permission.camera.value) bool hasMicrophonePermission = false;
: true; if (type == TYPE_VIDEO) {
final hasMicphonePermission = await Permissions.checkPermission( hasCameraPermission = await Permissions.checkPermission(context, Permission.camera.value);
hasMicrophonePermission = await Permissions.checkPermission(
context, Permission.microphone.value); context, Permission.microphone.value);
if (!hasCameraPermission || !hasMicphonePermission) { if (!hasCameraPermission || !hasMicrophonePermission) {
return; return;
} }
} else {
hasMicrophonePermission = await Permissions.checkPermission(
context, Permission.microphone.value);
if (!hasMicrophonePermission) {
return;
}
}
} }
final isGroup = widget.conversationType == ConvType.group; final isGroup = widget.conversationType == ConvType.group;

View File

@ -192,7 +192,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return stickerPackageList; return stickerPackageList;
} }
setCurrentCursor(int? value) { _setCurrentCursor(int? value) {
currentCursor = value; currentCursor = value;
} }
@ -203,7 +203,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return emojiRegex().hasMatch(input); return emojiRegex().hasMatch(input);
} }
void deleteStickerFromText() { void _deleteStickerFromText() {
String originalText = textEditingController.text; String originalText = textEditingController.text;
if (originalText == zeroWidthSpace) { if (originalText == zeroWidthSpace) {
@ -239,9 +239,21 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
focusNode.requestFocus(); focusNode.requestFocus();
} }
} }
if (originalText.isEmpty && textEditingController.text.isEmpty) {
widget.model.repliedMessage = null;
}
} }
void addStickerToText(String sticker) { void _onDeleteText(String oldText) {
if (oldText.isEmpty) {
if (widget.model.repliedMessage != null) {
widget.model.repliedMessage = null;
}
}
}
void _addStickerToText(String sticker) {
final currentText = textEditingController.text; final currentText = textEditingController.text;
if (currentCursor != null && currentCursor! > -1 && currentCursor! < currentText.length + 1) { if (currentCursor != null && currentCursor! > -1 && currentCursor! < currentText.length + 1) {
final firstString = currentText.substring(0, currentCursor); final firstString = currentText.substring(0, currentCursor);
@ -267,13 +279,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
String text = textEditingController.text; String text = textEditingController.text;
String convID = id ?? widget.conversationID; String convID = id ?? widget.conversationID;
final isTopic = convID.contains("@TOPIC#"); final isTopic = convID.contains("@TOPIC#");
String conversationID = isTopic ? convID : ((convType ?? widget.conversationType) == ConvType.c2c ? "c2c_$convID" : "group_$convID"); String conversationID = isTopic ? convID : ((convType ?? widget.conversationType) == ConvType.c2c ? "${TUIConversationViewModel.conversationC2CPrefix}$convID" : "${TUIConversationViewModel.conversationGroupPrefix}$convID");
String draftText = _filterU200b(text); String draftText = _filterU200b(text);
return await conversationModel.setConversationDraft(groupID: groupID ?? widget.groupID, isTopic: isTopic, isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb, conversationID: conversationID, draftText: draftText); return await conversationModel.setConversationDraft(groupID: groupID ?? widget.groupID, isTopic: isTopic, isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb, conversationID: conversationID, draftText: draftText);
} }
// onSubmitted一样 // onSubmitted一样
onEmojiSubmitted() { _onEmojiSubmitted() {
lastText = ""; lastText = "";
final text = textEditingController.text.trim(); final text = textEditingController.text.trim();
final convType = widget.conversationType; final convType = widget.conversationType;
@ -304,7 +316,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
} }
// index为emoji的index,data为baseurl+name // index为emoji的index,data为baseurl+name
onCustomEmojiFaceSubmitted(int index, String data) { _onCustomEmojiFaceSubmitted(int index, String data) {
final convType = widget.conversationType; final convType = widget.conversationType;
// This part of the code is written to adapt to the Native side requirements. // This part of the code is written to adapt to the Native side requirements.
@ -610,19 +622,25 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
isAddingAtSearchWords = false; isAddingAtSearchWords = false;
} }
} else if (textLength > 0 && text[textLength - 1] == "@" && lastText.length < textLength) { } else if (textLength > 0 && text[textLength - 1] == "@" && lastText.length < textLength) {
V2TimGroupMemberFullInfo? memberInfo = await Navigator.push( List<V2TimGroupMemberFullInfo> selectedAtMemberList = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AtText(groupMemberList: model.groupMemberList, groupInfo: model.groupInfo, groupID: groupID, canAtAll: canAtAll, groupType: widget.groupType), builder: (context) => AtText(groupMemberList: model.groupMemberList, groupInfo: model.groupInfo, groupID: groupID, canAtAll: canAtAll, groupType: widget.groupType),
), ),
); );
for (int i = 0; i < selectedAtMemberList.length; ++i) {
V2TimGroupMemberFullInfo memberInfo = selectedAtMemberList[i];
final showName = _getShowName(memberInfo); final showName = _getShowName(memberInfo);
if (memberInfo != null) { if (memberInfo != null) {
mentionedMembersMap["@$showName"] = memberInfo; mentionedMembersMap["@$showName"] = memberInfo;
textEditingController.text = "$text$showName "; String addAtCharacter = i == 0 ? "" : "@";
lastText = "$text$showName "; textEditingController.text = "${textEditingController.text}$addAtCharacter$showName ";
lastText = "${textEditingController.text}$addAtCharacter$showName ";
} }
} }
}
lastText = textEditingController.text; lastText = textEditingController.text;
} }
@ -858,12 +876,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
context: context, context: context,
defaultWidget: TIMUIKitTextFieldLayoutNarrow( defaultWidget: TIMUIKitTextFieldLayoutNarrow(
stickerPackageList: stickerPackageList, stickerPackageList: stickerPackageList,
onEmojiSubmitted: onEmojiSubmitted, onEmojiSubmitted: _onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted, onCustomEmojiFaceSubmitted: _onCustomEmojiFaceSubmitted,
backSpaceText: deleteStickerFromText, backSpaceText: _deleteStickerFromText,
addStickerToText: addStickerToText, addStickerToText: _addStickerToText,
customStickerPanel: widget.customStickerPanel, customStickerPanel: widget.customStickerPanel,
onChanged: widget.onChanged, onChanged: widget.onChanged,
onDeleteText: _onDeleteText,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
morePanelConfig: widget.morePanelConfig, morePanelConfig: widget.morePanelConfig,
repliedMessage: value, repliedMessage: value,
@ -876,7 +895,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
conversationType: widget.conversationType, conversationType: widget.conversationType,
focusNode: focusNode, focusNode: focusNode,
controller: widget.controller, controller: widget.controller,
setCurrentCursor: setCurrentCursor, setCurrentCursor: _setCurrentCursor,
onCursorChange: _onCursorChange, onCursorChange: _onCursorChange,
model: model, model: model,
handleSendEditStatus: _handleSendEditStatus, handleSendEditStatus: _handleSendEditStatus,
@ -895,10 +914,10 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
chatConfig: widget.chatConfig ?? widget.model.chatConfig, chatConfig: widget.chatConfig ?? widget.model.chatConfig,
theme: theme, theme: theme,
currentConversation: widget.currentConversation, currentConversation: widget.currentConversation,
onEmojiSubmitted: onEmojiSubmitted, onEmojiSubmitted: _onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted, onCustomEmojiFaceSubmitted: _onCustomEmojiFaceSubmitted,
backSpaceText: deleteStickerFromText, backSpaceText: _deleteStickerFromText,
addStickerToText: addStickerToText, addStickerToText: _addStickerToText,
customStickerPanel: widget.customStickerPanel, customStickerPanel: widget.customStickerPanel,
onChanged: widget.onChanged, onChanged: widget.onChanged,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
@ -913,7 +932,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
conversationType: widget.conversationType, conversationType: widget.conversationType,
focusNode: focusNode, focusNode: focusNode,
controller: widget.controller, controller: widget.controller,
setCurrentCursor: setCurrentCursor, setCurrentCursor: _setCurrentCursor,
onCursorChange: _onCursorChange, onCursorChange: _onCursorChange,
model: model, model: model,
handleSendEditStatus: _handleSendEditStatus, handleSendEditStatus: _handleSendEditStatus,

View File

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:extended_text_field/extended_text_field.dart'; import 'package:extended_text_field/extended_text_field.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
@ -81,6 +82,8 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget {
final V2TimMessage? repliedMessage; final V2TimMessage? repliedMessage;
final void Function(String)? onDeleteText;
/// show send emoji icon /// show send emoji icon
final bool showSendEmoji; final bool showSendEmoji;
@ -112,6 +115,7 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget {
required this.model, required this.model,
this.backgroundColor, this.backgroundColor,
this.onChanged, this.onChanged,
this.onDeleteText,
required this.handleSendEditStatus, required this.handleSendEditStatus,
required this.handleAtText, required this.handleAtText,
required this.handleSoftKeyBoardDelete, required this.handleSoftKeyBoardDelete,
@ -444,7 +448,10 @@ class _TIMUIKitTextFieldLayoutNarrowState extends TIMUIKitState<TIMUIKitTextFiel
Expanded( Expanded(
child: showSendSoundText child: showSendSoundText
? SendSoundMessage(onDownBottom: widget.goDownBottom, conversationID: widget.conversationID, conversationType: widget.conversationType) ? SendSoundMessage(onDownBottom: widget.goDownBottom, conversationID: widget.conversationID, conversationType: widget.conversationType)
: KeyboardVisibility( : Stack(
children: [
Center(
child: KeyboardVisibility(
child: ExtendedTextField( child: ExtendedTextField(
maxLines: 4, maxLines: 4,
minLines: 1, minLines: 1,
@ -490,6 +497,7 @@ class _TIMUIKitTextFieldLayoutNarrowState extends TIMUIKitState<TIMUIKitTextFiel
isUseTencentCloudChatPackage: widget.model.chatConfig.stickerPanelConfig?.useTencentCloudChatStickerPackage ?? true, isUseTencentCloudChatPackage: widget.model.chatConfig.stickerPanelConfig?.useTencentCloudChatStickerPackage ?? true,
customEmojiStickerList: widget.customEmojiStickerList, customEmojiStickerList: widget.customEmojiStickerList,
showAtBackground: true, showAtBackground: true,
checkHttpLink: false,
)), )),
onChanged: (bool visibility) { onChanged: (bool visibility) {
if (showKeyboard != visibility) { if (showKeyboard != visibility) {
@ -499,6 +507,20 @@ class _TIMUIKitTextFieldLayoutNarrowState extends TIMUIKitState<TIMUIKitTextFiel
} }
}), }),
), ),
RawKeyboardListener(
autofocus: true,
focusNode: FocusNode(),
onKey: (key) {
if (key is RawKeyDownEvent && key.logicalKey == LogicalKeyboardKey.backspace) {
if (widget.onDeleteText != null) {
widget.onDeleteText!(widget.textEditingController.text);
}
}
}, child: Container(),
),
]
),
),
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),

View File

@ -66,10 +66,12 @@ class TIMUIKitChatConfig {
/// Control if allowed to show reading status for group. /// Control if allowed to show reading status for group.
/// [Default]: true. /// [Default]: true.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isShowGroupReadingStatus; final bool isShowGroupReadingStatus;
/// Control if allowed to report reading status for group. /// Control if allowed to report reading status for group.
/// [Default]: true. /// [Default]: true.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isReportGroupReadingStatus; final bool isReportGroupReadingStatus;
/// Control if allowed to show the message operation menu after long pressing message. /// Control if allowed to show the message operation menu after long pressing message.
@ -101,12 +103,14 @@ class TIMUIKitChatConfig {
final bool Function(V2TimMessage message)? isAtWhenReplyDynamic; final bool Function(V2TimMessage message)? isAtWhenReplyDynamic;
/// The main switch of the group read receipt. /// The main switch of the group read receipt.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isShowGroupMessageReadReceipt; final bool isShowGroupMessageReadReceipt;
/// [Deprecated: ] Please use [groupReadReceiptPermissionList] instead. /// [Deprecated: ] not support.
final List<GroupReceptAllowType>? groupReadReceiptPermisionList; final List<GroupReceptAllowType>? groupReadReceiptPermisionList;
/// Control which group can send message read receipt. /// Control which group can send message read receipt.
/// [Deprecated: ] not support.
final List<GroupReceiptAllowType>? groupReadReceiptPermissionList; final List<GroupReceiptAllowType>? groupReadReceiptPermissionList;
/// Control if show self name in group chat. /// Control if show self name in group chat.
@ -271,7 +275,7 @@ class TIMUIKitChatConfig {
this.isShowSelfNameInGroup = false, this.isShowSelfNameInGroup = false,
this.isAtWhenReplyDynamic, this.isAtWhenReplyDynamic,
this.offlinePushInfo, this.offlinePushInfo,
@Deprecated("Please use [isShowGroupReadingStatus] instead") @Deprecated("Please use [isShowReadingStatus] instead")
this.isShowGroupMessageReadReceipt = true, this.isShowGroupMessageReadReceipt = true,
this.upperRecallTime = 120, this.upperRecallTime = 120,
this.isShowOthersNameInGroup = true, this.isShowOthersNameInGroup = true,
@ -281,8 +285,9 @@ class TIMUIKitChatConfig {
this.notificationTitle = "", this.notificationTitle = "",
this.notificationIOSSound = "", this.notificationIOSSound = "",
this.isAllowSoundMessage = true, this.isAllowSoundMessage = true,
@Deprecated("Please use [groupReadReceiptPermissionList] instead") @Deprecated("not support")
this.groupReadReceiptPermisionList, this.groupReadReceiptPermisionList,
@Deprecated("not support")
this.groupReadReceiptPermissionList, this.groupReadReceiptPermissionList,
this.isAllowEmojiPanel = true, this.isAllowEmojiPanel = true,
this.isAllowShowMorePanel = true, this.isAllowShowMorePanel = true,
@ -294,6 +299,7 @@ class TIMUIKitChatConfig {
this.isEnableTextSelection, this.isEnableTextSelection,
this.additionalDesktopMessageHoverBarItem, this.additionalDesktopMessageHoverBarItem,
this.isShowGroupReadingStatus = true, this.isShowGroupReadingStatus = true,
@Deprecated("Please use [isShowReadingStatus] instead")
this.isReportGroupReadingStatus = true, this.isReportGroupReadingStatus = true,
this.showC2cMessageEditStatus = true, this.showC2cMessageEditStatus = true,
this.additionalDesktopControlBarItems, this.additionalDesktopControlBarItems,

View File

@ -14,6 +14,8 @@ import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
class MultiSelectPanel extends TIMUIKitStatelessWidget { class MultiSelectPanel extends TIMUIKitStatelessWidget {
final int forwardMsgNumLimit = 30;
final ConvType conversationType; final ConvType conversationType;
MultiSelectPanel({Key? key, required this.conversationType}) MultiSelectPanel({Key? key, required this.conversationType})
@ -21,6 +23,39 @@ class MultiSelectPanel extends TIMUIKitStatelessWidget {
_handleForwardMessage(BuildContext context, bool isMergerForward, _handleForwardMessage(BuildContext context, bool isMergerForward,
TUIChatSeparateViewModel model) { TUIChatSeparateViewModel model) {
//
if (model.getSelectedMessageList().isEmpty) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("请选择要操作的消息!")));
return;
}
for (var v2TimMessage in model.getSelectedMessageList()) {
//
if (v2TimMessage.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("发送失败消息不支持转发!")));
return;
}
//
if (model.isVoteMessage(v2TimMessage)) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("投票消息不支持转发!")));
return;
}
}
// 30
if (!isMergerForward && model.getSelectedMessageList().length > forwardMsgNumLimit) {
_showForwardLimitDialog(context);
return;
}
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -31,6 +66,33 @@ class MultiSelectPanel extends TIMUIKitStatelessWidget {
))); )));
} }
//
Future<bool?> _showForwardLimitDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text(TIM_t("转发消息过多,暂不支持逐条转发")),
actions: [
CupertinoDialogAction(
child: Text(TIM_t("确定")),
onPressed: () {
Navigator.of(context).pop(true);
},
),
CupertinoDialogAction(
child: Text(TIM_t("取消")),
isDestructiveAction: true,
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
_handleForwardMessageWide(BuildContext context, bool isMergerForward, _handleForwardMessageWide(BuildContext context, bool isMergerForward,
TUIChatSeparateViewModel model) { TUIChatSeparateViewModel model) {
TUIKitWidePopup.showPopupWindow( TUIKitWidePopup.showPopupWindow(

View File

@ -362,7 +362,6 @@ class _TIMUIKitConversationState extends TIMUIKitState<TIMUIKitConversation> {
child: GestureDetector( child: GestureDetector(
child: TIMUIKitConversationItem( child: TIMUIKitConversationItem(
isCurrent: isCurrent, isCurrent: isCurrent,
isShowDraft: widget.isShowDraft,
lastMessageBuilder: widget.lastMessageBuilder, lastMessageBuilder: widget.lastMessageBuilder,
faceUrl: conversationItem.faceUrl ?? "", faceUrl: conversationItem.faceUrl ?? "",
nickName: conversationItem.showName ?? "", nickName: conversationItem.showName ?? "",

View File

@ -31,14 +31,8 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
final int? convType; final int? convType;
final bool isCurrent; final bool isCurrent;
/// Control if shows the identifier that the conversation has a draft text, inputted in previous.
/// Also, you'd better specifying the `draftText` field for `TIMUIKitChat`, from the `draftText` in `V2TimConversation`,
/// to meet the identifier shows here.
final bool isShowDraft;
TIMUIKitConversationItem({ TIMUIKitConversationItem({
Key? key, Key? key,
required this.isShowDraft,
required this.faceUrl, required this.faceUrl,
required this.nickName, required this.nickName,
required this.lastMsg, required this.lastMsg,
@ -56,22 +50,20 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
Widget _getShowMsgWidget(BuildContext context) { Widget _getShowMsgWidget(BuildContext context) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop; final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
if (isShowDraft && draftText != null && draftText != "") { if (lastMsg != null && lastMessageBuilder != null &&
return TIMUIKitDraftText(
context: context,
draftText: draftText ?? "",
fontSize: isDesktopScreen ? 12 : 14,
);
} else if (lastMsg != null) {
if (lastMessageBuilder != null &&
lastMessageBuilder!(lastMsg, groupAtInfoList) != null) { lastMessageBuilder!(lastMsg, groupAtInfoList) != null) {
return lastMessageBuilder!(lastMsg, groupAtInfoList)!; return lastMessageBuilder!(lastMsg, groupAtInfoList)!;
} }
if (lastMsg != null || (draftText != null && draftText != "")) {
return TIMUIKitLastMsg( return TIMUIKitLastMsg(
fontSize: isDesktopScreen ? 12 : 14, fontSize: isDesktopScreen ? 12 : 14,
groupAtInfoList: groupAtInfoList, groupAtInfoList: groupAtInfoList,
lastMsg: lastMsg, lastMsg: lastMsg,
isDisturb: isDisturb,
unreadCount: unreadCount,
context: context, context: context,
draftText: draftText ?? "",
); );
} }
@ -81,8 +73,7 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
} }
bool isHaveSecondLine() { bool isHaveSecondLine() {
return (isShowDraft && draftText != null && draftText != "") || return (draftText != null && draftText != "") || (lastMsg != null);
(lastMsg != null);
} }
Widget _getTimeStringForChatWidget(BuildContext context, TUITheme theme) { Widget _getTimeStringForChatWidget(BuildContext context, TUITheme theme) {

View File

@ -2,12 +2,16 @@
import 'dart:convert'; import 'dart:convert';
import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.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_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_uikit_conversation_draft_text.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
class TIMUIKitLastMsg extends StatefulWidget { class TIMUIKitLastMsg extends StatefulWidget {
@ -15,8 +19,20 @@ class TIMUIKitLastMsg extends StatefulWidget {
final List<V2TimGroupAtInfo?> groupAtInfoList; final List<V2TimGroupAtInfo?> groupAtInfoList;
final BuildContext context; final BuildContext context;
final double fontSize; final double fontSize;
bool isDisturb;
int unreadCount;
String draftText;
const TIMUIKitLastMsg({Key? key, this.lastMsg, required this.groupAtInfoList, required this.context, this.fontSize = 14.0}) : super(key: key); TIMUIKitLastMsg(
{Key? key,
this.lastMsg,
required this.groupAtInfoList,
this.isDisturb = false,
this.unreadCount = 0,
required this.draftText,
required this.context,
this.fontSize = 14.0})
: super(key: key);
@override @override
State<TIMUIKitLastMsg> createState() => _TIMUIKitLastMsgState(); State<TIMUIKitLastMsg> createState() => _TIMUIKitLastMsgState();
@ -34,7 +50,10 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
@override @override
void didUpdateWidget(covariant TIMUIKitLastMsg oldWidget) { void didUpdateWidget(covariant TIMUIKitLastMsg oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if ((oldWidget.lastMsg?.msgID != widget.lastMsg?.msgID) || (oldWidget.lastMsg?.id != widget.lastMsg?.id) || (oldWidget.lastMsg?.status != widget.lastMsg?.status)) { if ((oldWidget.lastMsg?.msgID != widget.lastMsg?.msgID) ||
(oldWidget.lastMsg?.id != widget.lastMsg?.id) ||
(oldWidget.lastMsg?.status != widget.lastMsg?.status) ||
(oldWidget.unreadCount != widget.unreadCount)) {
_getMsgElem(); _getMsgElem();
} }
} }
@ -63,22 +82,33 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
final isAdminRevoke = revokeStatus.$2; final isAdminRevoke = revokeStatus.$2;
if (isRevokedMessage) { if (isRevokedMessage) {
final isSelf = widget.lastMsg!.isSelf ?? true; final isSelf = widget.lastMsg!.isSelf ?? true;
final option1 = isAdminRevoke ? TIM_t("管理员") : (isSelf ? TIM_t("") : widget.lastMsg!.nickName ?? widget.lastMsg?.sender); final option1 =
isAdminRevoke ? TIM_t("管理员") : (isSelf ? TIM_t("") : widget.lastMsg!.nickName ?? widget.lastMsg?.sender);
if (mounted) { if (mounted) {
setState(() { setState(() {
groupTipsAbstractText = TIM_t_para("{{option1}}撤回了一条消息", "$option1撤回了一条消息")(option1: option1); groupTipsAbstractText = TIM_t_para("{{option1}}撤回了一条消息", "$option1撤回了一条消息")(option1: option1);
}); });
} }
} else { } else {
final newText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? ""; String msgShowText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? "";
if (mounted) { if (mounted) {
setState(() { setState(() {
groupTipsAbstractText = newText; groupTipsAbstractText = msgShowText;
}); });
} }
} }
} }
String _getDisturbUnreadCountInfo() {
if (widget.isDisturb && widget.unreadCount > 0) {
final option1 = widget.unreadCount.toString();
String unreadCountText = TIM_t_para("[{{option1}} 条]", "[$option1 条]")(option1: option1);
return unreadCountText;
}
return "";
}
Future<String?> _getLastMsgShowText(V2TimMessage? message, BuildContext context) async { Future<String?> _getLastMsgShowText(V2TimMessage? message, BuildContext context) async {
final msgType = message!.elemType; final msgType = message!.elemType;
switch (msgType) { switch (msgType) {
@ -121,37 +151,87 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
} }
String _getAtMessage() { String _getAtMessage() {
bool atMe = false;
bool atAll = false;
String msg = ""; String msg = "";
for (var item in widget.groupAtInfoList) { for (var item in widget.groupAtInfoList) {
if (item!.atType == 1) { if (item!.atType == 1) {
msg = TIM_t("[有人@我] "); atMe = true;
} else { continue;
} else if (item!.atType == 2) {
atAll = true;
continue;
} else if (item!.atType == 3) {
atMe = true;
atAll = true;
continue;
}
}
if (atAll && atMe) {
msg = TIM_t("[@所有人][有人@我]");
} else if (atAll) {
msg = TIM_t("[@所有人]"); msg = TIM_t("[@所有人]");
} else if (atMe) {
msg = TIM_t("[有人@我]");
} }
}
return msg; return msg;
} }
String _getDraftShowText() {
final draftShowText = TIM_t("草稿");
return '[$draftShowText]';
}
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final TUITheme theme = value.theme; final TUITheme theme = value.theme;
final icon = _getIconByMsgStatus(context); final icon = _getIconByMsgStatus(context);
String disturbUnreadCountInfo = _getDisturbUnreadCountInfo();
return Row(children: [ return Row(children: [
if (icon != null) if (icon != null)
Container( Container(
margin: const EdgeInsets.only(right: 2), margin: const EdgeInsets.only(right: 2),
child: icon, child: icon,
), ),
if (widget.groupAtInfoList.isNotEmpty) Text(_getAtMessage(), style: TextStyle(color: theme.cautionColor, fontSize: widget.fontSize)), if (widget.groupAtInfoList.isNotEmpty)
if (TencentUtils.checkString(groupTipsAbstractText) != null) Text(_getAtMessage(), style: TextStyle(color: theme.cautionColor, fontSize: widget.fontSize)),
if (widget.draftText != null && widget.draftText != "")
Text(_getDraftShowText(), style: TextStyle(color: theme.conversationItemDraftTextColor, fontSize: widget.fontSize)),
if (disturbUnreadCountInfo != "")
Text(disturbUnreadCountInfo, style: TextStyle(color: theme.weakTextColor, fontSize: widget.fontSize)),
if (widget.draftText != null && widget.draftText != "")
Expanded( Expanded(
child: Text( child: ExtendedText(
groupTipsAbstractText, groupTipsAbstractText,
softWrap: true, softWrap: true,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(height: 1, color: theme.weakTextColor, fontSize: widget.fontSize), style: TextStyle(height: 1, color: theme.weakTextColor, fontSize: widget.fontSize),
)), specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
isUseQQPackage: true,
isUseTencentCloudChatPackage: true,
showAtBackground: true,
)
),
),
if (widget.draftText == null || widget.draftText == "" && TencentUtils.checkString(groupTipsAbstractText) != null)
Expanded(
child: ExtendedText(
groupTipsAbstractText,
softWrap: true,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(height: 1, color: theme.weakTextColor, fontSize: widget.fontSize),
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
isUseQQPackage: true,
isUseTencentCloudChatPackage: true,
showAtBackground: true,
)
),
)
]); ]);
} }
} }

View File

@ -104,9 +104,9 @@ class GroupProfileMemberListPageState
memberList: searchMemberList ?? groupProfileModel.groupMemberList, memberList: searchMemberList ?? groupProfileModel.groupMemberList,
removeMember: _kickedOffMember, removeMember: _kickedOffMember,
touchBottomCallBack: () {}, touchBottomCallBack: () {},
onTapMemberItem: (friendInfo, details) { onTapMemberItem: (memberInfo, details) {
if (widget.model.onClickUser != null) { if (widget.model.onClickUser != null) {
widget.model.onClickUser!(friendInfo.userID, details); widget.model.onClickUser!(memberInfo, details);
} }
}, },
); );
@ -134,9 +134,9 @@ class GroupProfileMemberListPageState
memberList: searchMemberList ?? groupProfileModel.groupMemberList, memberList: searchMemberList ?? groupProfileModel.groupMemberList,
removeMember: _kickedOffMember, removeMember: _kickedOffMember,
touchBottomCallBack: () {}, touchBottomCallBack: () {},
onTapMemberItem: (friendInfo, details) { onTapMemberItem: (memberInfo, details) {
if (widget.model.onClickUser != null) { if (widget.model.onClickUser != null) {
widget.model.onClickUser!(friendInfo.userID, details); widget.model.onClickUser!(memberInfo, details);
} }
}, },
) )

View File

@ -55,7 +55,7 @@ class TIMUIKitGroupProfile extends StatefulWidget {
/// The callback after user clicking a user, /// The callback after user clicking a user,
/// you may navigating to the specific profile page, or anywhere you want. /// you may navigating to the specific profile page, or anywhere you want.
final Function(String userID, TapDownDetails? tapDetails)? onClickUser; final Function(V2TimGroupMemberFullInfo groupMemberFullInfo, TapDownDetails? tapDetails)? onClickUser;
const TIMUIKitGroupProfile( const TIMUIKitGroupProfile(
{Key? key, {Key? key,
@ -154,6 +154,10 @@ class _TIMUIKitGroupProfileState extends TIMUIKitState<TIMUIKitGroupProfile> {
groupListenerModel.needUpdate = null; groupListenerModel.needUpdate = null;
switch (needUpdate.updateType) { switch (needUpdate.updateType) {
case UpdateType.groupInfo: case UpdateType.groupInfo:
if (needUpdate.groupInfoSubType == GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER) {
model.onOwnerChanged(needUpdate.ownerID);
}
model.loadGroupInfo(widget.groupID); model.loadGroupInfo(widget.groupID);
break; break;
case UpdateType.memberList: case UpdateType.memberList:

View File

@ -5,7 +5,7 @@ import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/t
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_add_opt.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_add_opt.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_detail_card.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_detail_card.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_manage.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_member_tile.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_member_title.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_message_disturb.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_message_disturb.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_name_card.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_name_card.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_notification.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/widgets/tim_uikit_group_notification.dart';
@ -27,7 +27,7 @@ class TIMUIKitGroupProfileWidget {
} }
static Widget memberTile() { static Widget memberTile() {
return GroupMemberTile(); return GroupMemberTitle();
} }
static Widget groupNotification({ static Widget groupNotification({

View File

@ -15,8 +15,8 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
class GroupMemberTile extends TIMUIKitStatelessWidget { class GroupMemberTitle extends TIMUIKitStatelessWidget {
GroupMemberTile({ GroupMemberTitle({
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -44,7 +44,7 @@ class GroupMemberTile extends TIMUIKitStatelessWidget {
return InkWell( return InkWell(
onTapDown: (details) { onTapDown: (details) {
if (model.onClickUser != null && element?.userID != null) { if (model.onClickUser != null && element?.userID != null) {
model.onClickUser!(element!.userID, details); model.onClickUser!(element!, details);
} }
}, },
child: SizedBox( child: SizedBox(

View File

@ -45,26 +45,22 @@ class GroupProfileType extends TIMUIKitStatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
border: isDesktopScreen border: isDesktopScreen ? null : Border(
? null
: Border(
bottom: BorderSide( bottom: BorderSide(
color: theme.weakDividerColor ?? color:
CommonColor.weakDividerColor))), theme.weakDividerColor ?? CommonColor.weakDividerColor))),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
TIM_t("群类型"), TIM_t("群类型"),
style: TextStyle( style: TextStyle(
fontSize: isDesktopScreen ? 14 : 16, fontSize: isDesktopScreen ? 14 : 16, color: theme.darkTextColor),
color: theme.darkTextColor),
), ),
Text( Text(
groupType, groupType,
style: TextStyle( style: TextStyle(
fontSize: isDesktopScreen ? 14 : 16, fontSize: isDesktopScreen ? 14 : 16, color: theme.weakTextColor),
color: theme.weakTextColor),
) )
], ],
), ),

View File

@ -240,51 +240,14 @@ class _TIMUIKitProfileState extends TIMUIKitState<TIMUIKitProfile> {
} }
void handleAddFriend() async { void handleAddFriend() async {
model.addFriend(userInfo.userID).then((res) { model.addFriend(userInfo.userID);
if (res == null) {
throw Error();
}
if (res.resultCode == 0) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友添加成功"),
infoCode: 6661202));
} else if (res.resultCode == 30539) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友申请已发出"),
infoCode: 6661203));
} else if (res.resultCode == 30515) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("当前用户在黑名单"),
infoCode: 6661204));
}
}).catchError((error) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友添加失败"),
infoCode: 6661205));
});
} }
void handleDeleteFriend() { Future<void> handleDeleteFriend() async {
model.deleteFriend(userInfo.userID).then((res) { var result = await model.deleteFriend(userInfo.userID, needUpdateData: false);
if (res == null) { if (result != null) {
throw Error(); Navigator.pop(context);
} }
if (res.resultCode != 0 && res.resultCode != null) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友删除失败"),
infoCode: 6661207));
} else {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("好友删除成功"),
infoCode: 6661206));
}
});
} }
List<Widget> _renderWidgetsWithOrder(List<ProfileWidgetEnum> order) { List<Widget> _renderWidgetsWithOrder(List<ProfileWidgetEnum> order) {

View File

@ -263,7 +263,6 @@ class TIMUIKitProfileWidget extends TIMUIKitClass {
return TIMUIKitOperationItem( return TIMUIKitOperationItem(
smallCardMode: smallCardMode, smallCardMode: smallCardMode,
isEmpty: false, isEmpty: false,
showAllowEditStatus: false,
operationName: TIM_t("生日"), operationName: TIM_t("生日"),
operationRightWidget: Text(formatter.format(date), operationRightWidget: Text(formatter.format(date),
textAlign: isDesktopScreen ? null : TextAlign.end), textAlign: isDesktopScreen ? null : TextAlign.end),
@ -272,7 +271,6 @@ class TIMUIKitProfileWidget extends TIMUIKitClass {
return TIMUIKitOperationItem( return TIMUIKitOperationItem(
smallCardMode: smallCardMode, smallCardMode: smallCardMode,
isEmpty: false, isEmpty: false,
showAllowEditStatus: false,
operationName: TIM_t("生日"), operationName: TIM_t("生日"),
operationRightWidget: operationRightWidget:
Text(TIM_t("未填写"), textAlign: isDesktopScreen ? null : TextAlign.end), Text(TIM_t("未填写"), textAlign: isDesktopScreen ? null : TextAlign.end),

View File

@ -66,16 +66,24 @@ class TIMUIKitSearchFriendState extends TIMUIKitState<TIMUIKitSearchFriend> {
int convIndex = _conversationList int convIndex = _conversationList
.indexWhere((item) => conv.friendInfo?.userID == item?.userID); .indexWhere((item) => conv.friendInfo?.userID == item?.userID);
V2TimConversation conversation = _conversationList[convIndex]!; V2TimConversation conversation = _conversationList[convIndex]!;
late String? showNickName;
if (conv.friendInfo?.friendRemark != null && conv.friendInfo?.friendRemark != "") {
showNickName = conv.friendInfo?.friendRemark;
} else if (conv.friendInfo?.userProfile?.nickName != null && conv.friendInfo?.userProfile?.nickName != "") {
showNickName = conv.friendInfo?.userProfile?.nickName;
} else {
showNickName = conv.friendInfo?.userID;
}
return TIMUIKitSearchItem( return TIMUIKitSearchItem(
onClick: () { onClick: () {
widget.onTapConversation(conversation, null); widget.onTapConversation(conversation, null);
}, },
faceUrl: conv.friendInfo?.userProfile?.faceUrl ?? "", faceUrl: conv.friendInfo?.userProfile?.faceUrl ?? "",
showName: "", showName: "",
lineOne: conversation.showName ??
conversation.userID ?? lineOne: conversation.userID!,
conv.friendInfo?.userID ?? lineTwo: TIM_t("昵称") + ":" + showNickName!,
"",
); );
}).toList(), }).toList(),
_renderShowALl(filteredFriendResultList.length), _renderShowALl(filteredFriendResultList.length),

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
@ -53,7 +54,7 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
} }
List<String> _getAbstractList() { List<String> _getAbstractList() {
return widget.model.multiSelectedMessageList.map((e) { return widget.model.getSelectedMessageList().map((e) {
final sender = (e.nickName != null && e.nickName!.isNotEmpty) final sender = (e.nickName != null && e.nickName!.isNotEmpty)
? e.nickName ? e.nickName
: e.sender; : e.sender;
@ -62,6 +63,11 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
} }
handleForwardMessage() async { handleForwardMessage() async {
var confirmResult = await _showConfirmForwardDialog(context);
if (confirmResult == null) {
return;
}
if (widget.isMergerForward) { if (widget.isMergerForward) {
await widget.model.sendMergerMessage( await widget.model.sendMergerMessage(
conversationList: _conversationList, conversationList: _conversationList,
@ -82,6 +88,33 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
} }
} }
//
Future<bool?> _showConfirmForwardDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text(TIM_t("您确定进行转发吗?")),
actions: [
CupertinoDialogAction(
child: Text(TIM_t("确定")),
onPressed: () {
Navigator.of(context).pop(true);
},
),
CupertinoDialogAction(
child: Text(TIM_t("取消")),
isDestructiveAction: true,
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();

View File

@ -184,7 +184,8 @@ class GesturedImageState extends ExtendedImageGestureState {
gestureDetails = GestureDetails( gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale, totalScale: _gestureConfig!.initialScale,
offset: Offset.zero, offset: Offset.zero,
)..initialAlignment = _gestureConfig!.initialAlignment; initialAlignment: _gestureConfig!.initialAlignment,
);
} }
@override @override
@ -433,7 +434,8 @@ class GesturedImageState extends ExtendedImageGestureState {
_gestureDetails = GestureDetails( _gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale, totalScale: _gestureConfig!.initialScale,
offset: Offset.zero, offset: Offset.zero,
)..initialAlignment = _gestureConfig!.initialAlignment; initialAlignment: _gestureConfig!.initialAlignment,
);
} }
if (_gestureConfig!.cacheGesture) { if (_gestureConfig!.cacheGesture) {

View File

@ -15,6 +15,7 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/radio_button.dart';
import 'package:tencent_im_base/tencent_im_base.dart'; import 'package:tencent_im_base/tencent_im_base.dart';
class GroupProfileMemberList extends StatefulWidget { class GroupProfileMemberList extends StatefulWidget {
static String AT_ALL_USER_ID = "__kImSDK_MesssageAtALL__";
final List<V2TimGroupMemberFullInfo?> memberList; final List<V2TimGroupMemberFullInfo?> memberList;
final Function(String userID)? removeMember; final Function(String userID)? removeMember;
final bool canSlideDelete; final bool canSlideDelete;
@ -53,7 +54,7 @@ class GroupProfileMemberList extends StatefulWidget {
} }
class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList> { class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList> {
List<V2TimGroupMemberFullInfo> selectedMember = []; List<V2TimGroupMemberFullInfo> selectedMemberList = [];
_getShowName(V2TimGroupMemberFullInfo? item) { _getShowName(V2TimGroupMemberFullInfo? item) {
final friendRemark = item?.friendRemark ?? ""; final friendRemark = item?.friendRemark ?? "";
@ -93,7 +94,7 @@ class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList>
if (widget.canAtAll) { if (widget.canAtAll) {
final canAtGroupType = ["Work", "Public", "Meeting"]; final canAtGroupType = ["Work", "Public", "Meeting"];
if (canAtGroupType.contains(widget.groupType)) { if (canAtGroupType.contains(widget.groupType)) {
showList.insert(0, ISuspensionBeanImpl(memberInfo: V2TimGroupMemberFullInfo(userID: "__kImSDK_MesssageAtALL__", nickName: TIM_t("所有人")), tagIndex: "")); showList.insert(0, ISuspensionBeanImpl(memberInfo: V2TimGroupMemberFullInfo(userID: GroupProfileMemberList.AT_ALL_USER_ID, nickName: TIM_t("所有人")), tagIndex: ""));
} }
} }
@ -133,19 +134,21 @@ class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList>
child: CheckBoxButton( child: CheckBoxButton(
onChanged: (isChecked) { onChanged: (isChecked) {
if (isChecked) { if (isChecked) {
if (widget.maxSelectNum != null && selectedMember.length >= widget.maxSelectNum!) { if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) {
return; return;
} }
selectedMember.add(memberInfo); selectedMemberList.add(memberInfo);
} else { } else {
selectedMember.remove(memberInfo); selectedMemberList.removeWhere((element) => element.userID == memberInfo.userID);
} }
if (widget.onSelectedMemberChange != null) { if (widget.onSelectedMemberChange != null) {
widget.onSelectedMemberChange!(selectedMember); widget.onSelectedMemberChange!(selectedMemberList);
} }
setState(() {}); setState(() {});
}, },
isChecked: selectedMember.contains(memberInfo)), isChecked: selectedMemberList.where((element) => element.userID == memberInfo.userID).toList().isNotEmpty
),
), ),
Container( Container(
width: isDesktopScreen ? 30 : 36, width: isDesktopScreen ? 30 : 36,
@ -194,17 +197,17 @@ class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList>
widget.onTapMemberItem!(memberInfo, null); widget.onTapMemberItem!(memberInfo, null);
} }
if (widget.canSelectMember) { if (widget.canSelectMember) {
final isChecked = selectedMember.contains(memberInfo); final isChecked = selectedMemberList.contains(memberInfo);
if (isChecked) { if (isChecked) {
selectedMember.remove(memberInfo); selectedMemberList.remove(memberInfo);
} else { } else {
if (widget.maxSelectNum != null && selectedMember.length >= widget.maxSelectNum!) { if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) {
return; return;
} }
selectedMember.add(memberInfo); selectedMemberList.add(memberInfo);
} }
if (widget.onSelectedMemberChange != null) { if (widget.onSelectedMemberChange != null) {
widget.onSelectedMemberChange!(selectedMember); widget.onSelectedMemberChange!(selectedMemberList);
} }
setState(() {}); setState(() {});
} }

View File

@ -42,13 +42,13 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
double initialScale = 1.0; double initialScale = 1.0;
bool isLoading = false; bool isLoading = false;
GlobalKey<ExtendedImageSlidePageState> slidePagekey = GlobalKey<ExtendedImageSlidePageState> slidePageKey =
GlobalKey<ExtendedImageSlidePageState>(); GlobalKey<ExtendedImageSlidePageState>();
GlobalKey<ExtendedImageGestureState> extendedImageGestureKey = GlobalKey<ExtendedImageGestureState> extendedImageGestureKey =
GlobalKey<ExtendedImageGestureState>(); GlobalKey<ExtendedImageGestureState>();
void close() { void close() {
slidePagekey.currentState!.popPage(); slidePageKey.currentState!.popPage();
Navigator.pop(context); Navigator.pop(context);
} }
@ -78,6 +78,10 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
@override @override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return OrientationBuilder(builder: ((context, orientation) { return OrientationBuilder(builder: ((context, orientation) {
if (extendedImageGestureKey.currentState != null) {
extendedImageGestureKey.currentState!.reset();
}
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: Container( child: Container(
@ -94,7 +98,7 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
bottom: 0, bottom: 0,
right: 0, right: 0,
child: ExtendedImageSlidePage( child: ExtendedImageSlidePage(
key: slidePagekey, key: slidePageKey,
slideAxis: SlideAxis.both, slideAxis: SlideAxis.both,
slidePageBackgroundHandler: (Offset offset, Size size) { slidePageBackgroundHandler: (Offset offset, Size size) {
if (orientation == Orientation.landscape) { if (orientation == Orientation.landscape) {
@ -123,7 +127,7 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
onTap: close, onTap: close,
child: HeroWidget( child: HeroWidget(
tag: widget.heroTag, tag: widget.heroTag,
slidePagekey: slidePagekey, slidePagekey: slidePageKey,
child: ExtendedImage( child: ExtendedImage(
image: widget.imageProvider, image: widget.imageProvider,
extendedImageGestureKey: extendedImageGestureKey:
@ -179,6 +183,7 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
1 / fitWidthScale; // fittedHeight 1 / fitWidthScale; // fittedHeight
doubleTapScales[1] = 1 / fitWidthScale; doubleTapScales[1] = 1 / fitWidthScale;
} }
return GesturedImage(state, return GesturedImage(state,
key: extendedImageGestureKey); key: extendedImageGestureKey);
case LoadState.failed: case LoadState.failed:
@ -188,7 +193,7 @@ class _ImageScreenState extends TIMUIKitState<ImageScreen>
}, },
onDoubleTap: (ExtendedImageGestureState state) { onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can, ///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion. ///default value is double tap pointer down position.
final Offset? pointerDownPosition = final Offset? pointerDownPosition =
state.pointerDownPosition; state.pointerDownPosition;
final double? begin = final double? begin =

View File

@ -5,6 +5,7 @@ import 'package:extended_text/extended_text.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/http_text.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/compiler/md_text.dart'; import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/compiler/md_text.dart';
import 'package:tencent_im_base/base_widgets/tim_stateless_widget.dart'; import 'package:tencent_im_base/base_widgets/tim_stateless_widget.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
@ -125,7 +126,7 @@ class LinkText extends TIMStatelessWidget {
} }
if (LinkUtils.urlReg.hasMatch(c)) { if (LinkUtils.urlReg.hasMatch(c)) {
contentData += '\$' + c + '\$'; contentData += HttpText.flag + c + HttpText.flag;
_contentList.add(TextSpan( _contentList.add(TextSpan(
text: c, text: c,
style: TextStyle(color: LinkUtils.hexToColor("015fff")), style: TextStyle(color: LinkUtils.hexToColor("015fff")),
@ -160,12 +161,12 @@ class LinkText extends TIMStatelessWidget {
Widget timBuild(BuildContext context) { Widget timBuild(BuildContext context) {
return ExtendedText(_getContentSpan(messageText, context), softWrap: true, return ExtendedText(_getContentSpan(messageText, context), softWrap: true,
onSpecialTextTap: (dynamic parameter) { onSpecialTextTap: (dynamic parameter) {
if (parameter.toString().startsWith('\$')) { if (parameter.toString().startsWith(HttpText.flag)) {
if (onLinkTap != null) { if (onLinkTap != null) {
onLinkTap!((parameter.toString()).replaceAll('\$', '')); onLinkTap!((parameter.toString()).replaceAll(HttpText.flag, ''));
} else { } else {
LinkUtils.launchURL( LinkUtils.launchURL(
context, (parameter.toString()).replaceAll('\$', '')); context, (parameter.toString()).replaceAll(HttpText.flag, ''));
} }
} }
}, },

View File

@ -243,15 +243,14 @@ class _VideoScreenState extends TIMUIKitState<VideoScreen> {
)); ));
await player.initialize(); await player.initialize();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
double w = getVideoWidth(); double aspectRatio = player.value.aspectRatio;
double h = getVideoHeight();
ChewieController controller = ChewieController( ChewieController controller = ChewieController(
videoPlayerController: player, videoPlayerController: player,
autoPlay: true, autoPlay: true,
looping: false, looping: false,
showControlsOnInitialize: false, showControlsOnInitialize: false,
allowPlaybackSpeedChanging: false, allowPlaybackSpeedChanging: false,
aspectRatio: w == 0 || h == 0 ? null : w / h, aspectRatio: aspectRatio,
customControls: VideoCustomControls(downloadFn: () async { customControls: VideoCustomControls(downloadFn: () async {
return await _saveVideo(); return await _saveVideo();
})); }));

View File

@ -1,6 +1,6 @@
name: tencent_cloud_chat_uikit name: tencent_cloud_chat_uikit
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences. description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
version: 3.0.0 version: 3.1.0
homepage: https://trtc.io/products/chat?utm_source=gfs&utm_medium=link&utm_campaign=%E6%B8%A0%E9%81%93&_channel_track_key=k6WgfCKn 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 repository: https://github.com/TencentCloud/chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -21,17 +21,17 @@ dependencies:
sdk: flutter sdk: flutter
adaptive_action_sheet: ^2.0.1 adaptive_action_sheet: ^2.0.1
provider: ^6.0.1 provider: ^6.0.1
intl: any intl: ^0.19.0
get_it: ^7.2.0 get_it: ^7.2.0
dotted_border: ^2.0.0+2 dotted_border: ^2.0.0+2
flutter_svg: ^2.0.6 flutter_svg: ^2.0.6
image_picker: ^0.8.5+3 image_picker: ^0.8.5+3
file_picker: ^5.3.0 file_picker: ^5.3.0
tencent_super_tooltip: ^0.0.1 tencent_super_tooltip: ^0.0.1
video_player: ^2.7.0 video_player: ^2.9.0
chewie: ^1.7.5 chewie: ^1.8.5
flutter_slidable: ^3.0.1 flutter_slidable: ^3.0.1
flutter_plugin_record_plus: ^0.0.16 flutter_plugin_record_plus: ^0.0.17
azlistview_all_platforms: ^2.1.2 azlistview_all_platforms: ^2.1.2
lpinyin: ^2.0.3 lpinyin: ^2.0.3
transparent_image: ^2.0.0 transparent_image: ^2.0.0
@ -40,13 +40,13 @@ dependencies:
cached_network_image: ^3.3.0 cached_network_image: ^3.3.0
shared_preferences: ^2.0.13 shared_preferences: ^2.0.13
scroll_to_index: ^2.1.1 scroll_to_index: ^2.1.1
wechat_assets_picker: ^8.9.0-dev.1 wechat_assets_picker: ^9.3.3
wechat_camera_picker: ^4.2.0-dev.2 wechat_camera_picker: ^4.3.4
flutter_easyrefresh: ^2.2.1 flutter_easyrefresh: ^2.2.1
extended_image: '>=8.2.0 <=8.2.4' extended_image: ^9.0.0
extended_text_field: ^16.0.0 extended_text_field: ^16.0.0
extended_text: ^14.0.0 extended_text: ^14.0.0
package_info_plus: ^4.0.1 package_info_plus: ^8.0.0
loading_animation_widget: ^1.1.0+3 loading_animation_widget: ^1.1.0+3
permission_handler: ^10.2.0 permission_handler: ^10.2.0
tuple: ^2.0.0 tuple: ^2.0.0
@ -63,19 +63,20 @@ dependencies:
tencent_keyboard_visibility: ^1.0.1 tencent_keyboard_visibility: ^1.0.1
tim_ui_kit_sticker_plugin: ^3.2.0 tim_ui_kit_sticker_plugin: ^3.2.0
tencent_im_base: ^8.0.0 tencent_im_base: ^8.0.0
fc_native_video_thumbnail: ^0.11.1 fc_native_video_thumbnail: ^0.16.0
path: ^1.8.1 path: ^1.8.1
tencent_cloud_uikit_core: ^1.6.0 tencent_cloud_uikit_core: ^1.6.0
pasteboard: ^0.2.0 pasteboard: ^0.2.0
desktop_drop: ^0.4.4 desktop_drop: ^0.4.4
device_info_plus: any device_info_plus: ^10.1.2
cross_file: ^0.3.3+4 cross_file: ^0.3.3+4
csslib: 0.17.2 csslib: ^0.17.2
diff_match_patch: ^0.4.1 diff_match_patch: ^0.4.1
just_audio: ^0.9.34 just_audio: ^0.9.34
markdown: ^7.1.0 markdown: ^7.1.0
logger: ^2.0.1 logger: ^2.0.1
image_clipboard: ^1.0.0+2 image_clipboard: ^1.0.0+2
visibility_detector: ^0.4.0+2
dev_dependencies: dev_dependencies:
flutter_lints: ^1.0.0 flutter_lints: ^1.0.0